FreeRDP
Loading...
Searching...
No Matches
SdlRail Class Reference

#include <sdl_rail.hpp>

Public Member Functions

 SdlRail (SdlContext *context)
 
 SdlRail (const SdlRail &)=delete
 
 SdlRail (SdlRail &&)=delete
 
SdlRailoperator= (const SdlRail &)=delete
 
SdlRailoperator= (SdlRail &&)=delete
 
bool init (RailClientContext *rail)
 
bool uninit (RailClientContext *rail)
 
bool enabled () const
 
bool paint (SDL_Surface *primary, SDL_PixelFormat fallbackFormat, const std::vector< SDL_Rect > &damage)
 
UINT updateWindowFromSurface (gdiGfxSurface *surface)
 
bool ownsWindow (SDL_WindowID id)
 
void invalidateWindow (SDL_WindowID id)
 
void handleMaximized (SDL_WindowID id)
 
void handleMinimized (SDL_WindowID id)
 
void handleRestored (SDL_WindowID id)
 
void handleClose (SDL_WindowID id)
 
void handleFocus (SDL_WindowID id, bool gained)
 
void ensureActive (SDL_WindowID id)
 
bool translateToServer (SDL_WindowID id, float &x, float &y)
 
void noteLeftPress (float x, float y)
 
void noteServerPointer (float x, float y)
 
bool suppressServerInput (SDL_WindowID id)
 
bool suppressServerMotion (SDL_WindowID id)
 
void handleLocalMoveRequested (uint32_t windowId, uint16_t moveType)
 
void noteDragResize (SDL_WindowID id, int w, int h)
 
void syncGeometry (SDL_WindowID id)
 
void completeLocalMoveIfPending ()
 
void completeWaylandResize (bool definitive=false)
 
void noteResizeGrab (SDL_WindowID id)
 
void handleWaylandResize (SDL_WindowID id)
 

Static Public Member Functions

static UINT UpdateWindowFromSurface (RdpgfxClientContext *context, gdiGfxSurface *surface)
 

Detailed Description

RAIL channel handler for the SDL3 client.

Server-authoritative: the server drives window geometry via WINDOW_ORDER and the local borderless windows follow (the RemoteApp frame is server-drawn content). Interactive move/resize is handed to the local WM only on a server request (ServerLocalMoveSize).

Definition at line 45 of file sdl_rail.hpp.

Constructor & Destructor Documentation

◆ SdlRail()

SdlRail::SdlRail ( SdlContext context)
explicit

Definition at line 44 of file sdl_rail.cpp.

44 : _context(context)
45{
46}

Member Function Documentation

◆ completeLocalMoveIfPending()

void SdlRail::completeLocalMoveIfPending ( )

Definition at line 1244 of file sdl_rail.cpp.

