FreeRDP
Loading...
Searching...
No Matches
sdl_rail_window.hpp
1
19#pragma once
20
21#include <atomic>
22#include <cstdint>
23#include <memory>
24#include <mutex>
25#include <optional>
26#include <string>
27#include <vector>
28
29#include <SDL3/SDL.h>
30
31#include <freerdp/types.h>
32
33class SdlWindow;
34
35/* A decoded RAIL window icon (BGRA32 pixels). */
37{
38 uint32_t w = 0;
39 uint32_t h = 0;
40 std::vector<uint8_t> bgra;
41};
42
50{
51 public:
52 SdlRailWindow(uint64_t id, const SDL_Rect& rect);
53 SdlRailWindow(SdlRailWindow&&) = delete;
54 SdlRailWindow(const SdlRailWindow&) = delete;
55 SdlRailWindow& operator=(const SdlRailWindow&) = delete;
56 SdlRailWindow& operator=(SdlRailWindow&&) = delete;
58
59 [[nodiscard]] uint64_t id() const;
60 [[nodiscard]] SDL_WindowID sdlId() const;
61 [[nodiscard]] SDL_Window* window() const;
62 [[nodiscard]] SDL_Renderer* renderer() const;
63
64 /* --- state setters, safe to call from the RDP thread (no SDL window calls) --- */
65
66 /* Server-driven geometry (from WINDOW_ORDER). */
67 void updateWindowRect(const SDL_Rect& rect);
68 /* Server-reported window rect. */
69 [[nodiscard]] SDL_Rect windowRect() const;
70
71 /* Local move/resize state. */
72 void setLocalMoveActive(bool active);
73 [[nodiscard]] bool localMoveActive() const;
74 /* True if the server resized the window during the current/last local move (drag-restore). */
75 [[nodiscard]] bool localMoveSizeChanged() const;
76 /* Where the server re-anchored its window mid-move (valid when localMoveSizeChanged()). */
77 [[nodiscard]] SDL_Point localMoveServerPos() const;
78 void adoptLocalGeometry(const SDL_Rect& rect);
79 /* Resize anchor corner during local resize. */
80 void setResizeAnchor(bool right, bool bottom);
81
82 /* Deferred destroy: mark here (RDP thread), erased on the main thread in SdlRail::paint. */
83 void markDeleted();
84 /* Drop the local SDL window only (its SDL parent is going away); the server state is kept
85 * and the next reconcile re-creates the window under a new parent. Main thread. */
86 void releaseWindow();
87 [[nodiscard]] bool isDeleted() const;
88
89 /* In-flight local state application flag. */
90 [[nodiscard]] bool geomApplyPending() const;
91 void clearGeomApplyPending();
92
93 /* Deferred completion of server modal move/size loop. */
94 void armLoopEnd(); /* completion: now awaiting the END order */
95 [[nodiscard]] bool loopEndPending() const;
96 void deferMaximize(); /* WM snap-to-top mid-drag: SC_MAXIMIZE on close */
97 void deferSnap(const SDL_Rect& serverRect); /* WM snap/tile: resend rect on close */
98 void clearLoopEnd(); /* a fresh drag of this window supersedes the pending close */
100 {
101 bool maximize = false;
102 bool snap = false;
103 SDL_Rect snapRect = { 0, 0, 0, 0 };
104 };
105 [[nodiscard]] LoopEndActions takeLoopEnd(); /* END order: drain queued actions and reset */
106
107 /* Adopt WM-refused geometry. */
108 [[nodiscard]] bool takeWmOverride(SDL_Rect& outer);
109
110 /* Visible sub-rects (window-relative); only these are painted so windows on top don't bleed. */
111 void setVisibilityRects(std::vector<SDL_Rect> rects);
112 /* Server visible-region origin offset. */
113 void setVisibleOffset(SDL_Point offset);
114
115 /* Server min/max tracking size (RAIL MinMaxInfo); applied in reconcile. */
116 void setMinMaxSize(SDL_Point minSize, SDL_Point maxSize);
117
118 /* Server outside resize margins. */
119 void setResizeMargins(int left, int top, int right, int bottom);
120 [[nodiscard]] SDL_Rect resizeMargins() const; /* x=left y=top w=right h=bottom */
121 /* Raw server frame margins. */
122 void setFrameMargins(const SDL_Rect& m);
123 [[nodiscard]] SDL_Rect frameMargins() const;
124 [[nodiscard]] bool takeUnconfirmedReport(SDL_Rect& rect);
125
126 /* Owning window id (WINDOW_ORDER_FIELD_OWNER); popups position relative to it. */
127 void setOwner(uint64_t ownerId);
128 [[nodiscard]] uint64_t owner() const;
129 /* Popup = caption-less transient (menu/dropdown/tooltip); created as an SDL popup. */
130 [[nodiscard]] bool isPopup() const;
131 /* Full-screen popup treated as a top-level window. */
132 [[nodiscard]] bool isFullscreen() const;
133 /* App window or fullscreen popup without WS_EX_NOACTIVATE (e.g. on-screen keyboards). */
134 [[nodiscard]] bool activatable() const;
135 /* Layered decoration (drop shadow); realized only while anchored to a visible popup. */
136 [[nodiscard]] bool isLayered() const;
137 /* Mark shadow anchored to an active popup. */
138 void setShadowAnchored(bool anchored);
139 void setFrame(bool frame);
140 [[nodiscard]] bool isFrame() const;
141 /* Local window insets for resize bands. */
142 [[nodiscard]] SDL_Rect insets() const;
143 /* Server rect inflated by insets() = the local SDL window's on-screen geometry. */
144 [[nodiscard]] SDL_Rect outerRect() const;
145 /* Server coordinate for window-local (0,0), aligned with blit anchor. */
146 [[nodiscard]] SDL_Point serverOrigin() const;
147 /* Wayland popup placement, see notePlaced(). */
148 void notePlaced();
149 [[nodiscard]] bool placedRel(SDL_Point& rel) const;
150 void placePopup(const SDL_Rect& vis, const SDL_Rect& parentRect);
151 void commitPlacement();
152 /* Inverse of outerRect(): strip insets() off a local outer rect to recover the server rect. */
153 [[nodiscard]] SDL_Rect serverRect(const SDL_Rect& outer) const;
154 void setLocalShift(const SDL_Point& shift);
155
156 void setStyle(uint32_t style, uint32_t exStyle);
157 void setTitle(const std::string& title);
158 void setVisible(bool visible);
159 /* Window icon (WindowIcon/WindowCachedIcon orders); applied in reconcile. */
160 void setIcon(const SdlRailIcon& icon);
161
162 /* --- main thread only (SDL window ops) --- */
163
164 /* Window-mapped GFX surface (RDP thread): deep-copies the damaged gdi pixels for paint(). */
165 [[nodiscard]] bool takeSurfaceChange(uint32_t surfaceId);
166 void updateGfxSurface(const void* data, uint32_t stride, uint32_t width, uint32_t height,
167 const RECTANGLE_16* damage, uint32_t nbDamage, uint32_t format);
168 /* Mark the whole window dirty for re-blit (e.g. on expose). */
169 void invalidateAll();
170
171 /* Maximize/minimize sync: local state (rail) vs server-reported state (RDP thread). */
172 [[nodiscard]] bool railMaximized() const
173 {
174 return _maxState.rail;
175 }
176 void setRailMaximized(bool m)
177 {
178 std::unique_lock lock(_gfxLock);
179 _maxState.rail = m;
180 }
181 void setServerMaximized(bool m);
182 [[nodiscard]] bool railMinimized() const
183 {
184 return _minState.rail;
185 }
186 void setRailMinimized(bool m)
187 {
188 std::unique_lock lock(_gfxLock);
189 _minState.rail = m;
190 }
191 void setServerMinimized(bool m);
192 void noteLocalRestore();
193 [[nodiscard]] bool takeRestoreRetry();
194 /* Max or min pins the geometry (server/WM owns it): the client geometry apply is skipped. */
195 /* Re-arm geometry apply after server drag-restore. */
196 void markGeometryDirty()
197 {
198 std::unique_lock lock(_gfxLock);
199 _geometryDirty = true;
200 }
201
202 [[nodiscard]] bool geometryFrozen() const
203 {
204 return _maxState.rail || _maxState.server || _minState.rail || _minState.server;
205 }
206 /* A server restore has been applied but its rect has not arrived: geometry is unknown. */
207 [[nodiscard]] bool awaitingRestoreRect() const
208 {
209 std::unique_lock lock(_gfxLock);
210 return restoreRectPending();
211 }
212 /* Insets have not yet been updated for the current maximized state. */
213 [[nodiscard]] bool insetsStale() const;
214 /* Local or live-WM maximized (railMaximized may lag the SDL flag during a snap). */
215 [[nodiscard]] bool effectivelyMaximized() const;
216 /* Transition in flight (local intent vs server state). WM resizes only trusted outside. */
217 [[nodiscard]] bool stateTransitionPending() const
218 {
219 /* _maxState/_minState are written under _gfxLock on the RDP thread. */
220 std::unique_lock lock(_gfxLock);
221 return _maxState.dirty || _minState.dirty || (_maxState.rail != _maxState.server) ||
222 (_minState.rail != _minState.server);
223 }
224
225 /* Create/move/show the local SDL window to match pending state; popups use parent+rect. */
226 bool reconcile(SDL_Window* parent, const SDL_Rect& parentRect);
227 /* Render: GFX surface if mapped, else the shared desktop region. `damage` = updated rects. */
228 bool paint(SDL_Surface* primary, SDL_PixelFormat fallbackFormat,
229 const std::vector<SDL_Rect>& damage, SDL_Window* parent, const SDL_Rect& parentRect);
230
231 private:
232 /* One maximize/minimize state pair: local (sent to server), server-reported, pending apply. */
233 struct StateSync
234 {
235 /* Atomic flags accessed lock-free by railMaximized()/geometryFrozen(). */
236 std::atomic<bool> rail{ false };
237 std::atomic<bool> server{ false };
238 bool dirty = false;
239 };
240 void setServerState(StateSync& s, bool m);
241 /* Caller holds _gfxLock and checked _win. True when it left the state (restore branch). */
242 bool applyServerState(StateSync& s, const char* what, bool (*enter)(SDL_Window*));
243 /* Caller holds _gfxLock. */
244 [[nodiscard]] bool restoreRectPending() const;
245 void setAppliedInsets(const SDL_Rect& insets); /* caller holds _gfxLock */
246 [[nodiscard]] bool styleResizable() const; /* caller holds _gfxLock */
247 /* Popups whose GFX surface may carry per-pixel alpha (paintGfx). */
248 [[nodiscard]] bool honorsAlpha() const
249 {
250 return _isPopup && !_layered;
251 }
252 /* Window role for logging. */
253 [[nodiscard]] const char* role() const
254 {
255 if (_overlay)
256 return _layered ? "overlay-layered" : "overlay";
257 return _layered ? "layered" : (_isPopup ? "popup" : "app");
258 }
259 [[nodiscard]] bool isFullDisplaySize() const; /* caller holds _gfxLock */
260 /* Band eligibility margins. */
261 [[nodiscard]] SDL_Rect bandMargins() const;
262 [[nodiscard]] SDL_Rect bandInsets() const; /* caller holds _gfxLock */
263 [[nodiscard]] SDL_Point blitOffset() const; /* caller holds _gfxLock */
264 [[nodiscard]] SDL_Rect targetOuterRect() const; /* caller holds _gfxLock */
265 bool create(SDL_Window* parent, const SDL_Rect& parentRect);
266 bool paintGfx(SDL_PixelFormat format);
267 bool paintLegacy(SDL_Surface* primary, const std::vector<SDL_Rect>& damage);
268 void placeRequested();
269 [[nodiscard]] bool holdForPlacement();
270
271 uint64_t _id;
272 std::unique_ptr<SdlWindow> _win; /* local window+renderer (lazy, main thread) */
273 SDL_Rect _windowRect; /* server offset/size */
274 SDL_Point _localShift = { 0, 0 }; /* Offset applied to local window coordinates */
275 uint32_t _style = 0;
276 uint32_t _exStyle = 0;
277 /* The shadow-rule suppression report in reconcile is once per window. */
278 bool _shadowSuppressLogged = false;
279 bool _everResizable = false; /* latched: ever had a resizable style */
280 uint64_t _ownerId = 0;
281 SDL_Rect _resizeMargins = { 0, 0, 0, 0 }; /* x=left y=top w=right h=bottom */
282 SDL_Rect _frameMargins = { 0, 0, 0, 0 }; /* raw server margins, for ClientWindowMove */
283 /* Insets applied to active SDL window; stripped to recover server coordinates. */
284 SDL_Rect _appliedInsets = { 0, 0, 0, 0 };
285 bool _wasMaximized = false; /* refresh insets on the maximize rising edge */
286 SDL_Point _minSize = { 0, 0 };
287 SDL_Point _maxSize = { 0, 0 };
288 bool _minMaxDirty = false;
289 bool _isPopup = false;
290 bool _fullscreen = false; /* full-display popup as fullscreen toplevel (Wayland) */
291 bool _layered = false; /* caption-less WS_EX_LAYERED decoration (drop shadow) */
292 bool _overlay = false; /* layered tool window: drag image / drag feedback */
293 bool _popupClassified = false; /* isPopup/_layered frozen after the first (creation) style */
294 bool _shadowAnchored = false; /* a visible popup adjoins this shadow (see setShadowAnchored) */
295 bool _frame = false; /* companion shadow frame */
296 bool _parentApplied = false; /* transient-for owner applied (owned non-popup dialogs) */
297 StateSync _maxState; /* maximize sync */
298 StateSync _minState; /* minimize sync */
299 std::string _title = "RdpRailWindow";
300 SdlRailIcon _icon;
301 bool _iconDirty = false;
302 bool _visible = false;
303 bool _geometryDirty = true;
304 bool _titleDirty = false;
305 bool _topmost = false;
306 bool _topmostDirty = false;
307 bool _styleDirty = false; /* resizability needs re-applying (style change) */
308 bool _painted = false; /* full copy done; afterwards only damage regions are re-copied */
309 bool _gfxPresented = false; /* presented own GFX content once: gate the first map on it */
310 bool _mapped = false; /* shown at least once (one-shot show+raise) */
311 bool _localMoveActive = false; /* WM move/resize in progress: ignore server geometry + input */
312 bool _localMoveIsResize = false; /* local move is a resize vs a move */
313 bool _localMoveSizeChanged = false; /* server resized mid-move (drag-restore) */
314 SDL_Point _localMoveServerPos = { 0, 0 }; /* the re-anchored server origin (see above) */
315 /* Deferred server-modal-loop close for THIS window (see armLoopEnd()). */
316 struct
317 {
318 bool pending = false;
319 bool maximize = false;
320 bool snap = false;
321 SDL_Rect snapRect = { 0, 0, 0, 0 };
322 } _loopEnd;
323 bool _geomApplyPending = false; /* a client-issued geometry/state apply is still settling */
324 std::vector<SDL_Rect> _reported; /* rects sent to the server and not echoed yet */
325 uint64_t _reportCheckAt = 0; /* when to resend */
326 bool _wmRefused = false; /* WM refused resize; adopt its geometry (takeWmOverride) */
327 SDL_Rect _wmRefusedOuter = { 0, 0, 0, 0 };
328 bool _resizeAnchorRight = false; /* anchor stale frame to the right edge (left-side resize) */
329 bool _resizeAnchorBottom = false; /* anchor stale frame to the bottom edge (top-side resize) */
330 /* Timeout deadline for anchored frames during resize completion. */
331 uint64_t _awaitingFrameUntil = 0;
332 /* Deadline for the server's post-restore rect; 0 when none is awaited. */
333 uint64_t _awaitRestoreUntil = 0;
334 /* A server minimize before this is stale. */
335 uint64_t _localRestoreUntil = 0;
336 bool _restoreRetried = false;
337 bool _restoreRetry = false;
338 /* Content origin offset inside current GFX surface. */
339 SDL_Point _gfxVisOrigin = { 0, 0 };
340 SDL_Rect _extentsApplied = { 0, 0, 0, 0 }; /* X11 extents sent to the WM */
341
342 /* Guard for state shared between RDP and main threads. */
343 mutable std::mutex _gfxLock;
344 std::vector<SDL_Rect> _visRects;
345 SDL_Point _visOffset = { 0, 0 }; /* server-absolute origin of _visRects */
346 bool _visOffsetSet = false; /* until VIS_OFFSET arrives, rects are window-relative */
347 bool _visDirty = false; /* rects/offset changed: wipe + full reclip (layered app) */
348 bool _deleted = false;
349 std::vector<uint8_t> _gfxBuffer; /* owned deep-copy of the gdi surface (avoids UAF) */
350 uint32_t _gfxStride = 0;
351 uint32_t _surfaceId = 0;
352 uint32_t _gfxW = 0;
353 uint32_t _gfxH = 0;
354 bool _hasGfx = false;
355 /* Popup surface has real per-pixel alpha (blend); all-zero undefined alpha stays opaque. */
356 bool _gfxHasAlpha = false;
357 /* Regions changed since last paint. Guarded by _gfxLock. */
358 std::vector<SDL_Rect> _gfxDamage;
359 /* Pixel dimensions at last presentation. */
360 int _lastWinW = 0;
361 int _lastWinH = 0;
362 int _lastGfxW = 0;
363 int _lastGfxH = 0;
364 bool _needsFullBlit = true;
365 SDL_Point _requestedRel = { 0, 0 };
366 Uint64 _requestedAt = 0;
367 std::optional<SDL_Point> _pendingRel; /* configured, shown with our next commit */
368 std::optional<SDL_Point> _placedRel; /* committed, input is mapped with it */
369 bool _placeInFlight = false;
370};