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
 
SdlRail & operator= (const SdlRail &)=delete
 
SdlRail & operator= (SdlRail &&)=delete
 
bool init (RailClientContext *rail)
 
bool uninit (RailClientContext *rail)
 
bool enabled () const
 
bool desktopMayShow ()
 
void noteLogonPrompt ()
 
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 handleDisplayChanged (SDL_WindowID id, SDL_DisplayID display)
 
void updateCoordinates ()
 
void ensureActive (SDL_WindowID id)
 
bool translateToServer (SDL_WindowID id, float &x, float &y)
 
void notePopupMoved (SDL_WindowID id)
 
void noteLeftPress (float x, float y)
 
void noteServerPointer (float x, float y)
 
bool dragActive (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 settleWaylandResize ()
 

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 48 of file sdl_rail.cpp.

48 : _context(context)
49{
50}

◆ ~SdlRail()

SdlRail::~SdlRail ( )

Definition at line 52 of file sdl_rail.cpp.

53{
54 railPlatformSetNativeLoopHandler({});
55}

Member Function Documentation

◆ completeLocalMoveIfPending()

void SdlRail::completeLocalMoveIfPending ( )

Definition at line 1455 of file sdl_rail.cpp.

1456{
1457 std::unique_lock lock(_windowsLock);
1458 if (_localMove.id == 0)
1459 return;
1460 /* Skip Wayland button-up finalize. */
1461 if (compositorDrag())
1462 return;
1463
1464 /* Snapshot and clear drag state atomically under lock. */
1465 const LocalMove drag = _localMove;
1466 _localMove = {};
1467
1468 auto appWindow = getWindow(drag.id);
1469 if (!appWindow || !appWindow->window() || appWindow->isDeleted())
1470 return; /* window gone mid-drag: nothing to release into or report for */
1471
1472 /* Send synthetic button release if WM grab consumed physical release. */
1473 if ((SDL_GetMouseState(nullptr, nullptr) & SDL_BUTTON_LMASK) &&
1474 !(SDL_GetGlobalMouseState(nullptr, nullptr) & SDL_BUTTON_LMASK))
1475 if (!railPlatformReleaseButton(appWindow->window()))
1476 WLog_WARN(TAG, "synthetic button release failed");
1477
1478 /* Finalize WM resize before reading final size. */
1479 (void)SDL_SyncWindow(appWindow->window());
1480
1481 int x = 0;
1482 int y = 0;
1483 int w = 0;
1484 int h = 0;
1485 SDL_GetWindowSize(appWindow->window(), &w, &h);
1486 SDL_GetWindowPosition(appWindow->window(), &x, &y);
1487 const bool isMove = (drag.type == RAIL_WMSZ_MOVE);
1488 const SDL_Rect start = appWindow->outerRect(); /* position frozen at drag start */
1489 /* Preserve server size during move; defer WM snap/tile size sync until loop END to avoid
1490 * server drag-restore conflict. */
1491 if (isMove)
1492 {
1493 if (!appWindow->effectivelyMaximized() && drag.wmSized &&
1494 !appWindow->localMoveSizeChanged())
1495 {
1496 /* Adopt WM size if window was resized during move. */
1497 w = drag.wmSize.x;
1498 h = drag.wmSize.y;
1499 appWindow->deferSnap(appWindow->serverRect({ x, y, w, h }));
1500 }
1501 else
1502 {
1503 w = start.w;
1504 h = start.h;
1505 }
1506 }
1507 /* Finalize modal loop unless server closed it early. */
1508 if (!drag.serverEnded)
1509 {
1510 int px = drag.anchor.x;
1511 int py = drag.anchor.y;
1512 if (isMove && appWindow->localMoveSizeChanged())
1513 {
1514 /* Measure delta from server anchor on drag-restore. */
1515 const SDL_Point sp = appWindow->localMoveServerPos();
1516 const SDL_Rect now = appWindow->serverRect({ x, y, w, h });
1517 px = drag.pointer.x + now.x - sp.x;
1518 py = drag.pointer.y + now.y - sp.y;
1519 }
1520 else if (!appWindow->effectivelyMaximized())
1521 {
1522 const RailEdges e = railEdges(drag.type);
1523 if (isMove || e.left)
1524 px += x - start.x;
1525 else if (e.right)
1526 px += (x + w) - (start.x + start.w);
1527 if (isMove || e.top)
1528 py += y - start.y;
1529 else if (e.bottom)
1530 py += (y + h) - (start.y + start.h);
1531 }
1532 (void)freerdp_client_send_button_event(_context->common(), FALSE, PTR_FLAGS_BUTTON1, px,
1533 py);
1534 /* Server loop unwinding: wait for explicit END order to gate deferred SC_MAXIMIZE. */
1535 appWindow->armLoopEnd();
1536
1537 /* Adopt local geometry without sending ClientWindowMove during mouse drags. */
1538 const SDL_Rect rect = appWindow->serverRect({ x, y, w, h });
1539 const bool restoredMidDrag = appWindow->localMoveSizeChanged();
1540 appWindow->adoptLocalGeometry(rect);
1541 /* Drag complete: apply pending server resize delayed during WM grab. */
1542 if (restoredMidDrag)
1543 appWindow->markGeometryDirty();
1544 (void)sdl_push_user_event(SDL_EVENT_USER_UPDATE);
1545 }
1546 else if (!isMove)
1547 {
1548 /* Sync final resize geometry if server closed loop early. */
1549 clampIntoDesktop(*appWindow, x, y, w, h);
1550 reportAndAdopt(appWindow, x, y, w, h);
1551 }
1552
1553 railPlatformEndDrag(appWindow->window());
1554}

◆ completeWaylandResize()

void SdlRail::completeWaylandResize ( bool  definitive = false)

Definition at line 1410 of file sdl_rail.cpp.

1411{
1412 std::unique_lock lock(_windowsLock);
1413 if ((_localMove.id == 0) || !compositorDrag())
1414 return;
1415 /* Finalize drag resize on window enter if resize occurred. */
1416 if (!_localMove.sawResize)
1417 {
1418 if (definitive)
1419 {
1420 if (auto* appWindow = getWindow(_localMove.id))
1421 {
1422 appWindow->setLocalMoveActive(false);
1423 /* Repaint now: the dashed placeholder is on screen and an undamaged window would
1424 * otherwise keep it until the next server frame. */
1425 appWindow->invalidateAll();
1426 (void)sdl_push_user_event(SDL_EVENT_USER_UPDATE);
1427 }
1428 _localMove = {}; /* Snapshot and clear drag state under lock. */
1429 }
1430 return;
1431 }
1432
1433 /* Snapshot and clear drag state atomically under lock. */
1434 const LocalMove drag = _localMove;
1435 _localMove = {};
1436
1437 auto appWindow = getWindow(drag.id);
1438 if (!appWindow || !appWindow->window())
1439 return;
1440
1441 (void)SDL_SyncWindow(appWindow->window());
1442 int w = 0;
1443 int h = 0;
1444 SDL_GetWindowSize(appWindow->window(), &w, &h);
1445
1446 /* Derive Wayland origin from anchor edge. */
1447 const SDL_Rect start = appWindow->outerRect();
1448 const RailEdges e = railEdges(drag.type);
1449 int x = e.left ? (start.x + start.w - w) : start.x;
1450 int y = e.top ? (start.y + start.h - h) : start.y;
1451 clampIntoDesktop(*appWindow, x, y, w, h);
1452 reportAndAdopt(appWindow, x, y, w, h);
1453}

◆ desktopMayShow()

bool SdlRail::desktopMayShow ( )

Definition at line 477 of file sdl_rail.cpp.

478{
479 if (_desktopReleased)
480 return true;
481 const uint64_t now = SDL_GetTicks();
482 if (_desktopHoldUntil == 0)
483 {
484 _desktopHoldUntil = now + kDesktopHoldMs;
485 /* Frames may stop coming while the logon waits; wake up to re-check. */
486 if (!sdl_push_update_after(kDesktopHoldMs + 1))
487 WLog_WARN(TAG, "SDL_AddTimer failed: %s", SDL_GetError());
488 WLog_DBG(TAG, "RemoteApp starting: holding the logon desktop back");
489 return false;
490 }
491 if (now < _desktopHoldUntil)
492 return false;
493 WLog_DBG(TAG, "RemoteApp not started after %" PRIu32 " ms, showing the desktop",
494 kDesktopHoldMs);
495 _desktopReleased = true;
496 return true;
497}

◆ dragActive()

bool SdlRail::dragActive ( SDL_WindowID  id)

Definition at line 1049 of file sdl_rail.cpp.

1050{
1051 return !compositorDrag() && suppressServerMotion(id);
1052}

◆ 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 372 of file sdl_rail.cpp.

373{
374 std::unique_lock lock(_windowsLock);
375 auto* appWindow = getWindowBySdlId(id);
376 if (!appWindow || !appWindow->activatable() || !appWindow->window())
377 return;
378 const auto wid = static_cast<uint32_t>(appWindow->id());
379 if (wid == _clientActiveId)
380 return;
381 sendClientActivate(wid, true);
382}

◆ handleClose()

void SdlRail::handleClose ( SDL_WindowID  id)

Definition at line 280 of file sdl_rail.cpp.

281{
282 std::unique_lock lock(_windowsLock);
283 auto* appWindow = getWindowBySdlId(id);
284 if (!appWindow || !appWindow->window())
285 return;
286 WLog_DBG(TAG, "local close id=0x%08" PRIx32 " -> SC_CLOSE",
287 static_cast<UINT32>(appWindow->id()));
288 sendSystemCommand(appWindow, SC_CLOSE);
289}

◆ handleDisplayChanged()

void SdlRail::handleDisplayChanged ( SDL_WindowID  id,
SDL_DisplayID  display 
)

Definition at line 1167 of file sdl_rail.cpp.

1168{
1169 if (railPlatformCaps().supportsWindowPositions)
1170 return;
1171 std::unique_lock lock(_windowsLock);
1172 auto* appWindow = getWindowBySdlId(id);
1173 if (!appWindow || appWindow->isPopup() || appWindow->effectivelyMaximized())
1174 return;
1175 /* The end of the server move loop would undo it. */
1176 if (_moveLoop.id == appWindow->id())
1177 {
1178 _moveLoop.display = display;
1179 return;
1180 }
1181 moveToDisplay(appWindow, display);
1182}

◆ handleFocus()

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

Definition at line 319 of file sdl_rail.cpp.

320{
321 std::unique_lock lock(_windowsLock);
322 auto appWindow = getWindowBySdlId(id);
323 if (!appWindow || !appWindow->activatable() || !appWindow->window())
324 return;
325
326 /* Ignore focus loss while dragging: mapping restore shadow frames can steal focus
327 * and cancel the server modal loop. */
328 if (!gained && (_localMove.id == static_cast<uint32_t>(appWindow->id())))
329 return;
330
331 /* Fallback parent for orphaned popups. */
332 if (gained)
333 _focusedAppId = appWindow->id();
334
335 /* Only report activation, never deactivation (see the X11 client, #13422).
336 * The server treats a deactivation of the owner as "no active window in
337 * this application" and closes popups/menus that have just been opened
338 * when the compositor moves focus to them. The activation that follows
339 * (or an explicit click, see ensureActive()) is enough to route input. */
340 if (!gained)
341 return;
342
343 /* A window owned by this one is currently active (e.g. a profile card or
344 * a transient dialog). On a real desktop the owner is only activated by a
345 * click; focus-follows-mouse compositors hand focus back on hover, which
346 * would make the server close the owned popup. Leave that to
347 * ensureActive() (explicit click). */
348 const auto* active = getWindow(_clientActiveId);
349 if (active && (active != appWindow) &&
350 (active->owner() == static_cast<uint32_t>(appWindow->id())))
351 return;
352
353 /* ClientActivate only. Do NOT SDL_RaiseWindow to avoid WM focus loops. */
354 const auto wid = static_cast<uint32_t>(appWindow->id());
355 sendClientActivate(wid, true);
356}

◆ handleLocalMoveRequested()

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

Definition at line 1109 of file sdl_rail.cpp.

1110{
1111 /* Native modal loop: runs unlocked until the drag ends. */
1112 SDL_Window* sdlWindow = beginLocalMove(windowId, moveType);
1113 if (!sdlWindow)
1114 return;
1115 _sizeMoveRedraws = 0;
1116 railPlatformRunModalDrag(sdlWindow, moveType);
1117 WLog_DBG(TAG, "modal drag end id=0x%08" PRIx32 " redraws=%" PRIu32, windowId, _sizeMoveRedraws);
1118 completeLocalMoveIfPending();
1119}

◆ handleMaximized()

void SdlRail::handleMaximized ( SDL_WindowID  id)

Definition at line 230 of file sdl_rail.cpp.

231{
232 std::unique_lock lock(_windowsLock);
233 auto* appWindow = getWindowBySdlId(id);
234 if (!appWindow || appWindow->isPopup() || !appWindow->window())
235 return;
236 /* Skip echo of programmatic maximize, but trigger reconcile to update insets. */
237 if (appWindow->railMaximized())
238 {
239 WLog_DBG(TAG, "local maximize skipped id=0x%08" PRIx32 " (already rail-maximized)",
240 static_cast<UINT32>(appWindow->id()));
241
242 if (!sdl_push_user_event(SDL_EVENT_USER_UPDATE))
243 WLog_WARN(TAG, "sdl_push_user_event failed");
244 return;
245 }
246 appWindow->setRailMaximized(true);
247 /* Force full repaint (skips dirty-rect). */
248 appWindow->invalidateAll();
249 /* Defer SC_MAXIMIZE if modal move loop is still open. */
250 if (appWindow->loopEndPending())
251 {
252 appWindow->deferMaximize();
253 WLog_DBG(TAG, "local maximize deferred id=0x%08" PRIx32 " (modal loop open)",
254 static_cast<UINT32>(appWindow->id()));
255 }
256 else
257 {
258 WLog_DBG(TAG, "local maximize id=0x%08" PRIx32 " -> SC_MAXIMIZE",
259 static_cast<UINT32>(appWindow->id()));
260 sendSystemCommand(appWindow, SC_MAXIMIZE);
261 }
262
263 (void)sdl_push_user_event(SDL_EVENT_USER_UPDATE);
264}

◆ handleMinimized()

void SdlRail::handleMinimized ( SDL_WindowID  id)

Definition at line 266 of file sdl_rail.cpp.

267{
268 std::unique_lock lock(_windowsLock);
269 auto* appWindow = getWindowBySdlId(id);
270 if (!appWindow || appWindow->isPopup() || !appWindow->window())
271 return;
272 if (appWindow->railMinimized())
273 return; /* echo of reconcile's own SDL_MinimizeWindow */
274 appWindow->setRailMinimized(true);
275 WLog_DBG(TAG, "local minimize id=0x%08" PRIx32 " -> SC_MINIMIZE",
276 static_cast<UINT32>(appWindow->id()));
277 sendSystemCommand(appWindow, SC_MINIMIZE);
278}

◆ handleRestored()

void SdlRail::handleRestored ( SDL_WindowID  id)

Definition at line 291 of file sdl_rail.cpp.

292{
293 std::unique_lock lock(_windowsLock);
294 auto* appWindow = getWindowBySdlId(id);
295 if (!appWindow || appWindow->isPopup() || !appWindow->window())
296 return;
297 /* Resolve restored state; trigger reconcile in case WM cleared flags asynchronously. */
298 if (appWindow->railMinimized())
299 {
300 appWindow->setRailMinimized(false);
301 appWindow->noteLocalRestore();
302 }
303 else if (appWindow->railMaximized())
304 appWindow->setRailMaximized(false);
305 else
306 {
307 if (!sdl_push_user_event(SDL_EVENT_USER_UPDATE))
308 WLog_WARN(TAG, "sdl_push_user_event failed");
309 return;
310 }
311 WLog_DBG(TAG, "local restore id=0x%08" PRIx32 " -> SC_RESTORE",
312 static_cast<UINT32>(appWindow->id()));
313 appWindow->invalidateAll();
314 sendSystemCommand(appWindow, SC_RESTORE);
315
316 (void)sdl_push_user_event(SDL_EVENT_USER_UPDATE);
317}

◆ init()

bool SdlRail::init ( RailClientContext *  rail)

Definition at line 821 of file sdl_rail.cpp.

822{
823 _rail = rail;
824 if (!rail)
825 return false;
826
827 registerUpdateCallbacks(_context->context()->update);
828
829 {
830 std::unique_lock lock(_windowsLock);
831 /* Move previous windows to deadWindows for main-thread teardown. */
832 for (auto it = _windows.begin(); it != _windows.end();)
833 {
834 auto cur = it++;
835 _deadWindows.insert(_windows.extract(cur));
836 }
837 auto* settings = _context->context()->settings;
838 const uint32_t caches =
839 freerdp_settings_get_uint32(settings, FreeRDP_RemoteAppNumIconCaches);
840 _iconCacheEntries =
841 freerdp_settings_get_uint32(settings, FreeRDP_RemoteAppNumIconCacheEntries);
842 _iconCache.assign(static_cast<size_t>(caches) * _iconCacheEntries, {});
843 }
844
845 rail->custom = this;
846 rail->ServerExecuteResult = SdlRail::server_execute_result;
847 /* ServerSystemParam not implemented: the server only sends screensaver state here. */
848 rail->ServerLocalMoveSize = SdlRail::server_local_move_size;
849 rail->ServerMinMaxInfo = SdlRail::server_min_max_info;
850 /* Keep default ServerHandshake. */
851
852 WLog_WARN(TAG, "RemoteApp/RAIL support in the SDL client is experimental");
853 {
854 std::unique_lock lock(_windowsLock);
855 updateCoordinates();
856 }
857 const RailPlatformCaps& caps = railPlatformCaps();
858 railPlatformSetNativeLoopHandler({ this, redrawDuringSizeMove, nativeResize });
859 WLog_DBG(TAG, "RAIL channel initialized: driver=%s windowPositions=%d transparentWindows=%d",
860 SDL_GetCurrentVideoDriver(), caps.supportsWindowPositions ? 1 : 0,
861 caps.supportsTransparentWindows ? 1 : 0);
862#if SDL_VERSION_ATLEAST(3, 4, 10)
863 /* Synchronize WM-driven X11 resizes via _NET_WM_SYNC_REQUEST to prevent torn frames. */
864 if (sdl::utils::isX11Driver())
865 {
866 if (!SDL_SetHint(SDL_HINT_VIDEO_X11_ENABLE_XSYNC_EXT, "1"))
867 WLog_WARN(TAG, "SDL_SetHint(SDL_HINT_VIDEO_X11_ENABLE_XSYNC_EXT) failed: %s",
868 SDL_GetError());
869 }
870#endif
871 /* Like Windows, the click that activates a window also reaches the app. */
872 if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"))
873 WLog_WARN(TAG, "SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH) failed: %s", SDL_GetError());
874 return true;
875}
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 182 of file sdl_rail.cpp.

183{
184 std::unique_lock lock(_windowsLock);
185 auto* appWindow = getWindowBySdlId(id);
186 if (!appWindow)
187 return;
188 appWindow->invalidateAll();
189
190 (void)sdl_push_user_event(SDL_EVENT_USER_UPDATE);
191}

◆ noteDragResize()

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

Definition at line 1258 of file sdl_rail.cpp.

1259{
1260 std::unique_lock lock(_windowsLock);
1261 if ((_localMove.id == 0) || compositorDrag() || (_localMove.type != RAIL_WMSZ_MOVE))
1262 return;
1263 auto* appWindow = getWindowBySdlId(id);
1264 if (!appWindow || (static_cast<uint32_t>(appWindow->id()) != _localMove.id))
1265 return;
1266 /* Filter out reconcile echo from server drag-restore. */
1267 if (appWindow->localMoveSizeChanged())
1268 {
1269 const SDL_Rect outer = appWindow->outerRect();
1270 if ((w == outer.w) && (h == outer.h))
1271 return;
1272 }
1273 _localMove.wmSized = true;
1274 _localMove.wmSize = { w, h };
1275}

◆ noteLeftPress()

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

Definition at line 100 of file sdl_rail.hpp.

101 {
102 _lastPressServer = { static_cast<int>(x), static_cast<int>(y) };
103 _lastPointerServer = _lastPressServer;
104 }

◆ noteLogonPrompt()

void SdlRail::noteLogonPrompt ( )

Definition at line 499 of file sdl_rail.cpp.

500{
501 if (_desktopReleased.exchange(true))
502 return;
503 WLog_DBG(TAG, "logon dialog on the server, showing the desktop");
504 if (!sdl_push_user_event(SDL_EVENT_USER_UPDATE))
505 WLog_WARN(TAG, "sdl_push_user_event failed");
506}

◆ notePopupMoved()

void SdlRail::notePopupMoved ( SDL_WindowID  id)

Definition at line 412 of file sdl_rail.cpp.

413{
414 /* X11 input already matches the server rect; only Wayland lags behind it. */
415 if (railPlatformCaps().supportsWindowPositions)
416 return;
417 std::unique_lock lock(_windowsLock);
418 auto* w = getWindowBySdlId(id);
419 if (!w || !w->isPopup())
420 return;
421 w->notePlaced();
422 /* Show the held frame now: that commit applies the move. */
423 if (!sdl_push_user_event(SDL_EVENT_USER_UPDATE))
424 WLog_WARN(TAG, "could not wake the paint loop for a moved popup");
425}

◆ noteResizeGrab()

void SdlRail::noteResizeGrab ( SDL_WindowID  id)

Definition at line 1394 of file sdl_rail.cpp.

1395{
1396 std::unique_lock lock(_windowsLock);
1397 if (_localMove.id == 0)
1398 return;
1399 auto* appWindow = getWindowBySdlId(id);
1400 /* Mouse leave or a configure during the drag: the compositor resize is under way. */
1401 if (!appWindow || (static_cast<uint32_t>(appWindow->id()) != _localMove.id) ||
1402 !compositorDrag())
1403 return;
1404 _localMove.sawResize = true;
1405 _localMove.lastResize = SDL_GetTicks();
1406 if (SDL_AddTimer(kResizeSettleMs, resizeSettleTimer, this) == 0)
1407 WLog_WARN(TAG, "resize settle timer failed: %s", SDL_GetError());
1408}

◆ noteServerPointer()

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

Definition at line 106 of file sdl_rail.hpp.

107 {
108 _lastPointerServer = { static_cast<int>(x), static_cast<int>(y) };
109 }

◆ ownsWindow()

bool SdlRail::ownsWindow ( SDL_WindowID  id)

Definition at line 176 of file sdl_rail.cpp.

177{
178 std::unique_lock lock(_windowsLock);
179 return getWindowBySdlId(id) != nullptr;
180}

◆ paint()

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

Definition at line 508 of file sdl_rail.cpp.

510{
511 if (!_enabled)
512 return true;
513
514 std::unique_lock lock(_windowsLock);
515
516 /* Report workarea once (avoids maximizing under local panels). */
517 if (_sentWorkArea.w == 0)
518 {
519 SDL_Rect usable{};
520 if (SDL_GetDisplayUsableBounds(SDL_GetPrimaryDisplay(), &usable))
521 sendWorkArea(usable);
522 }
523
524 /* Windows whose SDL parent is going away (deleted by the server, or displaced by a same-id
525 * recreate, see addWindow). Popups owned by that window die with it (the server deletes
526 * them too), and so do non-popup windows, as before. Any other popup keeps its entry: the
527 * server still owns it. A popup without an owner (e.g. a tray notification) is parented to
528 * the focused app window as a fallback; deleting it locally when that window closes
529 * desynchronizes us from the server, which keeps sending orders for it. It only drops its
530 * local SDL window here and the next pass re-creates it under a new parent.
531 * SDL destroys child windows together with their parent, so every window that is about to
532 * lose its SDL window (deleted, displaced, or under one of those) releases it here, deepest
533 * child first, before any entry is destroyed below. */
534 std::vector<std::pair<size_t, SdlRailWindow*>> release;
535 for (auto& [wid, win] : _windows)
536 {
537 if (!win.window())
538 continue;
539 size_t depth = 0;
540 bool orphan = false;
541 bool ownerDeleted = false;
542 for (SDL_Window* p = SDL_GetWindowParent(win.window()); p; p = SDL_GetWindowParent(p))
543 {
544 depth++;
545 auto* owner = getWindowBySdlId(SDL_GetWindowID(p));
546 if (!owner)
547 orphan = true;
548 else if (owner->isDeleted())
549 {
550 orphan = true;
551 if (owner->id() == win.owner())
552 ownerDeleted = true;
553 }
554 }
555 if (orphan && !win.isDeleted() && (ownerDeleted || !win.isPopup()))
556 win.markDeleted();
557 if (orphan || win.isDeleted())
558 release.emplace_back(depth, &win);
559 }
560 for (auto& [wid, win] : _deadWindows)
561 {
562 if (!win.window())
563 continue;
564 size_t depth = 0;
565 for (SDL_Window* p = SDL_GetWindowParent(win.window()); p; p = SDL_GetWindowParent(p))
566 depth++;
567 release.emplace_back(depth, &win);
568 }
569 std::sort(release.begin(), release.end(),
570 [](const auto& a, const auto& b) { return a.first > b.first; });
571 for (auto& [depth, win] : release)
572 win->releaseWindow();
573
574 /* Erase deleted windows; their SDL windows are gone already. */
575 for (auto it = _windows.begin(); it != _windows.end();)
576 it = it->second.isDeleted() ? _windows.erase(it) : std::next(it);
577 /* Windows displaced by a same-id recreate (addWindow) also die here, on the main thread. */
578 _deadWindows.clear();
579
580 /* App windows first so popup parents exist before their popups. */
581 for (auto& it : _windows)
582 {
583 auto& win = it.second;
584 if (win.isPopup())
585 continue;
586 /* Parent owned dialogs to owner. */
587 SDL_Window* parent = nullptr;
588 SDL_Rect parentRect{};
589 auto* owner = resolveParent(win.owner());
590 if (owner && (owner != &win))
591 {
592 parent = owner->window();
593 parentRect = owner->outerRect(); /* the SDL window's on-screen geometry */
594 }
595 win.paint(primary, fallbackFormat, damage, parent, parentRect);
596 /* Adopt WM-refused geometry and update server. */
597 SDL_Rect refusedOuter{};
598 if (win.takeWmOverride(refusedOuter))
599 {
600 const SDL_Rect rect = win.serverRect(refusedOuter);
601 const SDL_Rect cur = win.windowRect();
602 WLog_DBG(TAG,
603 "wm-refused adopt id=0x%08" PRIx32
604 " outer=%dx%d -> srv=%d,%d %dx%d (was %dx%d)",
605 static_cast<uint32_t>(win.id()), refusedOuter.w, refusedOuter.h, rect.x,
606 rect.y, rect.w, rect.h, cur.w, cur.h);
607 if (!SDL_RectsEqual(&rect, &cur))
608 sendClientWindowMove(&win, rect);
609 win.adoptLocalGeometry(rect);
610 }
611 /* Resend a size the server left unanswered. */
612 SDL_Rect unconfirmed{};
613 if (win.takeUnconfirmedReport(unconfirmed) && !win.railMinimized() &&
614 !win.effectivelyMaximized())
615 {
616 WLog_DBG(TAG, "size resent id=0x%08" PRIx32 " %dx%d (not confirmed)",
617 static_cast<uint32_t>(win.id()), unconfirmed.w, unconfirmed.h);
618 sendClientWindowMove(&win, unconfirmed);
619 }
620 if (win.takeRestoreRetry())
621 {
622 WLog_DBG(TAG, "restore resent id=0x%08" PRIx32, static_cast<uint32_t>(win.id()));
623 sendSystemCommand(&win, SC_RESTORE);
624 }
625 }
626
627 /* Request full repaint after first window is realized (fixes reconnect blank windows). */
628 if (!_refreshSent)
629 {
630 for (auto& it : _windows)
631 {
632 if (it.second.isPopup() || !it.second.window())
633 continue;
634 auto* ctx = &_context->common()->context;
635 const auto dw = static_cast<UINT16>(
636 freerdp_settings_get_uint32(ctx->settings, FreeRDP_DesktopWidth));
637 const auto dh = static_cast<UINT16>(
638 freerdp_settings_get_uint32(ctx->settings, FreeRDP_DesktopHeight));
639 const RECTANGLE_16 all = { 0, 0, dw, dh };
640 if (ctx->update && ctx->update->RefreshRect)
641 (void)ctx->update->RefreshRect(ctx, 1, &all);
642 _refreshSent = true;
643 WLog_DBG(TAG, "refresh-rect sent (first app window realized)");
644 break;
645 }
646 }
647
648 for (auto& it : _windows)
649 {
650 auto& popup = it.second;
651 if (!popup.isPopup())
652 continue;
653
654 /* Latch shadow frame classification once identified. */
655 if (!popup.isFrame() && isShadowFrame(popup))
656 popup.setFrame(true);
657
658 /* Anchor drop shadow to adjoining visible popup. */
659 if (popup.isLayered())
660 {
661 constexpr int reach = 48; /* shadow offset+blur spread from its popup, server px */
662 const SDL_Rect sr = popup.windowRect();
663 bool anchored = false;
664 for (auto& other : _windows)
665 {
666 auto& cand = other.second;
667 if ((&cand == &popup) || !cand.isPopup() || cand.isLayered() || !cand.window() ||
668 ((SDL_GetWindowFlags(cand.window()) & SDL_WINDOW_HIDDEN) != 0))
669 continue;
670 SDL_Rect zone = cand.windowRect();
671 zone.x -= reach;
672 zone.y -= reach;
673 zone.w += 2 * reach;
674 zone.h += 2 * reach;
675 /* Shadow must be contained within reach zone to distinguish popup shadows from
676 * app frame edge shadows. */
677 const bool inside = (sr.x >= zone.x) && (sr.y >= zone.y) &&
678 (sr.x + sr.w <= zone.x + zone.w) &&
679 (sr.y + sr.h <= zone.y + zone.h);
680 if (inside)
681 {
682 anchored = true;
683 break;
684 }
685 }
686 popup.setShadowAnchored(anchored);
687 }
688
689 SdlRailWindow* chosen = nullptr;
690 /* Existing popups have an immutable SDL parent; position against it. */
691 if (auto* pw = popup.window())
692 {
693 SDL_Window* p = SDL_GetWindowParent(pw);
694 chosen = p ? getWindowBySdlId(SDL_GetWindowID(p)) : nullptr;
695 }
696 else
697 {
698 chosen = resolvePopupParent(popup);
699 if (!chosen)
700 WLog_DBG(TAG, "popup id=0x%08" PRIx32 " has no parent app window",
701 static_cast<UINT32>(popup.id()));
702 }
703
704 SDL_Window* parent = nullptr;
705 SDL_Rect parentRect{};
706 if (chosen)
707 {
708 parent = chosen->window();
709 parentRect = chosen->outerRect(); /* the SDL window's on-screen geometry */
710 }
711 popup.paint(primary, fallbackFormat, damage, parent, parentRect);
712 }
713
714 /* All live windows now have real X11 handles: realize the server's z-order. */
715 applyZOrder();
716 return true;
717}

◆ settleWaylandResize()

void SdlRail::settleWaylandResize ( )

Definition at line 1383 of file sdl_rail.cpp.

1384{
1385 {
1386 std::unique_lock lock(_windowsLock);
1387 if ((_localMove.id == 0) || !compositorDrag() || !_localMove.sawResize ||
1388 (SDL_GetTicks() - _localMove.lastResize < kResizeSettleMs))
1389 return;
1390 }
1391 completeWaylandResize();
1392}

◆ suppressServerMotion()

bool SdlRail::suppressServerMotion ( SDL_WindowID  id)

Definition at line 1054 of file sdl_rail.cpp.

1055{
1056 std::unique_lock lock(_windowsLock);
1057 if (_localMove.id == 0)
1058 return false;
1059 auto* appWindow = getWindowBySdlId(id);
1060 /* Suppress motion events during active local move. */
1061 return appWindow && appWindow->localMoveActive();
1062}

◆ syncGeometry()

void SdlRail::syncGeometry ( SDL_WindowID  id)

Definition at line 1304 of file sdl_rail.cpp.

1305{
1306 std::unique_lock lock(_windowsLock);
1307 auto* w = getWindowBySdlId(id);
1308 if (!w || !w->window() || w->isDeleted() || w->isPopup())
1309 return;
1310
1311 /* Geometry is server/WM-owned when maximized or minimized. */
1312 if (w->effectivelyMaximized())
1313 {
1314 reportMaximizedWorkArea(w);
1315 return;
1316 }
1317 if (w->railMinimized())
1318 return;
1319
1320 const auto wid = static_cast<uint32_t>(w->id());
1321 /* Skip geometry sync while awaiting server restored rect. */
1322 if (w->localMoveActive() || (_localMove.id == wid) || w->loopEndPending() ||
1323 w->stateTransitionPending() || w->awaitingRestoreRect() || w->insetsStale())
1324 return;
1325
1326 int lw = 0;
1327 int lh = 0;
1328 SDL_GetWindowSize(w->window(), &lw, &lh);
1329 if ((lw <= 0) || (lh <= 0))
1330 return;
1331
1332 /* Ignore invalid zero-content window size updates. */
1333 const SDL_Rect ins = w->insets();
1334 if (((lw - ins.x - ins.w) <= 0) || ((lh - ins.y - ins.h) <= 0))
1335 {
1336 WLog_DBG(TAG, "geometry sync id=0x%08" PRIx32 " %dx%d dropped (content collapsed)", wid, lw,
1337 lh);
1338 return;
1339 }
1340
1341 const SDL_Rect vis = w->outerRect();
1342 const bool posKnown = railPlatformCaps().supportsWindowPositions;
1343 int lx = vis.x;
1344 int ly = vis.y;
1345 if (posKnown)
1346 SDL_GetWindowPosition(w->window(), &lx, &ly);
1347 else
1348 clampIntoDesktop(*w, lx, ly, lw, lh);
1349
1350 /* Check if window settled at applied target. */
1351 if ((lw == vis.w) && (lh == vis.h) && (!posKnown || ((lx == vis.x) && (ly == vis.y))))
1352 {
1353 w->clearGeomApplyPending();
1354 return; /* Ignore self-echo when geometry converged. */
1355 }
1356
1357 /* Drop transient echo events while client geometry application is in flight. */
1358 if (w->geomApplyPending())
1359 {
1360 /* Allow live Wayland compositor resize to advance beyond in-flight apply. */
1361 const bool overtaken = !posKnown && ((lw > vis.w) || (lh > vis.h));
1362 if (!overtaken)
1363 return;
1364 w->clearGeomApplyPending();
1365 }
1366
1367 WLog_DBG(TAG, "geometry sync id=0x%08" PRIx32 " local=%d,%d %dx%d (sync)", wid, lx, ly, lw, lh);
1368 reportAndAdopt(w, lx, ly, lw, lh);
1369}

◆ translateToServer()

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

Definition at line 384 of file sdl_rail.cpp.

385{
386 std::unique_lock lock(_windowsLock);
387 auto* appWindow = getWindowBySdlId(id);
388 if (!appWindow)
389 return false;
390 SDL_FPoint rpos = { x, y };
391 if (auto* renderer = appWindow->renderer())
392 (void)SDL_RenderCoordinatesFromWindow(renderer, x, y, &rpos.x, &rpos.y);
393 /* Window-local to server-absolute, aligned with surface blit anchor. */
394 SDL_Point origin = appWindow->serverOrigin();
395 SDL_Point rel{};
396 if (appWindow->isPopup() && !railPlatformCaps().supportsWindowPositions &&
397 appWindow->placedRel(rel))
398 {
399 auto* sdlParent = SDL_GetWindowParent(appWindow->window());
400 if (auto* parent = sdlParent ? getWindowBySdlId(SDL_GetWindowID(sdlParent)) : nullptr)
401 {
402 const SDL_Rect pr = parent->outerRect();
403 origin = { pr.x + rel.x, pr.y + rel.y };
404 }
405 }
406 /* Input is relative to the top left of the remote desktop, not to the primary monitor. */
407 x = rpos.x + static_cast<float>(origin.x - _inputOrigin.x);
408 y = rpos.y + static_cast<float>(origin.y - _inputOrigin.y);
409 return true;
410}

◆ uninit()

bool SdlRail::uninit ( RailClientContext *  rail)

Definition at line 877 of file sdl_rail.cpp.

878{
879 WINPR_ASSERT(rail);
880 /* The caller clears rail->custom next; server callbacks return early on nullptr. */
881 std::unique_lock lock(_windowsLock);
882 _refreshSent = false;
883 _appStarted = false;
884 /* Reset session and move state on reconnect. */
885 _localMove = {};
886 _sessionMargins = {};
887 _sentWorkArea = {};
888 _clientActiveId = 0;
889 _focusedAppId = 0;
890 _zOrder.clear();
891 _appliedZOrder.clear();
892 _zOrderDirty = false;
893 _iconCache.clear();
894 _iconScratch = {};
895 /* Mark windows for main-thread teardown. */
896 WLog_DBG(TAG, "RAIL channel uninit, marking %zu windows for main-thread teardown",
897 _windows.size());
898 for (auto& kv : _windows)
899 kv.second.markDeleted();
900 _rail = nullptr;
901 _enabled = false;
902 /* The next connection logs on again: hold its logon desktop back too. */
903 _desktopReleased = false;
904 _desktopHoldUntil = 0;
905
906 (void)sdl_push_user_event(SDL_EVENT_USER_UPDATE);
907 return true;
908}

◆ updateCoordinates()

void SdlRail::updateCoordinates ( )

Definition at line 445 of file sdl_rail.cpp.

446{
447 const auto* settings = _context->common()->context.settings;
448 _displayShift = { freerdp_settings_get_int32(settings, FreeRDP_MonitorLocalShiftX),
449 freerdp_settings_get_int32(settings, FreeRDP_MonitorLocalShiftY) };
450 _localShift = railPlatformCaps().supportsWindowPositions ? _displayShift : SDL_Point{ 0, 0 };
451 _inputOrigin = { 0, 0 };
452 const UINT32 count = freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount);
453 for (UINT32 x = 0; x < count; x++)
454 {
455 const auto* mon = static_cast<const rdpMonitor*>(
456 freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorDefArray, x));
457 if (!mon)
458 continue;
459 _inputOrigin.x = std::min(_inputOrigin.x, mon->x);
460 _inputOrigin.y = std::min(_inputOrigin.y, mon->y);
461 }
462 WLog_DBG(TAG, "coordinates: display shift %d,%d window shift %d,%d input origin %d,%d",
463 _displayShift.x, _displayShift.y, _localShift.x, _localShift.y, _inputOrigin.x,
464 _inputOrigin.y);
465}
WINPR_ATTR_NODISCARD FREERDP_API INT32 freerdp_settings_get_int32(const rdpSettings *settings, FreeRDP_Settings_Keys_Int32 id)
Returns a INT32 settings value.