1245{
1246 std::unique_lock lock(_windowsLock);
1247 if (_localMove.id == 0)
1248 return;
1249 /* Skip Wayland button-up finalize. */
1250 if (_localMove.wayland)
1251 return;
1252
1253 /* Snapshot and clear drag state atomically under lock. */
1254 const LocalMove drag = _localMove;
1255 _localMove = {};
1256
1257 auto appWindow = getWindow(drag.id);
1258 if (!appWindow || !appWindow->window() || appWindow->isDeleted())
1259 return; /* window gone mid-drag: nothing to release into or report for */
1260
1261 /* Send synthetic button release if WM grab consumed physical release. */
1262 if ((SDL_GetMouseState(nullptr, nullptr) & SDL_BUTTON_LMASK) &&
1263 !(SDL_GetGlobalMouseState(nullptr, nullptr) & SDL_BUTTON_LMASK))
1264 (void)sdl_x11_send_left_button_release(appWindow->window());
1265
1266 /* Finalize WM resize before reading final size. */
1267 (void)SDL_SyncWindow(appWindow->window());
1268
1269 int x = 0;
1270 int y = 0;
1271 int w = 0;
1272 int h = 0;
1273 SDL_GetWindowSize(appWindow->window(), &w, &h);
1274 SDL_GetWindowPosition(appWindow->window(), &x, &y);
1275 const bool isMove = (drag.type == RAIL_WMSZ_MOVE);
1276 const SDL_Rect start = appWindow->outerRect(); /* position frozen at drag start */
1277 /* Preserve server size during move; defer WM snap/tile size sync until loop END to avoid
1278 * server drag-restore conflict. */
1279 if (isMove)
1280 {
1281 if (!appWindow->effectivelyMaximized() && drag.wmSized &&
1282 !appWindow->localMoveSizeChanged())
1283 {
1284 /* Adopt WM size if window was resized during move. */
1285 w = drag.wmSize.x;
1286 h = drag.wmSize.y;
1287 appWindow->deferSnap(appWindow->serverRect({ x, y, w, h }));
1288 }
1289 else
1290 {
1291 w = start.w;
1292 h = start.h;
1293 }
1294 }
1295 /* Finalize modal loop unless server closed it early. */
1296 if (!drag.serverEnded)
1297 {
1298 int px = drag.anchor.x;
1299 int py = drag.anchor.y;
1300 if (isMove && appWindow->localMoveSizeChanged())
1301 {
1302 /* Measure delta from server anchor on drag-restore. */
1303 const SDL_Point sp = appWindow->localMoveServerPos();
1304 const SDL_Rect ii = appWindow->insets();
1305 px = drag.pointer.x + (x + ii.x) - sp.x;
1306 py = drag.pointer.y + (y + ii.y) - sp.y;
1307 }
1308 else if (!appWindow->effectivelyMaximized())
1309 {
1310 const RailEdges e = railEdges(drag.type);
1311 if (isMove || e.left)
1312 px += x - start.x;
1313 else if (e.right)
1314 px += (x + w) - (start.x + start.w);
1315 if (isMove || e.top)
1316 py += y - start.y;
1317 else if (e.bottom)
1318 py += (y + h) - (start.y + start.h);
1319 }
1320 (void)freerdp_client_send_button_event(_context->common(), FALSE, PTR_FLAGS_BUTTON1, px,
1321 py);
1322 /* Server loop unwinding: wait for explicit END order to gate deferred SC_MAXIMIZE. */
1323 appWindow->armLoopEnd();
1324
1325 /* Adopt local geometry without sending ClientWindowMove during mouse drags. */
1326 const SDL_Rect rect = appWindow->serverRect({ x, y, w, h });
1327 const bool restoredMidDrag = appWindow->localMoveSizeChanged();
1328 appWindow->adoptLocalGeometry(rect);
1329 /* Drag complete: apply pending server resize delayed during WM grab. */
1330 if (restoredMidDrag)
1331 appWindow->markGeometryDirty();
1332 (void)sdl_push_user_event(SDL_EVENT_USER_UPDATE);
1333 }
1334 else if (!isMove)
1335 {
1336 /* Sync final resize geometry if server closed loop early. */
1337 clampIntoDesktop(x, y, w, h);
1338 reportAndAdopt(appWindow, x, y, w, h);
1339 }
1340
1341 (void)sdl_x11_set_bit_gravity(appWindow->window(), 0 /* reset to default */);
1342}

◆ completeWaylandResize()

void SdlRail::completeWaylandResize ( bool  definitive = false)

Definition at line 1199 of file sdl_rail.cpp.

1200{
1201 std::unique_lock lock(_windowsLock);
1202 if ((_localMove.id == 0) || !_localMove.wayland)
1203 return;
1204 /* Finalize drag resize on window enter if resize occurred. */
1205 if (!_localMove.sawResize)
1206 {
1207 if (definitive)
1208 {
1209 if (auto* appWindow = getWindow(_localMove.id))
1210 {
1211 appWindow->setLocalMoveActive(false);
1212 /* Repaint now: the dashed placeholder is on screen and an undamaged window would
1213 * otherwise keep it until the next server frame. */
1214 appWindow->invalidateAll();
1215 (void)sdl_push_user_event(SDL_EVENT_USER_UPDATE);
1216 }
1217 _localMove = {}; /* Snapshot and clear drag state under lock. */
1218 }
1219 return;
1220 }
1221
1222 /* Snapshot and clear drag state atomically under lock. */
1223 const LocalMove drag = _localMove;
1224 _localMove = {};
1225
1226 auto appWindow = getWindow(drag.id);
1227 if (!appWindow || !appWindow->window())
1228 return;
1229
1230 (void)SDL_SyncWindow(appWindow->window());
1231 int w = 0;
1232 int h = 0;
1233 SDL_GetWindowSize(appWindow->window(), &w, &h);
1234
1235 /* Derive Wayland origin from anchor edge. */
1236 const SDL_Rect start = appWindow->windowRect();
1237 const RailEdges e = railEdges(drag.type);
1238 int x = e.left ? (start.x + start.w - w) : start.x;
1239 int y = e.top ? (start.y + start.h - h) : start.y;
1240 clampIntoDesktop(x, y, w, h);
1241 reportAndAdopt(appWindow, x, y, w, h);
1242}

◆ enabled()

bool SdlRail::enabled ( ) const
inline

Definition at line 59 of file sdl_rail.hpp.

60 {
61 return _enabled;
62 }

◆ ensureActive()

void SdlRail::ensureActive ( SDL_WindowID  id)

Definition at line 329 of file sdl_rail.cpp.

330{
331 std::unique_lock lock(_windowsLock);
332 auto* appWindow = getWindowBySdlId(id);
333 if (!appWindow || (appWindow->isPopup() && !appWindow->isFullscreen()) || !appWindow->window())
334 return;
335 const auto wid = static_cast<uint32_t>(appWindow->id());
336 if (wid == _clientActiveId)
337 return;
338 sendClientActivate(wid, true);
339}

◆ handleClose()

void SdlRail::handleClose ( SDL_WindowID  id)

