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