FreeRDP
Loading...
Searching...
No Matches
sdl_rail.hpp
1
19#pragma once
20
21#include <cstdint>
22#include <map>
23#include <atomic>
24#include <mutex>
25#include <vector>
26
27#include <SDL3/SDL.h>
28
29#include <freerdp/client/rail.h>
30#include <freerdp/client/rdpgfx.h>
31#include <freerdp/gdi/gfx.h>
32#include <freerdp/update.h>
33
34#include "sdl_rail_window.hpp"
35
36class SdlContext;
37
46{
47 public:
48 explicit SdlRail(SdlContext* context);
49 ~SdlRail();
50
51 SdlRail(const SdlRail&) = delete;
52 SdlRail(SdlRail&&) = delete;
53 SdlRail& operator=(const SdlRail&) = delete;
54 SdlRail& operator=(SdlRail&&) = delete;
55
56 bool init(RailClientContext* rail);
57 bool uninit(RailClientContext* rail);
58
59 [[nodiscard]] bool enabled() const
60 {
61 return _enabled;
62 }
63
64 /* Main thread: whether the desktop window may show; before the RemoteApp starts, only
65 * for a logon dialog or a slow start. */
66 [[nodiscard]] bool desktopMayShow();
67 /* Any thread: the server shows a logon dialog that needs the user (LogonErrorInfo). */
68 void noteLogonPrompt();
69
70 /* Paint all RAIL windows (window-mapped GFX surface, or legacy desktop region).
71 * `damage` = server-updated desktop rects for this cycle (empty = nothing new). */
72 bool paint(SDL_Surface* primary, SDL_PixelFormat fallbackFormat,
73 const std::vector<SDL_Rect>& damage);
74
75 /* Called from the GFX pipeline when a window-mapped surface updated (RDP thread). */
76 UINT updateWindowFromSurface(gdiGfxSurface* surface);
77
78 /* GFX callback: install as gfx->UpdateWindowFromSurface after gdi_graphics_pipeline_init. */
79 static UINT UpdateWindowFromSurface(RdpgfxClientContext* context, gdiGfxSurface* surface);
80
81 /* Input routing (main thread): true if the SDL window id is one of our RAIL windows. */
82 [[nodiscard]] bool ownsWindow(SDL_WindowID id);
83 /* Mark a RAIL window fully dirty and request a repaint (e.g. on expose). */
84 void invalidateWindow(SDL_WindowID id);
85 /* Local maximize/minimize/restore state handlers. */
86 void handleMaximized(SDL_WindowID id);
87 void handleMinimized(SDL_WindowID id);
88 void handleRestored(SDL_WindowID id);
89 void handleClose(SDL_WindowID id);
90 /* Local focus change: send ClientActivate, like xf FocusIn/FocusOut. */
91 void handleFocus(SDL_WindowID id, bool gained);
92 void handleDisplayChanged(SDL_WindowID id, SDL_DisplayID display);
93 void updateCoordinates();
94 /* Activate window on server if not already active. */
95 void ensureActive(SDL_WindowID id);
96 /* Rewrite window-local (x,y) to server-absolute; false if id is not a RAIL window. */
97 bool translateToServer(SDL_WindowID id, float& x, float& y);
98 void notePopupMoved(SDL_WindowID id);
99 /* Record position of last left button-down. */
100 void noteLeftPress(float x, float y)
101 {
102 _lastPressServer = { static_cast<int>(x), static_cast<int>(y) };
103 _lastPointerServer = _lastPressServer;
104 }
105 /* Record pointer position on server. */
106 void noteServerPointer(float x, float y)
107 {
108 _lastPointerServer = { static_cast<int>(x), static_cast<int>(y) };
109 }
110 /* Local move/resize under the WM grab: server input is suppressed meanwhile. */
111 bool dragActive(SDL_WindowID id);
112 /* Suppress motion events during active local move/resize. */
113 bool suppressServerMotion(SDL_WindowID id);
114
115 /* Start native interactive window move/resize. */
116 void handleLocalMoveRequested(uint32_t windowId, uint16_t moveType);
117 /* Track size change during local move. */
118 void noteDragResize(SDL_WindowID id, int w, int h);
119 /* Sync window geometry to server. */
120 void syncGeometry(SDL_WindowID id);
121 /* Finalize pending local move/resize. */
122 void completeLocalMoveIfPending();
123
124 /* Finalize Wayland resize. */
125 void completeWaylandResize(bool definitive = false);
126 /* Wayland: the compositor runs the interactive resize (mouse leave or configure). */
127 void noteResizeGrab(SDL_WindowID id);
128 void settleWaylandResize();
129
130 private:
131 /* Latch a local move/resize; returns the window for a native modal loop. */
132 [[nodiscard]] SDL_Window* beginLocalMove(uint32_t windowId, uint16_t moveType);
133 /* _windows helpers: callers must hold _windowsLock. */
134 [[nodiscard]] SdlRailWindow* getWindow(uint64_t id);
135 [[nodiscard]] SdlRailWindow* getWindowBySdlId(SDL_WindowID id);
136 /* Send RAIL_ACTIVATE_ORDER and track _clientActiveId; caller holds _windowsLock. */
137 void sendClientActivate(uint32_t wid, bool enabled);
138 SdlRailWindow* addWindow(uint64_t id, const SDL_Rect& rect);
139 /* The window `ownerId` names, if it is a live non-popup app window (a valid popup/dialog
140 * parent); else nullptr. Caller holds _windowsLock. */
141 [[nodiscard]] SdlRailWindow* resolveParent(uint64_t ownerId);
142 [[nodiscard]] SdlRailWindow* resolvePopupParent(const SdlRailWindow& popup);
143 [[nodiscard]] bool isShadowFrame(const SdlRailWindow& popup);
144
145 void enableRemoteAppMode(bool enable);
146 /* Report a work area to the server (SPI_SET_WORK_AREA, server coords). Main thread. No-op if it
147 * matches the last one sent. */
148 void sendWorkArea(const SDL_Rect& local);
149 /* Report a maximized window's WM-given geometry as the work area. Caller holds _windowsLock. */
150 void reportMaximizedWorkArea(SdlRailWindow* appWindow);
151 /* Clamp an outer origin so its server rect stays on the desktop. */
152 void clampIntoDesktop(const SdlRailWindow& appWindow, int& x, int& y, int w, int h) const;
153 /* Send a RAIL_SYSCOMMAND_ORDER for the window. Caller holds _windowsLock. */
154 void sendSystemCommand(SdlRailWindow* appWindow, uint16_t command);
155 /* Realize the server's top-level z-order (X11 only, focus-neutral). Caller holds _windowsLock.
156 */
157 void applyZOrder();
158 /* Session-wide resize margins (guarded by _windowsLock). */
159 SDL_Rect _sessionMargins = { 0, 0, 0, 0 };
160 SDL_Point _lastPressServer = { 0, 0 }; /* last forwarded left press; main thread */
161 SDL_Point _lastPointerServer = { 0, 0 }; /* last forwarded pointer position; main thread */
162 /* Server window coordinates are relative to the primary monitor, input coordinates to the
163 * top left of the remote desktop. */
164 SDL_Point _displayShift = { 0, 0 }; /* local display minus server coordinates */
165 SDL_Point _localShift = { 0, 0 }; /* applied to local windows where positions work */
166 SDL_Point _inputOrigin = { 0, 0 }; /* top left of the remote desktop */
167
168 /* --- RAIL server callbacks (static, dispatched to the instance) --- */
169 static UINT server_execute_result(RailClientContext* context,
170 const RAIL_EXEC_RESULT_ORDER* execResult);
171 static UINT server_local_move_size(RailClientContext* context,
172 const RAIL_LOCALMOVESIZE_ORDER* localMoveSize);
173 static UINT server_min_max_info(RailClientContext* context,
174 const RAIL_MINMAXINFO_ORDER* minMaxInfo);
175
176 /* --- window order callbacks (registered on rdpUpdate::window) --- */
177 void registerUpdateCallbacks(rdpUpdate* update);
178 static BOOL window_common(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
179 const WINDOW_STATE_ORDER* windowState);
180 /* window_common field handlers (split out for readability). */
181 void updateMargins(SdlRailWindow* appWindow, const WINDOW_ORDER_INFO* orderInfo,
182 const WINDOW_STATE_ORDER* state);
183 static void updateShowState(SdlRailWindow* appWindow, const WINDOW_ORDER_INFO* orderInfo,
184 const WINDOW_STATE_ORDER* state);
185 static void updateVisRects(SdlRailWindow* appWindow, const WINDOW_ORDER_INFO* orderInfo,
186 const WINDOW_STATE_ORDER* state);
187 static BOOL window_icon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
188 const WINDOW_ICON_ORDER* windowIcon);
189 static BOOL window_cached_icon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
190 const WINDOW_CACHED_ICON_ORDER* windowCachedIcon);
191 static BOOL window_delete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo);
192 static BOOL monitored_desktop(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
193 const MONITORED_DESKTOP_ORDER* monitoredDesktop);
194 static BOOL non_monitored_desktop(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo);
195
196 static SdlRail* get(rdpContext* context);
197
198 /* Shared finalize tail (caller holds _windowsLock): report the final rect to the server and
199 * adopt it locally. Both the X11 and Wayland completion paths funnel here. */
200 void reportAndAdopt(SdlRailWindow* appWindow, int x, int y, int w, int h);
201 /* Send ClientWindowMove to server. */
202 void sendClientWindowMove(SdlRailWindow* appWindow, const SDL_Rect& serverRect);
203 /* Caller holds _windowsLock. */
204 void moveToDisplay(SdlRailWindow* appWindow, SDL_DisplayID display);
205
206 /* MS-RDPERP icon cache slot for cacheId:cacheEntry; nullptr if out of range. cacheId 0xFF =
207 * "do not cache" scratch slot (the spec says 0xFFFF but the field is one byte). */
208 [[nodiscard]] SdlRailIcon* iconCacheLookup(uint32_t cacheId, uint32_t cacheEntry);
209
210 private:
211 /* Win32: redraw while a native size/move loop blocks our loop. */
212 static void redrawDuringSizeMove(void* userdata);
213 /* Win32: a resize on the native frame drives the local resize frame like a server one. */
214 static void nativeResize(void* userdata, SDL_WindowID id, int moveType, bool end);
215 SdlContext* _context;
216 RailClientContext* _rail = nullptr;
217 /* Written on the RDP thread (RemoteApp mode toggles), read lock-free on the main thread. */
218 std::atomic<bool> _enabled = false;
219 bool _refreshSent = false; /* one full RefreshRect per connection, at first window realize */
220 std::atomic<bool> _appStarted = false;
221 /* The desktop window may show: RAIL started, a logon prompt, or the hold ran out. */
222 std::atomic<bool> _desktopReleased = false;
223 std::atomic<uint64_t> _desktopHoldUntil = 0; /* end of the logon desktop hold (ticks) */
224 uint32_t _sizeMoveRedraws = 0;
225 SDL_WindowID _nativeResizeId = 0; /* window resized on its native frame, 0 = none */
226 /* Last work area reported to the server (server coordinates). */
227 SDL_Rect _sentWorkArea = { 0, 0, 0, 0 };
228 /* Guards _windows (RDP thread mutates, main thread paints/iterates, GFX thread looks up).
229 * Erased on the main thread only, so SDL windows are destroyed there. */
230 mutable std::mutex _windowsLock;
231 std::map<uint64_t, SdlRailWindow> _windows;
232 /* Windows awaiting destruction on the main thread. */
233 std::multimap<uint64_t, SdlRailWindow> _deadWindows;
234 /* State of active interactive WM move/resize. */
235 struct LocalMove
236 {
237 uint32_t id = 0; /* windowId of the active move, 0 = none (RAIL ids are non-zero) */
238 uint16_t type = 0; /* RAIL_WMSZ_* of the active local move */
239 SDL_Point anchor = { 0, 0 }; /* modal-loop anchor (forwarded press) of this drag */
240 SDL_Point pointer = { 0, 0 }; /* server's frozen cursor (last forward) of this drag */
241 /* Wayland: a WINDOW_RESIZED arrived since the grab started, so the op really resized
242 * (guards against a bare pointer re-enter finalizing a no-op click). Main thread. */
243 bool sawResize = false;
244 Uint64 lastResize = 0; /* time of the last size step */
245 /* Server END order arrived while the drag was still pending (app closed its loop early):
246 * the completion must not send the synthetic button-up. Guarded by _windowsLock. */
247 bool serverEnded = false;
248 /* Last WM-imposed size while plain X11 move was active (self-echoes filtered out). */
249 bool wmSized = false;
250 SDL_Point wmSize = { 0, 0 };
251 };
252 LocalMove _localMove;
253 /* Server move/size loop in progress (_windowsLock). */
254 struct
255 {
256 uint64_t id = 0;
257 SDL_DisplayID display = 0;
258 } _moveLoop;
259 /* Last focused app window ID. */
260 uint64_t _focusedAppId = 0;
261 /* Server-driven z-order list. */
262 std::vector<uint32_t> _zOrder;
263 std::vector<uint32_t> _appliedZOrder;
264 uint32_t _clientActiveId = 0; /* last window we told the server to activate (ClientActivate) */
265 bool _zOrderDirty = false;
266 /* Icon cache (RDP thread): index = cacheId * entries + cacheEntry, sized from settings. */
267 std::vector<SdlRailIcon> _iconCache;
268 SdlRailIcon _iconScratch;
269 uint32_t _iconCacheEntries = 0;
270};