Definition at line 262 of file sdl_rail.cpp.

263{
264 std::unique_lock lock(_windowsLock);
265 auto* appWindow = getWindowBySdlId(id);
266 if (!appWindow || !appWindow->window())
267 return;
268 WLog_DBG(TAG, "local close id=0x%08" PRIx32 " -> SC_CLOSE",
269 static_cast<UINT32>(appWindow->id()));
270 sendSystemCommand(appWindow, SC_CLOSE);
271}

◆ handleFocus()

void SdlRail::handleFocus ( SDL_WindowID  id,
bool  gained 
)

Definition at line 294 of file sdl_rail.cpp.

295{
296 std::unique_lock lock(_windowsLock);
297 auto appWindow = getWindowBySdlId(id);
298 if (!appWindow || (appWindow->isPopup() && !appWindow->isFullscreen()) || !appWindow->window())
299 return;
300
301 /* Ignore focus loss while dragging: mapping restore shadow frames can steal focus
302 * and cancel the server modal loop. */
303 if (!gained && (_localMove.id == static_cast<uint32_t>(appWindow->id())))
304 return;
305
306 /* Fallback parent for orphaned popups. */
307 if (gained)
308 _focusedAppId = appWindow->id();
309
310 /* ClientActivate only. Do NOT SDL_RaiseWindow to avoid WM focus loops. */
311 const auto wid = static_cast<uint32_t>(appWindow->id());
312 sendClientActivate(wid, gained);
313}

◆ handleLocalMoveRequested()

void SdlRail::handleLocalMoveRequested ( uint32_t  windowId,
uint16_t  moveType 
)

Definition at line 929 of file sdl_rail.cpp.

930{
931 const bool wayland = sdl::utils::isWaylandDriver();
932 const bool x11 = sdl::utils::isX11Driver();
933 if (!wayland && !x11)
934 return;
935
936 std::unique_lock lock(_windowsLock);
937 auto appWindow = getWindow(windowId);
938 if (!appWindow || !appWindow->window() || appWindow->isPopup())
939 return;
940
941 const bool isMove = (moveType == RAIL_WMSZ_MOVE);
942 WLog_DBG(TAG, "local move start id=0x%08" PRIx32 " driver=%s type=%" PRIu16 " %s", windowId,
943 wayland ? "wayland" : "x11", moveType, isMove ? "move" : "resize");
944 bool started = false;
945 if (wayland)
946 {
947 /* Wayland: positions unreadable, sizes readable. */
948 if (isMove)
949 started = sdl_wayland_begin_move(appWindow->window());
950 else
951 started = sdl_wayland_begin_resize(appWindow->window(), railToXdgEdge(moveType));
952 if (started && !isMove)
953 {
954 _localMove = {}; /* clear all per-drag state as a unit before latching this drag */
955 _localMove.id = windowId;
956 _localMove.wayland = true;
957 _localMove.type = moveType;
958 }
959 }
960 else
961 {
962 /* X11: hand resize to WM, suppress input. */
963 started = sdl_x11_begin_move_size(appWindow->window(), railToNetDirection(moveType));
964 if (started)
965 {
966 _localMove = {}; /* clear all per-drag state as a unit before latching this drag */
967 _localMove.id = windowId;
968 _localMove.type = moveType;
969 /* Latch drag anchors for release calculation. */
970 _localMove.anchor = _lastPressServer;
971 _localMove.pointer = _lastPointerServer;
972 /* Clear prior loop-end state on new drag. */
973 appWindow->clearLoopEnd();
974 /* Set bit gravity to fixed edge. */
975 const RailEdges e = railEdges(moveType);
976 const int row = e.top ? 2 : (e.bottom ? 0 : 1);
977 const int col = e.left ? 2 : (e.right ? 0 : 1);
978 (void)sdl_x11_set_bit_gravity(appWindow->window(), row * 3 + col + 1);
979 }
980 }
981 /* Drive local frame during X11/Wayland drag. */
982 if (started && (!wayland || !isMove))
983 {
984 appWindow->setLocalMoveActive(true);
985 if (!isMove)
986 {
987 /* Anchor the stale frame to the fixed edge (opposite the dragged one). */
988 const RailEdges e = railEdges(moveType);
989 appWindow->setResizeAnchor(e.left, e.top);
990 }
991 }
992 else if (!started)
993 {
994 WLog_WARN(TAG, "WM move failed for RAIL window 0x%08" PRIx32, windowId);
995 }
996}

◆ handleMaximized()

void SdlRail::handleMaximized ( SDL_WindowID  id)

Definition at line 215 of file sdl_rail.cpp.