◆ updateWindowFromSurface()

UINT SdlRail::updateWindowFromSurface ( gdiGfxSurface *  surface)

Definition at line 774 of file sdl_rail.cpp.

775{
776 if (!surface)
777 return CHANNEL_RC_OK;
778
779 std::unique_lock lock(_windowsLock);
780 auto appWindow = getWindow(surface->windowId);
781 if (!appWindow)
782 {
783 /* Drop GFX surface for unknown windows (repaints later). */
784 WLog_VRB(TAG, "gfx surface for untracked id=0x%08" PRIx64, surface->windowId);
785 return CHANNEL_RC_OK;
786 }
787
788 /* Clamp mapped dimensions to allocated surface bounds. */
789 const uint32_t w =
790 std::min(surface->mappedWidth ? surface->mappedWidth : surface->width, surface->width);
791 const uint32_t h =
792 std::min(surface->mappedHeight ? surface->mappedHeight : surface->height, surface->height);
793
794 /* Consume per-frame damage. */
795 UINT32 nbRects = 0;
796 const RECTANGLE_16* rects = region16_rects(&surface->invalidRegion, &nbRects);
797 /* Skip undamaged surfaces during EndFrame. */
798 const bool remapped = appWindow->takeSurfaceChange(surface->surfaceId);
799 if ((nbRects == 0) && !remapped)
800 return CHANNEL_RC_OK;
801 appWindow->updateGfxSurface(surface->data, surface->scanline, w, h, rects, nbRects,
802 surface->format);
803 region16_clear(&surface->invalidRegion);
804
805 (void)sdl_push_user_event(SDL_EVENT_USER_UPDATE);
806 return CHANNEL_RC_OK;
807}

◆ UpdateWindowFromSurface()

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

Definition at line 809 of file sdl_rail.cpp.

810{
811 WINPR_ASSERT(context);
812 auto gdi = static_cast<rdpGdi*>(context->custom);
813 if (!gdi || !gdi->context)
814 return CHANNEL_RC_OK;
815 auto sdl = get_context(gdi->context);
816 if (!sdl)
817 return CHANNEL_RC_OK;
818 return sdl->getRailChannelContext().updateWindowFromSurface(surface);
819}

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