24#include <winpr/assert.h>
25#include <winpr/platform.h>
26#include <winpr/cast.h>
28#include <freerdp/freerdp.h>
29#include <freerdp/log.h>
30#include <freerdp/codec/color.h>
31#include <freerdp/codec/region.h>
32#include <freerdp/client/rail.h>
34#include "sdl_rail.hpp"
35#include "sdl_context.hpp"
36#include "sdl_rail_platform.hpp"
37#include "sdl_types.hpp"
38#include "sdl_utils.hpp"
40#define TAG CLIENT_TAG("sdl.rail")
43[[nodiscard]]
static bool compositorDrag()
45 return railPlatformCaps().drag == RailDragModel::CompositorResize;
48SdlRail::SdlRail(
SdlContext* context) : _context(context)
54 railPlatformSetNativeLoopHandler({});
57SdlRail* SdlRail::get(rdpContext* context)
59 auto sdl = get_context(context);
62 return &
sdl->getRailChannelContext();
67 auto it = _windows.find(
id);
68 return (it == _windows.end()) ? nullptr : &it->second;
75 for (
auto& it : _windows)
77 if (it.second.sdlId() ==
id)
84static bool adjoinsOutside(
const SDL_Rect& popup,
const SDL_Rect& win)
87 if (SDL_GetRectIntersection(&popup, &win, &hit))
89 const SDL_Rect grown = { win.x - 1, win.y - 1, win.w + 2, win.h + 2 };
90 return SDL_GetRectIntersection(&popup, &grown, &hit);
96 return (!w.isPopup() || w.isFullscreen()) && (w.window() !=
nullptr);
101 auto* owner = getWindow(ownerId);
102 if (owner && canParentPopup(*owner))
114 const SDL_Rect pr = popup.windowRect();
115 const SDL_Point origin = { pr.x, pr.y };
116 for (uint32_t
id : _zOrder)
118 auto* w = getWindow(
id);
119 if (!w || !canParentPopup(*w))
121 const SDL_Rect wr = w->windowRect();
122 if (SDL_PointInRect(&origin, &wr))
130 for (
auto& other : _windows)
132 auto& w = other.second;
133 if (!canParentPopup(w))
135 const SDL_Rect wr = w.windowRect();
136 if (SDL_PointInRect(&origin, &wr))
145 chosen = resolveParent(_focusedAppId);
148 for (
auto& other : _windows)
150 auto& w = other.second;
151 if (canParentPopup(w))
164 if (!popup.isLayered())
166 const SDL_Rect pr = popup.windowRect();
167 for (
auto& other : _windows)
169 auto& w = other.second;
170 if (canParentPopup(w) && adjoinsOutside(pr, w.windowRect()))
176bool SdlRail::ownsWindow(SDL_WindowID
id)
178 std::unique_lock lock(_windowsLock);
179 return getWindowBySdlId(
id) !=
nullptr;
182void SdlRail::invalidateWindow(SDL_WindowID
id)
184 std::unique_lock lock(_windowsLock);
185 auto* appWindow = getWindowBySdlId(
id);
188 appWindow->invalidateAll();
190 (void)sdl_push_user_event(SDL_EVENT_USER_UPDATE);
194void SdlRail::sendSystemCommand(
SdlRailWindow* appWindow, uint16_t command)
196 if (!_rail || !_rail->ClientSystemCommand)
199 syscommand.windowId =
static_cast<UINT32
>(appWindow->id());
200 syscommand.command = command;
201 std::ignore = _rail->ClientSystemCommand(_rail, &syscommand);
204void SdlRail::sendWorkArea(
const SDL_Rect& local)
207 const SDL_Rect area = { local.x - _displayShift.x, local.y - _displayShift.y, local.w,
209 if (!_rail || !_rail->ClientSystemParam || (area.w <= 0) || (area.h <= 0))
211 if ((area.x < 0) || (area.y < 0))
213 WLog_WARN(TAG,
"work area %d,%d is outside the primary monitor, not sent", area.x, area.y);
216 if (SDL_RectsEqual(&area, &_sentWorkArea))
221 param.params = SPI_MASK_SET_WORK_AREA;
222 param.workArea.left = WINPR_ASSERTING_INT_CAST(UINT16, area.x);
223 param.workArea.top = WINPR_ASSERTING_INT_CAST(UINT16, area.y);
224 param.workArea.right = WINPR_ASSERTING_INT_CAST(UINT16, area.x + area.w);
225 param.workArea.bottom = WINPR_ASSERTING_INT_CAST(UINT16, area.y + area.h);
226 if (_rail->ClientSystemParam(_rail, ¶m) == CHANNEL_RC_OK)
227 _sentWorkArea = area;
230void SdlRail::handleMaximized(SDL_WindowID
id)
232 std::unique_lock lock(_windowsLock);
233 auto* appWindow = getWindowBySdlId(
id);
234 if (!appWindow || appWindow->isPopup() || !appWindow->window())
237 if (appWindow->railMaximized())
239 WLog_DBG(TAG,
"local maximize skipped id=0x%08" PRIx32
" (already rail-maximized)",
240 static_cast<UINT32
>(appWindow->id()));
242 if (!sdl_push_user_event(SDL_EVENT_USER_UPDATE))
243 WLog_WARN(TAG,
"sdl_push_user_event failed");
246 appWindow->setRailMaximized(
true);
248 appWindow->invalidateAll();
250 if (appWindow->loopEndPending())
252 appWindow->deferMaximize();
253 WLog_DBG(TAG,
"local maximize deferred id=0x%08" PRIx32
" (modal loop open)",
254 static_cast<UINT32
>(appWindow->id()));
258 WLog_DBG(TAG,
"local maximize id=0x%08" PRIx32
" -> SC_MAXIMIZE",
259 static_cast<UINT32
>(appWindow->id()));
260 sendSystemCommand(appWindow, SC_MAXIMIZE);
263 (void)sdl_push_user_event(SDL_EVENT_USER_UPDATE);
266void SdlRail::handleMinimized(SDL_WindowID
id)
268 std::unique_lock lock(_windowsLock);
269 auto* appWindow = getWindowBySdlId(
id);
270 if (!appWindow || appWindow->isPopup() || !appWindow->window())
272 if (appWindow->railMinimized())
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);
280void SdlRail::handleClose(SDL_WindowID
id)
282 std::unique_lock lock(_windowsLock);
283 auto* appWindow = getWindowBySdlId(
id);
284 if (!appWindow || !appWindow->window())
286 WLog_DBG(TAG,
"local close id=0x%08" PRIx32
" -> SC_CLOSE",
287 static_cast<UINT32
>(appWindow->id()));
288 sendSystemCommand(appWindow, SC_CLOSE);
291void SdlRail::handleRestored(SDL_WindowID
id)
293 std::unique_lock lock(_windowsLock);
294 auto* appWindow = getWindowBySdlId(
id);
295 if (!appWindow || appWindow->isPopup() || !appWindow->window())
298 if (appWindow->railMinimized())
300 appWindow->setRailMinimized(
false);
301 appWindow->noteLocalRestore();
303 else if (appWindow->railMaximized())
304 appWindow->setRailMaximized(
false);
307 if (!sdl_push_user_event(SDL_EVENT_USER_UPDATE))
308 WLog_WARN(TAG,
"sdl_push_user_event failed");
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);
316 (void)sdl_push_user_event(SDL_EVENT_USER_UPDATE);
319void SdlRail::handleFocus(SDL_WindowID
id,
bool gained)
321 std::unique_lock lock(_windowsLock);
322 auto appWindow = getWindowBySdlId(
id);
323 if (!appWindow || !appWindow->activatable() || !appWindow->window())
328 if (!gained && (_localMove.id ==
static_cast<uint32_t
>(appWindow->id())))
333 _focusedAppId = appWindow->id();
348 const auto* active = getWindow(_clientActiveId);
349 if (active && (active != appWindow) &&
350 (active->owner() ==
static_cast<uint32_t
>(appWindow->id())))
354 const auto wid =
static_cast<uint32_t
>(appWindow->id());
355 sendClientActivate(wid,
true);
359void SdlRail::sendClientActivate(uint32_t wid,
bool enabled)
361 if (!_rail || !_rail->ClientActivate)
364 _clientActiveId = wid;
366 activate.windowId = wid;
367 activate.enabled = enabled;
368 std::ignore = _rail->ClientActivate(_rail, &activate);
372void SdlRail::ensureActive(SDL_WindowID
id)
374 std::unique_lock lock(_windowsLock);
375 auto* appWindow = getWindowBySdlId(
id);
376 if (!appWindow || !appWindow->activatable() || !appWindow->window())
378 const auto wid =
static_cast<uint32_t
>(appWindow->id());
379 if (wid == _clientActiveId)
381 sendClientActivate(wid,
true);
384bool SdlRail::translateToServer(SDL_WindowID
id,
float& x,
float& y)
386 std::unique_lock lock(_windowsLock);
387 auto* appWindow = getWindowBySdlId(
id);
390 SDL_FPoint rpos = { x, y };
391 if (
auto* renderer = appWindow->renderer())
392 (void)SDL_RenderCoordinatesFromWindow(renderer, x, y, &rpos.x, &rpos.y);
394 SDL_Point origin = appWindow->serverOrigin();
396 if (appWindow->isPopup() && !railPlatformCaps().supportsWindowPositions &&
397 appWindow->placedRel(rel))
399 auto* sdlParent = SDL_GetWindowParent(appWindow->window());
400 if (
auto* parent = sdlParent ? getWindowBySdlId(SDL_GetWindowID(sdlParent)) : nullptr)
402 const SDL_Rect pr = parent->outerRect();
403 origin = { pr.x + rel.x, pr.y + rel.y };
407 x = rpos.x +
static_cast<float>(origin.x - _inputOrigin.x);
408 y = rpos.y +
static_cast<float>(origin.y - _inputOrigin.y);
412void SdlRail::notePopupMoved(SDL_WindowID
id)
415 if (railPlatformCaps().supportsWindowPositions)
417 std::unique_lock lock(_windowsLock);
418 auto* w = getWindowBySdlId(
id);
419 if (!w || !w->isPopup())
423 if (!sdl_push_user_event(SDL_EVENT_USER_UPDATE))
424 WLog_WARN(TAG,
"could not wake the paint loop for a moved popup");
427SdlRailWindow* SdlRail::addWindow(uint64_t
id,
const SDL_Rect& rect)
430 auto it = _windows.find(
id);
431 if ((it != _windows.end()) && it->second.isDeleted())
433 WLog_DBG(TAG,
"window recreate id=0x%08" PRIx32
" reused a deleted entry",
434 static_cast<uint32_t
>(
id));
435 _deadWindows.insert(_windows.extract(it));
438 auto res = _windows.emplace(std::piecewise_construct, std::forward_as_tuple(
id),
439 std::forward_as_tuple(
id, rect));
440 res.first->second.setLocalShift(_localShift);
441 return &res.first->second;
445void SdlRail::updateCoordinates()
447 const auto* settings = _context->common()->context.settings;
450 _localShift = railPlatformCaps().supportsWindowPositions ? _displayShift : SDL_Point{ 0, 0 };
451 _inputOrigin = { 0, 0 };
453 for (UINT32 x = 0; x < count; x++)
455 const auto* mon =
static_cast<const rdpMonitor*
>(
456 freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorDefArray, x));
459 _inputOrigin.x = std::min(_inputOrigin.x, mon->x);
460 _inputOrigin.y = std::min(_inputOrigin.y, mon->y);
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,
467void SdlRail::enableRemoteAppMode(
bool enable)
471 _desktopReleased =
true;
475static constexpr Uint32 kDesktopHoldMs = 10000;
477bool SdlRail::desktopMayShow()
479 if (_desktopReleased)
481 const uint64_t now = SDL_GetTicks();
482 if (_desktopHoldUntil == 0)
484 _desktopHoldUntil = now + kDesktopHoldMs;
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");
491 if (now < _desktopHoldUntil)
493 WLog_DBG(TAG,
"RemoteApp not started after %" PRIu32
" ms, showing the desktop",
495 _desktopReleased =
true;
499void SdlRail::noteLogonPrompt()
501 if (_desktopReleased.exchange(
true))
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");
508bool SdlRail::paint(SDL_Surface* primary, SDL_PixelFormat fallbackFormat,
509 const std::vector<SDL_Rect>& damage)
514 std::unique_lock lock(_windowsLock);
517 if (_sentWorkArea.w == 0)
520 if (SDL_GetDisplayUsableBounds(SDL_GetPrimaryDisplay(), &usable))
521 sendWorkArea(usable);
534 std::vector<std::pair<size_t, SdlRailWindow*>> release;
535 for (
auto& [wid, win] : _windows)
541 bool ownerDeleted =
false;
542 for (SDL_Window* p = SDL_GetWindowParent(win.window()); p; p = SDL_GetWindowParent(p))
545 auto* owner = getWindowBySdlId(SDL_GetWindowID(p));
548 else if (owner->isDeleted())
551 if (owner->id() == win.owner())
555 if (orphan && !win.isDeleted() && (ownerDeleted || !win.isPopup()))
557 if (orphan || win.isDeleted())
558 release.emplace_back(depth, &win);
560 for (
auto& [wid, win] : _deadWindows)
565 for (SDL_Window* p = SDL_GetWindowParent(win.window()); p; p = SDL_GetWindowParent(p))
567 release.emplace_back(depth, &win);
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();
575 for (
auto it = _windows.begin(); it != _windows.end();)
576 it = it->second.isDeleted() ? _windows.erase(it) : std::next(it);
578 _deadWindows.clear();
581 for (
auto& it : _windows)
583 auto& win = it.second;
587 SDL_Window* parent =
nullptr;
588 SDL_Rect parentRect{};
589 auto* owner = resolveParent(win.owner());
590 if (owner && (owner != &win))
592 parent = owner->window();
593 parentRect = owner->outerRect();
595 win.paint(primary, fallbackFormat, damage, parent, parentRect);
597 SDL_Rect refusedOuter{};
598 if (win.takeWmOverride(refusedOuter))
600 const SDL_Rect rect = win.serverRect(refusedOuter);
601 const SDL_Rect cur = win.windowRect();
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);
612 SDL_Rect unconfirmed{};
613 if (win.takeUnconfirmedReport(unconfirmed) && !win.railMinimized() &&
614 !win.effectivelyMaximized())
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);
620 if (win.takeRestoreRetry())
622 WLog_DBG(TAG,
"restore resent id=0x%08" PRIx32,
static_cast<uint32_t
>(win.id()));
623 sendSystemCommand(&win, SC_RESTORE);
630 for (
auto& it : _windows)
632 if (it.second.isPopup() || !it.second.window())
634 auto* ctx = &_context->common()->context;
635 const auto dw =
static_cast<UINT16
>(
637 const auto dh =
static_cast<UINT16
>(
640 if (ctx->update && ctx->update->RefreshRect)
641 (void)ctx->update->RefreshRect(ctx, 1, &all);
643 WLog_DBG(TAG,
"refresh-rect sent (first app window realized)");
648 for (
auto& it : _windows)
650 auto& popup = it.second;
651 if (!popup.isPopup())
655 if (!popup.isFrame() && isShadowFrame(popup))
656 popup.setFrame(
true);
659 if (popup.isLayered())
661 constexpr int reach = 48;
662 const SDL_Rect sr = popup.windowRect();
663 bool anchored =
false;
664 for (
auto& other : _windows)
666 auto& cand = other.second;
667 if ((&cand == &popup) || !cand.isPopup() || cand.isLayered() || !cand.window() ||
668 ((SDL_GetWindowFlags(cand.window()) & SDL_WINDOW_HIDDEN) != 0))
670 SDL_Rect zone = cand.windowRect();
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);
686 popup.setShadowAnchored(anchored);
691 if (
auto* pw = popup.window())
693 SDL_Window* p = SDL_GetWindowParent(pw);
694 chosen = p ? getWindowBySdlId(SDL_GetWindowID(p)) : nullptr;
698 chosen = resolvePopupParent(popup);
700 WLog_DBG(TAG,
"popup id=0x%08" PRIx32
" has no parent app window",
701 static_cast<UINT32
>(popup.id()));
704 SDL_Window* parent =
nullptr;
705 SDL_Rect parentRect{};
708 parent = chosen->window();
709 parentRect = chosen->outerRect();
711 popup.paint(primary, fallbackFormat, damage, parent, parentRect);
720void SdlRail::applyZOrder()
725 if (!railPlatformCaps().supportsRestack)
727 _zOrderDirty =
false;
731 if (_localMove.id != 0)
733 WLog_VRB(TAG,
"zorder apply deferred: local move 0x%08" PRIx32
" active", _localMove.id);
737 if (_nativeResizeId != 0)
739 const auto* resizing = getWindowBySdlId(_nativeResizeId);
740 if (resizing && !resizing->isDeleted())
745 std::vector<SDL_Window*> stack;
746 std::vector<uint32_t> applied;
747 stack.reserve(_zOrder.size());
748 applied.reserve(_zOrder.size());
749 for (uint32_t
id : _zOrder)
751 auto* w = getWindow(
id);
752 if (w && !w->isPopup() && w->window() &&
753 ((SDL_GetWindowFlags(w->window()) & SDL_WINDOW_HIDDEN) == 0))
755 stack.push_back(w->window());
756 applied.push_back(
id);
760 if (applied == _appliedZOrder)
762 _zOrderDirty =
false;
765 if (stack.size() >= 2)
767 std::ignore = railPlatformRestack(stack);
770 _appliedZOrder = std::move(applied);
771 _zOrderDirty =
false;
774UINT SdlRail::updateWindowFromSurface(gdiGfxSurface* surface)
777 return CHANNEL_RC_OK;
779 std::unique_lock lock(_windowsLock);
780 auto appWindow = getWindow(surface->windowId);
784 WLog_VRB(TAG,
"gfx surface for untracked id=0x%08" PRIx64, surface->windowId);
785 return CHANNEL_RC_OK;
790 std::min(surface->mappedWidth ? surface->mappedWidth : surface->width, surface->width);
792 std::min(surface->mappedHeight ? surface->mappedHeight : surface->height, surface->height);
796 const RECTANGLE_16* rects = region16_rects(&surface->invalidRegion, &nbRects);
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,
803 region16_clear(&surface->invalidRegion);
805 (void)sdl_push_user_event(SDL_EVENT_USER_UPDATE);
806 return CHANNEL_RC_OK;
809UINT SdlRail::UpdateWindowFromSurface(RdpgfxClientContext* context, gdiGfxSurface* surface)
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);
817 return CHANNEL_RC_OK;
818 return sdl->getRailChannelContext().updateWindowFromSurface(surface);
821bool SdlRail::init(RailClientContext* rail)
827 registerUpdateCallbacks(_context->context()->update);
830 std::unique_lock lock(_windowsLock);
832 for (
auto it = _windows.begin(); it != _windows.end();)
835 _deadWindows.insert(_windows.extract(cur));
837 auto* settings = _context->context()->settings;
838 const uint32_t caches =
842 _iconCache.assign(
static_cast<size_t>(caches) * _iconCacheEntries, {});
846 rail->ServerExecuteResult = SdlRail::server_execute_result;
848 rail->ServerLocalMoveSize = SdlRail::server_local_move_size;
849 rail->ServerMinMaxInfo = SdlRail::server_min_max_info;
852 WLog_WARN(TAG,
"RemoteApp/RAIL support in the SDL client is experimental");
854 std::unique_lock lock(_windowsLock);
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)
864 if (sdl::utils::isX11Driver())
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",
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());
877bool SdlRail::uninit(WINPR_ATTR_UNUSED RailClientContext* rail)
881 std::unique_lock lock(_windowsLock);
882 _refreshSent =
false;
886 _sessionMargins = {};
891 _appliedZOrder.clear();
892 _zOrderDirty =
false;
896 WLog_DBG(TAG,
"RAIL channel uninit, marking %zu windows for main-thread teardown",
898 for (
auto& kv : _windows)
899 kv.second.markDeleted();
903 _desktopReleased =
false;
904 _desktopHoldUntil = 0;
906 (void)sdl_push_user_event(SDL_EVENT_USER_UPDATE);
910void SdlRail::registerUpdateCallbacks(rdpUpdate* update)
912 WINPR_ASSERT(update);
913 auto window = update->window;
914 WINPR_ASSERT(window);
916 window->WindowCreate = SdlRail::window_common;
917 window->WindowUpdate = SdlRail::window_common;
918 window->WindowIcon = SdlRail::window_icon;
919 window->WindowCachedIcon = SdlRail::window_cached_icon;
920 window->WindowDelete = SdlRail::window_delete;
921 window->MonitoredDesktop = SdlRail::monitored_desktop;
922 window->NonMonitoredDesktop = SdlRail::non_monitored_desktop;
927UINT SdlRail::server_execute_result(RailClientContext* context,
930 WINPR_ASSERT(context);
931 WINPR_ASSERT(execResult);
932 auto rail =
static_cast<SdlRail*
>(context->custom);
934 return CHANNEL_RC_OK;
936 if (execResult->execResult != RAIL_EXEC_S_OK)
938 WLog_ERR(TAG,
"RAIL exec error: execResult=0x%04" PRIx16
" rawResult=0x%08" PRIx32,
939 execResult->execResult, execResult->rawResult);
940 freerdp_abort_connect_context(rail->_context->context());
944 WLog_DBG(TAG,
"RemoteApp exec OK, enabling RAIL mode");
945 rail->enableRemoteAppMode(
true);
947 return CHANNEL_RC_OK;
950UINT SdlRail::server_local_move_size(RailClientContext* context,
953 WINPR_ASSERT(context);
954 WINPR_ASSERT(localMoveSize);
957 if (localMoveSize->isMoveSizeStart && (localMoveSize->moveSizeType >= RAIL_WMSZ_LEFT) &&
958 (localMoveSize->moveSizeType <= RAIL_WMSZ_MOVE))
960 WLog_DBG(TAG,
"server move/size start id=0x%08" PRIx32
" type=%" PRIu16
" pos=%d,%d",
961 localMoveSize->windowId, localMoveSize->moveSizeType, localMoveSize->posX,
962 localMoveSize->posY);
963 if (
auto rail =
static_cast<SdlRail*
>(context->custom))
965 std::unique_lock lock(rail->_windowsLock);
966 rail->_moveLoop = { localMoveSize->windowId, 0 };
968 (void)sdl_push_user_event(SDL_EVENT_USER_RAIL_MOVE, localMoveSize->windowId,
969 static_cast<int>(localMoveSize->moveSizeType));
971 else if (!localMoveSize->isMoveSizeStart)
973 WLog_DBG(TAG,
"server move/size end id=0x%08" PRIx32, localMoveSize->windowId);
975 SDL_WindowID sdlId = 0;
977 auto rail =
static_cast<SdlRail*
>(context->custom);
980 std::unique_lock lock(rail->_windowsLock);
981 auto* appWindow = rail->getWindow(localMoveSize->windowId);
983 const bool ownedByWaylandResize = compositorDrag() &&
984 (rail->_localMove.id == localMoveSize->windowId) &&
985 rail->_localMove.sawResize;
986 if (!ownedByWaylandResize)
989 appWindow->setLocalMoveActive(
false);
990 if (rail->_localMove.id == localMoveSize->windowId)
992 if (compositorDrag())
995 rail->_localMove = {};
997 appWindow->invalidateAll();
998 (void)sdl_push_user_event(SDL_EVENT_USER_UPDATE);
1003 rail->_localMove.serverEnded =
true;
1007 if (rail->_moveLoop.id == localMoveSize->windowId)
1009 const SDL_DisplayID display = rail->_moveLoop.display;
1010 rail->_moveLoop = {};
1011 auto* moved = rail->getWindow(localMoveSize->windowId);
1012 if (moved && (display != 0) && !moved->effectivelyMaximized())
1013 rail->moveToDisplay(moved, display);
1016 if (appWindow && appWindow->loopEndPending())
1018 const auto actions = appWindow->takeLoopEnd();
1019 if (actions.maximize && appWindow->railMaximized())
1021 WLog_DBG(TAG,
"deferred SC_MAXIMIZE id=0x%08" PRIx32, localMoveSize->windowId);
1022 rail->sendSystemCommand(appWindow, SC_MAXIMIZE);
1027 WLog_DBG(TAG,
"deferred snap resize id=0x%08" PRIx32
" rect=%d,%d %dx%d",
1028 localMoveSize->windowId, actions.snapRect.x, actions.snapRect.y,
1029 actions.snapRect.w, actions.snapRect.h);
1030 rail->sendClientWindowMove(appWindow, actions.snapRect);
1035 if (appWindow->window() && !appWindow->effectivelyMaximized() &&
1036 !appWindow->localMoveSizeChanged())
1038 sdlId = appWindow->sdlId();
1043 rail->syncGeometry(sdlId);
1045 return CHANNEL_RC_OK;
1049bool SdlRail::dragActive(SDL_WindowID
id)
1051 return !compositorDrag() && suppressServerMotion(
id);
1054bool SdlRail::suppressServerMotion(SDL_WindowID
id)
1056 std::unique_lock lock(_windowsLock);
1057 if (_localMove.id == 0)
1059 auto* appWindow = getWindowBySdlId(
id);
1061 return appWindow && appWindow->localMoveActive();
1064void SdlRail::redrawDuringSizeMove(
void* userdata)
1066 auto* rail =
static_cast<SdlRail*
>(userdata);
1068 rail->_sizeMoveRedraws++;
1069 if (!rail->_context->drawToWindows())
1070 WLog_WARN(TAG,
"redraw during the native size/move loop failed");
1073void SdlRail::nativeResize(
void* userdata, SDL_WindowID
id,
int moveType,
bool end)
1075 auto* rail =
static_cast<SdlRail*
>(userdata);
1077 std::unique_lock lock(rail->_windowsLock);
1081 if (rail->_localMove.id != 0)
1083 auto* appWindow = rail->getWindowBySdlId(
id);
1084 if (!appWindow || appWindow->isPopup() || appWindow->effectivelyMaximized())
1086 WLog_DBG(TAG,
"native resize start id=0x%08" PRIx64
" type=%d", appWindow->id(), moveType);
1087 appWindow->setLocalMoveActive(
true);
1088 const RailEdges e = railEdges(
static_cast<uint16_t
>(moveType));
1089 appWindow->setResizeAnchor(e.left, e.top);
1090 rail->_nativeResizeId = id;
1093 if (rail->_nativeResizeId !=
id)
1095 rail->_nativeResizeId = 0;
1096 auto* appWindow = rail->getWindowBySdlId(
id);
1097 if (!appWindow || !appWindow->window() || appWindow->isDeleted())
1103 SDL_GetWindowSize(appWindow->window(), &w, &h);
1104 SDL_GetWindowPosition(appWindow->window(), &x, &y);
1105 WLog_DBG(TAG,
"native resize end id=0x%08" PRIx64
" %d,%d %dx%d", appWindow->id(), x, y, w, h);
1106 rail->reportAndAdopt(appWindow, x, y, w, h);
1109void SdlRail::handleLocalMoveRequested(uint32_t windowId, uint16_t moveType)
1112 SDL_Window* sdlWindow = beginLocalMove(windowId, moveType);
1115 _sizeMoveRedraws = 0;
1116 railPlatformRunModalDrag(sdlWindow, moveType);
1117 WLog_DBG(TAG,
"modal drag end id=0x%08" PRIx32
" redraws=%" PRIu32, windowId, _sizeMoveRedraws);
1118 completeLocalMoveIfPending();
1121SDL_Window* SdlRail::beginLocalMove(uint32_t windowId, uint16_t moveType)
1123 const RailDragModel model = railPlatformCaps().drag;
1124 if (model == RailDragModel::None)
1126 const bool compositor = (model == RailDragModel::CompositorResize);
1128 std::unique_lock lock(_windowsLock);
1129 auto appWindow = getWindow(windowId);
1130 if (!appWindow || !appWindow->window() || appWindow->isPopup())
1133 const bool isMove = (moveType == RAIL_WMSZ_MOVE);
1134 WLog_DBG(TAG,
"local move start id=0x%08" PRIx32
" driver=%s type=%" PRIu16
" %s", windowId,
1135 SDL_GetCurrentVideoDriver(), moveType, isMove ?
"move" :
"resize");
1136 const bool started = railPlatformBeginDrag(appWindow->window(), moveType);
1138 if (started && (!compositor || !isMove))
1141 _localMove.id = windowId;
1142 _localMove.type = moveType;
1146 _localMove.anchor = _lastPressServer;
1147 _localMove.pointer = _lastPointerServer;
1149 appWindow->clearLoopEnd();
1151 appWindow->setLocalMoveActive(
true);
1155 const RailEdges e = railEdges(moveType);
1156 appWindow->setResizeAnchor(e.left, e.top);
1161 WLog_WARN(TAG,
"WM move failed for RAIL window 0x%08" PRIx32, windowId);
1163 return (started && (model == RailDragModel::ModalLoop)) ? appWindow->window() :
nullptr;
1167void SdlRail::handleDisplayChanged(SDL_WindowID
id, SDL_DisplayID display)
1169 if (railPlatformCaps().supportsWindowPositions)
1171 std::unique_lock lock(_windowsLock);
1172 auto* appWindow = getWindowBySdlId(
id);
1173 if (!appWindow || appWindow->isPopup() || appWindow->effectivelyMaximized())
1176 if (_moveLoop.id == appWindow->id())
1178 _moveLoop.display = display;
1181 moveToDisplay(appWindow, display);
1184void SdlRail::moveToDisplay(
SdlRailWindow* appWindow, SDL_DisplayID display)
1186 const auto* settings = _context->common()->context.settings;
1188 for (UINT32 x = 0; x < count; x++)
1190 const auto* mon =
static_cast<const rdpMonitor*
>(
1191 freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorDefArray, x));
1192 if (!mon || (mon->orig_screen != display))
1194 const SDL_Rect area = { mon->x, mon->y, mon->width, mon->height };
1195 SDL_Rect rect = appWindow->windowRect();
1196 const SDL_Point center = { rect.x + rect.w / 2, rect.y + rect.h / 2 };
1197 if (SDL_PointInRect(¢er, &area))
1199 rect.x = area.x + std::max(0, (area.w - rect.w) / 2);
1200 rect.y = area.y + std::max(0, (area.h - rect.h) / 2);
1201 WLog_DBG(TAG,
"window 0x%08" PRIx32
" now on display %" PRIu32
", server move to %d,%d",
1202 static_cast<uint32_t
>(appWindow->id()), display, rect.x, rect.y);
1203 sendClientWindowMove(appWindow, rect);
1204 appWindow->adoptLocalGeometry(rect);
1211void SdlRail::sendClientWindowMove(
SdlRailWindow* appWindow,
const SDL_Rect& serverRect)
1213 const SDL_Rect fm = appWindow->frameMargins();
1215 move.windowId =
static_cast<UINT32
>(appWindow->id());
1216 move.left = WINPR_ASSERTING_INT_CAST(INT16, serverRect.x - fm.x);
1217 move.top = WINPR_ASSERTING_INT_CAST(INT16, serverRect.y - fm.y);
1218 move.right = WINPR_ASSERTING_INT_CAST(INT16, serverRect.x + serverRect.w + fm.w);
1219 move.bottom = WINPR_ASSERTING_INT_CAST(INT16, serverRect.y + serverRect.h + fm.h);
1220 if (_rail && _rail->ClientWindowMove &&
1221 (_rail->ClientWindowMove(_rail, &move) != CHANNEL_RC_OK))
1222 WLog_WARN(TAG,
"ClientWindowMove failed for RAIL window 0x%08" PRIx32, move.windowId);
1226void SdlRail::reportAndAdopt(
SdlRailWindow* appWindow,
int x,
int y,
int w,
int h)
1229 const SDL_Rect rect = appWindow->serverRect({ x, y, w, h });
1231 const bool maximized = appWindow->effectivelyMaximized();
1233 const SDL_Rect cur = appWindow->windowRect();
1234 const bool unchanged = SDL_RectsEqual(&rect, &cur);
1235 if (!maximized && !unchanged)
1236 sendClientWindowMove(appWindow, rect);
1240 appWindow->setLocalMoveActive(
false);
1242 appWindow->adoptLocalGeometry(rect);
1244 (void)sdl_push_user_event(SDL_EVENT_USER_UPDATE);
1247void SdlRail::clampIntoDesktop(
const SdlRailWindow& appWindow,
int& x,
int& y,
int w,
int h)
const
1249 auto* settings = _context->context()->settings;
1253 const SDL_Rect s = appWindow.serverRect({ x, y, w, h });
1254 x += std::clamp(s.x, _inputOrigin.x, _inputOrigin.x + std::max(0, dw - s.w)) - s.x;
1255 y += std::clamp(s.y, _inputOrigin.y, _inputOrigin.y + std::max(0, dh - s.h)) - s.y;
1258void SdlRail::noteDragResize(SDL_WindowID
id,
int w,
int h)
1260 std::unique_lock lock(_windowsLock);
1261 if ((_localMove.id == 0) || compositorDrag() || (_localMove.type != RAIL_WMSZ_MOVE))
1263 auto* appWindow = getWindowBySdlId(
id);
1264 if (!appWindow || (
static_cast<uint32_t
>(appWindow->id()) != _localMove.id))
1267 if (appWindow->localMoveSizeChanged())
1269 const SDL_Rect outer = appWindow->outerRect();
1270 if ((w == outer.w) && (h == outer.h))
1273 _localMove.wmSized =
true;
1274 _localMove.wmSize = { w, h };
1278void SdlRail::reportMaximizedWorkArea(
SdlRailWindow* appWindow)
1281 if ((SDL_GetWindowFlags(appWindow->window()) & SDL_WINDOW_MAXIMIZED) == 0)
1285 SDL_GetWindowSize(appWindow->window(), &w, &h);
1286 if ((w <= 0) || (h <= 0))
1290 if (!SDL_GetDisplayBounds(SDL_GetPrimaryDisplay(), &primary))
1295 if (railPlatformCaps().supportsWindowPositions)
1296 SDL_GetWindowPosition(appWindow->window(), &x, &y);
1297 const SDL_Rect area = { x, y, w, h };
1299 if (!SDL_GetRectIntersection(&area, &primary, &overlap) || !SDL_RectsEqual(&overlap, &area))
1304void SdlRail::syncGeometry(SDL_WindowID
id)
1306 std::unique_lock lock(_windowsLock);
1307 auto* w = getWindowBySdlId(
id);
1308 if (!w || !w->window() || w->isDeleted() || w->isPopup())
1312 if (w->effectivelyMaximized())
1314 reportMaximizedWorkArea(w);
1317 if (w->railMinimized())
1320 const auto wid =
static_cast<uint32_t
>(w->id());
1322 if (w->localMoveActive() || (_localMove.id == wid) || w->loopEndPending() ||
1323 w->stateTransitionPending() || w->awaitingRestoreRect() || w->insetsStale())
1328 SDL_GetWindowSize(w->window(), &lw, &lh);
1329 if ((lw <= 0) || (lh <= 0))
1333 const SDL_Rect ins = w->insets();
1334 if (((lw - ins.x - ins.w) <= 0) || ((lh - ins.y - ins.h) <= 0))
1336 WLog_DBG(TAG,
"geometry sync id=0x%08" PRIx32
" %dx%d dropped (content collapsed)", wid, lw,
1341 const SDL_Rect vis = w->outerRect();
1342 const bool posKnown = railPlatformCaps().supportsWindowPositions;
1346 SDL_GetWindowPosition(w->window(), &lx, &ly);
1348 clampIntoDesktop(*w, lx, ly, lw, lh);
1351 if ((lw == vis.w) && (lh == vis.h) && (!posKnown || ((lx == vis.x) && (ly == vis.y))))
1353 w->clearGeomApplyPending();
1358 if (w->geomApplyPending())
1361 const bool overtaken = !posKnown && ((lw > vis.w) || (lh > vis.h));
1364 w->clearGeomApplyPending();
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);
1372static constexpr Uint32 kResizeSettleMs = 500;
1374static Uint32 resizeSettleTimer(
void* userdata, [[maybe_unused]] SDL_TimerID timerID,
1375 [[maybe_unused]] Uint32 interval)
1377 if (!SDL_RunOnMainThread([](
void* rail) {
static_cast<SdlRail*
>(rail)->settleWaylandResize(); },
1379 WLog_WARN(TAG,
"could not queue the resize settle check: %s", SDL_GetError());
1383void SdlRail::settleWaylandResize()
1386 std::unique_lock lock(_windowsLock);
1387 if ((_localMove.id == 0) || !compositorDrag() || !_localMove.sawResize ||
1388 (SDL_GetTicks() - _localMove.lastResize < kResizeSettleMs))
1391 completeWaylandResize();
1394void SdlRail::noteResizeGrab(SDL_WindowID
id)
1396 std::unique_lock lock(_windowsLock);
1397 if (_localMove.id == 0)
1399 auto* appWindow = getWindowBySdlId(
id);
1401 if (!appWindow || (
static_cast<uint32_t
>(appWindow->id()) != _localMove.id) ||
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());
1410void SdlRail::completeWaylandResize(
bool definitive)
1412 std::unique_lock lock(_windowsLock);
1413 if ((_localMove.id == 0) || !compositorDrag())
1416 if (!_localMove.sawResize)
1420 if (
auto* appWindow = getWindow(_localMove.id))
1422 appWindow->setLocalMoveActive(
false);
1425 appWindow->invalidateAll();
1426 (void)sdl_push_user_event(SDL_EVENT_USER_UPDATE);
1434 const LocalMove drag = _localMove;
1437 auto appWindow = getWindow(drag.id);
1438 if (!appWindow || !appWindow->window())
1441 (void)SDL_SyncWindow(appWindow->window());
1444 SDL_GetWindowSize(appWindow->window(), &w, &h);
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);
1455void SdlRail::completeLocalMoveIfPending()
1457 std::unique_lock lock(_windowsLock);
1458 if (_localMove.id == 0)
1461 if (compositorDrag())
1465 const LocalMove drag = _localMove;
1468 auto appWindow = getWindow(drag.id);
1469 if (!appWindow || !appWindow->window() || appWindow->isDeleted())
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");
1479 (void)SDL_SyncWindow(appWindow->window());
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();
1493 if (!appWindow->effectivelyMaximized() && drag.wmSized &&
1494 !appWindow->localMoveSizeChanged())
1499 appWindow->deferSnap(appWindow->serverRect({ x, y, w, h }));
1508 if (!drag.serverEnded)
1510 int px = drag.anchor.x;
1511 int py = drag.anchor.y;
1512 if (isMove && appWindow->localMoveSizeChanged())
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;
1520 else if (!appWindow->effectivelyMaximized())
1522 const RailEdges e = railEdges(drag.type);
1523 if (isMove || e.left)
1526 px += (x + w) - (start.x + start.w);
1527 if (isMove || e.top)
1530 py += (y + h) - (start.y + start.h);
1532 (void)freerdp_client_send_button_event(_context->common(), FALSE, PTR_FLAGS_BUTTON1, px,
1535 appWindow->armLoopEnd();
1538 const SDL_Rect rect = appWindow->serverRect({ x, y, w, h });
1539 const bool restoredMidDrag = appWindow->localMoveSizeChanged();
1540 appWindow->adoptLocalGeometry(rect);
1542 if (restoredMidDrag)
1543 appWindow->markGeometryDirty();
1544 (void)sdl_push_user_event(SDL_EVENT_USER_UPDATE);
1549 clampIntoDesktop(*appWindow, x, y, w, h);
1550 reportAndAdopt(appWindow, x, y, w, h);
1553 railPlatformEndDrag(appWindow->window());
1556UINT SdlRail::server_min_max_info(RailClientContext* context,
1559 WINPR_ASSERT(context);
1560 WINPR_ASSERT(minMaxInfo);
1561 auto rail =
static_cast<SdlRail*
>(context->custom);
1563 return CHANNEL_RC_OK;
1565 std::unique_lock lock(rail->_windowsLock);
1566 auto appWindow = rail->getWindow(minMaxInfo->windowId);
1569 WLog_VRB(TAG,
"server minmax id=0x%08" PRIx32
" min=%dx%d max=%dx%d", minMaxInfo->windowId,
1570 minMaxInfo->minTrackWidth, minMaxInfo->minTrackHeight, minMaxInfo->maxTrackWidth,
1571 minMaxInfo->maxTrackHeight);
1572 appWindow->setMinMaxSize({ minMaxInfo->minTrackWidth, minMaxInfo->minTrackHeight },
1573 { minMaxInfo->maxTrackWidth, minMaxInfo->maxTrackHeight });
1575 (void)sdl_push_user_event(SDL_EVENT_USER_UPDATE);
1577 return CHANNEL_RC_OK;
1583static bool marginsSet(
const SDL_Rect& m)
1585 return m.x || m.y || m.w || m.h;
1592 const UINT32 fieldFlags = orderInfo->fieldFlags;
1593 SDL_Rect m = appWindow->frameMargins();
1594 if (fieldFlags & WINDOW_ORDER_FIELD_RESIZE_MARGIN_X)
1596 m.x =
static_cast<int>(state->resizeMarginLeft);
1597 m.w =
static_cast<int>(state->resizeMarginRight);
1599 if (fieldFlags & WINDOW_ORDER_FIELD_RESIZE_MARGIN_Y)
1601 m.y =
static_cast<int>(state->resizeMarginTop);
1602 m.h =
static_cast<int>(state->resizeMarginBottom);
1605 appWindow->setFrameMargins(m);
1606 SDL_Rect& sm = _sessionMargins;
1607 if (marginsSet(m) && !marginsSet(sm))
1611 for (
auto& [
id, w] : _windows)
1612 if (!marginsSet(w.resizeMargins()))
1613 w.setResizeMargins(m.x, m.y, m.w, m.h);
1615 const SDL_Rect bandSrc = marginsSet(m) ? m : sm;
1616 appWindow->setResizeMargins(bandSrc.x, bandSrc.y, bandSrc.w, bandSrc.h);
1617 WLog_DBG(TAG,
"margins id=0x%08" PRIx32
" raw L%d T%d R%d B%d", orderInfo->windowId, m.x, m.y,
1625 WLog_DBG(TAG,
"server showState id=0x%08" PRIx32
" state=0x%02" PRIx32, orderInfo->windowId,
1627 appWindow->setVisible(state->showState != WINDOW_HIDE);
1628 switch (state->showState)
1630 case WINDOW_SHOW_MAXIMIZED:
1631 appWindow->setServerMaximized(
true);
1632 appWindow->setServerMinimized(
false);
1634 case WINDOW_SHOW_MINIMIZED:
1635 appWindow->setServerMinimized(
true);
1638 appWindow->setServerMaximized(
false);
1639 appWindow->setServerMinimized(
false);
1650 std::vector<SDL_Rect> rects;
1651 rects.reserve(state->numVisibilityRects);
1652 for (UINT32 i = 0; i < state->numVisibilityRects; i++)
1655 rects.push_back({ r.left, r.top, r.right - r.left, r.bottom - r.top });
1658 WLog_DBG(TAG,
"visrects id=0x%08" PRIx32
" n=0", orderInfo->windowId);
1660 WLog_DBG(TAG,
"visrects id=0x%08" PRIx32
" n=%zu first=%dx%d+%d+%d", orderInfo->windowId,
1661 rects.size(), rects.at(0).w, rects.at(0).h, rects.at(0).x, rects.at(0).y);
1662 appWindow->setVisibilityRects(std::move(rects));
1665BOOL SdlRail::window_common(rdpContext* context,
const WINDOW_ORDER_INFO* orderInfo,
1668 WINPR_ASSERT(orderInfo);
1669 WINPR_ASSERT(windowState);
1670 auto rail = SdlRail::get(context);
1674 const UINT32 fieldFlags = orderInfo->fieldFlags;
1675 std::unique_lock lock(rail->_windowsLock);
1676 auto appWindow = rail->getWindow(orderInfo->windowId);
1678 if (fieldFlags & WINDOW_ORDER_STATE_NEW)
1680 const SDL_Rect rect = {
static_cast<int>(windowState->windowOffsetX),
1681 static_cast<int>(windowState->windowOffsetY),
1682 static_cast<int>(windowState->windowWidth),
1683 static_cast<int>(windowState->windowHeight) };
1684 appWindow = rail->addWindow(orderInfo->windowId, rect);
1689 "window create id=0x%08" PRIx32
" %dx%d+%d+%d style=0x%08" PRIx32
1690 " ex=0x%08" PRIx32
" owner=0x%08" PRIx32,
1691 orderInfo->windowId, rect.w, rect.h, rect.x, rect.y, windowState->style,
1692 windowState->extendedStyle, windowState->ownerWindowId);
1694 const SDL_Rect& sm = rail->_sessionMargins;
1696 appWindow->setResizeMargins(sm.x, sm.y, sm.w, sm.h);
1707 WLog_WARN(TAG,
"ignoring order flags=0x%08" PRIx32
" for unknown window id=0x%08" PRIx32,
1708 fieldFlags, orderInfo->windowId);
1713 if (fieldFlags & WINDOW_ORDER_FIELD_SHOW)
1714 updateShowState(appWindow, orderInfo, windowState);
1716 if (fieldFlags & (WINDOW_ORDER_FIELD_WND_OFFSET | WINDOW_ORDER_FIELD_WND_SIZE))
1718 SDL_Rect r = appWindow->windowRect();
1719 if (fieldFlags & WINDOW_ORDER_FIELD_WND_OFFSET)
1721 r.x =
static_cast<int>(windowState->windowOffsetX);
1722 r.y =
static_cast<int>(windowState->windowOffsetY);
1724 if (fieldFlags & WINDOW_ORDER_FIELD_WND_SIZE)
1726 r.w =
static_cast<int>(windowState->windowWidth);
1727 r.h =
static_cast<int>(windowState->windowHeight);
1729 WLog_VRB(TAG,
"server geom id=0x%08" PRIx32
" rect=%d,%d %dx%d", orderInfo->windowId, r.x,
1731 appWindow->updateWindowRect(r);
1733 if (fieldFlags & WINDOW_ORDER_FIELD_OWNER)
1734 appWindow->setOwner(windowState->ownerWindowId);
1735 if (fieldFlags & (WINDOW_ORDER_FIELD_RESIZE_MARGIN_X | WINDOW_ORDER_FIELD_RESIZE_MARGIN_Y))
1736 rail->updateMargins(appWindow, orderInfo, windowState);
1737 if (fieldFlags & WINDOW_ORDER_FIELD_STYLE)
1738 appWindow->setStyle(windowState->style, windowState->extendedStyle);
1739 if (fieldFlags & WINDOW_ORDER_FIELD_TITLE)
1741 char* title = rail_string_to_utf8_string(&windowState->titleInfo);
1744 appWindow->setTitle(title);
1749 if (fieldFlags & WINDOW_ORDER_FIELD_VIS_OFFSET)
1751 WLog_DBG(TAG,
"visoff id=0x%08" PRIx32
" %d,%d", orderInfo->windowId,
1752 windowState->visibleOffsetX, windowState->visibleOffsetY);
1753 appWindow->setVisibleOffset({
static_cast<int>(windowState->visibleOffsetX),
1754 static_cast<int>(windowState->visibleOffsetY) });
1756 if (fieldFlags & WINDOW_ORDER_FIELD_VISIBILITY)
1757 updateVisRects(appWindow, orderInfo, windowState);
1760 (void)sdl_push_user_event(SDL_EVENT_USER_UPDATE);
1764SdlRailIcon* SdlRail::iconCacheLookup(uint32_t cacheId, uint32_t cacheEntry)
1766 if (cacheId == 0xFF)
1767 return &_iconScratch;
1768 const size_t idx =
static_cast<size_t>(cacheId) * _iconCacheEntries + cacheEntry;
1769 if ((_iconCacheEntries == 0) || (cacheEntry >= _iconCacheEntries) || (idx >= _iconCache.size()))
1771 return &_iconCache.at(idx);
1777 icon.w = info->width;
1778 icon.h = info->height;
1779 icon.bgra.assign(4ULL * info->width * info->height, 0);
1780 return freerdp_image_copy_from_icon_data(
1781 icon.bgra.data(), PIXEL_FORMAT_BGRA32, 0, 0, 0,
1782 WINPR_ASSERTING_INT_CAST(UINT16, info->width),
1783 WINPR_ASSERTING_INT_CAST(UINT16, info->height), info->bitsColor,
1784 WINPR_ASSERTING_INT_CAST(UINT16, info->cbBitsColor), info->bitsMask,
1785 WINPR_ASSERTING_INT_CAST(UINT16, info->cbBitsMask), info->colorTable,
1786 WINPR_ASSERTING_INT_CAST(UINT16, info->cbColorTable), info->bpp) == TRUE;
1789BOOL SdlRail::window_icon(rdpContext* context,
const WINDOW_ORDER_INFO* orderInfo,
1792 WINPR_ASSERT(orderInfo);
1793 WINPR_ASSERT(windowIcon);
1794 WINPR_ASSERT(windowIcon->iconInfo);
1795 auto rail = SdlRail::get(context);
1799 std::unique_lock lock(rail->_windowsLock);
1800 auto appWindow = rail->getWindow(orderInfo->windowId);
1805 const ICON_INFO* info = windowIcon->iconInfo;
1806 auto* icon = rail->iconCacheLookup(info->cacheId, info->cacheEntry);
1807 if (!icon || !convertRailIcon(info, *icon))
1809 WLog_WARN(TAG,
"failed to decode icon %02" PRIX32
":%04" PRIX32
" for window 0x%08" PRIx32,
1810 info->cacheId, info->cacheEntry, orderInfo->windowId);
1813 appWindow->setIcon(*icon);
1815 (void)sdl_push_user_event(SDL_EVENT_USER_UPDATE);
1819BOOL SdlRail::window_cached_icon(rdpContext* context,
const WINDOW_ORDER_INFO* orderInfo,
1822 WINPR_ASSERT(orderInfo);
1823 WINPR_ASSERT(windowCachedIcon);
1824 auto rail = SdlRail::get(context);
1828 std::unique_lock lock(rail->_windowsLock);
1829 auto appWindow = rail->getWindow(orderInfo->windowId);
1834 auto* icon = rail->iconCacheLookup(cached.cacheId, cached.cacheEntry);
1835 if (!icon || icon->bgra.empty())
1838 "cached icon %02" PRIX32
":%04" PRIX32
" not in cache (window 0x%08" PRIx32
")",
1839 cached.cacheId, cached.cacheEntry, orderInfo->windowId);
1842 appWindow->setIcon(*icon);
1844 (void)sdl_push_user_event(SDL_EVENT_USER_UPDATE);
1848BOOL SdlRail::window_delete(rdpContext* context,
const WINDOW_ORDER_INFO* orderInfo)
1850 WINPR_ASSERT(orderInfo);
1851 auto rail = SdlRail::get(context);
1856 std::unique_lock lock(rail->_windowsLock);
1857 auto appWindow = rail->getWindow(orderInfo->windowId);
1860 WLog_DBG(TAG,
"window delete id=0x%08" PRIx32, orderInfo->windowId);
1861 appWindow->markDeleted();
1865 if (rail->_localMove.id == orderInfo->windowId)
1866 rail->_localMove = {};
1867 if (rail->_clientActiveId == orderInfo->windowId)
1868 rail->_clientActiveId = 0;
1869 if (rail->_focusedAppId == orderInfo->windowId)
1870 rail->_focusedAppId = 0;
1872 (void)sdl_push_user_event(SDL_EVENT_USER_UPDATE);
1876BOOL SdlRail::monitored_desktop(rdpContext* context,
const WINDOW_ORDER_INFO* orderInfo,
1879 WINPR_ASSERT(orderInfo);
1880 WINPR_ASSERT(monitoredDesktop);
1881 auto rail = SdlRail::get(context);
1886 if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_DESKTOP_ARC_COMPLETED)
1888 rail->enableRemoteAppMode(
true);
1892 if (app && (strnlen(app, 1) > 0) && !rail->_appStarted)
1894 WLog_DBG(TAG,
"RAIL mode enabled (monitored desktop); launching '%s'", app);
1895 if (client_rail_server_start_cmd(rail->_rail) != CHANNEL_RC_OK)
1897 WLog_ERR(TAG,
"client_rail_server_start_cmd failed for '%s'", app);
1900 rail->_appStarted =
true;
1905 if (orderInfo->fieldFlags &
1906 (WINDOW_ORDER_FIELD_DESKTOP_ZORDER | WINDOW_ORDER_FIELD_DESKTOP_ACTIVE_WND))
1908 std::unique_lock lock(rail->_windowsLock);
1909 if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_DESKTOP_ACTIVE_WND)
1913 rail->_clientActiveId = monitoredDesktop->activeWindowId;
1915 if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_DESKTOP_ZORDER)
1917 if (monitoredDesktop->windowIds && (monitoredDesktop->numWindowIds > 0))
1918 rail->_zOrder.assign(monitoredDesktop->windowIds,
1919 monitoredDesktop->windowIds + monitoredDesktop->numWindowIds);
1921 rail->_zOrder.clear();
1922 rail->_zOrderDirty =
true;
1923 (void)sdl_push_user_event(SDL_EVENT_USER_UPDATE);
1929BOOL SdlRail::non_monitored_desktop(rdpContext* context,
1932 WINPR_ASSERT(orderInfo);
1933 auto rail = SdlRail::get(context);
1936 WLog_DBG(TAG,
"RAIL mode disabled (non-monitored desktop)");
1937 rail->enableRemoteAppMode(
false);
WINPR_ATTR_NODISCARD FREERDP_API const char * freerdp_settings_get_string(const rdpSettings *settings, FreeRDP_Settings_Keys_String id)
Returns a immutable string settings value.
WINPR_ATTR_NODISCARD FREERDP_API INT32 freerdp_settings_get_int32(const rdpSettings *settings, FreeRDP_Settings_Keys_Int32 id)
Returns a INT32 settings value.
WINPR_ATTR_NODISCARD FREERDP_API UINT32 freerdp_settings_get_uint32(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id)
Returns a UINT32 settings value.