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 /* Paint all RAIL windows (window-mapped GFX surface, or legacy desktop region).
65 * `damage` = server-updated desktop rects for this cycle (empty = nothing new). */
66 bool paint(SDL_Surface* primary, SDL_PixelFormat fallbackFormat,
67 const std::vector<SDL_Rect>& damage);
68
69 /* Called from the GFX pipeline when a window-mapped surface updated (RDP thread). */
70 UINT updateWindowFromSurface(gdiGfxSurface* surface);
71
72 /* GFX callback: install as gfx->UpdateWindowFromSurface after gdi_graphics_pipeline_init. */
73 static UINT UpdateWindowFromSurface(RdpgfxClientContext* context, gdiGfxSurface* surface);
74
75 /* Input routing (main thread): true if the SDL window id is one of our RAIL windows. */
76 [[nodiscard]] bool ownsWindow(SDL_WindowID id);
77 /* Mark a RAIL window fully dirty and request a repaint (e.g. on expose). */
78 void invalidateWindow(SDL_WindowID id);
79 /* Local maximize/minimize/restore state handlers. */
80 void handleMaximized(SDL_WindowID id);
81 void handleMinimized(SDL_WindowID id);
82 void handleRestored(SDL_WindowID id);
83 void handleClose(SDL_WindowID id);
84 /* Local focus change: send ClientActivate, like xf FocusIn/FocusOut. */
85 void handleFocus(SDL_WindowID id, bool gained);
86 /* Activate window on server if not already active. */
87 void ensureActive(SDL_WindowID id);
88 /* Rewrite window-local (x,y) to server-absolute; false if id is not a RAIL window. */
89 bool translateToServer(SDL_WindowID id, float& x, float& y);
90 /* Record position of last left button-down. */
91 void noteLeftPress(float x, float y)
92 {
93 _lastPressServer = { static_cast<int>(x), static_cast<int>(y) };
94 _lastPointerServer = _lastPressServer;
95 }
96 /* Record pointer position on server. */
97 void noteServerPointer(float x, float y)
98 {
99 _lastPointerServer = { static_cast<int>(x), static_cast<int>(y) };
100 }
101 /* Suppress button-up to server during active local move/resize. */
102 bool suppressServerInput(SDL_WindowID id);
103 /* Suppress motion events during active local move/resize. */
104 bool suppressServerMotion(SDL_WindowID id);
105
106 /* Start native interactive window move/resize. */
107 void handleLocalMoveRequested(uint32_t windowId, uint16_t moveType);
108 /* Track size change during local move. */
109 void noteDragResize(SDL_WindowID id, int w, int h);
110 /* Sync window geometry to server. */
111 void syncGeometry(SDL_WindowID id);
112 /* Finalize pending local move/resize. */
113 void completeLocalMoveIfPending();
114
115 /* Finalize Wayland resize. */
116 void completeWaylandResize(bool definitive = false);
117 /* Wayland: confirm compositor pointer grab on mouse leave during interactive resize. */
118 void noteResizeGrab(SDL_WindowID id);
119 /* Handle Wayland resize event. */
120 void handleWaylandResize(SDL_WindowID id);
121
122 private:
123 /* _windows helpers: callers must hold _windowsLock. */
124 [[nodiscard]] SdlRailWindow* getWindow(uint64_t id);
125 [[nodiscard]] SdlRailWindow* getWindowBySdlId(SDL_WindowID id);
126 /* Send RAIL_ACTIVATE_ORDER and track _clientActiveId; caller holds _windowsLock. */
127 void sendClientActivate(uint32_t wid, bool enabled);
128 SdlRailWindow* addWindow(uint64_t id, const SDL_Rect& rect);
129 /* The window `ownerId` names, if it is a live non-popup app window (a valid popup/dialog
130 * parent); else nullptr. Caller holds _windowsLock. */
131 [[nodiscard]] SdlRailWindow* resolveParent(uint64_t ownerId);
132 [[nodiscard]] SdlRailWindow* resolvePopupParent(const SdlRailWindow& popup);
133 [[nodiscard]] bool isShadowFrame(const SdlRailWindow& popup);
134
135 void enableRemoteAppMode(bool enable);
136 /* Report a work area to the server (SPI_SET_WORK_AREA, server coords). Main thread. No-op if it
137 * matches the last one sent. */
138 void sendWorkArea(const SDL_Rect& area);
139 /* Report a maximized window's WM-given geometry as the work area. Caller holds _windowsLock. */
140 void reportMaximizedWorkArea(SdlRailWindow* appWindow);
141 /* Clamp a window origin (x,y) so a w x h window stays inside the server desktop. */
142 void clampIntoDesktop(int& x, int& y, int w, int h) const;
143 /* Send a RAIL_SYSCOMMAND_ORDER for the window. Caller holds _windowsLock. */
144 void sendSystemCommand(SdlRailWindow* appWindow, uint16_t command);
145 /* Realize the server's top-level z-order (X11 only, focus-neutral). Caller holds _windowsLock.
146 */
147 void applyZOrder();
148 /* Session-wide resize margins (guarded by _windowsLock). */
149 SDL_Rect _sessionMargins = { 0, 0, 0, 0 };
150 SDL_Point _lastPressServer = { 0, 0 }; /* last forwarded left press; main thread */
151 SDL_Point _lastPointerServer = { 0, 0 }; /* last forwarded pointer position; main thread */
152
153 /* --- RAIL server callbacks (static, dispatched to the instance) --- */
154 static UINT server_execute_result(RailClientContext* context,
155 const RAIL_EXEC_RESULT_ORDER* execResult);
156 static UINT server_local_move_size(RailClientContext* context,
157 const RAIL_LOCALMOVESIZE_ORDER* localMoveSize);
158 static UINT server_min_max_info(RailClientContext* context,
159 const RAIL_MINMAXINFO_ORDER* minMaxInfo);
160
161 /* --- window order callbacks (registered on rdpUpdate::window) --- */
162 void registerUpdateCallbacks(rdpUpdate* update);
163 static BOOL window_common(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
164 const WINDOW_STATE_ORDER* windowState);
165 /* window_common field handlers (split out for readability). */
166 void updateMargins(SdlRailWindow* appWindow, const WINDOW_ORDER_INFO* orderInfo,
167 const WINDOW_STATE_ORDER* state);
168 static void updateShowState(SdlRailWindow* appWindow, const WINDOW_ORDER_INFO* orderInfo,
169 const WINDOW_STATE_ORDER* state);
170 static void updateVisRects(SdlRailWindow* appWindow, const WINDOW_ORDER_INFO* orderInfo,
171 const WINDOW_STATE_ORDER* state);
172 static BOOL window_icon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
173 const WINDOW_ICON_ORDER* windowIcon);
174 static BOOL window_cached_icon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
175 const WINDOW_CACHED_ICON_ORDER* windowCachedIcon);
176 static BOOL window_delete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo);
177 static BOOL monitored_desktop(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
178 const MONITORED_DESKTOP_ORDER* monitoredDesktop);
179 static BOOL non_monitored_desktop(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo);
180
181 static SdlRail* get(rdpContext* context);
182
183 /* Shared finalize tail (caller holds _windowsLock): report the final rect to the server and
184 * adopt it locally. Both the X11 and Wayland completion paths funnel here. */
185 void reportAndAdopt(SdlRailWindow* appWindow, int x, int y, int w, int h);
186 /* Send ClientWindowMove to server. */
187 void sendClientWindowMove(SdlRailWindow* appWindow, const SDL_Rect& serverRect);
188
189 /* MS-RDPERP icon cache slot for cacheId:cacheEntry; nullptr if out of range. cacheId 0xFF =
190 * "do not cache" scratch slot (the spec says 0xFFFF but the field is one byte). */
191 [[nodiscard]] SdlRailIcon* iconCacheLookup(uint32_t cacheId, uint32_t cacheEntry);
192
193 private:
194 SdlContext* _context;
195 RailClientContext* _rail = nullptr;
196 /* Written on the RDP thread (RemoteApp mode toggles), read lock-free on the main thread. */
197 std::atomic<bool> _enabled = false;
198 bool _refreshSent = false; /* one full RefreshRect per connection, at first window realize */
199 /* Last work area reported to the server (server coordinates). */
200 SDL_Rect _sentWorkArea = { 0, 0, 0, 0 };
201 /* Guards _windows (RDP thread mutates, main thread paints/iterates, GFX thread looks up).
202 * Erased on the main thread only, so SDL windows are destroyed there. */
203 mutable std::mutex _windowsLock;
204 std::map<uint64_t, SdlRailWindow> _windows;
205 /* Windows awaiting destruction on the main thread. */
206 std::multimap<uint64_t, SdlRailWindow> _deadWindows;
207 /* State of active interactive WM move/resize. */
208 struct LocalMove
209 {
210 uint32_t id = 0; /* windowId of the active move, 0 = none (RAIL ids are non-zero) */
211 bool wayland = false; /* Wayland tracks size only */
212 uint16_t type = 0; /* RAIL_WMSZ_* of the active local move */
213 SDL_Point anchor = { 0, 0 }; /* modal-loop anchor (forwarded press) of this drag */
214 SDL_Point pointer = { 0, 0 }; /* server's frozen cursor (last forward) of this drag */
215 /* Wayland: a WINDOW_RESIZED arrived since the grab started, so the op really resized
216 * (guards against a bare pointer re-enter finalizing a no-op click). Main thread. */
217 bool sawResize = false;
218 /* Server END order arrived while the drag was still pending (app closed its loop early):
219 * the completion must not send the synthetic button-up. Guarded by _windowsLock. */
220 bool serverEnded = false;
221 /* Last WM-imposed size while plain X11 move was active (self-echoes filtered out). */
222 bool wmSized = false;
223 SDL_Point wmSize = { 0, 0 };
224 };
225 LocalMove _localMove;
226 /* Last focused app window ID. */
227 uint64_t _focusedAppId = 0;
228 /* Server-driven z-order list. */
229 std::vector<uint32_t> _zOrder;
230 std::vector<uint32_t> _appliedZOrder;
231 uint32_t _clientActiveId = 0; /* last window we told the server to activate (ClientActivate) */
232 bool _zOrderDirty = false;
233 /* Icon cache (RDP thread): index = cacheId * entries + cacheEntry, sized from settings. */
234 std::vector<SdlRailIcon> _iconCache;
235 SdlRailIcon _iconScratch;
236 uint32_t _iconCacheEntries = 0;
237};