216{
217 std::unique_lock lock(_windowsLock);
218 auto* appWindow = getWindowBySdlId(id);
219 if (!appWindow || appWindow->isPopup() || !appWindow->window())
220 return;
221 /* Skip the echo of reconcile's own SDL_MaximizeWindow (railMaximized already set). */
222 if (appWindow->railMaximized())
223 {
224 WLog_DBG(TAG, "local maximize skipped id=0x%08" PRIx32 " (already rail-maximized)",
225 static_cast<UINT32>(appWindow->id()));
226 return;
227 }
228 appWindow->setRailMaximized(true);
229 /* Force full repaint (skips dirty-rect). */
230 appWindow->invalidateAll();
231 /* Defer SC_MAXIMIZE if modal move loop is still open. */
232 if (appWindow->loopEndPending())
233 {
234 appWindow->deferMaximize();
235 WLog_DBG(TAG, "local maximize deferred id=0x%08" PRIx32 " (modal loop open)",
236 static_cast<UINT32>(appWindow->id()));
237 }
238 else
239 {
240 WLog_DBG(TAG, "local maximize id=0x%08" PRIx32 " -> SC_MAXIMIZE",
241 static_cast<UINT32>(appWindow->id()));
242 sendSystemCommand(appWindow, SC_MAXIMIZE);
243 }
244 lock.unlock();
245 (void)sdl_push_user_event(SDL_EVENT_USER_UPDATE);
246}

◆ handleMinimized()

void SdlRail::handleMinimized ( SDL_WindowID  id)

Definition at line 248 of file sdl_rail.cpp.

249{
250 std::unique_lock lock(_windowsLock);
251 auto* appWindow = getWindowBySdlId(id);
252 if (!appWindow || appWindow->isPopup() || !appWindow->window())
253 return;
254 if (appWindow->railMinimized())
255 return; /* echo of reconcile's own SDL_MinimizeWindow */
256 appWindow->setRailMinimized(true);
257 WLog_DBG(TAG, "local minimize id=0x%08" PRIx32 " -> SC_MINIMIZE",
258 static_cast<UINT32>(appWindow->id()));
259 sendSystemCommand(appWindow, SC_MINIMIZE);
260}

◆ handleRestored()

void SdlRail::handleRestored ( SDL_WindowID  id)

Definition at line 273 of file sdl_rail.cpp.

274{
275 std::unique_lock lock(_windowsLock);
276 auto* appWindow = getWindowBySdlId(id);
277 if (!appWindow || appWindow->isPopup() || !appWindow->window())
278 return;
279 /* Resolve which state a RESTORED event ends; both clear = echo of our own restore, skip. */
280 if (appWindow->railMinimized())
281 appWindow->setRailMinimized(false);
282 else if (appWindow->railMaximized())
283 appWindow->setRailMaximized(false);
284 else
285 return;
286 WLog_DBG(TAG, "local restore id=0x%08" PRIx32 " -> SC_RESTORE",
287 static_cast<UINT32>(appWindow->id()));
288 appWindow->invalidateAll();
289 sendSystemCommand(appWindow, SC_RESTORE);
290 lock.unlock();
291 (void)sdl_push_user_event(SDL_EVENT_USER_UPDATE);
292}

◆ handleWaylandResize()

void SdlRail::handleWaylandResize ( SDL_WindowID  id)

Definition at line 1167 of file sdl_rail.cpp.

1168{
1169 if (!sdl::utils::isWaylandDriver())
1170 return;
1171 std::unique_lock lock(_windowsLock);
1172 auto* appWindow = getWindowBySdlId(id);
1173 if (!appWindow || appWindow->isPopup() || !appWindow->window())
1174 return;
1175 /* Track resize for active dragged window. */
1176 if (static_cast<uint32_t>(appWindow->id()) == _localMove.id)
1177 {
1178 if (_localMove.wayland)
1179 _localMove.sawResize = true;
1180 return;
1181 }
1182
1183 int w = 0;
1184 int h = 0;
1185 SDL_GetWindowSize(appWindow->window(), &w, &h);
1186 if ((w <= 0) || (h <= 0))
1187 return;
1188
1189 /* Local window size is the OUTER frame (rect + insets; identical off the inset paths). */
1190 const SDL_Rect vis = appWindow->outerRect();
1191 if ((w == vis.w) && (h == vis.h))
1192 return; /* echo of a size we already reported/applied - nothing new */
1193
1194 /* Compositor snap/tile: report via the debounced sync (never from transitional state). */
1195 lock.unlock();
1196 syncGeometry(id);
1197}

◆ init()

bool SdlRail::init ( RailClientContext *  rail)

Definition at line 648 of file sdl_rail.cpp.

