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_wayland.hpp"
31#include "sdl_window.hpp"
34#define TAG CLIENT_TAG("sdl.rail.window")
37static constexpr uint64_t kAwaitServerFrameMs = 1000;
40static constexpr uint64_t kAwaitRestoreRectMs = 500;
43static bool regionHasAlpha(
const uint8_t* base, uint32_t stride,
const SDL_Rect& r)
45 for (
int y = r.y; y < r.y + r.h; y++)
47 const uint8_t* row = base +
static_cast<size_t>(y) * stride;
48 for (
int x = r.x; x < r.x + r.w; x++)
49 if (row[
static_cast<size_t>(x) * 4 + 3] != 0)
55static bool regionIsBlank(
const uint8_t* base, uint32_t stride,
const SDL_Rect& r)
57 for (
int y = r.y; y < r.y + r.h; y++)
59 const auto* row =
reinterpret_cast<const uint32_t*
>(base +
static_cast<size_t>(y) * stride);
60 for (
int x = r.x; x < r.x + r.w; x++)
67SdlRailWindow::SdlRailWindow(uint64_t
id,
const SDL_Rect& rect) : _id(id), _windowRect(rect)
72SdlRailWindow::~SdlRailWindow() =
default;
74uint64_t SdlRailWindow::id()
const
79SDL_WindowID SdlRailWindow::sdlId()
const
81 return _win ? _win->id() : 0;
84SDL_Window* SdlRailWindow::window()
const
86 return _win ? _win->window() :
nullptr;
89SDL_Renderer* SdlRailWindow::renderer()
const
91 return _win ? _win->renderer() :
nullptr;
94void SdlRailWindow::updateWindowRect(
const SDL_Rect& rect)
96 std::unique_lock lock(_gfxLock);
97 _awaitRestoreUntil = 0;
101 if (!_localMoveIsResize && ((rect.w != _windowRect.w) || (rect.h != _windowRect.h)))
103 WLog_DBG(TAG,
"size adopted mid-move id=0x%08" PRIx32
" %dx%d -> %dx%d at %d,%d",
104 static_cast<uint32_t
>(_id), _windowRect.w, _windowRect.h, rect.w, rect.h,
106 _windowRect.w = rect.w;
107 _windowRect.h = rect.h;
109 _localMoveServerPos = { rect.x, rect.y };
110 _localMoveSizeChanged =
true;
116 if (SDL_RectsEqual(&rect, &_windowRect))
119 if ((rect.w != _windowRect.w) || (rect.h != _windowRect.h))
123 _geometryDirty =
true;
126void SdlRailWindow::setLocalMoveActive(
bool active)
128 std::unique_lock lock(_gfxLock);
129 _localMoveActive = active;
132 _localMoveIsResize =
false;
133 _localMoveSizeChanged =
false;
137void SdlRailWindow::setResizeAnchor(
bool right,
bool bottom)
139 std::unique_lock lock(_gfxLock);
140 _resizeAnchorRight = right;
141 _resizeAnchorBottom = bottom;
142 _localMoveIsResize =
true;
145bool SdlRailWindow::localMoveActive()
const
147 std::unique_lock lock(_gfxLock);
148 return _localMoveActive;
151bool SdlRailWindow::localMoveSizeChanged()
const
153 std::unique_lock lock(_gfxLock);
154 return _localMoveSizeChanged;
157SDL_Point SdlRailWindow::localMoveServerPos()
const
159 std::unique_lock lock(_gfxLock);
160 return _localMoveServerPos;
163void SdlRailWindow::adoptLocalGeometry(
const SDL_Rect& rect)
166 std::unique_lock lock(_gfxLock);
168 _geomApplyPending =
false;
172 _visOffset.x += rect.x - _windowRect.x;
173 _visOffset.y += rect.y - _windowRect.y;
176 _geometryDirty =
false;
178 _awaitingFrameUntil =
179 (_localMoveActive && _localMoveIsResize) ? (SDL_GetTicks() + kAwaitServerFrameMs) : 0;
180 _localMoveActive =
false;
181 _needsFullBlit =
true;
184 _gfxDamage.assign(1, SDL_Rect{ 0, 0,
static_cast<int>(_gfxW),
static_cast<int>(_gfxH) });
187SDL_Rect SdlRailWindow::windowRect()
const
189 std::unique_lock lock(_gfxLock);
193void SdlRailWindow::markDeleted()
195 std::unique_lock lock(_gfxLock);
199bool SdlRailWindow::isDeleted()
const
201 std::unique_lock lock(_gfxLock);
205bool SdlRailWindow::geomApplyPending()
const
207 std::unique_lock lock(_gfxLock);
208 return _geomApplyPending;
211void SdlRailWindow::clearGeomApplyPending()
213 std::unique_lock lock(_gfxLock);
214 _geomApplyPending =
false;
217void SdlRailWindow::armLoopEnd()
219 std::unique_lock lock(_gfxLock);
220 _loopEnd.pending =
true;
223bool SdlRailWindow::loopEndPending()
const
225 std::unique_lock lock(_gfxLock);
226 return _loopEnd.pending;
229void SdlRailWindow::deferMaximize()
231 std::unique_lock lock(_gfxLock);
232 _loopEnd.maximize =
true;
235void SdlRailWindow::deferSnap(
const SDL_Rect& serverRect)
237 std::unique_lock lock(_gfxLock);
238 _loopEnd.snap =
true;
239 _loopEnd.snapRect = serverRect;
242void SdlRailWindow::clearLoopEnd()
244 std::unique_lock lock(_gfxLock);
250 std::unique_lock lock(_gfxLock);
251 const LoopEndActions actions{ _loopEnd.maximize, _loopEnd.snap, _loopEnd.snapRect };
256bool SdlRailWindow::takeWmOverride(SDL_Rect& outer)
258 std::unique_lock lock(_gfxLock);
261 outer = _wmRefusedOuter;
266void SdlRailWindow::setVisibilityRects(std::vector<SDL_Rect> rects)
268 std::unique_lock lock(_gfxLock);
269 _visRects = std::move(rects);
273void SdlRailWindow::setVisibleOffset(SDL_Point offset)
275 std::unique_lock lock(_gfxLock);
277 if (_localMoveActive)
279 if (_visOffsetSet && (offset.x == _visOffset.x) && (offset.y == _visOffset.y))
282 _visOffsetSet =
true;
286void SdlRailWindow::setMinMaxSize(SDL_Point minSize, SDL_Point maxSize)
288 std::unique_lock lock(_gfxLock);
289 _minSize = { std::max(0, minSize.x), std::max(0, minSize.y) };
290 _maxSize = { std::max(0, maxSize.x), std::max(0, maxSize.y) };
293 _geometryDirty =
true;
296void SdlRailWindow::setResizeMargins(
int left,
int top,
int right,
int bottom)
298 std::unique_lock lock(_gfxLock);
299 const SDL_Rect m = { left, top, right, bottom };
300 if (SDL_RectsEqual(&m, &_resizeMargins))
304 if (!railMaximized())
305 _geometryDirty =
true;
308SDL_Rect SdlRailWindow::resizeMargins()
const
310 std::unique_lock lock(_gfxLock);
311 return _resizeMargins;
314void SdlRailWindow::setFrameMargins(
const SDL_Rect& m)
316 std::unique_lock lock(_gfxLock);
320SDL_Rect SdlRailWindow::frameMargins()
const
322 std::unique_lock lock(_gfxLock);
323 return _frameMargins;
326void SdlRailWindow::setOwner(uint64_t ownerId)
328 std::unique_lock lock(_gfxLock);
332uint64_t SdlRailWindow::owner()
const
334 std::unique_lock lock(_gfxLock);
338bool SdlRailWindow::isPopup()
const
340 std::unique_lock lock(_gfxLock);
344bool SdlRailWindow::isFullscreen()
const
346 std::unique_lock lock(_gfxLock);
350bool SdlRailWindow::isLayered()
const
352 std::unique_lock lock(_gfxLock);
356void SdlRailWindow::setShadowAnchored(
bool anchored)
358 std::unique_lock lock(_gfxLock);
359 _shadowAnchored = anchored;
362void SdlRailWindow::setFrame(
bool frame)
364 std::unique_lock lock(_gfxLock);
368bool SdlRailWindow::isFrame()
const
370 std::unique_lock lock(_gfxLock);
374bool SdlRailWindow::styleResizable()
const
377 return _everResizable || ((_style & (WS_THICKFRAME | WS_MAXIMIZEBOX)) != 0);
381static constexpr int kMinGrip = 4;
383SDL_Rect SdlRailWindow::bandMargins()
const
385 if (!_visible || _isPopup || _layered || !styleResizable() || effectivelyMaximized())
386 return { 0, 0, 0, 0 };
388 const SDL_Rect& m = _resizeMargins;
389 return { std::max(m.x, kMinGrip), std::max(m.y, kMinGrip), std::max(m.w, kMinGrip),
390 std::max(m.h, kMinGrip) };
393SDL_Rect SdlRailWindow::bandInsets()
const
396 const auto& caps = railPlatformCaps();
397 if (!caps.positionsReadable || !caps.supportsTransparentWindows)
398 return { 0, 0, 0, 0 };
399 return bandMargins();
403static SDL_Rect addInsets(
const SDL_Rect& r,
const SDL_Rect& i)
405 return { r.x - i.x, r.y - i.y, r.w + i.x + i.w, r.h + i.y + i.h };
410static SDL_Rect stripInsets(
const SDL_Rect& r,
const SDL_Rect& i)
412 return { r.x + i.x, r.y + i.y, r.w - i.x - i.w, r.h - i.y - i.h };
416bool SdlRailWindow::isFullDisplaySize()
const
419 return SDL_GetDisplayBounds(SDL_GetPrimaryDisplay(), &disp) && (_windowRect.w >= disp.w) &&
420 (_windowRect.h >= disp.h);
425SDL_Rect SdlRailWindow::targetOuterRect()
const
427 return addInsets(_windowRect, bandInsets());
431SDL_Rect SdlRailWindow::insets()
const
433 std::unique_lock lock(_gfxLock);
434 return _appliedInsets;
438SDL_Rect SdlRailWindow::outerRect()
const
440 std::unique_lock lock(_gfxLock);
441 return addInsets(_windowRect, _appliedInsets);
444SDL_Rect SdlRailWindow::serverRect(
const SDL_Rect& outer)
const
446 return stripInsets(outer, insets());
450SDL_Point SdlRailWindow::blitOffset()
const
452 if (!effectivelyMaximized())
453 return { _appliedInsets.x, _appliedInsets.y };
455 const bool frameInSurface =
456 ((_frameMargins.x > 0) || (_frameMargins.y > 0)) &&
457 (
static_cast<int>(_gfxW) == _windowRect.w + _frameMargins.x + _frameMargins.w) &&
458 (
static_cast<int>(_gfxH) == _windowRect.h + _frameMargins.y + _frameMargins.h);
459 SDL_Point content = { 0, 0 };
461 content = { _frameMargins.x, _frameMargins.y };
462 else if (_visOffsetSet)
463 content = { _visOffset.x - _windowRect.x, _visOffset.y - _windowRect.y };
464 return { -std::clamp(content.x, 0,
static_cast<int>(_gfxW)),
465 -std::clamp(content.y, 0,
static_cast<int>(_gfxH)) };
468SDL_Point SdlRailWindow::serverOrigin()
const
470 std::unique_lock lock(_gfxLock);
472 const SDL_Point dst = blitOffset();
473 return { _windowRect.x - dst.x, _windowRect.y - dst.y };
476void SdlRailWindow::setStyle(uint32_t style, uint32_t exStyle)
478 std::unique_lock lock(_gfxLock);
479 const bool wasResizable = styleResizable();
480 const bool wasTopmost = _topmost;
483 _topmost = (exStyle & WS_EX_TOPMOST) != 0;
484 if (_topmost != wasTopmost)
485 _topmostDirty =
true;
486 if ((style & (WS_THICKFRAME | WS_MAXIMIZEBOX)) != 0)
487 _everResizable =
true;
489 if (styleResizable() != wasResizable)
492 if (!_popupClassified)
494 const auto classification = classifySdlRailWindowStyle(style, exStyle, _ownerId != 0);
495 _isPopup = classification.popup;
496 _layered = classification.layered;
497 _clickThrough = classification.clickThrough;
498 _overlay = classification.overlay;
499 _popupClassified =
true;
503void SdlRailWindow::setTitle(
const std::string& title)
505 std::unique_lock lock(_gfxLock);
510void SdlRailWindow::setVisible(
bool visible)
512 std::unique_lock lock(_gfxLock);
516void SdlRailWindow::setIcon(
const SdlRailIcon& icon)
518 std::unique_lock lock(_gfxLock);
523bool SdlRailWindow::create(SDL_Window* parent,
const SDL_Rect& parentRect)
531 const SDL_Rect vis = targetOuterRect();
532 _appliedInsets = bandInsets();
534 const bool fullscreen = _isPopup && !caps.positionsReadable && isFullDisplaySize();
535 if (_isPopup && parent && !fullscreen)
538 const SDL_Rect rel = { vis.x - parentRect.x, vis.y - parentRect.y, vis.w, vis.h };
539 _win = std::make_unique<SdlWindow>(
540 SdlWindow::createPopup(parent, rel, caps.supportsTransparentWindows, _overlay));
542 else if (_isPopup && !caps.positionsReadable && !fullscreen)
545 WLog_VRB(TAG,
"popup create deferred id=0x%08" PRIx32
": no parent yet",
546 static_cast<uint32_t
>(_id));
552 Uint32 flags = SDL_WINDOW_BORDERLESS | SDL_WINDOW_HIDDEN;
553 if (caps.supportsTransparentWindows)
554 flags |= SDL_WINDOW_TRANSPARENT;
555 _win = std::make_unique<SdlWindow>(
556 SdlWindow::create(SDL_GetPrimaryDisplay(), _title, flags, vis));
558 if (!_win || !_win->window() || !_win->renderer())
561 WLog_WARN(TAG,
"create failed id=0x%08" PRIx32
" %s",
static_cast<uint32_t
>(_id), role());
564 _win->resizeable(styleResizable());
569 SDL_SetWindowFullscreen(_win->window(),
true);
570 WLog_DBG(TAG,
"fullscreen id=0x%08" PRIx32
" %dx%d (WS_POPUP spans display)",
571 static_cast<uint32_t
>(_id), _windowRect.w, _windowRect.h);
575 if (!_isPopup && !caps.positionsReadable)
576 sdl_wayland_move_prepare(_win->window());
579 "create id=0x%08" PRIx32
" sdl=%" PRIu32
" %s vis=%dx%d+%d+%d margins=L%d,T%d,R%d,B%d "
581 static_cast<uint32_t
>(_id),
static_cast<uint32_t
>(_win->id()), role(), vis.w, vis.h,
582 vis.x, vis.y, _resizeMargins.x, _resizeMargins.y, _resizeMargins.w, _resizeMargins.h,
583 caps.supportsTransparentWindows ? 1 : 0);
587bool SdlRailWindow::reconcile(SDL_Window* parent,
const SDL_Rect& parentRect)
589 std::unique_lock lock(_gfxLock);
593 const bool popupReady = !_isPopup || _hasGfx;
595 const bool unplaceable =
596 _frame || (_overlay && !railPlatformCaps().positionsReadable && isFullDisplaySize());
598 const bool drawable =
599 _visible && (_windowRect.w > 0) && (_windowRect.h > 0) && popupReady && !unplaceable &&
600 (!_layered || (_hasGfx && !_visRects.empty() && (_shadowAnchored || _overlay)));
605 if (_layered && _hasGfx && !_visRects.empty() && !_shadowAnchored && !_overlay &&
606 !_shadowSuppressLogged)
608 _shadowSuppressLogged =
true;
610 "shadow-rule suppressed id=0x%08" PRIx32
" %dx%d style=0x%08" PRIx32
612 static_cast<uint32_t
>(_id), _windowRect.w, _windowRect.h, _style, _exStyle);
616 if ((SDL_GetWindowFlags(_win->window()) & SDL_WINDOW_HIDDEN) == 0)
618 WLog_DBG(TAG,
"hide id=0x%08" PRIx32
" %s",
static_cast<uint32_t
>(_id), role());
619 SDL_HideWindow(_win->window());
623 if (_isPopup && !_visible)
625 _gfxPresented =
false;
633 if (!create(parent, parentRect))
635 _geometryDirty =
false;
642 _topmostDirty =
false;
644 if (!_isPopup && !_layered)
645 std::ignore = SDL_SetWindowAlwaysOnTop(_win->window(), _topmost);
651 _win->resizeable(styleResizable());
658 if (applyServerState(_maxState,
"maximize", SDL_MaximizeWindow))
659 _awaitRestoreUntil = SDL_GetTicks() + kAwaitRestoreRectMs;
660 std::ignore = applyServerState(_minState,
"minimize", SDL_MinimizeWindow);
661 const bool restorePending = restoreRectPending();
664 const bool maxed = effectivelyMaximized();
665 if ((maxed != _wasMaximized) && !_localMoveActive && !restorePending)
667 _appliedInsets = bandInsets();
669 _geometryDirty =
true;
670 _wasMaximized = maxed;
674 const SDL_Rect ext = bandInsets();
675 if (!SDL_RectsEqual(&ext, &_extentsApplied) &&
676 sdl_x11_set_frame_extents(_win->window(), ext.x, ext.w, ext.y, ext.h))
677 _extentsApplied = ext;
681 const bool wmOwnsGeometry =
682 _localMoveActive || geometryFrozen() || _fullscreen || restorePending;
683 if ((_minMaxDirty || _geometryDirty) && !wmOwnsGeometry)
685 const SDL_Rect vis = targetOuterRect();
686 SDL_SetWindowMinimumSize(_win->window(), std::clamp(_minSize.x, 0, vis.w),
687 std::clamp(_minSize.y, 0, vis.h));
688 int maxW = (_maxSize.x > 0) ? std::max(1, _maxSize.x) : 0;
689 int maxH = (_maxSize.y > 0) ? std::max(1, _maxSize.y) : 0;
692 if (!railPlatformCaps().positionsReadable &&
693 SDL_GetDisplayUsableBounds(SDL_GetPrimaryDisplay(), &usable))
696 const SDL_Rect bi = bandInsets();
697 const int capW = usable.w + bi.x + bi.w;
698 const int capH = usable.h + bi.y + bi.h;
699 if ((maxW == 0) || (maxW > capW))
701 if ((maxH == 0) || (maxH > capH))
704 SDL_SetWindowMaximumSize(_win->window(), maxW, maxH);
705 _minMaxDirty =
false;
709 if (_geometryDirty && !wmOwnsGeometry)
711 const SDL_Rect vis = targetOuterRect();
714 _appliedInsets = bandInsets();
717 SDL_GetWindowSize(_win->window(), &cw, &ch);
718 bool applied =
false;
720 if (_isPopup && parent)
722 if (!SDL_SetWindowPosition(_win->window(), vis.x - parentRect.x, vis.y - parentRect.y))
723 WLog_VRB(TAG,
"popup reposition unsupported id=0x%08" PRIx32
": %s",
724 static_cast<uint32_t
>(_id), SDL_GetError());
725 else if (!railPlatformCaps().positionsReadable)
726 _needsFullBlit =
true;
729 else if (railPlatformCaps().positionsReadable &&
730 !(_isPopup && SDL_GetWindowParent(_win->window())))
734 SDL_GetWindowPosition(_win->window(), &cx, &cy);
735 if ((cx != vis.x) || (cy != vis.y))
737 SDL_SetWindowPosition(_win->window(), vis.x, vis.y);
741 if ((cw != vis.w) || (ch != vis.h))
743 std::ignore = _win->resize({ vis.w, vis.h });
747 if (railPlatformCaps().positionsReadable && !_isPopup && !_localMoveActive &&
750 const bool settled = SDL_SyncWindow(_win->window());
753 SDL_GetWindowSize(_win->window(), &aw, &ah);
754 if (settled && ((aw != vis.w) || (ah != vis.h)))
757 _wmRefusedOuter = { vis.x, vis.y, aw, ah };
763 _geometryDirty =
false;
765 _geomApplyPending = applied && !_isPopup;
768 if (!_isPopup && parent && !_parentApplied)
770 if (SDL_SetWindowParent(_win->window(), parent))
771 _parentApplied =
true;
776 SDL_SetWindowTitle(_win->window(), _title.c_str());
781 if (!_isPopup && !_icon.bgra.empty())
783 SDL_Surface* s = SDL_CreateSurfaceFrom(
784 static_cast<int>(_icon.w),
static_cast<int>(_icon.h), SDL_PIXELFORMAT_BGRA32,
785 _icon.bgra.data(),
static_cast<int>(_icon.w * 4));
788 if (!SDL_SetWindowIcon(_win->window(), s))
789 WLog_WARN(TAG,
"SDL_SetWindowIcon failed for window 0x%08" PRIx32
": %s",
790 static_cast<uint32_t
>(_id), SDL_GetError());
791 SDL_DestroySurface(s);
798 if (!_minState.rail && _gfxPresented &&
799 (SDL_GetWindowFlags(_win->window()) & SDL_WINDOW_HIDDEN))
802 SDL_Window* owner = _isPopup ? nullptr : SDL_GetWindowParent(_win->window());
803 if (owner && ((SDL_GetWindowFlags(owner) & SDL_WINDOW_HIDDEN) != 0))
804 std::ignore = SDL_SetWindowParent(_win->window(),
nullptr);
805 SDL_ShowWindow(_win->window());
810bool SdlRailWindow::takeSurfaceChange(uint32_t surfaceId)
812 std::unique_lock lock(_gfxLock);
813 if (_surfaceId == surfaceId)
815 _surfaceId = surfaceId;
819void SdlRailWindow::updateGfxSurface(
const void* data, uint32_t stride, uint32_t width,
820 uint32_t height,
const RECTANGLE_16* damage, uint32_t nbDamage,
821 WINPR_ATTR_UNUSED uint32_t format)
823 std::unique_lock lock(_gfxLock);
825 WINPR_ASSERT((format == PIXEL_FORMAT_BGRA32) || (format == PIXEL_FORMAT_BGRX32));
827 const size_t bytes =
static_cast<size_t>(stride) * height;
828 if (!data || (bytes == 0) || (width == 0) || (height == 0))
831 WLog_DBG(TAG,
"gfx cleared id=0x%08" PRIx32
" (surface unmapped)",
832 static_cast<uint32_t
>(_id));
839 _gfxVisOrigin = { 0, 0 };
843 const auto* src =
static_cast<const uint8_t*
>(data);
845 const bool full = !_hasGfx || (_gfxBuffer.size() != bytes) || (_gfxStride != stride) ||
846 (_gfxW != width) || (_gfxH != height) || (nbDamage == 0);
849 const SDL_Rect bounds{ 0, 0,
static_cast<int>(width),
static_cast<int>(height) };
851 if (!_hasGfx && regionIsBlank(src, stride, bounds))
853 _gfxBuffer.assign(src, src + bytes);
854 _gfxDamage.assign(1, SDL_Rect{ 0, 0,
static_cast<int>(width),
static_cast<int>(height) });
855 _needsFullBlit =
true;
857 _gfxHasAlpha = honorsAlpha() &&
858 regionHasAlpha(_gfxBuffer.data(), stride,
859 { 0, 0, static_cast<int>(width), static_cast<int>(height) });
863 const SDL_Rect bounds{ 0, 0,
static_cast<int>(width),
static_cast<int>(height) };
864 for (uint32_t i = 0; i < nbDamage; i++)
866 const SDL_Rect r{ damage[i].left, damage[i].top, damage[i].right - damage[i].left,
867 damage[i].bottom - damage[i].top };
869 if (!SDL_GetRectIntersection(&r, &bounds, &clip))
871 std::ignore = freerdp_image_copy_no_overlap(
872 _gfxBuffer.data(), PIXEL_FORMAT_BGRA32, stride,
static_cast<UINT32
>(clip.x),
873 static_cast<UINT32
>(clip.y),
static_cast<UINT32
>(clip.w),
874 static_cast<UINT32
>(clip.h), src, PIXEL_FORMAT_BGRA32, stride,
875 static_cast<UINT32
>(clip.x),
static_cast<UINT32
>(clip.y),
nullptr,
877 _gfxDamage.push_back(clip);
879 if (honorsAlpha() && !_gfxHasAlpha && regionHasAlpha(_gfxBuffer.data(), stride, clip))
883 if (_gfxDamage.size() > 32)
885 SDL_Rect{ 0, 0,
static_cast<int>(width),
static_cast<int>(height) });
887 if ((_gfxW != width) || (_gfxH != height))
889 _resizeAnchorRight =
false;
890 _resizeAnchorBottom =
false;
891 _needsFullBlit =
true;
893 _gfxVisOrigin = { 0, 0 };
899 WLog_VRB(TAG,
"gfx id=0x%08" PRIx32
" %ux%u full=%d nDamage=%u",
static_cast<uint32_t
>(_id),
900 width, height, full ? 1 : 0, nbDamage);
903void SdlRailWindow::invalidateAll()
905 std::unique_lock lock(_gfxLock);
906 _needsFullBlit =
true;
908 _gfxDamage.assign(1, SDL_Rect{ 0, 0,
static_cast<int>(_gfxW),
static_cast<int>(_gfxH) });
911void SdlRailWindow::setServerState(StateSync& s,
bool m)
913 std::unique_lock lock(_gfxLock);
921void SdlRailWindow::setServerMaximized(
bool m)
923 setServerState(_maxState, m);
926void SdlRailWindow::setServerMinimized(
bool m)
928 setServerState(_minState, m);
931bool SdlRailWindow::applyServerState(StateSync& s,
const char* what,
bool (*enter)(SDL_Window*))
935 bool restored =
false;
936 if (s.server && !s.rail)
940 WLog_DBG(TAG,
"%s id=0x%08" PRIx32
"", what,
static_cast<uint32_t
>(_id));
941 enter(_win->window());
943 else if (!s.server && s.rail)
947 WLog_DBG(TAG,
"restore id=0x%08" PRIx32
" (%s)",
static_cast<uint32_t
>(_id), what);
948 SDL_RestoreWindow(_win->window());
956 (void)SDL_SyncWindow(_win->window());
962bool SdlRailWindow::restoreRectPending()
const
964 return (_awaitRestoreUntil != 0) && (SDL_GetTicks() < _awaitRestoreUntil);
967bool SdlRailWindow::effectivelyMaximized()
const
969 return _maxState.rail ||
970 (_win && (SDL_GetWindowFlags(_win->window()) & SDL_WINDOW_MAXIMIZED) != 0);
973bool SdlRailWindow::paint(SDL_Surface* primary, SDL_PixelFormat fallbackFormat,
974 const std::vector<SDL_Rect>& damage, SDL_Window* parent,
975 const SDL_Rect& parentRect)
977 if (!reconcile(parent, parentRect))
982 std::unique_lock lock(_gfxLock);
985 ok = paintGfx(fallbackFormat);
990 ok = paintLegacy(primary, damage);
995 if (_gfxPresented && !_mapped && !_minState.rail)
998 WLog_DBG(TAG,
"map id=0x%08" PRIx32
" %s",
static_cast<uint32_t
>(_id), role());
999 SDL_ShowWindow(_win->window());
1001 if (_isPopup && parent)
1003 std::unique_lock lock(_gfxLock);
1004 const SDL_Rect vis = targetOuterRect();
1005 SDL_SetWindowPosition(_win->window(), vis.x - parentRect.x, vis.y - parentRect.y);
1006 if (!railPlatformCaps().positionsReadable)
1007 _needsFullBlit =
true;
1009 if ((!_isPopup && !_layered) || _fullscreen)
1016bool SdlRailWindow::paintGfx(SDL_PixelFormat format)
1019 const SDL_Rect bi = _appliedInsets;
1022 SDL_GetWindowSizeInPixels(_win->window(), &ww, &wh);
1023 const int cw = ww - bi.x - bi.w;
1024 const int ch = wh - bi.y - bi.h;
1026 static_cast<int>(_gfxW);
1027 const int gh =
static_cast<int>(_gfxH);
1029 if ((_visRects.size() == 1) && (_visRects.at(0).w == gw) && (_visRects.at(0).h == gh))
1030 _gfxVisOrigin = { _visRects.at(0).x, _visRects.at(0).y };
1032 if (!_localMoveActive && (cw == gw) && (ch == gh))
1034 _resizeAnchorRight =
false;
1035 _resizeAnchorBottom =
false;
1036 _awaitingFrameUntil = 0;
1039 if ((_awaitingFrameUntil != 0) && (SDL_GetTicks() > _awaitingFrameUntil))
1041 WLog_DBG(TAG,
"resize frame timeout id=0x%08" PRIx32
" win=%dx%d gfx=%dx%d",
1042 static_cast<uint32_t
>(_id), cw, ch, gw, gh);
1043 _awaitingFrameUntil = 0;
1045 if ((cw != gw) || (ch != gh))
1047 if (_resizeAnchorRight)
1048 _windowRect.x += _windowRect.w - gw;
1049 if (_resizeAnchorBottom)
1050 _windowRect.y += _windowRect.h - gh;
1053 _resizeAnchorRight =
false;
1054 _resizeAnchorBottom =
false;
1055 _geometryDirty =
true;
1056 _needsFullBlit =
true;
1061 const bool awaitingServerResize = (_awaitingFrameUntil != 0) && ((cw != gw) || (ch != gh));
1062 const bool localResize = awaitingServerResize ||
1063 (_localMoveActive && (_localMoveIsResize || (cw != gw) || (ch != gh)));
1066 const bool winResized = (ww != _lastWinW) || (wh != _lastWinH);
1067 const bool gfxResized = (gw != _lastGfxW) || (gh != _lastGfxH);
1068 const bool serverResize =
1069 !_localMoveActive && !awaitingServerResize && (winResized || gfxResized || _needsFullBlit);
1072 if (!localResize && !serverResize && _gfxDamage.empty() && !(_layered && _visDirty))
1074 WLog_VRB(TAG,
"paintGfx skip id=0x%08" PRIx32
" no-damage no-resize",
1075 static_cast<uint32_t
>(_id));
1080 if (_isPopup && !_gfxPresented && (_gfxW > 0) && (_gfxH > 0))
1082 const uint32_t first = *
reinterpret_cast<const uint32_t*
>(_gfxBuffer.data());
1084 bool uniform = (first & 0x00FFFFFFu) == 0;
1085 for (uint32_t y = 0; uniform && (y < _gfxH); y++)
1087 const auto* row =
reinterpret_cast<const uint32_t*
>(
1088 _gfxBuffer.data() +
static_cast<size_t>(y) * _gfxStride);
1089 for (uint32_t x = 0; x < _gfxW; x++)
1090 if (row[x] != first)
1100 _needsFullBlit =
false;
1110 SDL_PixelFormat contentFormat = format;
1111 if (format == SDL_PIXELFORMAT_BGRA32)
1113 const bool blend = railPlatformCaps().supportsTransparentWindows;
1114 if (!blend || (_isPopup && !_layered && !_gfxHasAlpha))
1115 contentFormat = SDL_PIXELFORMAT_BGRX32;
1118 SDL_Surface* s = SDL_CreateSurfaceFrom(gw, gh, contentFormat, _gfxBuffer.data(),
1119 static_cast<int>(_gfxStride));
1122 WLog_WARN(TAG,
"paintGfx id=0x%08" PRIx32
" SDL_CreateSurfaceFrom failed: %s",
1123 static_cast<uint32_t
>(_id), SDL_GetError());
1131 const SDL_Point crop = { std::clamp(_gfxVisOrigin.x, 0, gw),
1132 std::clamp(_gfxVisOrigin.y, 0, gh) };
1134 const SDL_Point off = { (_resizeAnchorRight ? (ww - bi.w - gw) : bi.x) - crop.x,
1135 (_resizeAnchorBottom ? (wh - bi.h - gh) : bi.y) - crop.y };
1137 const bool showDashes = _localMoveActive && _localMoveIsResize;
1139 const bool fillRevealed = showDashes || awaitingServerResize;
1141 _win->paintResizeFrame(s, off, !_gfxDamage.empty(), bi, fillRevealed, showDashes);
1146 const SDL_Rect full = { 0, 0, gw, gh };
1147 const SDL_Point dst = blitOffset();
1148 const bool maxed = effectivelyMaximized();
1149 if (_layered && !_visRects.empty() && !maxed)
1152 const bool logClip = _visDirty;
1154 if (_visDirty || serverResize)
1157 std::ignore = _win->fill(
static_cast<Uint8
>(0), 0, 0, 0);
1158 _gfxDamage.assign(1, full);
1160 _needsFullBlit =
false;
1162 const SDL_Point off = { _visOffsetSet ? (_visOffset.x - _windowRect.x) : 0,
1163 _visOffsetSet ? (_visOffset.y - _windowRect.y) : 0 };
1166 "clip id=0x%08" PRIx32
" off=%d,%d nVis=%zu vis0=%dx%d+%d+%d win=%dx%d "
1168 static_cast<uint32_t
>(_id), off.x, off.y, _visRects.size(),
1169 _visRects.at(0).w, _visRects.at(0).h, _visRects.at(0).x, _visRects.at(0).y,
1170 ww, wh, _gfxW, _gfxH);
1171 const std::vector<SDL_Rect>& damageRects = _gfxDamage;
1172 std::vector<SDL_Rect> draw;
1173 draw.reserve(damageRects.size() * _visRects.size());
1174 for (
const auto& d : damageRects)
1176 for (
auto v : _visRects)
1181 if (SDL_GetRectIntersection(&d, &v, &part))
1182 draw.push_back(part);
1186 std::ignore = _win->drawRects(s, dst, draw);
1188 else if (_gfxDamage.empty() || serverResize)
1193 const bool transparentWin =
1194 (SDL_GetWindowFlags(_win->window()) & SDL_WINDOW_TRANSPARENT) != 0;
1196 std::ignore = _win->fill(
static_cast<Uint8
>(0), 0, 0,
1197 static_cast<Uint8
>(transparentWin ? 0x00 : 0xFF));
1198 _needsFullBlit =
false;
1200 std::ignore = _win->drawRects(s, dst, { full });
1204 std::ignore = _win->drawRects(s, dst, _gfxDamage);
1207 _win->updateSurface();
1208 _gfxPresented =
true;
1210 SDL_DestroySurface(s);
1211 WLog_VRB(TAG,
"paintGfx id=0x%08" PRIx32
" mode=%s win=%dx%d dmg=%zu",
1212 static_cast<uint32_t
>(_id),
1213 localResize ?
"resize" : (serverResize ?
"server-resize" :
"gfx"), ww, wh,
1227bool SdlRailWindow::paintLegacy(SDL_Surface* primary,
const std::vector<SDL_Rect>& damage)
1231 std::vector<SDL_Rect> vis;
1234 std::unique_lock lock(_gfxLock);
1236 if (!primary || _layered || (_windowRect.w <= 0) || (_windowRect.h <= 0))
1241 if (!full && damage.empty())
1243 WLog_VRB(TAG,
"paintLegacy skip id=0x%08" PRIx32
" no-damage",
1244 static_cast<uint32_t
>(_id));
1254 vis.push_back({ 0, 0, rect.w, rect.h });
1257 const SDL_Rect bounds = { 0, 0, primary->w, primary->h };
1258 bool blitted =
false;
1259 for (
const auto& v : vis)
1261 SDL_Rect src = { rect.x + v.x, rect.y + v.y, v.w, v.h };
1263 if (!SDL_GetRectIntersection(&src, &bounds, &clipped))
1268 SDL_Rect dst = { clipped.x - rect.x + bi.x, clipped.y - rect.y + bi.y, clipped.w,
1270 if (_win->blit(primary, clipped, dst))
1274 for (
const auto& d : damage)
1277 if (!SDL_GetRectIntersection(&clipped, &d, &part))
1279 SDL_Rect dst = { part.x - rect.x + bi.x, part.y - rect.y + bi.y, part.w, part.h };
1280 if (_win->blit(primary, part, dst))
1287 std::unique_lock lock(_gfxLock);
1290 _win->updateSurface();
1291 WLog_VRB(TAG,
"paintLegacy id=0x%08" PRIx32
" full=%d visRects=%zu",
1292 static_cast<uint32_t
>(_id), full ? 1 : 0, vis.size());