23#include <freerdp/codec/color.h>
24#include <freerdp/log.h>
25#include <freerdp/window.h>
27#include "sdl_rail_platform.hpp"
28#include "sdl_rail_window.hpp"
29#include "sdl_rail_window_style.hpp"
30#include "sdl_utils.hpp"
31#include "sdl_window.hpp"
33#define TAG CLIENT_TAG("sdl.rail.window")
36static constexpr uint64_t kAwaitServerFrameMs = 1000;
39static constexpr uint64_t kAwaitRestoreRectMs = 500;
42static constexpr size_t kMaxReported = 16;
45static constexpr uint64_t kReportConfirmMs = 1000;
47static void armDeadline(uint64_t& until, uint64_t ms)
49 until = SDL_GetTicks() + ms;
50 if (!sdl_push_update_after(WINPR_ASSERTING_INT_CAST(Uint32, ms + 1)))
51 WLog_WARN(TAG,
"SDL_AddTimer failed: %s", SDL_GetError());
55static constexpr uint64_t kMoveActiveMs = 250;
58static constexpr uint64_t kPlaceWaitMs = 50;
61static bool regionHasAlpha(
const uint8_t* base, uint32_t stride,
const SDL_Rect& r)
63 for (
int y = r.y; y < r.y + r.h; y++)
65 const uint8_t* row = base +
static_cast<size_t>(y) * stride;
66 for (
int x = r.x; x < r.x + r.w; x++)
67 if (row[
static_cast<size_t>(x) * 4 + 3] != 0)
73static bool regionIsBlank(
const uint8_t* base, uint32_t stride,
const SDL_Rect& r)
75 for (
int y = r.y; y < r.y + r.h; y++)
77 const auto* row =
reinterpret_cast<const uint32_t*
>(base +
static_cast<size_t>(y) * stride);
78 for (
int x = r.x; x < r.x + r.w; x++)
85SdlRailWindow::SdlRailWindow(uint64_t
id,
const SDL_Rect& rect) : _id(id), _windowRect(rect)
90SdlRailWindow::~SdlRailWindow() =
default;
92uint64_t SdlRailWindow::id()
const
97SDL_WindowID SdlRailWindow::sdlId()
const
99 return _win ? _win->id() : 0;
102SDL_Window* SdlRailWindow::window()
const
104 return _win ? _win->window() :
nullptr;
107SDL_Renderer* SdlRailWindow::renderer()
const
109 return _win ? _win->renderer() :
nullptr;
112void SdlRailWindow::updateWindowRect(
const SDL_Rect& rect)
114 std::unique_lock lock(_gfxLock);
116 if (_minState.server)
118 _awaitRestoreUntil = 0;
119 if (_localMoveActive)
122 if (!_localMoveIsResize && ((rect.w != _windowRect.w) || (rect.h != _windowRect.h)))
124 WLog_DBG(TAG,
"size adopted mid-move id=0x%08" PRIx32
" %dx%d -> %dx%d at %d,%d",
125 static_cast<uint32_t
>(_id), _windowRect.w, _windowRect.h, rect.w, rect.h,
127 _windowRect.w = rect.w;
128 _windowRect.h = rect.h;
130 _localMoveServerPos = { rect.x, rect.y };
131 _localMoveSizeChanged =
true;
137 if (SDL_RectsEqual(&rect, &_windowRect))
143 const auto echo = std::find_if(_reported.begin(), _reported.end(),
144 [&](
const SDL_Rect& r) { return SDL_RectsEqual(&r, &rect); });
145 if (echo != _reported.end())
147 WLog_DBG(TAG,
"late echo id=0x%08" PRIx32
" %dx%d ignored",
static_cast<uint32_t
>(_id),
149 _reported.erase(_reported.begin(), echo + 1);
154 if ((rect.w != _windowRect.w) || (rect.h != _windowRect.h))
158 _geometryDirty =
true;
161void SdlRailWindow::setLocalMoveActive(
bool active)
163 std::unique_lock lock(_gfxLock);
164 _localMoveActive = active;
167 _localMoveIsResize =
false;
168 _localMoveSizeChanged =
false;
172void SdlRailWindow::setResizeAnchor(
bool right,
bool bottom)
174 std::unique_lock lock(_gfxLock);
175 _resizeAnchorRight = right;
176 _resizeAnchorBottom = bottom;
177 _localMoveIsResize =
true;
180bool SdlRailWindow::localMoveActive()
const
182 std::unique_lock lock(_gfxLock);
183 return _localMoveActive;
186bool SdlRailWindow::localMoveSizeChanged()
const
188 std::unique_lock lock(_gfxLock);
189 return _localMoveSizeChanged;
192SDL_Point SdlRailWindow::localMoveServerPos()
const
194 std::unique_lock lock(_gfxLock);
195 return _localMoveServerPos;
198void SdlRailWindow::adoptLocalGeometry(
const SDL_Rect& rect)
201 std::unique_lock lock(_gfxLock);
203 _geomApplyPending =
false;
207 _visOffset.x += rect.x - _windowRect.x;
208 _visOffset.y += rect.y - _windowRect.y;
210 if (!SDL_RectsEqual(&rect, &_windowRect))
212 if (_reported.size() >= kMaxReported)
213 _reported.erase(_reported.begin());
214 _reported.push_back(rect);
215 armDeadline(_reportCheckAt, kReportConfirmMs);
218 _geometryDirty =
false;
220 _awaitingFrameUntil =
221 (_localMoveActive && _localMoveIsResize) ? (SDL_GetTicks() + kAwaitServerFrameMs) : 0;
222 _localMoveActive =
false;
223 _needsFullBlit =
true;
226 _gfxDamage.assign(1, SDL_Rect{ 0, 0,
static_cast<int>(_gfxW),
static_cast<int>(_gfxH) });
229SDL_Rect SdlRailWindow::windowRect()
const
231 std::unique_lock lock(_gfxLock);
235void SdlRailWindow::markDeleted()
237 std::unique_lock lock(_gfxLock);
241void SdlRailWindow::releaseWindow()
243 std::unique_lock lock(_gfxLock);
246 WLog_DBG(TAG,
"release id=0x%08" PRIx32
" %s%s",
static_cast<uint32_t
>(_id), role(),
247 _deleted ?
"" :
" (SDL parent gone, re-created on next pass)");
252 _gfxPresented =
false;
253 _needsFullBlit =
true;
254 _geometryDirty =
true;
255 _parentApplied =
false;
257 _appliedInsets = { 0, 0, 0, 0 };
258 _extentsApplied = { 0, 0, 0, 0 };
259 _lastWinW = _lastWinH = _lastGfxW = _lastGfxH = 0;
262bool SdlRailWindow::isDeleted()
const
264 std::unique_lock lock(_gfxLock);
268bool SdlRailWindow::geomApplyPending()
const
270 std::unique_lock lock(_gfxLock);
271 return _geomApplyPending;
274void SdlRailWindow::clearGeomApplyPending()
276 std::unique_lock lock(_gfxLock);
277 _geomApplyPending =
false;
280void SdlRailWindow::armLoopEnd()
282 std::unique_lock lock(_gfxLock);
283 _loopEnd.pending =
true;
286bool SdlRailWindow::loopEndPending()
const
288 std::unique_lock lock(_gfxLock);
289 return _loopEnd.pending;
292void SdlRailWindow::deferMaximize()
294 std::unique_lock lock(_gfxLock);
295 _loopEnd.maximize =
true;
298void SdlRailWindow::deferSnap(
const SDL_Rect& serverRect)
300 std::unique_lock lock(_gfxLock);
301 _loopEnd.snap =
true;
302 _loopEnd.snapRect = serverRect;
305void SdlRailWindow::clearLoopEnd()
307 std::unique_lock lock(_gfxLock);
313 std::unique_lock lock(_gfxLock);
314 const LoopEndActions actions{ _loopEnd.maximize, _loopEnd.snap, _loopEnd.snapRect };
319bool SdlRailWindow::takeWmOverride(SDL_Rect& outer)
321 std::unique_lock lock(_gfxLock);
324 outer = _wmRefusedOuter;
329void SdlRailWindow::setVisibilityRects(std::vector<SDL_Rect> rects)
331 std::unique_lock lock(_gfxLock);
332 _visRects = std::move(rects);
336void SdlRailWindow::setVisibleOffset(SDL_Point offset)
338 std::unique_lock lock(_gfxLock);
340 if (_localMoveActive)
342 if (_visOffsetSet && (offset.x == _visOffset.x) && (offset.y == _visOffset.y))
345 _visOffsetSet =
true;
349void SdlRailWindow::setMinMaxSize(SDL_Point minSize, SDL_Point maxSize)
351 std::unique_lock lock(_gfxLock);
352 _minSize = { std::max(0, minSize.x), std::max(0, minSize.y) };
353 _maxSize = { std::max(0, maxSize.x), std::max(0, maxSize.y) };
356 _geometryDirty =
true;
359void SdlRailWindow::setResizeMargins(
int left,
int top,
int right,
int bottom)
361 std::unique_lock lock(_gfxLock);
362 const SDL_Rect m = { left, top, right, bottom };
363 if (SDL_RectsEqual(&m, &_resizeMargins))
367 if (!railMaximized())
368 _geometryDirty =
true;
371SDL_Rect SdlRailWindow::resizeMargins()
const
373 std::unique_lock lock(_gfxLock);
374 return _resizeMargins;
377void SdlRailWindow::setFrameMargins(
const SDL_Rect& m)
379 std::unique_lock lock(_gfxLock);
383SDL_Rect SdlRailWindow::frameMargins()
const
385 std::unique_lock lock(_gfxLock);
386 return _frameMargins;
389bool SdlRailWindow::takeUnconfirmedReport(SDL_Rect& rect)
391 std::unique_lock lock(_gfxLock);
392 if ((_reportCheckAt == 0) || (SDL_GetTicks() < _reportCheckAt))
395 if (_reported.empty())
397 rect = _reported.back();
402void SdlRailWindow::setOwner(uint64_t ownerId)
404 std::unique_lock lock(_gfxLock);
408uint64_t SdlRailWindow::owner()
const
410 std::unique_lock lock(_gfxLock);
414bool SdlRailWindow::isPopup()
const
416 std::unique_lock lock(_gfxLock);
420bool SdlRailWindow::isFullscreen()
const
422 std::unique_lock lock(_gfxLock);
426bool SdlRailWindow::activatable()
const
428 std::unique_lock lock(_gfxLock);
429 return (!_isPopup || _fullscreen) && ((_exStyle & WS_EX_NOACTIVATE) == 0);
432bool SdlRailWindow::isLayered()
const
434 std::unique_lock lock(_gfxLock);
438void SdlRailWindow::setShadowAnchored(
bool anchored)
440 std::unique_lock lock(_gfxLock);
441 _shadowAnchored = anchored;
444void SdlRailWindow::setFrame(
bool frame)
446 std::unique_lock lock(_gfxLock);
450bool SdlRailWindow::isFrame()
const
452 std::unique_lock lock(_gfxLock);
456bool SdlRailWindow::styleResizable()
const
459 return _everResizable;
463static constexpr int kMinGrip = 4;
465SDL_Rect SdlRailWindow::bandMargins()
const
467 if (!_visible || _isPopup || _layered || !styleResizable() || effectivelyMaximized())
468 return { 0, 0, 0, 0 };
470 const SDL_Rect& m = _resizeMargins;
471 return { std::max(m.x, kMinGrip), std::max(m.y, kMinGrip), std::max(m.w, kMinGrip),
472 std::max(m.h, kMinGrip) };
475SDL_Rect SdlRailWindow::bandInsets()
const
478 const auto band = railPlatformCaps().band;
479 if ((band == RailBandMechanism::None) || (band == RailBandMechanism::NativeFrame))
480 return { 0, 0, 0, 0 };
481 return bandMargins();
485static SDL_Rect addInsets(
const SDL_Rect& r,
const SDL_Rect& i)
487 return { r.x - i.x, r.y - i.y, r.w + i.x + i.w, r.h + i.y + i.h };
492static SDL_Rect stripInsets(
const SDL_Rect& r,
const SDL_Rect& i)
494 return { r.x + i.x, r.y + i.y, r.w - i.x - i.w, r.h - i.y - i.h };
498bool SdlRailWindow::isFullDisplaySize()
const
501 return SDL_GetDisplayBounds(SDL_GetPrimaryDisplay(), &disp) && (_windowRect.w >= disp.w) &&
502 (_windowRect.h >= disp.h);
507SDL_Rect SdlRailWindow::targetOuterRect()
const
509 SDL_Rect r = addInsets(_windowRect, bandInsets());
510 r.x += _localShift.x;
511 r.y += _localShift.y;
516SDL_Rect SdlRailWindow::insets()
const
518 std::unique_lock lock(_gfxLock);
519 return _appliedInsets;
523SDL_Rect SdlRailWindow::outerRect()
const
525 std::unique_lock lock(_gfxLock);
526 SDL_Rect r = addInsets(_windowRect, _appliedInsets);
527 r.x += _localShift.x;
528 r.y += _localShift.y;
532SDL_Rect SdlRailWindow::serverRect(
const SDL_Rect& outer)
const
534 SDL_Rect r = stripInsets(outer, insets());
535 r.x -= _localShift.x;
536 r.y -= _localShift.y;
540void SdlRailWindow::setLocalShift(
const SDL_Point& shift)
542 std::unique_lock lock(_gfxLock);
547SDL_Point SdlRailWindow::blitOffset()
const
549 if (!effectivelyMaximized())
550 return { _appliedInsets.x, _appliedInsets.y };
552 const bool frameInSurface =
553 ((_frameMargins.x > 0) || (_frameMargins.y > 0)) &&
554 (
static_cast<int>(_gfxW) == _windowRect.w + _frameMargins.x + _frameMargins.w) &&
555 (
static_cast<int>(_gfxH) == _windowRect.h + _frameMargins.y + _frameMargins.h);
556 SDL_Point content = { 0, 0 };
558 content = { _frameMargins.x, _frameMargins.y };
559 else if (_visOffsetSet)
560 content = { _visOffset.x - _windowRect.x, _visOffset.y - _windowRect.y };
561 return { -std::clamp(content.x, 0,
static_cast<int>(_gfxW)),
562 -std::clamp(content.y, 0,
static_cast<int>(_gfxH)) };
566static Uint32 placeWaitTimer([[maybe_unused]]
void* userdata, [[maybe_unused]] SDL_TimerID timerID,
567 [[maybe_unused]] Uint32 interval)
569 if (!sdl_push_user_event(SDL_EVENT_USER_UPDATE))
570 WLog_WARN(TAG,
"could not wake the paint loop for a held popup");
576void SdlRailWindow::notePlaced()
578 std::unique_lock lock(_gfxLock);
583void SdlRailWindow::placeRequested()
585 _pendingRel = _requestedRel;
586 _placeInFlight =
false;
590bool SdlRailWindow::holdForPlacement()
594 if (SDL_GetTicks() - _requestedAt < kPlaceWaitMs)
601void SdlRailWindow::placePopup(
const SDL_Rect& vis,
const SDL_Rect& parentRect)
603 const SDL_Point rel = { vis.x - parentRect.x, vis.y - parentRect.y };
604 const bool wayland = !railPlatformCaps().supportsWindowPositions;
605 const bool moved = (rel.x != _requestedRel.x) || (rel.y != _requestedRel.y);
609 _requestedAt = SDL_GetTicks();
611 if (!SDL_SetWindowPosition(_win->window(), rel.x, rel.y))
613 WLog_VRB(TAG,
"popup reposition unsupported id=0x%08" PRIx32
": %s",
614 static_cast<uint32_t
>(_id), SDL_GetError());
619 _needsFullBlit =
true;
620 if (!moved || !_mapped)
622 _placeInFlight =
true;
623 if (SDL_AddTimer(
static_cast<Uint32
>(kPlaceWaitMs + 1), placeWaitTimer,
nullptr) == 0)
624 WLog_WARN(TAG,
"popup placement timer failed: %s", SDL_GetError());
628void SdlRailWindow::commitPlacement()
632 _placedRel = _pendingRel;
636bool SdlRailWindow::placedRel(SDL_Point& rel)
const
638 std::unique_lock lock(_gfxLock);
640 if (!_placedRel || (SDL_GetTicks() - _requestedAt >= kMoveActiveMs))
646SDL_Point SdlRailWindow::serverOrigin()
const
648 std::unique_lock lock(_gfxLock);
650 const SDL_Point dst = blitOffset();
651 return { _windowRect.x - dst.x, _windowRect.y - dst.y };
654void SdlRailWindow::setStyle(uint32_t style, uint32_t exStyle)
656 std::unique_lock lock(_gfxLock);
657 const bool wasTopmost = _topmost;
660 _topmost = (exStyle & WS_EX_TOPMOST) != 0;
661 if (_topmost != wasTopmost)
662 _topmostDirty =
true;
663 if (!_everResizable && ((style & (WS_THICKFRAME | WS_MAXIMIZEBOX)) != 0))
665 _everResizable =
true;
669 if (!_popupClassified)
671 const auto classification = classifySdlRailWindowStyle(style, exStyle, _ownerId != 0);
672 _isPopup = classification.popup;
673 _layered = classification.layered;
674 _overlay = classification.overlay;
675 _popupClassified =
true;
679void SdlRailWindow::setTitle(
const std::string& title)
681 std::unique_lock lock(_gfxLock);
686void SdlRailWindow::setVisible(
bool visible)
688 std::unique_lock lock(_gfxLock);
692void SdlRailWindow::setIcon(
const SdlRailIcon& icon)
694 std::unique_lock lock(_gfxLock);
699bool SdlRailWindow::create(SDL_Window* parent,
const SDL_Rect& parentRect)
707 const SDL_Rect vis = targetOuterRect();
708 _appliedInsets = bandInsets();
710 const bool fullscreen = _isPopup && !caps.supportsWindowPositions && isFullDisplaySize();
711 if (_isPopup && parent && !fullscreen)
714 const SDL_Rect rel = { vis.x - parentRect.x, vis.y - parentRect.y, vis.w, vis.h };
715 _requestedRel = { rel.x, rel.y };
716 _win = std::make_unique<SdlWindow>(
717 SdlWindow::createPopup(parent, rel, caps.supportsTransparentWindows, _overlay));
719 else if (_isPopup && !caps.supportsWindowPositions && !fullscreen)
722 WLog_VRB(TAG,
"popup create deferred id=0x%08" PRIx32
": no parent yet",
723 static_cast<uint32_t
>(_id));
729 Uint32 flags = SDL_WINDOW_HIDDEN;
730 if (_isPopup || (caps.band != RailBandMechanism::NativeFrame))
731 flags |= SDL_WINDOW_BORDERLESS;
732 if (caps.supportsTransparentWindows)
733 flags |= SDL_WINDOW_TRANSPARENT;
734 if ((_exStyle & WS_EX_NOACTIVATE) != 0)
735 flags |= SDL_WINDOW_NOT_FOCUSABLE;
736 _win = std::make_unique<SdlWindow>(
737 SdlWindow::create(SDL_GetPrimaryDisplay(), _title, flags, vis));
739 if (!_win || !_win->window() || !_win->renderer())
742 WLog_WARN(TAG,
"create failed id=0x%08" PRIx32
" %s",
static_cast<uint32_t
>(_id), role());
745 _win->resizeable(styleResizable());
750 SDL_SetWindowFullscreen(_win->window(),
true);
751 WLog_DBG(TAG,
"fullscreen id=0x%08" PRIx32
" %dx%d (WS_POPUP spans display)",
752 static_cast<uint32_t
>(_id), _windowRect.w, _windowRect.h);
759 railPlatformPrepareWindow(_win->window());
760 std::ignore = railPlatformSetWindowRect(_win->window(), vis);
764 "create id=0x%08" PRIx32
" sdl=%" PRIu32
" %s vis=%dx%d+%d+%d margins=L%d,T%d,R%d,B%d "
766 static_cast<uint32_t
>(_id),
static_cast<uint32_t
>(_win->id()), role(), vis.w, vis.h,
767 vis.x, vis.y, _resizeMargins.x, _resizeMargins.y, _resizeMargins.w, _resizeMargins.h,
768 caps.supportsTransparentWindows ? 1 : 0);
772bool SdlRailWindow::reconcile(SDL_Window* parent,
const SDL_Rect& parentRect)
774 std::unique_lock lock(_gfxLock);
778 const bool popupReady = !_isPopup || _hasGfx;
780 const bool unplaceable =
781 _frame || (_overlay && !railPlatformCaps().supportsWindowPositions && isFullDisplaySize());
783 const bool drawable =
784 _visible && (_windowRect.w > 0) && (_windowRect.h > 0) && popupReady && !unplaceable &&
785 (!_layered || (_hasGfx && !_visRects.empty() && (_shadowAnchored || _overlay)));
790 if (_layered && _hasGfx && !_visRects.empty() && !_shadowAnchored && !_overlay &&
791 !_shadowSuppressLogged)
793 _shadowSuppressLogged =
true;
795 "shadow-rule suppressed id=0x%08" PRIx32
" %dx%d style=0x%08" PRIx32
797 static_cast<uint32_t
>(_id), _windowRect.w, _windowRect.h, _style, _exStyle);
801 if ((SDL_GetWindowFlags(_win->window()) & SDL_WINDOW_HIDDEN) == 0)
803 WLog_DBG(TAG,
"hide id=0x%08" PRIx32
" %s",
static_cast<uint32_t
>(_id), role());
804 SDL_HideWindow(_win->window());
808 if (_isPopup && !_visible)
810 _gfxPresented =
false;
818 if (!create(parent, parentRect))
820 _geometryDirty =
false;
827 _topmostDirty =
false;
829 if (!_isPopup && !_layered)
830 std::ignore = SDL_SetWindowAlwaysOnTop(_win->window(), _topmost);
836 _win->resizeable(styleResizable());
843 if (applyServerState(_maxState,
"maximize", SDL_MaximizeWindow))
844 armDeadline(_awaitRestoreUntil, kAwaitRestoreRectMs);
846 if ((_localRestoreUntil != 0) && (SDL_GetTicks() >= _localRestoreUntil))
848 _localRestoreUntil = 0;
849 if (_minState.server && !_minState.rail && !_restoreRetried)
851 _restoreRetried =
true;
852 _restoreRetry =
true;
853 armDeadline(_localRestoreUntil, kAwaitRestoreRectMs);
855 else if (_minState.server != _minState.rail)
856 _minState.dirty =
true;
858 std::ignore = applyServerState(_minState,
"minimize", SDL_MinimizeWindow);
859 const bool restorePending = restoreRectPending();
862 const bool maxed = effectivelyMaximized();
863 const SDL_Rect band = bandInsets();
864 if ((maxed != _wasMaximized) && !_localMoveActive && !restorePending)
866 setAppliedInsets(band);
868 _geometryDirty =
true;
869 _wasMaximized = maxed;
873 if ((railPlatformCaps().band == RailBandMechanism::FrameExtents) && !restorePending &&
874 !SDL_RectsEqual(&band, &_extentsApplied) && railPlatformSetBand(_win->window(), band))
876 _extentsApplied = band;
878 const bool wmOwnsGeometry =
879 _localMoveActive || geometryFrozen() || _fullscreen || restorePending;
882 if ((_minMaxDirty || _geometryDirty) && !wmOwnsGeometry)
884 const SDL_Rect vis = targetOuterRect();
885 const int bandW = band.x + band.w;
886 const int bandH = band.y + band.h;
888 const SDL_Rect& m = _resizeMargins;
889 const int padW = bandW - m.x - m.w;
890 const int padH = bandH - m.y - m.h;
891 const auto toOuter = [](
int track,
int pad)
892 {
return (track > 0) ? std::max(1, track + pad) : 0; };
893 const int minW = toOuter(_minSize.x, padW);
894 const int minH = toOuter(_minSize.y, padH);
895 SDL_SetWindowMinimumSize(_win->window(), std::clamp(minW, 0, vis.w),
896 std::clamp(minH, 0, vis.h));
897 int maxW = toOuter(_maxSize.x, padW);
898 int maxH = toOuter(_maxSize.y, padH);
901 if (!railPlatformCaps().supportsWindowPositions &&
902 SDL_GetDisplayUsableBounds(SDL_GetPrimaryDisplay(), &usable))
904 const int capW = usable.w + bandW;
905 const int capH = usable.h + bandH;
906 if ((maxW == 0) || (maxW > capW))
908 if ((maxH == 0) || (maxH > capH))
911 SDL_SetWindowMaximumSize(_win->window(), maxW, maxH);
912 _minMaxDirty =
false;
916 if (_geometryDirty && !wmOwnsGeometry)
918 const SDL_Rect vis = targetOuterRect();
920 setAppliedInsets(band);
923 SDL_GetWindowSize(_win->window(), &cw, &ch);
924 bool applied =
false;
927 if (_isPopup && parent)
929 placePopup(vis, parentRect);
932 else if (railPlatformCaps().supportsWindowPositions &&
933 !(_isPopup && SDL_GetWindowParent(_win->window())))
937 SDL_GetWindowPosition(_win->window(), &cx, &cy);
938 if ((cx != vis.x) || (cy != vis.y))
940 placed = railPlatformSetWindowRect(_win->window(), vis);
942 SDL_SetWindowPosition(_win->window(), vis.x, vis.y);
946 if ((cw != vis.w) || (ch != vis.h))
948 if (!placed && !railPlatformSetWindowRect(_win->window(), vis))
949 std::ignore = _win->resize({ vis.w, vis.h });
953 if (railPlatformCaps().supportsWindowPositions && !_isPopup && !_localMoveActive &&
956 const bool settled = SDL_SyncWindow(_win->window());
959 SDL_GetWindowSize(_win->window(), &aw, &ah);
960 if (settled && ((aw != vis.w) || (ah != vis.h)))
963 _wmRefusedOuter = { vis.x, vis.y, aw, ah };
969 _geometryDirty =
false;
971 _geomApplyPending = applied && !_isPopup;
975 if (!_isPopup && parent && !_parentApplied &&
976 ((SDL_GetWindowFlags(parent) & SDL_WINDOW_HIDDEN) == 0))
978 if (SDL_SetWindowParent(_win->window(), parent))
980 _parentApplied =
true;
982 if (((SDL_GetWindowFlags(_win->window()) & SDL_WINDOW_HIDDEN) == 0) &&
983 !railPlatformRaiseOwned(_win->window()))
984 WLog_WARN(TAG,
"raising dialog id=0x%08" PRIx32
" failed",
985 static_cast<uint32_t
>(_id));
991 SDL_SetWindowTitle(_win->window(), _title.c_str());
996 if (!_isPopup && !_icon.bgra.empty())
998 SDL_Surface* s = SDL_CreateSurfaceFrom(
999 static_cast<int>(_icon.w),
static_cast<int>(_icon.h), SDL_PIXELFORMAT_BGRA32,
1000 _icon.bgra.data(),
static_cast<int>(_icon.w * 4));
1003 if (!SDL_SetWindowIcon(_win->window(), s))
1004 WLog_WARN(TAG,
"SDL_SetWindowIcon failed for window 0x%08" PRIx32
": %s",
1005 static_cast<uint32_t
>(_id), SDL_GetError());
1006 SDL_DestroySurface(s);
1013 if (!_minState.rail && _gfxPresented &&
1014 (SDL_GetWindowFlags(_win->window()) & SDL_WINDOW_HIDDEN))
1017 SDL_Window* owner = _isPopup ? nullptr : SDL_GetWindowParent(_win->window());
1018 if (owner && ((SDL_GetWindowFlags(owner) & SDL_WINDOW_HIDDEN) != 0))
1020 if (SDL_SetWindowParent(_win->window(),
nullptr))
1021 _parentApplied =
false;
1023 WLog_WARN(TAG,
"detaching dialog id=0x%08" PRIx32
" failed: %s",
1024 static_cast<uint32_t
>(_id), SDL_GetError());
1026 SDL_ShowWindow(_win->window());
1031bool SdlRailWindow::takeSurfaceChange(uint32_t surfaceId)
1033 std::unique_lock lock(_gfxLock);
1034 if (_surfaceId == surfaceId)
1036 _surfaceId = surfaceId;
1040void SdlRailWindow::updateGfxSurface(
const void* data, uint32_t stride, uint32_t width,
1041 uint32_t height,
const RECTANGLE_16* damage, uint32_t nbDamage,
1042 WINPR_ATTR_UNUSED uint32_t format)
1044 std::unique_lock lock(_gfxLock);
1046 WINPR_ASSERT((format == PIXEL_FORMAT_BGRA32) || (format == PIXEL_FORMAT_BGRX32));
1048 const size_t bytes =
static_cast<size_t>(stride) * height;
1049 if (!data || (bytes == 0) || (width == 0) || (height == 0))
1052 WLog_DBG(TAG,
"gfx cleared id=0x%08" PRIx32
" (surface unmapped)",
1053 static_cast<uint32_t
>(_id));
1057 _gfxStride = stride;
1060 _gfxVisOrigin = { 0, 0 };
1064 const auto* src =
static_cast<const uint8_t*
>(data);
1066 const bool full = !_hasGfx || (_gfxBuffer.size() != bytes) || (_gfxStride != stride) ||
1067 (_gfxW != width) || (_gfxH != height) || (nbDamage == 0);
1070 const SDL_Rect bounds{ 0, 0,
static_cast<int>(width),
static_cast<int>(height) };
1072 if (!_hasGfx && regionIsBlank(src, stride, bounds))
1074 _gfxBuffer.assign(src, src + bytes);
1075 _gfxDamage.assign(1, SDL_Rect{ 0, 0,
static_cast<int>(width),
static_cast<int>(height) });
1076 _needsFullBlit =
true;
1078 _gfxHasAlpha = honorsAlpha() &&
1079 regionHasAlpha(_gfxBuffer.data(), stride,
1080 { 0, 0, static_cast<int>(width), static_cast<int>(height) });
1084 const SDL_Rect bounds{ 0, 0,
static_cast<int>(width),
static_cast<int>(height) };
1085 for (uint32_t i = 0; i < nbDamage; i++)
1087 const SDL_Rect r{ damage[i].left, damage[i].top, damage[i].right - damage[i].left,
1088 damage[i].bottom - damage[i].top };
1090 if (!SDL_GetRectIntersection(&r, &bounds, &clip))
1092 std::ignore = freerdp_image_copy_no_overlap(
1093 _gfxBuffer.data(), PIXEL_FORMAT_BGRA32, stride,
static_cast<UINT32
>(clip.x),
1094 static_cast<UINT32
>(clip.y),
static_cast<UINT32
>(clip.w),
1095 static_cast<UINT32
>(clip.h), src, PIXEL_FORMAT_BGRA32, stride,
1096 static_cast<UINT32
>(clip.x),
static_cast<UINT32
>(clip.y),
nullptr,
1098 _gfxDamage.push_back(clip);
1100 if (honorsAlpha() && !_gfxHasAlpha && regionHasAlpha(_gfxBuffer.data(), stride, clip))
1101 _gfxHasAlpha =
true;
1104 if (_gfxDamage.size() > 32)
1105 _gfxDamage.assign(1,
1106 SDL_Rect{ 0, 0,
static_cast<int>(width),
static_cast<int>(height) });
1108 if ((_gfxW != width) || (_gfxH != height))
1110 _resizeAnchorRight =
false;
1111 _resizeAnchorBottom =
false;
1112 _needsFullBlit =
true;
1114 _gfxVisOrigin = { 0, 0 };
1117 _gfxStride = stride;
1120 WLog_VRB(TAG,
"gfx id=0x%08" PRIx32
" %ux%u full=%d nDamage=%u",
static_cast<uint32_t
>(_id),
1121 width, height, full ? 1 : 0, nbDamage);
1124void SdlRailWindow::invalidateAll()
1126 std::unique_lock lock(_gfxLock);
1127 _needsFullBlit =
true;
1129 _gfxDamage.assign(1, SDL_Rect{ 0, 0,
static_cast<int>(_gfxW),
static_cast<int>(_gfxH) });
1132void SdlRailWindow::setServerState(StateSync& s,
bool m)
1134 std::unique_lock lock(_gfxLock);
1142void SdlRailWindow::setServerMaximized(
bool m)
1144 setServerState(_maxState, m);
1147void SdlRailWindow::setServerMinimized(
bool m)
1150 std::unique_lock lock(_gfxLock);
1152 if (m && (_localRestoreUntil != 0) && (SDL_GetTicks() < _localRestoreUntil))
1154 _minState.server =
true;
1158 _localRestoreUntil = 0;
1160 setServerState(_minState, m);
1163void SdlRailWindow::noteLocalRestore()
1165 std::unique_lock lock(_gfxLock);
1166 _restoreRetried =
false;
1167 armDeadline(_localRestoreUntil, kAwaitRestoreRectMs);
1170bool SdlRailWindow::takeRestoreRetry()
1172 std::unique_lock lock(_gfxLock);
1173 return std::exchange(_restoreRetry,
false);
1176bool SdlRailWindow::applyServerState(StateSync& s,
const char* what,
bool (*enter)(SDL_Window*))
1180 bool restored =
false;
1181 if (s.server && !s.rail)
1185 WLog_DBG(TAG,
"%s id=0x%08" PRIx32
"", what,
static_cast<uint32_t
>(_id));
1186 enter(_win->window());
1188 else if (!s.server && s.rail)
1192 WLog_DBG(TAG,
"restore id=0x%08" PRIx32
" (%s)",
static_cast<uint32_t
>(_id), what);
1193 SDL_RestoreWindow(_win->window());
1201 (void)SDL_SyncWindow(_win->window());
1207void SdlRailWindow::setAppliedInsets(
const SDL_Rect& insets)
1209 if (SDL_RectsEqual(&insets, &_appliedInsets))
1211 _appliedInsets = insets;
1212 _needsFullBlit =
true;
1215bool SdlRailWindow::insetsStale()
const
1217 std::unique_lock lock(_gfxLock);
1218 return _wasMaximized != effectivelyMaximized();
1222bool SdlRailWindow::restoreRectPending()
const
1224 return (_awaitRestoreUntil != 0) && (SDL_GetTicks() < _awaitRestoreUntil);
1227bool SdlRailWindow::effectivelyMaximized()
const
1229 return _maxState.rail ||
1230 (_win && (SDL_GetWindowFlags(_win->window()) & SDL_WINDOW_MAXIMIZED) != 0);
1233bool SdlRailWindow::paint(SDL_Surface* primary, SDL_PixelFormat fallbackFormat,
1234 const std::vector<SDL_Rect>& damage, SDL_Window* parent,
1235 const SDL_Rect& parentRect)
1237 if (!reconcile(parent, parentRect))
1241 auto legacy =
false;
1243 std::unique_lock lock(_gfxLock);
1245 ok = paintGfx(fallbackFormat);
1250 ok = paintLegacy(primary, damage);
1253 if (_gfxPresented && !_mapped && !_minState.rail)
1256 WLog_DBG(TAG,
"map id=0x%08" PRIx32
" %s",
static_cast<uint32_t
>(_id), role());
1257 SDL_ShowWindow(_win->window());
1259 if (_isPopup && parent)
1261 std::unique_lock lock(_gfxLock);
1262 const SDL_Rect vis = targetOuterRect();
1263 placePopup(vis, parentRect);
1265 if (((!_isPopup && !_layered) || _fullscreen) && ((_exStyle & WS_EX_NOACTIVATE) == 0))
1272bool SdlRailWindow::paintGfx(SDL_PixelFormat format)
1274 if (holdForPlacement())
1277 const SDL_Rect bi = _appliedInsets;
1280 SDL_GetWindowSizeInPixels(_win->window(), &ww, &wh);
1281 const int cw = ww - bi.x - bi.w;
1282 const int ch = wh - bi.y - bi.h;
1284 static_cast<int>(_gfxW);
1285 const int gh =
static_cast<int>(_gfxH);
1287 if ((_visRects.size() == 1) && (_visRects.at(0).w == gw) && (_visRects.at(0).h == gh))
1288 _gfxVisOrigin = { _visRects.at(0).x, _visRects.at(0).y };
1290 if (!_localMoveActive && (cw == gw) && (ch == gh))
1292 _resizeAnchorRight =
false;
1293 _resizeAnchorBottom =
false;
1294 _awaitingFrameUntil = 0;
1297 if ((_awaitingFrameUntil != 0) && (SDL_GetTicks() > _awaitingFrameUntil))
1299 WLog_DBG(TAG,
"resize frame timeout id=0x%08" PRIx32
" win=%dx%d gfx=%dx%d",
1300 static_cast<uint32_t
>(_id), cw, ch, gw, gh);
1301 _awaitingFrameUntil = 0;
1303 if ((cw != gw) || (ch != gh))
1305 if (_resizeAnchorRight)
1306 _windowRect.x += _windowRect.w - gw;
1307 if (_resizeAnchorBottom)
1308 _windowRect.y += _windowRect.h - gh;
1311 _resizeAnchorRight =
false;
1312 _resizeAnchorBottom =
false;
1313 _geometryDirty =
true;
1314 _needsFullBlit =
true;
1319 const bool awaitingServerResize = (_awaitingFrameUntil != 0) && ((cw != gw) || (ch != gh));
1320 const bool localResize = awaitingServerResize ||
1321 (_localMoveActive && (_localMoveIsResize || (cw != gw) || (ch != gh)));
1324 const bool winResized = (ww != _lastWinW) || (wh != _lastWinH);
1325 const bool gfxResized = (gw != _lastGfxW) || (gh != _lastGfxH);
1326 const bool serverResize =
1327 !_localMoveActive && !awaitingServerResize &&
1328 (winResized || gfxResized || _needsFullBlit || _pendingRel.has_value());
1331 if (!localResize && !serverResize && _gfxDamage.empty() && !(_layered && _visDirty))
1333 WLog_VRB(TAG,
"paintGfx skip id=0x%08" PRIx32
" no-damage no-resize",
1334 static_cast<uint32_t
>(_id));
1339 if (_isPopup && !_gfxPresented && (_gfxW > 0) && (_gfxH > 0))
1341 const uint32_t first = *
reinterpret_cast<const uint32_t*
>(_gfxBuffer.data());
1343 bool uniform = (first & 0x00FFFFFFu) == 0;
1344 for (uint32_t y = 0; uniform && (y < _gfxH); y++)
1346 const auto* row =
reinterpret_cast<const uint32_t*
>(
1347 _gfxBuffer.data() +
static_cast<size_t>(y) * _gfxStride);
1348 for (uint32_t x = 0; x < _gfxW; x++)
1349 if (row[x] != first)
1359 _needsFullBlit =
false;
1369 SDL_PixelFormat contentFormat = format;
1370 if (format == SDL_PIXELFORMAT_BGRA32)
1372 const bool blend = railPlatformCaps().supportsTransparentWindows;
1373 if (!blend || (_isPopup && !_layered && !_gfxHasAlpha))
1374 contentFormat = SDL_PIXELFORMAT_BGRX32;
1377 SDL_Surface* s = SDL_CreateSurfaceFrom(gw, gh, contentFormat, _gfxBuffer.data(),
1378 static_cast<int>(_gfxStride));
1381 WLog_WARN(TAG,
"paintGfx id=0x%08" PRIx32
" SDL_CreateSurfaceFrom failed: %s",
1382 static_cast<uint32_t
>(_id), SDL_GetError());
1390 const SDL_Point crop = { std::clamp(_gfxVisOrigin.x, 0, gw),
1391 std::clamp(_gfxVisOrigin.y, 0, gh) };
1393 const SDL_Point off = { (_resizeAnchorRight ? (ww - bi.w - gw) : bi.x) - crop.x,
1394 (_resizeAnchorBottom ? (wh - bi.h - gh) : bi.y) - crop.y };
1396 const bool showDashes = _localMoveActive && _localMoveIsResize;
1398 const bool fillRevealed = showDashes || awaitingServerResize;
1400 _win->paintResizeFrame(s, off, !_gfxDamage.empty(), bi, fillRevealed, showDashes);
1405 const SDL_Rect full = { 0, 0, gw, gh };
1406 const SDL_Point dst = blitOffset();
1407 const bool maxed = effectivelyMaximized();
1408 if (_layered && !_visRects.empty() && !maxed)
1411 const bool logClip = _visDirty;
1413 if (_visDirty || serverResize)
1416 std::ignore = _win->fill(
static_cast<Uint8
>(0), 0, 0, 0);
1417 _gfxDamage.assign(1, full);
1419 _needsFullBlit =
false;
1421 const SDL_Point off = { _visOffsetSet ? (_visOffset.x - _windowRect.x) : 0,
1422 _visOffsetSet ? (_visOffset.y - _windowRect.y) : 0 };
1425 "clip id=0x%08" PRIx32
" off=%d,%d nVis=%zu vis0=%dx%d+%d+%d win=%dx%d "
1427 static_cast<uint32_t
>(_id), off.x, off.y, _visRects.size(),
1428 _visRects.at(0).w, _visRects.at(0).h, _visRects.at(0).x, _visRects.at(0).y,
1429 ww, wh, _gfxW, _gfxH);
1430 const std::vector<SDL_Rect>& damageRects = _gfxDamage;
1431 std::vector<SDL_Rect> draw;
1432 draw.reserve(damageRects.size() * _visRects.size());
1433 for (
const auto& d : damageRects)
1435 for (
auto v : _visRects)
1440 if (SDL_GetRectIntersection(&d, &v, &part))
1441 draw.push_back(part);
1445 std::ignore = _win->drawRects(s, dst, draw);
1447 else if (_gfxDamage.empty() || serverResize)
1452 const bool transparentWin =
1453 (SDL_GetWindowFlags(_win->window()) & SDL_WINDOW_TRANSPARENT) != 0;
1455 std::ignore = _win->fill(
static_cast<Uint8
>(0), 0, 0,
1456 static_cast<Uint8
>(transparentWin ? 0x00 : 0xFF));
1457 _needsFullBlit =
false;
1459 std::ignore = _win->drawRects(s, dst, { full });
1463 std::ignore = _win->drawRects(s, dst, _gfxDamage);
1466 _win->updateSurface();
1467 _gfxPresented =
true;
1470 SDL_DestroySurface(s);
1471 WLog_VRB(TAG,
"paintGfx id=0x%08" PRIx32
" mode=%s win=%dx%d dmg=%zu",
1472 static_cast<uint32_t
>(_id),
1473 localResize ?
"resize" : (serverResize ?
"server-resize" :
"gfx"), ww, wh,
1487bool SdlRailWindow::paintLegacy(SDL_Surface* primary,
const std::vector<SDL_Rect>& damage)
1491 std::vector<SDL_Rect> vis;
1494 std::unique_lock lock(_gfxLock);
1496 if (!primary || _layered || (_windowRect.w <= 0) || (_windowRect.h <= 0) ||
1501 full = !_painted || _pendingRel.has_value();
1502 if (!full && damage.empty())
1504 WLog_VRB(TAG,
"paintLegacy skip id=0x%08" PRIx32
" no-damage",
1505 static_cast<uint32_t
>(_id));
1510 bi = _appliedInsets;
1515 vis.push_back({ 0, 0, rect.w, rect.h });
1518 const SDL_Rect bounds = { 0, 0, primary->w, primary->h };
1519 bool blitted =
false;
1520 for (
const auto& v : vis)
1522 SDL_Rect src = { rect.x + v.x, rect.y + v.y, v.w, v.h };
1524 if (!SDL_GetRectIntersection(&src, &bounds, &clipped))
1529 SDL_Rect dst = { clipped.x - rect.x + bi.x, clipped.y - rect.y + bi.y, clipped.w,
1531 if (_win->blit(primary, clipped, dst))
1535 for (
const auto& d : damage)
1538 if (!SDL_GetRectIntersection(&clipped, &d, &part))
1540 SDL_Rect dst = { part.x - rect.x + bi.x, part.y - rect.y + bi.y, part.w, part.h };
1541 if (_win->blit(primary, part, dst))
1548 std::unique_lock lock(_gfxLock);
1551 _win->updateSurface();
1553 std::unique_lock lock(_gfxLock);
1556 WLog_VRB(TAG,
"paintLegacy id=0x%08" PRIx32
" full=%d visRects=%zu",
1557 static_cast<uint32_t
>(_id), full ? 1 : 0, vis.size());