649{
650 _rail = rail;
651 if (!rail)
652 return false;
653
654 registerUpdateCallbacks(_context->context()->update);
655
656 {
657 std::unique_lock lock(_windowsLock);
658 /* Move previous windows to deadWindows for main-thread teardown. */
659 for (auto it = _windows.begin(); it != _windows.end();)
660 {
661 auto cur = it++;
662 _deadWindows.insert(_windows.extract(cur));
663 }
664 auto* settings = _context->context()->settings;
665 const uint32_t caches =
666 freerdp_settings_get_uint32(settings, FreeRDP_RemoteAppNumIconCaches);
667 _iconCacheEntries =
668 freerdp_settings_get_uint32(settings, FreeRDP_RemoteAppNumIconCacheEntries);
669 _iconCache.assign(static_cast<size_t>(caches) * _iconCacheEntries, {});
670 }
671
672 rail->custom = this;
673 rail->ServerExecuteResult = SdlRail::server_execute_result;
674 /* ServerSystemParam not implemented: the server only sends screensaver state here. */
675 rail->ServerLocalMoveSize = SdlRail::server_local_move_size;
676 rail->ServerMinMaxInfo = SdlRail::server_min_max_info;
677 /* Keep default ServerHandshake. */
678
679 WLog_WARN(TAG, "RemoteApp/RAIL support in the SDL client is experimental");
680 const RailPlatformCaps& caps = railPlatformCaps();
681 WLog_DBG(TAG, "RAIL channel initialized: driver=%s positionsReadable=%d transparentWindows=%d",
682 sdl::utils::isWaylandDriver() ? "wayland"
683 : (sdl::utils::isX11Driver() ? "x11" : "other"),
684 caps.positionsReadable ? 1 : 0, caps.supportsTransparentWindows ? 1 : 0);
685#if SDL_VERSION_ATLEAST(3, 4, 10)
686 /* Synchronize WM-driven X11 resizes via _NET_WM_SYNC_REQUEST to prevent torn frames. */
687 if (sdl::utils::isX11Driver())
688 SDL_SetHint(SDL_HINT_VIDEO_X11_ENABLE_XSYNC_EXT, "1");
689#endif
690 return true;
691}
WINPR_ATTR_NODISCARD FREERDP_API UINT32 freerdp_settings_get_uint32(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id)
Returns a UINT32 settings value.

◆ invalidateWindow()

void SdlRail::invalidateWindow ( SDL_WindowID  id)

Definition at line 175 of file sdl_rail.cpp.

176{
177 std::unique_lock lock(_windowsLock);
178 auto* appWindow = getWindowBySdlId(id);
179 if (!appWindow)
180 return;
181 appWindow->invalidateAll();
182 lock.unlock();
183 (void)sdl_push_user_event(SDL_EVENT_USER_UPDATE);
184}

◆ noteDragResize()

void SdlRail::noteDragResize ( SDL_WindowID  id,
int  w,
int  h 
)

Definition at line 1045 of file sdl_rail.cpp.

1046{
1047 std::unique_lock lock(_windowsLock);
1048 if ((_localMove.id == 0) || _localMove.wayland || (_localMove.type != RAIL_WMSZ_MOVE))
1049 return;
1050 auto* appWindow = getWindowBySdlId(id);
1051 if (!appWindow || (static_cast<uint32_t>(appWindow->id()) != _localMove.id))
1052 return;
1053 /* Filter out reconcile echo from server drag-restore. */
1054 if (appWindow->localMoveSizeChanged())
1055 {
1056 const SDL_Rect outer = appWindow->outerRect();
1057 if ((w == outer.w) && (h == outer.h))
1058 return;
1059 }
1060 _localMove.wmSized = true;
1061 _localMove.wmSize = { w, h };
1062}

◆ noteLeftPress()

void SdlRail::noteLeftPress ( float  x,
float  y 
)
inline

Definition at line 91 of file sdl_rail.hpp.

92 {
93 _lastPressServer = { static_cast<int>(x), static_cast<int>(y) };
94 _lastPointerServer = _lastPressServer;
95 }

◆ noteResizeGrab()

void SdlRail::noteResizeGrab ( SDL_WindowID  id)

Definition at line 1155 of file sdl_rail.cpp.

1156{
1157 std::unique_lock lock(_windowsLock);
1158 if (_localMove.id == 0)
1159 return;
1160 auto* appWindow = getWindowBySdlId(id);
1161 /* Confirm compositor pointer grab on mouse leave during active drag resize. */
1162 if (appWindow && (static_cast<uint32_t>(appWindow->id()) == _localMove.id) &&
1163 _localMove.wayland)
1164 _localMove.sawResize = true;
1165}

◆ noteServerPointer()

void SdlRail::noteServerPointer ( float  x,
float  y 
)
inline

Definition at line 97 of file sdl_rail.hpp.

98 {
99 _lastPointerServer = { static_cast<int>(x), static_cast<int>(y) };
100 }

◆ ownsWindow()

bool SdlRail::ownsWindow ( SDL_WindowID  id)

Definition at line 169 of file sdl_rail.cpp.

170{
171 std::unique_lock lock(_windowsLock);
172 return getWindowBySdlId(id) != nullptr;
173}

◆ paint()

bool SdlRail::paint ( SDL_Surface *  primary,
SDL_PixelFormat  fallbackFormat,
const std::vector< SDL_Rect > &  damage 
)

Definition at line 378 of file sdl_rail.cpp.

380{
381 if (!_enabled)
382 return true;
383
384 std::unique_lock lock(_windowsLock);
385
386 /* Report workarea once (avoids maximizing under local panels). */
387 if (_sentWorkArea.w == 0)
388 {
389 SDL_Rect usable{};
390 if (SDL_GetDisplayUsableBounds(SDL_GetPrimaryDisplay(), &usable))
391 sendWorkArea(usable);
392 }
393
394 /* Cascade deletion to child popups whose SDL parent was deleted. */
395 for (auto& [wid, win] : _windows)
396 {
397 if (win.isDeleted() || !win.window())
398 continue;
399 for (SDL_Window* p = SDL_GetWindowParent(win.window()); p; p = SDL_GetWindowParent(p))
400 {
401 auto* owner = getWindowBySdlId(SDL_GetWindowID(p));
402 if (!owner || owner->isDeleted())
403 {
404 win.markDeleted();
405 break;
406 }
407 }
408 }
409
410 /* Erase deleted windows deepest child first to avoid SDL child double-free. */
411 std::vector<std::pair<size_t, uint64_t>> dead;
412 for (auto& [wid, win] : _windows)
413 {
414 if (!win.isDeleted())
415 continue;
416 size_t depth = 0;
417 for (SDL_Window* p = win.window() ? SDL_GetWindowParent(win.window()) : nullptr; p;
418 p = SDL_GetWindowParent(p))
419 depth++;
420 dead.emplace_back(depth, wid);
421 }
422 std::sort(dead.begin(), dead.end(),
423 [](const auto& a, const auto& b) { return a.first > b.first; });
424 for (const auto& [depth, wid] : dead)
425 _windows.erase(wid);
426 /* Windows displaced by a same-id recreate (addWindow) also die here, on the main thread. */
427 _deadWindows.clear();
428
429 /* App windows first so popup parents exist before their popups. */
430 for (auto& it : _windows)
431 {
432 auto& win = it.second;
433 if (win.isPopup())
434 continue;
435 /* Parent owned dialogs to owner. */
436 SDL_Window* parent = nullptr;
437 SDL_Rect parentRect{};
438 auto* owner = resolveParent(win.owner());
439 if (owner && (owner != &win))
440 {
441 parent = owner->window();
442 parentRect = owner->outerRect(); /* the SDL window's on-screen geometry */
443 }
444 win.paint(primary, fallbackFormat, damage, parent, parentRect);
445 /* Adopt WM-refused geometry and update server. */
446 SDL_Rect refusedOuter{};
447 if (win.takeWmOverride(refusedOuter))
448 {
449 const SDL_Rect rect = win.serverRect(refusedOuter);
450 const SDL_Rect cur = win.windowRect();
451 WLog_DBG(TAG,
452 "wm-refused adopt id=0x%08" PRIx32
453 " outer=%dx%d -> srv=%d,%d %dx%d (was %dx%d)",
454 static_cast<uint32_t>(win.id()), refusedOuter.w, refusedOuter.h, rect.x,
455 rect.y, rect.w, rect.h, cur.w, cur.h);
456 if (!SDL_RectsEqual(&rect, &cur))
457 sendClientWindowMove(&win, rect);
458 win.adoptLocalGeometry(rect);
459 }
460 }
461
462 /* Request full repaint after first window is realized (fixes reconnect blank windows). */
463 if (!_refreshSent)
464 {
465 for (auto& it : _windows)
466 {
467 if (it.second.isPopup() || !it.second.window())
468 continue;
469 auto* ctx = &_context->common()->context;
470 const auto dw = static_cast<UINT16>(
471 freerdp_settings_get_uint32(ctx->settings, FreeRDP_DesktopWidth));
472 const auto dh = static_cast<UINT16>(
473 freerdp_settings_get_uint32(ctx->settings, FreeRDP_DesktopHeight));
474 const RECTANGLE_16 all = { 0, 0, dw, dh };
475 if (ctx->update && ctx->update->RefreshRect)
476 (void)ctx->update->RefreshRect(ctx, 1, &all);
477 _refreshSent = true;
478 WLog_DBG(TAG, "refresh-rect sent (first app window realized)");
479 break;
480 }
481 }
482
483 for (auto& it : _windows)
484 {
485 auto& popup = it.second;
486 if (!popup.isPopup())
487 continue;
488
489 /* Latch shadow frame classification once identified. */
490 if (!popup.isFrame() && isShadowFrame(popup))
491 popup.setFrame(true);
492
493 /* Anchor drop shadow to adjoining visible popup. */
494 if (popup.isLayered())
495 {
496 constexpr int reach = 48; /* shadow offset+blur spread from its popup, server px */
497 const SDL_Rect sr = popup.windowRect();
498 bool anchored = false;
499 for (auto& other : _windows)
500 {
501 auto& cand = other.second;
502 if ((&cand == &popup) || !cand.isPopup() || cand.isLayered() || !cand.window() ||
503 ((SDL_GetWindowFlags(cand.window()) & SDL_WINDOW_HIDDEN) != 0))
504 continue;
505 SDL_Rect zone = cand.windowRect();
506 zone.x -= reach;
507 zone.y -= reach;
508 zone.w += 2 * reach;
509 zone.h += 2 * reach;
510 /* Shadow must be contained within reach zone to distinguish popup shadows from
511 * app frame edge shadows. */
512 const bool inside = (sr.x >= zone.x) && (sr.y >= zone.y) &&
513 (sr.x + sr.w <= zone.x + zone.w) &&
514 (sr.y + sr.h <= zone.y + zone.h);
515 if (inside)
516 {
517 anchored = true;
518 break;
519 }
520 }
521 popup.setShadowAnchored(anchored);
522 }
523
524 SdlRailWindow* chosen = nullptr;
525 /* Existing popups have an immutable SDL parent; position against it. */
526 if (auto* pw = popup.window())
527 {
528 SDL_Window* p = SDL_GetWindowParent(pw);
529 chosen = p ? getWindowBySdlId(SDL_GetWindowID(p)) : nullptr;
530 }
531 else
532 {
533 chosen = resolvePopupParent(popup);
534 if (!chosen)
535 WLog_DBG(TAG, "popup id=0x%08" PRIx32 " has no parent app window",
536 static_cast<UINT32>(popup.id()));
537 }
538
539 SDL_Window* parent = nullptr;
540 SDL_Rect parentRect{};
541 if (chosen)
542 {
543 parent = chosen->window();
544 parentRect = chosen->outerRect(); /* the SDL window's on-screen geometry */
545 }
546 popup.paint(primary, fallbackFormat, damage, parent, parentRect);
547 }
548
549 /* All live windows now have real X11 handles: realize the server's z-order. */
550 applyZOrder();
551 return true;
552}

◆ suppressServerInput()

bool SdlRail::suppressServerInput ( SDL_WindowID  id)

Definition at line 846 of file sdl_rail.cpp.

847{
848 std::unique_lock lock(_windowsLock);
849 if (_localMove.id == 0)
850 return false;
851 auto* appWindow = getWindowBySdlId(id);
852 return appWindow && appWindow->localMoveActive() && !_localMove.wayland;
853}

◆ suppressServerMotion()

bool SdlRail::suppressServerMotion ( SDL_WindowID  id)

Definition at line 855 of file sdl_rail.cpp.

856{
857 std::unique_lock lock(_windowsLock);
858 if (_localMove.id == 0)
859 return false;
860 auto* appWindow = getWindowBySdlId(id);
861 /* Suppress motion events during active local move. */
862 return appWindow && appWindow->localMoveActive();
863}

◆ syncGeometry()

void SdlRail::syncGeometry ( SDL_WindowID  id)

Definition at line 1088 of file sdl_rail.cpp.

1089{
1090 std::unique_lock lock(_windowsLock);
1091 auto* w = getWindowBySdlId(id);
1092 if (!w || !w->window() || w->isDeleted() || w->isPopup())
1093 return;
1094
1095 /* Geometry is server/WM-owned when maximized or minimized. */
1096 if (w->effectivelyMaximized())
1097 {
1098 reportMaximizedWorkArea(w);
1099 return;
1100 }
1101 if (w->railMinimized())
1102 return;
1103
1104 const auto wid = static_cast<uint32_t>(w->id());
1105 /* Skip geometry sync while awaiting server restored rect. */
1106 if (w->localMoveActive() || (_localMove.id == wid) || w->loopEndPending() ||
1107 w->stateTransitionPending() || w->awaitingRestoreRect())
1108 return;
1109
1110 int lw = 0;
1111 int lh = 0;
1112 SDL_GetWindowSize(w->window(), &lw, &lh);
1113 if ((lw <= 0) || (lh <= 0))
1114 return;
1115
1116 /* Ignore invalid zero-content window size updates. */
1117 const SDL_Rect ins = w->insets();
1118 if (((lw - ins.x - ins.w) <= 0) || ((lh - ins.y - ins.h) <= 0))
1119 {
1120 WLog_DBG(TAG, "geometry sync id=0x%08" PRIx32 " %dx%d dropped (content collapsed)", wid, lw,
1121 lh);
1122 return;
1123 }
1124
1125 const SDL_Rect vis = w->outerRect();
1126 const bool posKnown = railPlatformCaps().positionsReadable;
1127 int lx = vis.x;
1128 int ly = vis.y;
1129 if (posKnown)
1130 SDL_GetWindowPosition(w->window(), &lx, &ly);
1131 else
1132 clampIntoDesktop(lx, ly, lw, lh);
1133
1134 /* Check if window settled at applied target. */
1135 if ((lw == vis.w) && (lh == vis.h) && (!posKnown || ((lx == vis.x) && (ly == vis.y))))
1136 {
1137 w->clearGeomApplyPending();
1138 return; /* Ignore self-echo when geometry converged. */
1139 }
1140
1141 /* Drop transient echo events while client geometry application is in flight. */
1142 if (w->geomApplyPending())
1143 {
1144 /* Allow live Wayland compositor resize to advance beyond in-flight apply. */
1145 const bool overtaken = !posKnown && ((lw > vis.w) || (lh > vis.h));
1146 if (!overtaken)
1147 return;
1148 w->clearGeomApplyPending();
1149 }
1150
1151 WLog_DBG(TAG, "geometry sync id=0x%08" PRIx32 " local=%d,%d %dx%d (sync)", wid, lx, ly, lw, lh);
1152 reportAndAdopt(w, lx, ly, lw, lh);
1153}

◆ translateToServer()

bool SdlRail::translateToServer ( SDL_WindowID  id,
float &  x,
float &  y 
)

Definition at line 341 of file sdl_rail.cpp.

342{
343 std::unique_lock lock(_windowsLock);
344 auto* appWindow = getWindowBySdlId(id);
345 if (!appWindow)
346 return false;
347 SDL_FPoint rpos = { x, y };
348 if (auto* renderer = appWindow->renderer())
349 (void)SDL_RenderCoordinatesFromWindow(renderer, x, y, &rpos.x, &rpos.y);
350 /* Window-local to server-absolute, aligned with surface blit anchor. */
351 const SDL_Point origin = appWindow->serverOrigin();
352 x = rpos.x + static_cast<float>(origin.x);
353 y = rpos.y + static_cast<float>(origin.y);
354 return true;
355}

◆ uninit()

bool SdlRail::uninit ( RailClientContext *  rail)

Definition at line 693 of file sdl_rail.cpp.

694{
695 WINPR_ASSERT(rail);
696 /* Leave rail->custom set: the server callbacks only assert it, so clearing it would crash a
697 * late order in a release build. _enabled gates them instead. */
698 std::unique_lock lock(_windowsLock);
699 _refreshSent = false;
700 /* Reset session and move state on reconnect. */
701 _localMove = {};
702 _sessionMargins = {};
703 _sentWorkArea = {};
704 _clientActiveId = 0;
705 _focusedAppId = 0;
706 _zOrder.clear();
707 _appliedZOrder.clear();
708 _zOrderDirty = false;
709 _iconCache.clear();
710 _iconScratch = {};
711 /* Mark windows for main-thread teardown. */
712 WLog_DBG(TAG, "RAIL channel uninit, marking %zu windows for main-thread teardown",
713 _windows.size());
714 for (auto& kv : _windows)
715 kv.second.markDeleted();
716 _rail = nullptr;
717 _enabled = false;
718 lock.unlock();
719 (void)sdl_push_user_event(SDL_EVENT_USER_UPDATE);
720 return true;
721}

◆ updateWindowFromSurface()

UINT SdlRail::updateWindowFromSurface ( gdiGfxSurface *  surface)

Definition at line 601 of file sdl_rail.cpp.

602{
603 if (!surface)
604 return CHANNEL_RC_OK;
605
606 std::unique_lock lock(_windowsLock);
607 auto appWindow = getWindow(surface->windowId);
608 if (!appWindow)
609 {
610 /* Drop GFX surface for unknown windows (repaints later). */
611 WLog_VRB(TAG, "gfx surface for untracked id=0x%08" PRIx64, surface->windowId);
612 return CHANNEL_RC_OK;
613 }
614
615 /* Clamp mapped dimensions to allocated surface bounds. */
616 const uint32_t w =
617 std::min(surface->mappedWidth ? surface->mappedWidth : surface->width, surface->width);
618 const uint32_t h =
619 std::min(surface->mappedHeight ? surface->mappedHeight : surface->height, surface->height);
620
621 /* Consume per-frame damage. */
622 UINT32 nbRects = 0;
623 const RECTANGLE_16* rects = region16_rects(&surface->invalidRegion, &nbRects);
624 /* Skip undamaged surfaces during EndFrame. */
625 const bool remapped = appWindow->takeSurfaceChange(surface->surfaceId);
626 if ((nbRects == 0) && !remapped)
627 return CHANNEL_RC_OK;
628 appWindow->updateGfxSurface(surface->data, surface->scanline, w, h, rects, nbRects,
629 surface->format);
630 region16_clear(&surface->invalidRegion);
631
632 (void)sdl_push_user_event(SDL_EVENT_USER_UPDATE);
633 return CHANNEL_RC_OK;
634}

◆ UpdateWindowFromSurface()

UINT SdlRail::UpdateWindowFromSurface ( RdpgfxClientContext *  context,
gdiGfxSurface *  surface 
)
static

Definition at line 636 of file sdl_rail.cpp.

637{
638 WINPR_ASSERT(context);
639 auto gdi = static_cast<rdpGdi*>(context->custom);
640 if (!gdi || !gdi->context)
641 return CHANNEL_RC_OK;
642 auto sdl = get_context(gdi->context);
643 if (!sdl)
644 return CHANNEL_RC_OK;
645 return sdl->getRailChannelContext().updateWindowFromSurface(surface);
646}

The documentation for this class was generated from the following files: