2 * Copyright (C) 2017-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
9 #include "WinSystemWayland.h"
11 #include "CompileInfo.h"
12 #include "Connection.h"
13 #include "OSScreenSaverIdleInhibitUnstableV1.h"
14 #include "OptionalsReg.h"
16 #include "ServiceBroker.h"
17 #include "ShellSurfaceWlShell.h"
18 #include "ShellSurfaceXdgShell.h"
19 #include "ShellSurfaceXdgShellUnstableV6.h"
21 #include "VideoSyncWpPresentation.h"
22 #include "WinEventsWayland.h"
23 #include "WindowDecorator.h"
24 #include "application/Application.h"
25 #include "cores/RetroPlayer/process/wayland/RPProcessInfoWayland.h"
26 #include "cores/VideoPlayer/Process/wayland/ProcessInfoWayland.h"
27 #include "cores/VideoPlayer/VideoReferenceClock.h"
28 #include "guilib/DispResource.h"
29 #include "guilib/LocalizeStrings.h"
30 #include "input/InputManager.h"
31 #include "input/touch/generic/GenericTouchActionHandler.h"
32 #include "input/touch/generic/GenericTouchInputHandler.h"
33 #include "messaging/ApplicationMessenger.h"
34 #include "settings/AdvancedSettings.h"
35 #include "settings/DisplaySettings.h"
36 #include "settings/Settings.h"
37 #include "settings/SettingsComponent.h"
38 #include "settings/lib/Setting.h"
39 #include "utils/ActorProtocol.h"
40 #include "utils/MathUtils.h"
41 #include "utils/StringUtils.h"
42 #include "utils/TimeUtils.h"
43 #include "utils/log.h"
44 #include "windowing/linux/OSScreenSaverFreedesktop.h"
46 #include "platform/linux/TimeUtils.h"
54 using namespace KODI::WINDOWING
;
55 using namespace KODI::WINDOWING::WAYLAND
;
56 using namespace std::placeholders
;
57 using namespace std::chrono_literals
;
62 RESOLUTION
FindMatchingCustomResolution(CSizeInt size
, float refreshRate
)
64 for (size_t res
{RES_DESKTOP
}; res
< CDisplaySettings::GetInstance().ResolutionInfoSize(); ++res
)
66 auto const& resInfo
= CDisplaySettings::GetInstance().GetResolutionInfo(res
);
67 if (resInfo
.iWidth
== size
.Width() && resInfo
.iHeight
== size
.Height() && MathUtils::FloatEquals(resInfo
.fRefreshRate
, refreshRate
, 0.0005f
))
69 return static_cast<RESOLUTION
> (res
);
75 struct OutputScaleComparer
77 bool operator()(std::shared_ptr
<COutput
> const& output1
, std::shared_ptr
<COutput
> const& output2
)
79 return output1
->GetScale() < output2
->GetScale();
83 struct OutputCurrentRefreshRateComparer
85 bool operator()(std::shared_ptr
<COutput
> const& output1
, std::shared_ptr
<COutput
> const& output2
)
87 return output1
->GetCurrentMode().refreshMilliHz
< output2
->GetCurrentMode().refreshMilliHz
;
91 /// Scope guard for Actor::Message
92 class MessageHandle
: public KODI::UTILS::CScopeGuard
<Actor::Message
*, nullptr, void(Actor::Message
*)>
95 MessageHandle() : CScopeGuard
{std::bind(&Actor::Message::Release
, std::placeholders::_1
), nullptr} {}
96 explicit MessageHandle(Actor::Message
* message
) : CScopeGuard
{std::bind(&Actor::Message::Release
, std::placeholders::_1
), message
} {}
97 Actor::Message
* Get() { return static_cast<Actor::Message
*> (*this); }
101 * Protocol for communication between Wayland event thread and main thread
103 * Many messages received from the Wayland compositor must be processed at a
104 * defined time between frame rendering, such as resolution switches. Thus
105 * they are pushed to the main thread for processing.
107 * The protocol is strictly uni-directional from event to main thread at the moment,
108 * so \ref Actor::Protocol is mainly used as an event queue.
110 namespace WinSystemWaylandProtocol
122 std::uint32_t serial
;
123 CSizeInt surfaceSize
;
124 IShellSurface::StateBitset state
;
127 struct MsgBufferScale
136 CWinSystemWayland::CWinSystemWayland()
137 : CWinSystemBase
{}, m_protocol
{"WinSystemWaylandInternal"}
139 m_winEvents
= std::make_unique
<CWinEventsWayland
>();
142 CWinSystemWayland::~CWinSystemWayland() noexcept
144 DestroyWindowSystem();
147 bool CWinSystemWayland::InitWindowSystem()
149 const char* env
= getenv("WAYLAND_DISPLAY");
152 CLog::Log(LOGDEBUG
, "CWinSystemWayland::{} - WAYLAND_DISPLAY env not set", __FUNCTION__
);
156 wayland::set_log_handler([](const std::string
& message
)
157 { CLog::Log(LOGWARNING
, "wayland-client log message: {}", message
); });
159 CLog::LogF(LOGINFO
, "Connecting to Wayland server");
160 m_connection
= std::make_unique
<CConnection
>();
161 if (!m_connection
->HasDisplay())
164 VIDEOPLAYER::CProcessInfoWayland::Register();
165 RETRO::CRPProcessInfoWayland::Register();
167 m_registry
= std::make_unique
<CRegistry
>(*m_connection
);
169 m_registry
->RequestSingleton(m_compositor
, 1, 4);
170 m_registry
->RequestSingleton(m_shm
, 1, 1);
171 m_registry
->RequestSingleton(m_presentation
, 1, 1, false);
172 // version 2 adds done() -> required
173 // version 3 adds destructor -> optional
174 m_registry
->Request
<wayland::output_t
>(2, 3, std::bind(&CWinSystemWayland::OnOutputAdded
, this, _1
, _2
), std::bind(&CWinSystemWayland::OnOutputRemoved
, this, _1
));
180 m_presentation
.on_clock_id() = [this](std::uint32_t clockId
)
182 CLog::Log(LOGINFO
, "Wayland presentation clock: {}", clockId
);
183 m_presentationClock
= static_cast<clockid_t
> (clockId
);
187 // Do another roundtrip to get initial wl_output information
188 m_connection
->GetDisplay().roundtrip();
189 if (m_outputs
.empty())
191 throw std::runtime_error("No outputs received from compositor");
194 // Event loop is started in CreateWindow
196 // pointer is by default not on this window, will be immediately rectified
197 // by the enter() events if it is
198 CServiceBroker::GetInputManager().SetMouseActive(false);
199 // Always use the generic touch action handler
200 CGenericTouchInputHandler::GetInstance().RegisterHandler(&CGenericTouchActionHandler::GetInstance());
202 CServiceBroker::GetSettingsComponent()
204 ->GetSetting(CSettings::SETTING_VIDEOSCREEN_LIMITEDRANGE
)
207 return CWinSystemBase::InitWindowSystem();
210 bool CWinSystemWayland::DestroyWindowSystem()
213 // wl_display_disconnect frees all proxy objects, so we have to make sure
214 // all stuff is gone on the C++ side before that
215 m_cursorSurface
= wayland::surface_t
{};
216 m_cursorBuffer
= wayland::buffer_t
{};
217 m_cursorImage
= wayland::cursor_image_t
{};
218 m_cursorTheme
= wayland::cursor_theme_t
{};
219 m_outputsInPreparation
.clear();
221 m_frameCallback
= wayland::callback_t
{};
222 m_screenSaverManager
.reset();
224 m_seatInputProcessing
.reset();
228 m_registry
->UnbindSingletons();
231 m_connection
.reset();
233 CGenericTouchInputHandler::GetInstance().UnregisterHandler();
235 return CWinSystemBase::DestroyWindowSystem();
238 bool CWinSystemWayland::CreateNewWindow(const std::string
& name
,
240 RESOLUTION_INFO
& res
)
242 CLog::LogF(LOGINFO
, "Starting {} size {}x{}", fullScreen
? "full screen" : "windowed", res
.iWidth
,
245 m_surface
= m_compositor
.create_surface();
246 m_surface
.on_enter() = [this](const wayland::output_t
& wloutput
) {
247 if (auto output
= FindOutputByWaylandOutput(wloutput
))
249 CLog::Log(LOGDEBUG
, "Entering output \"{}\" with scale {} and {:.3f} dpi",
250 UserFriendlyOutputName(output
), output
->GetScale(), output
->GetCurrentDpi());
251 std::unique_lock
<CCriticalSection
> lock(m_surfaceOutputsMutex
);
252 m_surfaceOutputs
.emplace(output
);
259 CLog::Log(LOGWARNING
, "Entering output that was not configured yet, ignoring");
262 m_surface
.on_leave() = [this](const wayland::output_t
& wloutput
) {
263 if (auto output
= FindOutputByWaylandOutput(wloutput
))
265 CLog::Log(LOGDEBUG
, "Leaving output \"{}\" with scale {}", UserFriendlyOutputName(output
),
267 std::unique_lock
<CCriticalSection
> lock(m_surfaceOutputsMutex
);
268 m_surfaceOutputs
.erase(output
);
275 CLog::Log(LOGWARNING
, "Leaving output that was not configured yet, ignoring");
279 m_windowDecorator
= std::make_unique
<CWindowDecorator
>(*this, *m_connection
, m_surface
);
281 m_seatInputProcessing
= std::make_unique
<CSeatInputProcessing
>(m_surface
, *this);
282 m_seatRegistry
= std::make_unique
<CRegistry
>(*m_connection
);
283 // version 2 adds name event -> optional
284 // version 4 adds wl_keyboard repeat_info -> optional
285 // version 5 adds discrete axis events in wl_pointer -> unused
286 m_seatRegistry
->Request
<wayland::seat_t
>(1, 5, std::bind(&CWinSystemWayland::OnSeatAdded
, this, _1
, _2
), std::bind(&CWinSystemWayland::OnSeatRemoved
, this, _1
));
287 m_seatRegistry
->Bind();
291 CLog::Log(LOGWARNING
, "Wayland compositor did not announce a wl_seat - you will not have any input devices for the time being");
296 m_shellSurfaceState
.set(IShellSurface::STATE_FULLSCREEN
);
298 // Assume we're active on startup until someone tells us otherwise
299 m_shellSurfaceState
.set(IShellSurface::STATE_ACTIVATED
);
300 // Try with this resolution if compositor does not say otherwise
301 UpdateSizeVariables({res
.iWidth
, res
.iHeight
}, m_scale
, m_shellSurfaceState
, false);
303 // Use AppName as the desktop file name. This is required to lookup the app icon of the same name.
304 m_shellSurface
.reset(CreateShellSurface(name
));
308 // Try to start on correct monitor and with correct buffer scale
309 auto output
= FindOutputByUserFriendlyName(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR
));
310 auto wlOutput
= output
? output
->GetWaylandOutput() : wayland::output_t
{};
311 m_lastSetOutput
= wlOutput
;
312 m_shellSurface
->SetFullScreen(wlOutput
, res
.fRefreshRate
);
313 if (output
&& m_surface
.can_set_buffer_scale())
315 m_scale
= output
->GetScale();
320 // Just remember initial width/height for context creation in OnConfigure
321 // This is used for sizing the EGLSurface
322 m_shellSurfaceInitializing
= true;
323 m_shellSurface
->Initialize();
324 m_shellSurfaceInitializing
= false;
326 // Apply window decorations if necessary
327 m_windowDecorator
->SetState(m_configuredSize
, m_scale
, m_shellSurfaceState
);
329 // Set initial opaque region and window geometry
331 ApplyWindowGeometry();
333 // Update resolution with real size as it could have changed due to configure()
334 UpdateDesktopResolution(res
, res
.strOutput
, m_bufferSize
.Width(), m_bufferSize
.Height(), res
.fRefreshRate
, 0);
335 res
.bFullScreen
= fullScreen
;
337 // Now start processing events
339 // There are two stages to the event handling:
340 // * Initialization (which ends here): Everything runs synchronously and init
341 // code that needs events processed must call roundtrip().
342 // This is done for simplicity because it is a lot easier than to make
343 // everything event-based and thread-safe everywhere in the startup code,
344 // which is also not really necessary.
345 // * Runtime (which starts here): Every object creation from now on
346 // needs to take great care to be thread-safe:
347 // Since the event pump is always running now, there is a tiny window between
348 // creating an object and attaching the C++ event handlers during which
349 // events can get queued and dispatched for the object but the handlers have
350 // not been set yet. Consequently, the events would get lost.
351 // However, this does not apply to objects that are created in response to
352 // compositor events. Since the callbacks are called from the event processing
353 // thread and ran strictly sequentially, no other events are dispatched during
354 // the runtime of a callback. Luckily this applies to global binding like
355 // wl_output and wl_seat and thus to most if not all runtime object creation
356 // cases we have to support.
357 // There is another problem when Wayland objects are destructed from the main
358 // thread: An event handler could be running in parallel, resulting in certain
359 // doom. So objects should only be deleted in response to compositor events, too.
360 // They might be hiding behind class member variables, so be wary.
361 // Note that this does not apply to global teardown since the event pump is
363 CWinEventsWayland::SetDisplay(&m_connection
->GetDisplay());
368 IShellSurface
* CWinSystemWayland::CreateShellSurface(const std::string
& name
)
370 IShellSurface
* shell
= CShellSurfaceXdgShell::TryCreate(*this, *m_connection
, m_surface
, name
,
371 std::string(CCompileInfo::GetAppName()));
374 shell
= CShellSurfaceXdgShellUnstableV6::TryCreate(*this, *m_connection
, m_surface
, name
,
375 std::string(CCompileInfo::GetAppName()));
379 CLog::LogF(LOGWARNING
, "Compositor does not support xdg_shell protocol (stable or unstable v6) "
380 "- falling back to wl_shell, not all features might work");
381 shell
= new CShellSurfaceWlShell(*this, *m_connection
, m_surface
, name
,
382 std::string(CCompileInfo::GetAppName()));
388 bool CWinSystemWayland::DestroyWindow()
390 // Make sure no more events get processed when we kill the instances
391 CWinEventsWayland::SetDisplay(nullptr);
393 m_shellSurface
.reset();
394 // waylandpp automatically calls wl_surface_destroy when the last reference is removed
395 m_surface
= wayland::surface_t();
396 m_windowDecorator
.reset();
398 m_lastSetOutput
.proxy_release();
399 m_surfaceOutputs
.clear();
400 m_surfaceSubmissions
.clear();
401 m_seatRegistry
.reset();
406 bool CWinSystemWayland::CanDoWindowed()
411 std::vector
<std::string
> CWinSystemWayland::GetConnectedOutputs()
413 std::unique_lock
<CCriticalSection
> lock(m_outputsMutex
);
414 std::vector
<std::string
> outputs
;
415 std::transform(m_outputs
.cbegin(), m_outputs
.cend(), std::back_inserter(outputs
),
416 [this](decltype(m_outputs
)::value_type
const& pair
)
417 { return UserFriendlyOutputName(pair
.second
); });
422 bool CWinSystemWayland::UseLimitedColor()
424 return CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOSCREEN_LIMITEDRANGE
);
427 void CWinSystemWayland::UpdateResolutions()
429 CWinSystemBase::UpdateResolutions();
431 CDisplaySettings::GetInstance().ClearCustomResolutions();
434 // Only show resolutions for the currently selected output
435 std::string userOutput
= CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR
);
437 std::unique_lock
<CCriticalSection
> lock(m_outputsMutex
);
439 if (m_outputs
.empty())
441 // *Usually* this should not happen - just give up
445 auto output
= FindOutputByUserFriendlyName(userOutput
);
446 if (!output
&& m_lastSetOutput
)
448 // Fallback to current output
449 output
= FindOutputByWaylandOutput(m_lastSetOutput
);
453 // Well just use the first one
454 output
= m_outputs
.begin()->second
;
457 std::string outputName
= UserFriendlyOutputName(output
);
459 auto const& modes
= output
->GetModes();
460 auto const& currentMode
= output
->GetCurrentMode();
461 auto physicalSize
= output
->GetPhysicalSize();
463 "User wanted output \"{}\", we now have \"{}\" size {}x{} mm with {} mode(s):",
464 userOutput
, outputName
, physicalSize
.Width(), physicalSize
.Height(), modes
.size());
466 for (auto const& mode
: modes
)
468 bool isCurrent
= (mode
== currentMode
);
469 float pixelRatio
= output
->GetPixelRatioForMode(mode
);
470 CLog::LogF(LOGINFO
, "- {}x{} @{:.3f} Hz pixel ratio {:.3f}{}", mode
.size
.Width(),
471 mode
.size
.Height(), mode
.refreshMilliHz
/ 1000.0f
, pixelRatio
,
472 isCurrent
? " current" : "");
475 UpdateDesktopResolution(res
, outputName
, mode
.size
.Width(), mode
.size
.Height(), mode
.GetRefreshInHz(), 0);
476 res
.fPixelRatio
= pixelRatio
;
480 CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP
) = res
;
484 CDisplaySettings::GetInstance().AddResolutionInfo(res
);
488 CDisplaySettings::GetInstance().ApplyCalibrations();
491 std::shared_ptr
<COutput
> CWinSystemWayland::FindOutputByUserFriendlyName(const std::string
& name
)
493 std::unique_lock
<CCriticalSection
> lock(m_outputsMutex
);
494 auto outputIt
= std::find_if(m_outputs
.begin(), m_outputs
.end(),
495 [this, &name
](decltype(m_outputs
)::value_type
const& entry
)
497 return (name
== UserFriendlyOutputName(entry
.second
));
500 return (outputIt
== m_outputs
.end() ? nullptr : outputIt
->second
);
503 std::shared_ptr
<COutput
> CWinSystemWayland::FindOutputByWaylandOutput(wayland::output_t
const& output
)
505 std::unique_lock
<CCriticalSection
> lock(m_outputsMutex
);
506 auto outputIt
= std::find_if(m_outputs
.begin(), m_outputs
.end(),
507 [&output
](decltype(m_outputs
)::value_type
const& entry
)
509 return (output
== entry
.second
->GetWaylandOutput());
512 return (outputIt
== m_outputs
.end() ? nullptr : outputIt
->second
);
516 * Change resolution and window state on Kodi request
518 * This function is used for updating resolution when Kodi initiates a resolution
519 * change, such as when changing between full screen and windowed mode or when
520 * selecting a different monitor or resolution in the settings.
522 * Size updates originating from compositor events (such as configure or buffer
523 * scale changes) should not use this function, but \ref SetResolutionInternal
526 * \param fullScreen whether to go full screen or windowed
527 * \param res resolution to set
528 * \return whether the requested resolution was actually set - is false e.g.
529 * when already in full screen mode since the application cannot
532 bool CWinSystemWayland::SetResolutionExternal(bool fullScreen
, RESOLUTION_INFO
const& res
)
534 // In fullscreen modes, we never change the surface size on Kodi's request,
535 // but only when the compositor tells us to. At least xdg_shell specifies
536 // that with state fullscreen the dimensions given in configure() must
537 // always be observed.
538 // This does mean that the compositor has no way of knowing which resolution
539 // we would (in theory) want. Since no compositor implements dynamic resolution
540 // switching at the moment, this is not a problem. If it is some day implemented
541 // in compositors, this code must be changed to match the behavior that is
542 // expected then anyway.
544 // We can honor the Kodi-requested size only if we are not bound by configure rules,
545 // which applies for maximized and fullscreen states.
546 // Also, setting an unconfigured size when just going fullscreen makes no sense.
547 // Give precedence to the size we have still pending, if any.
548 bool mustHonorSize
{m_waitingForApply
|| m_shellSurfaceState
.test(IShellSurface::STATE_MAXIMIZED
) || m_shellSurfaceState
.test(IShellSurface::STATE_FULLSCREEN
) || fullScreen
};
550 CLog::LogF(LOGINFO
, "Kodi asked to switch mode to {}x{} @{:.3f} Hz on output \"{}\" {}",
551 res
.iWidth
, res
.iHeight
, res
.fRefreshRate
, res
.strOutput
,
552 fullScreen
? "full screen" : "windowed");
556 // Try to match output
557 auto output
= FindOutputByUserFriendlyName(res
.strOutput
);
558 auto wlOutput
= output
? output
->GetWaylandOutput() : wayland::output_t
{};
559 if (!m_shellSurfaceState
.test(IShellSurface::STATE_FULLSCREEN
) || (m_lastSetOutput
!= wlOutput
))
561 // Remember the output we set last so we don't set it again until we
562 // either go windowed or were on a different output
563 m_lastSetOutput
= wlOutput
;
567 CLog::LogF(LOGDEBUG
, "Resolved output \"{}\" to bound Wayland global {}", res
.strOutput
,
568 output
->GetGlobalName());
573 "Could not match output \"{}\" to a currently available Wayland output, falling "
574 "back to default output",
578 CLog::LogF(LOGDEBUG
, "Setting full-screen with refresh rate {:.3f}", res
.fRefreshRate
);
579 m_shellSurface
->SetFullScreen(wlOutput
, res
.fRefreshRate
);
583 CLog::LogF(LOGDEBUG
, "Not setting full screen: already full screen on requested output");
588 if (m_shellSurfaceState
.test(IShellSurface::STATE_FULLSCREEN
))
590 CLog::LogF(LOGDEBUG
, "Setting windowed");
591 m_shellSurface
->SetWindowed();
595 CLog::LogF(LOGDEBUG
, "Not setting windowed: already windowed");
599 // Set Kodi-provided size only if we are free to choose any size, otherwise
600 // wait for the compositor configure
603 CLog::LogF(LOGDEBUG
, "Directly setting windowed size {}x{} on Kodi request", res
.iWidth
,
605 // Kodi is directly setting window size, apply
606 auto updateResult
= UpdateSizeVariables({res
.iWidth
, res
.iHeight
}, m_scale
, m_shellSurfaceState
, false);
607 ApplySizeUpdate(updateResult
);
610 bool wasInitialSetFullScreen
{m_isInitialSetFullScreen
};
611 m_isInitialSetFullScreen
= false;
613 // Need to return true
614 // * when this SetFullScreen() call was free to change the context size (and possibly did so)
615 // * on first SetFullScreen so GraphicsContext gets resolution
616 // Otherwise, Kodi must keep the old resolution.
617 return !mustHonorSize
|| wasInitialSetFullScreen
;
620 bool CWinSystemWayland::ResizeWindow(int, int, int, int)
622 // CGraphicContext is "smart" and calls ResizeWindow or SetFullScreen depending
623 // on some state like whether we were already fullscreen. But actually the processing
624 // here is always identical, so we are using a common function to handle both.
625 const auto& res
= CDisplaySettings::GetInstance().GetResolutionInfo(RES_WINDOW
);
626 // The newWidth/newHeight parameters are taken from RES_WINDOW anyway, so we can just
628 return SetResolutionExternal(false, res
);
631 bool CWinSystemWayland::SetFullScreen(bool fullScreen
, RESOLUTION_INFO
& res
, bool)
633 return SetResolutionExternal(fullScreen
, res
);
636 void CWinSystemWayland::ApplySizeUpdate(SizeUpdateInformation update
)
638 if (update
.bufferScaleChanged
)
640 // Buffer scale must also match egl size configuration
643 if (update
.surfaceSizeChanged
)
645 // Update opaque region here so size always matches the configured egl surface
648 if (update
.configuredSizeChanged
)
650 // Update window decoration state
651 m_windowDecorator
->SetState(m_configuredSize
, m_scale
, m_shellSurfaceState
);
652 ApplyWindowGeometry();
654 // Set always, because of initialization order GL context has to keep track of
655 // whether the size changed. If we skip based on update.bufferSizeChanged here,
656 // GL context will never get its initial size set.
657 SetContextSize(m_bufferSize
);
660 void CWinSystemWayland::ApplyOpaqueRegion()
662 // Mark everything opaque so the compositor can render it faster
663 CLog::LogF(LOGDEBUG
, "Setting opaque region size {}x{}", m_surfaceSize
.Width(),
664 m_surfaceSize
.Height());
665 wayland::region_t opaqueRegion
{m_compositor
.create_region()};
666 opaqueRegion
.add(0, 0, m_surfaceSize
.Width(), m_surfaceSize
.Height());
667 m_surface
.set_opaque_region(opaqueRegion
);
670 void CWinSystemWayland::ApplyWindowGeometry()
672 m_shellSurface
->SetWindowGeometry(m_windowDecorator
->GetWindowGeometry());
675 void CWinSystemWayland::ProcessMessages()
677 if (m_waitingForApply
)
679 // Do not put multiple size updates into the pipeline, this would only make
680 // it more complicated without any real benefit. Wait until the size was reconfigured,
681 // then process events again.
685 Actor::Message
* message
{};
686 MessageHandle lastConfigureMessage
;
687 int skippedConfigures
{-1};
688 int newScale
{m_scale
};
690 while (m_protocol
.ReceiveOutMessage(&message
))
692 MessageHandle guard
{message
};
693 switch (message
->signal
)
695 case WinSystemWaylandProtocol::CONFIGURE
:
696 // Do not directly process configures, get the last one queued:
697 // While resizing, the compositor will usually send a configure event
698 // each time the mouse moves without any throttling (i.e. multiple times
699 // per rendered frame).
700 // Going through all those and applying them would waste a lot of time when
701 // we already know that the size is not final and will change again anyway.
703 lastConfigureMessage
= std::move(guard
);
705 case WinSystemWaylandProtocol::OUTPUT_HOTPLUG
:
707 CLog::LogF(LOGDEBUG
, "Output hotplug, re-reading resolutions");
709 std::unique_lock
<CCriticalSection
> lock(m_outputsMutex
);
710 auto const& desktopRes
= CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP
);
711 auto output
= FindOutputByUserFriendlyName(desktopRes
.strOutput
);
712 auto const& wlOutput
= output
->GetWaylandOutput();
713 // Maybe the output that was added was the one we should be on?
714 if (m_bFullScreen
&& m_lastSetOutput
!= wlOutput
)
716 CLog::LogF(LOGDEBUG
, "Output hotplug resulted in monitor set in settings appearing, switching");
717 // Switch to this output
718 m_lastSetOutput
= wlOutput
;
719 m_shellSurface
->SetFullScreen(wlOutput
, desktopRes
.fRefreshRate
);
720 // SetOutput will result in a configure that updates the actual context size
724 case WinSystemWaylandProtocol::BUFFER_SCALE
:
725 // Never update buffer scale if not possible to set it
726 if (m_surface
.can_set_buffer_scale())
728 newScale
= (reinterpret_cast<WinSystemWaylandProtocol::MsgBufferScale
*> (message
->data
))->scale
;
734 if (lastConfigureMessage
)
736 if (skippedConfigures
> 0)
738 CLog::LogF(LOGDEBUG
, "Skipped {} configures", skippedConfigures
);
740 // Wayland will tell us here the size of the surface that was actually created,
741 // which might be different from what we expected e.g. when fullscreening
742 // on an output we chose - the compositor might have decided to use a different
743 // output for example
744 // It is very important that the EGL native module and the rendering system use the
745 // Wayland-announced size for rendering or corrupted graphics output will result.
746 auto configure
= reinterpret_cast<WinSystemWaylandProtocol::MsgConfigure
*> (lastConfigureMessage
.Get()->data
);
747 CLog::LogF(LOGDEBUG
, "Configure serial {}: size {}x{} state {}", configure
->serial
,
748 configure
->surfaceSize
.Width(), configure
->surfaceSize
.Height(),
749 IShellSurface::StateToString(configure
->state
));
752 CSizeInt size
= configure
->surfaceSize
;
753 bool sizeIncludesDecoration
= true;
757 if (configure
->state
.test(IShellSurface::STATE_FULLSCREEN
))
759 // Do not change current size - UpdateWithConfiguredSize must be called regardless in case
760 // scale or something else changed
761 size
= m_configuredSize
;
765 // Compositor has no preference and we're windowed
766 // -> adopt windowed size that Kodi wants
767 auto const& windowed
= CDisplaySettings::GetInstance().GetResolutionInfo(RES_WINDOW
);
768 // Kodi resolution is buffer size, but SetResolutionInternal expects
769 // surface size, so divide by m_scale
770 size
= CSizeInt
{windowed
.iWidth
, windowed
.iHeight
} / newScale
;
771 CLog::LogF(LOGDEBUG
, "Adapting Kodi windowed size {}x{}", size
.Width(), size
.Height());
772 sizeIncludesDecoration
= false;
776 SetResolutionInternal(size
, newScale
, configure
->state
, sizeIncludesDecoration
, true, configure
->serial
);
778 // If we were also configured, scale is already taken care of. But it could
779 // also be a scale change without configure, so apply that.
780 else if (m_scale
!= newScale
)
782 SetResolutionInternal(m_configuredSize
, newScale
, m_shellSurfaceState
, true, false);
786 void CWinSystemWayland::ApplyShellSurfaceState(IShellSurface::StateBitset state
)
788 m_windowDecorator
->SetState(m_configuredSize
, m_scale
, state
);
789 m_shellSurfaceState
= state
;
792 void CWinSystemWayland::OnConfigure(std::uint32_t serial
, CSizeInt size
, IShellSurface::StateBitset state
)
794 if (m_shellSurfaceInitializing
)
796 CLog::LogF(LOGDEBUG
, "Initial configure serial {}: size {}x{} state {}", serial
, size
.Width(),
797 size
.Height(), IShellSurface::StateToString(state
));
798 m_shellSurfaceState
= state
;
801 UpdateSizeVariables(size
, m_scale
, m_shellSurfaceState
, true);
803 AckConfigure(serial
);
807 WinSystemWaylandProtocol::MsgConfigure msg
{serial
, size
, state
};
808 m_protocol
.SendOutMessage(WinSystemWaylandProtocol::CONFIGURE
, &msg
, sizeof(msg
));
812 void CWinSystemWayland::AckConfigure(std::uint32_t serial
)
814 // Send ack if we have a new serial number or this is the first time
815 // this function is called
816 if (serial
!= m_lastAckedSerial
|| !m_firstSerialAcked
)
818 CLog::LogF(LOGDEBUG
, "Acking serial {}", serial
);
819 m_shellSurface
->AckConfigure(serial
);
820 m_lastAckedSerial
= serial
;
821 m_firstSerialAcked
= true;
826 * Recalculate sizes from given parameters, apply them and update Kodi CDisplaySettings
827 * resolution if necessary
829 * This function should be called when events internal to the windowing system
830 * such as a compositor configure lead to a size change.
832 * Call only from main thread.
834 * \param size configured size, can be zero if compositor does not have a preference
835 * \param scale new buffer scale
836 * \param sizeIncludesDecoration whether size includes the size of the window decorations if present
838 void CWinSystemWayland::SetResolutionInternal(CSizeInt size
, std::int32_t scale
, IShellSurface::StateBitset state
, bool sizeIncludesDecoration
, bool mustAck
, std::uint32_t configureSerial
)
840 // This should never be called while a size set is pending
841 assert(!m_waitingForApply
);
843 bool fullScreen
{state
.test(IShellSurface::STATE_FULLSCREEN
)};
844 auto sizes
= CalculateSizes(size
, scale
, state
, sizeIncludesDecoration
);
846 CLog::LogF(LOGDEBUG
, "Set size for serial {}: {}x{} {} decoration at scale {} state {}",
847 configureSerial
, size
.Width(), size
.Height(),
848 sizeIncludesDecoration
? "including" : "excluding", scale
,
849 IShellSurface::StateToString(state
));
851 // Get actual frame rate from monitor, take highest frame rate if multiple
852 float refreshRate
{m_fRefreshRate
};
854 std::unique_lock
<CCriticalSection
> lock(m_surfaceOutputsMutex
);
855 auto maxRefreshIt
= std::max_element(m_surfaceOutputs
.cbegin(), m_surfaceOutputs
.cend(), OutputCurrentRefreshRateComparer());
856 if (maxRefreshIt
!= m_surfaceOutputs
.cend())
858 refreshRate
= (*maxRefreshIt
)->GetCurrentMode().GetRefreshInHz();
859 CLog::LogF(LOGDEBUG
, "Resolved actual (maximum) refresh rate to {:.3f} Hz on output \"{}\"",
860 refreshRate
, UserFriendlyOutputName(*maxRefreshIt
));
864 m_next
.mustBeAcked
= mustAck
;
865 m_next
.configureSerial
= configureSerial
;
866 m_next
.configuredSize
= sizes
.configuredSize
;
867 m_next
.scale
= scale
;
868 m_next
.shellSurfaceState
= state
;
870 // Check if any parameters of the Kodi resolution configuration changed
871 if (refreshRate
!= m_fRefreshRate
|| sizes
.bufferSize
!= m_bufferSize
|| m_bFullScreen
!= fullScreen
)
878 msg
.type
= XBMC_MODECHANGE
;
879 msg
.mode
.res
= RES_WINDOW
;
880 SetWindowResolution(sizes
.bufferSize
.Width(), sizes
.bufferSize
.Height());
882 dynamic_cast<CWinEventsWayland
&>(*m_winEvents
).MessagePush(&msg
);
883 m_waitingForApply
= true;
884 CLog::LogF(LOGDEBUG
, "Queued change to windowed mode size {}x{}", sizes
.bufferSize
.Width(),
885 sizes
.bufferSize
.Height());
890 msg
.type
= XBMC_VIDEORESIZE
;
891 msg
.resize
= {sizes
.bufferSize
.Width(), sizes
.bufferSize
.Height()};
893 dynamic_cast<CWinEventsWayland
&>(*m_winEvents
).MessagePush(&msg
);
894 m_waitingForApply
= true;
895 CLog::LogF(LOGDEBUG
, "Queued change to windowed buffer size {}x{}",
896 sizes
.bufferSize
.Width(), sizes
.bufferSize
.Height());
901 // Find matching Kodi resolution member
902 RESOLUTION res
{FindMatchingCustomResolution(sizes
.bufferSize
, refreshRate
)};
903 if (res
== RES_INVALID
)
905 // Add new resolution if none found
906 RESOLUTION_INFO newResInfo
;
907 // we just assume the compositor put us on the right output
908 UpdateDesktopResolution(newResInfo
, CDisplaySettings::GetInstance().GetCurrentResolutionInfo().strOutput
, sizes
.bufferSize
.Width(), sizes
.bufferSize
.Height(), refreshRate
, 0);
909 CDisplaySettings::GetInstance().AddResolutionInfo(newResInfo
);
910 CDisplaySettings::GetInstance().ApplyCalibrations();
911 res
= static_cast<RESOLUTION
> (CDisplaySettings::GetInstance().ResolutionInfoSize() - 1);
915 msg
.type
= XBMC_MODECHANGE
;
918 dynamic_cast<CWinEventsWayland
&>(*m_winEvents
).MessagePush(&msg
);
919 m_waitingForApply
= true;
920 CLog::LogF(LOGDEBUG
, "Queued change to resolution {} surface size {}x{} scale {} state {}",
921 res
, sizes
.surfaceSize
.Width(), sizes
.surfaceSize
.Height(), scale
,
922 IShellSurface::StateToString(state
));
927 // Apply directly, Kodi resolution does not change
932 void CWinSystemWayland::FinishModeChange(RESOLUTION res
)
934 const auto& resInfo
= CDisplaySettings::GetInstance().GetResolutionInfo(res
);
938 m_fRefreshRate
= resInfo
.fRefreshRate
;
939 m_bFullScreen
= resInfo
.bFullScreen
;
940 m_waitingForApply
= false;
943 void CWinSystemWayland::FinishWindowResize(int, int)
946 m_waitingForApply
= false;
949 void CWinSystemWayland::ApplyNextState()
951 CLog::LogF(LOGDEBUG
, "Applying next state: serial {} configured size {}x{} scale {} state {}",
952 m_next
.configureSerial
, m_next
.configuredSize
.Width(), m_next
.configuredSize
.Height(),
953 m_next
.scale
, IShellSurface::StateToString(m_next
.shellSurfaceState
));
955 ApplyShellSurfaceState(m_next
.shellSurfaceState
);
956 auto updateResult
= UpdateSizeVariables(m_next
.configuredSize
, m_next
.scale
, m_next
.shellSurfaceState
, true);
957 ApplySizeUpdate(updateResult
);
959 if (m_next
.mustBeAcked
)
961 AckConfigure(m_next
.configureSerial
);
965 CWinSystemWayland::Sizes
CWinSystemWayland::CalculateSizes(CSizeInt size
, int scale
, IShellSurface::StateBitset state
, bool sizeIncludesDecoration
)
969 // Clamp to a sensible range
970 constexpr int MIN_WIDTH
{300};
971 constexpr int MIN_HEIGHT
{200};
972 if (size
.Width() < MIN_WIDTH
)
974 CLog::LogF(LOGWARNING
, "Width {} is very small, clamping to {}", size
.Width(), MIN_WIDTH
);
975 size
.SetWidth(MIN_WIDTH
);
977 if (size
.Height() < MIN_HEIGHT
)
979 CLog::LogF(LOGWARNING
, "Height {} is very small, clamping to {}", size
.Height(), MIN_HEIGHT
);
980 size
.SetHeight(MIN_HEIGHT
);
983 // Depending on whether the size has decorations included (i.e. comes from the
984 // compositor or from Kodi), we need to calculate differently
985 if (sizeIncludesDecoration
)
987 result
.configuredSize
= size
;
988 result
.surfaceSize
= m_windowDecorator
->CalculateMainSurfaceSize(size
, state
);
992 result
.surfaceSize
= size
;
993 result
.configuredSize
= m_windowDecorator
->CalculateFullSurfaceSize(size
, state
);
996 result
.bufferSize
= result
.surfaceSize
* scale
;
1003 * Calculate internal resolution from surface size and set variables
1005 * \param next surface size
1006 * \param scale new buffer scale
1007 * \param state window state to determine whether decorations are enabled at all
1008 * \param sizeIncludesDecoration if true, given size includes potential window decorations
1009 * \return whether main buffer (not surface) size changed
1011 CWinSystemWayland::SizeUpdateInformation
CWinSystemWayland::UpdateSizeVariables(CSizeInt size
, int scale
, IShellSurface::StateBitset state
, bool sizeIncludesDecoration
)
1013 CLog::LogF(LOGDEBUG
, "Set size {}x{} scale {} {} decorations with state {}", size
.Width(),
1014 size
.Height(), scale
, sizeIncludesDecoration
? "including" : "excluding",
1015 IShellSurface::StateToString(state
));
1017 auto oldSurfaceSize
= m_surfaceSize
;
1018 auto oldBufferSize
= m_bufferSize
;
1019 auto oldConfiguredSize
= m_configuredSize
;
1020 auto oldBufferScale
= m_scale
;
1023 auto sizes
= CalculateSizes(size
, scale
, state
, sizeIncludesDecoration
);
1024 m_surfaceSize
= sizes
.surfaceSize
;
1025 m_bufferSize
= sizes
.bufferSize
;
1026 m_configuredSize
= sizes
.configuredSize
;
1028 SizeUpdateInformation changes
{m_surfaceSize
!= oldSurfaceSize
, m_bufferSize
!= oldBufferSize
, m_configuredSize
!= oldConfiguredSize
, m_scale
!= oldBufferScale
};
1030 if (changes
.surfaceSizeChanged
)
1032 CLog::LogF(LOGINFO
, "Surface size changed: {}x{} -> {}x{}", oldSurfaceSize
.Width(),
1033 oldSurfaceSize
.Height(), m_surfaceSize
.Width(), m_surfaceSize
.Height());
1035 if (changes
.bufferSizeChanged
)
1037 CLog::LogF(LOGINFO
, "Buffer size changed: {}x{} -> {}x{}", oldBufferSize
.Width(),
1038 oldBufferSize
.Height(), m_bufferSize
.Width(), m_bufferSize
.Height());
1040 if (changes
.configuredSizeChanged
)
1042 CLog::LogF(LOGINFO
, "Configured size changed: {}x{} -> {}x{}", oldConfiguredSize
.Width(),
1043 oldConfiguredSize
.Height(), m_configuredSize
.Width(), m_configuredSize
.Height());
1045 if (changes
.bufferScaleChanged
)
1047 CLog::LogF(LOGINFO
, "Buffer scale changed: {} -> {}", oldBufferScale
, m_scale
);
1053 std::string
CWinSystemWayland::UserFriendlyOutputName(std::shared_ptr
<COutput
> const& output
)
1055 std::vector
<std::string
> parts
;
1056 if (!output
->GetMake().empty())
1058 parts
.emplace_back(output
->GetMake());
1060 if (!output
->GetModel().empty())
1062 parts
.emplace_back(output
->GetModel());
1066 // Fallback to "unknown" if no name received from compositor
1067 parts
.emplace_back(g_localizeStrings
.Get(13205));
1071 auto pos
= output
->GetPosition();
1072 if (pos
.x
!= 0 || pos
.y
!= 0)
1074 parts
.emplace_back(StringUtils::Format("@{}x{}", pos
.x
, pos
.y
));
1077 return StringUtils::Join(parts
, " ");
1080 bool CWinSystemWayland::Minimize()
1082 m_shellSurface
->SetMinimized();
1086 bool CWinSystemWayland::HasCursor()
1088 std::unique_lock
<CCriticalSection
> lock(m_seatsMutex
);
1089 return std::any_of(m_seats
.cbegin(), m_seats
.cend(),
1090 [](decltype(m_seats
)::value_type
const& entry
)
1091 { return entry
.second
->HasPointerCapability(); });
1094 void CWinSystemWayland::ShowOSMouse(bool show
)
1096 m_osCursorVisible
= show
;
1099 void CWinSystemWayland::LoadDefaultCursor()
1101 if (!m_cursorSurface
)
1103 // Load default cursor theme and default cursor
1104 // Size of 24px is what most themes seem to have
1105 m_cursorTheme
= wayland::cursor_theme_t("", 24, m_shm
);
1106 wayland::cursor_t cursor
;
1109 cursor
= CCursorUtil::LoadFromTheme(m_cursorTheme
, "default");
1111 catch (std::exception
const& e
)
1113 CLog::Log(LOGWARNING
, "Could not load default cursor from theme, continuing without OS cursor");
1115 // Just use the first image, do not handle animation
1116 m_cursorImage
= cursor
.image(0);
1117 m_cursorBuffer
= m_cursorImage
.get_buffer();
1118 m_cursorSurface
= m_compositor
.create_surface();
1120 // Attach buffer to a surface - it seems that the compositor may change
1121 // the cursor surface when the pointer leaves our surface, so we reattach the
1123 m_cursorSurface
.attach(m_cursorBuffer
, 0, 0);
1124 m_cursorSurface
.damage(0, 0, m_cursorImage
.width(), m_cursorImage
.height());
1125 m_cursorSurface
.commit();
1128 void CWinSystemWayland::Register(IDispResource
* resource
)
1130 std::unique_lock
<CCriticalSection
> lock(m_dispResourcesMutex
);
1131 m_dispResources
.emplace(resource
);
1134 void CWinSystemWayland::Unregister(IDispResource
* resource
)
1136 std::unique_lock
<CCriticalSection
> lock(m_dispResourcesMutex
);
1137 m_dispResources
.erase(resource
);
1140 void CWinSystemWayland::OnSeatAdded(std::uint32_t name
, wayland::proxy_t
&& proxy
)
1142 std::unique_lock
<CCriticalSection
> lock(m_seatsMutex
);
1144 wayland::seat_t
seat(proxy
);
1145 auto newSeatEmplace
= m_seats
.emplace(std::piecewise_construct
, std::forward_as_tuple(name
),
1146 std::forward_as_tuple(CreateSeat(name
, seat
)));
1148 auto& seatInst
= newSeatEmplace
.first
->second
;
1149 m_seatInputProcessing
->AddSeat(seatInst
.get());
1150 m_windowDecorator
->AddSeat(seatInst
.get());
1153 std::unique_ptr
<CSeat
> CWinSystemWayland::CreateSeat(std::uint32_t name
, wayland::seat_t
& seat
)
1155 return std::make_unique
<CSeat
>(name
, seat
, *m_connection
);
1158 void CWinSystemWayland::OnSeatRemoved(std::uint32_t name
)
1160 std::unique_lock
<CCriticalSection
> lock(m_seatsMutex
);
1162 auto seatI
= m_seats
.find(name
);
1163 if (seatI
!= m_seats
.end())
1165 m_seatInputProcessing
->RemoveSeat(seatI
->second
.get());
1166 m_windowDecorator
->RemoveSeat(seatI
->second
.get());
1167 m_seats
.erase(name
);
1171 void CWinSystemWayland::OnOutputAdded(std::uint32_t name
, wayland::proxy_t
&& proxy
)
1173 wayland::output_t
output(proxy
);
1174 // This is not accessed from multiple threads
1175 m_outputsInPreparation
.emplace(name
, std::make_shared
<COutput
>(name
, output
, std::bind(&CWinSystemWayland::OnOutputDone
, this, name
)));
1178 void CWinSystemWayland::OnOutputDone(std::uint32_t name
)
1180 auto it
= m_outputsInPreparation
.find(name
);
1181 if (it
!= m_outputsInPreparation
.end())
1183 // This output was added for the first time - done is also sent when
1184 // output parameters change later
1187 std::unique_lock
<CCriticalSection
> lock(m_outputsMutex
);
1188 // Move from m_outputsInPreparation to m_outputs
1189 m_outputs
.emplace(std::move(*it
));
1190 m_outputsInPreparation
.erase(it
);
1193 m_protocol
.SendOutMessage(WinSystemWaylandProtocol::OUTPUT_HOTPLUG
);
1196 UpdateBufferScale();
1199 void CWinSystemWayland::OnOutputRemoved(std::uint32_t name
)
1201 m_outputsInPreparation
.erase(name
);
1203 std::unique_lock
<CCriticalSection
> lock(m_outputsMutex
);
1204 if (m_outputs
.erase(name
) != 0)
1206 // Theoretically, the compositor should automatically put us on another
1207 // (visible and connected) output if the output we were on is lost,
1208 // so there is nothing in particular to do here
1212 void CWinSystemWayland::SendFocusChange(bool focus
)
1214 g_application
.m_AppFocused
= focus
;
1215 std::unique_lock
<CCriticalSection
> lock(m_dispResourcesMutex
);
1216 for (auto dispResource
: m_dispResources
)
1218 dispResource
->OnAppFocusChange(focus
);
1222 void CWinSystemWayland::OnEnter(InputType type
)
1224 // Couple to keyboard focus
1225 if (type
== InputType::KEYBOARD
)
1227 SendFocusChange(true);
1229 if (type
== InputType::POINTER
)
1231 CServiceBroker::GetInputManager().SetMouseActive(true);
1235 void CWinSystemWayland::OnLeave(InputType type
)
1237 // Couple to keyboard focus
1238 if (type
== InputType::KEYBOARD
)
1240 SendFocusChange(false);
1242 if (type
== InputType::POINTER
)
1244 CServiceBroker::GetInputManager().SetMouseActive(false);
1248 void CWinSystemWayland::OnEvent(InputType type
, XBMC_Event
& event
)
1251 dynamic_cast<CWinEventsWayland
&>(*m_winEvents
).MessagePush(&event
);
1254 void CWinSystemWayland::OnSetCursor(std::uint32_t seatGlobalName
, std::uint32_t serial
)
1256 auto seatI
= m_seats
.find(seatGlobalName
);
1257 if (seatI
== m_seats
.end())
1262 if (m_osCursorVisible
)
1264 LoadDefaultCursor();
1265 if (m_cursorSurface
) // Cursor loading could have failed
1267 seatI
->second
->SetCursor(serial
, m_cursorSurface
, m_cursorImage
.hotspot_x(),
1268 m_cursorImage
.hotspot_y());
1273 seatI
->second
->SetCursor(serial
, wayland::surface_t
{}, 0, 0);
1277 void CWinSystemWayland::UpdateBufferScale()
1279 // Adjust our surface size to the output with the biggest scale in order
1280 // to get the best quality
1281 auto const maxBufferScaleIt
= std::max_element(m_surfaceOutputs
.cbegin(), m_surfaceOutputs
.cend(), OutputScaleComparer());
1282 if (maxBufferScaleIt
!= m_surfaceOutputs
.cend())
1284 WinSystemWaylandProtocol::MsgBufferScale msg
{(*maxBufferScaleIt
)->GetScale()};
1285 m_protocol
.SendOutMessage(WinSystemWaylandProtocol::BUFFER_SCALE
, &msg
, sizeof(msg
));
1289 void CWinSystemWayland::ApplyBufferScale()
1291 CLog::LogF(LOGINFO
, "Setting Wayland buffer scale to {}", m_scale
);
1292 m_surface
.set_buffer_scale(m_scale
);
1293 m_windowDecorator
->SetState(m_configuredSize
, m_scale
, m_shellSurfaceState
);
1294 m_seatInputProcessing
->SetCoordinateScale(m_scale
);
1297 void CWinSystemWayland::UpdateTouchDpi()
1299 // If we have multiple outputs with wildly different DPI, this is really just
1300 // guesswork to get a halfway reasonable value. min/max would probably also be OK.
1301 float dpiSum
= std::accumulate(m_surfaceOutputs
.cbegin(), m_surfaceOutputs
.cend(), 0.0f
,
1302 [](float acc
, std::shared_ptr
<COutput
> const& output
)
1304 return acc
+ output
->GetCurrentDpi();
1306 float dpi
= dpiSum
/ m_surfaceOutputs
.size();
1307 CLog::LogF(LOGDEBUG
, "Computed average dpi of {:.3f} for touch handler", dpi
);
1308 CGenericTouchInputHandler::GetInstance().SetScreenDPI(dpi
);
1311 CWinSystemWayland::SurfaceSubmission::SurfaceSubmission(timespec
const& submissionTime
, wayland::presentation_feedback_t
const& feedback
)
1312 : submissionTime
{submissionTime
}, feedback
{feedback
}
1316 timespec
CWinSystemWayland::GetPresentationClockTime()
1319 if (clock_gettime(m_presentationClock
, &time
) != 0)
1321 throw std::system_error(errno
, std::generic_category(), "Error getting time from Wayland presentation clock with clock_gettime");
1326 void CWinSystemWayland::PrepareFramePresentation()
1328 // Continuously measure display latency (i.e. time between when the frame was rendered
1329 // and when it becomes visible to the user) to correct AV sync
1332 auto tStart
= GetPresentationClockTime();
1333 // wp_presentation_feedback creation is coupled to the surface's commit().
1334 // eglSwapBuffers() (which will be called after this) will call commit().
1335 // This creates a new Wayland protocol object in the main thread, but this
1336 // will not result in a race since the corresponding events are never sent
1337 // before commit() on the surface, which only occurs afterwards.
1338 auto feedback
= m_presentation
.feedback(m_surface
);
1339 // Save feedback objects in list so they don't get destroyed upon exit of this function
1340 // Hand iterator to lambdas so they do not hold a (then circular) reference
1341 // to the actual object
1342 decltype(m_surfaceSubmissions
)::iterator iter
;
1344 std::unique_lock
<CCriticalSection
> lock(m_surfaceSubmissionsMutex
);
1345 iter
= m_surfaceSubmissions
.emplace(m_surfaceSubmissions
.end(), tStart
, feedback
);
1348 feedback
.on_sync_output() = [this](const wayland::output_t
& wloutput
) {
1349 m_syncOutputID
= wloutput
.get_id();
1350 auto output
= FindOutputByWaylandOutput(wloutput
);
1353 m_syncOutputRefreshRate
= output
->GetCurrentMode().GetRefreshInHz();
1357 CLog::Log(LOGWARNING
, "Could not find Wayland output that is supposedly the sync output");
1360 feedback
.on_presented() = [this, iter
](std::uint32_t tvSecHi
, std::uint32_t tvSecLo
,
1361 std::uint32_t tvNsec
, std::uint32_t refresh
,
1362 std::uint32_t seqHi
, std::uint32_t seqLo
,
1363 const wayland::presentation_feedback_kind
& flags
) {
1364 timespec tv
= { .tv_sec
= static_cast<std::time_t> ((static_cast<std::uint64_t>(tvSecHi
) << 32) + tvSecLo
), .tv_nsec
= static_cast<long>(tvNsec
) };
1365 std::int64_t latency
{KODI::LINUX::TimespecDifference(iter
->submissionTime
, tv
)};
1366 std::uint64_t msc
{(static_cast<std::uint64_t>(seqHi
) << 32) + seqLo
};
1367 m_presentationFeedbackHandlers
.Invoke(tv
, refresh
, m_syncOutputID
, m_syncOutputRefreshRate
, msc
);
1369 iter
->latency
= latency
/ 1e9f
; // nanoseconds to seconds
1372 std::unique_lock
<CCriticalSection
> lock(m_surfaceSubmissionsMutex
);
1373 if (m_surfaceSubmissions
.size() > LATENCY_MOVING_AVERAGE_SIZE
)
1375 adjust
= - m_surfaceSubmissions
.front().latency
/ LATENCY_MOVING_AVERAGE_SIZE
;
1376 m_surfaceSubmissions
.pop_front();
1379 m_latencyMovingAverage
= m_latencyMovingAverage
+ iter
->latency
/ LATENCY_MOVING_AVERAGE_SIZE
+ adjust
;
1381 CLog::Log(LOGDEBUG
, LOGAVTIMING
, "Presentation feedback: {} ns -> moving average {:f} s",
1382 latency
, static_cast<double>(m_latencyMovingAverage
));
1384 feedback
.on_discarded() = [this,iter
]()
1386 CLog::Log(LOGDEBUG
, "Presentation: Frame was discarded by compositor");
1387 std::unique_lock
<CCriticalSection
> lock(m_surfaceSubmissionsMutex
);
1388 m_surfaceSubmissions
.erase(iter
);
1392 // Now wait for the frame callback that tells us that it is a good time to start drawing
1395 // 1. wait until a frame() drawing hint from the compositor arrives,
1396 // 2. request a new frame() hint for the next presentation
1397 // 2. then commit the backbuffer to the surface and immediately
1398 // return, i.e. drawing can start again
1399 // This means that rendering is optimized for maximum time available for
1400 // our repaint and reliable timing rather than latency. With weston, latency
1401 // will usually be on the order of two frames plus a few milliseconds.
1402 // The frame timings become irregular though when nothing is rendered because
1403 // kodi then sleeps for a fixed time without swapping buffers. This makes us
1404 // immediately attach the next buffer because the frame callback has already arrived when
1405 // this function is called and step 1. above is skipped. As we render with full
1406 // FPS during video playback anyway and the timing is otherwise not relevant,
1407 // this should not be a problem.
1408 if (m_frameCallback
)
1410 // If the window is e.g. minimized, chances are that we will *never* get frame
1411 // callbacks from the compositor for optimization reasons.
1412 // Still, the app should remain functional, which means that we can't
1413 // just block forever here - if the render thread is blocked, Kodi will not
1414 // function normally. It would also be impossible to close the application
1415 // while it is minimized (since the wait needs to be interrupted for that).
1416 // -> Use Wait with timeout here so we can maintain a reasonable frame rate
1417 // even when the window is not visible and we do not get any frame callbacks.
1418 if (m_frameCallbackEvent
.Wait(50ms
))
1420 // Only reset frame callback object a callback was received so a
1421 // new one is not requested continuously
1422 m_frameCallback
= {};
1423 m_frameCallbackEvent
.Reset();
1427 if (!m_frameCallback
)
1429 // Get frame callback event for checking in the next call to this function
1430 m_frameCallback
= m_surface
.frame();
1431 m_frameCallback
.on_done() = [this](std::uint32_t)
1433 m_frameCallbackEvent
.Set();
1438 void CWinSystemWayland::FinishFramePresentation()
1442 m_frameStartTime
= std::chrono::steady_clock::now();
1445 float CWinSystemWayland::GetFrameLatencyAdjustment()
1447 const auto now
= std::chrono::steady_clock::now();
1448 const std::chrono::duration
<float, std::milli
> duration
= now
- m_frameStartTime
;
1449 return duration
.count();
1452 float CWinSystemWayland::GetDisplayLatency()
1456 return m_latencyMovingAverage
* 1000.0f
;
1460 return CWinSystemBase::GetDisplayLatency();
1464 float CWinSystemWayland::GetSyncOutputRefreshRate()
1466 return m_syncOutputRefreshRate
;
1469 KODI::CSignalRegistration
CWinSystemWayland::RegisterOnPresentationFeedback(
1470 const PresentationFeedbackHandler
& handler
)
1472 return m_presentationFeedbackHandlers
.Register(handler
);
1475 std::unique_ptr
<CVideoSync
> CWinSystemWayland::GetVideoSync(CVideoReferenceClock
* clock
)
1477 if (m_surface
&& m_presentation
)
1479 CLog::LogF(LOGINFO
, "Using presentation protocol for video sync");
1480 return std::unique_ptr
<CVideoSync
>(new CVideoSyncWpPresentation(clock
, *this));
1484 CLog::LogF(LOGINFO
, "No supported method for video sync found");
1489 std::unique_ptr
<IOSScreenSaver
> CWinSystemWayland::GetOSScreenSaverImpl()
1493 std::unique_ptr
<IOSScreenSaver
> ptr
;
1494 ptr
.reset(COSScreenSaverIdleInhibitUnstableV1::TryCreate(*m_connection
, m_surface
));
1497 CLog::LogF(LOGINFO
, "Using idle-inhibit-unstable-v1 protocol for screen saver inhibition");
1502 #if defined(HAS_DBUS)
1503 if (KODI::WINDOWING::LINUX::COSScreenSaverFreedesktop::IsAvailable())
1505 CLog::LogF(LOGINFO
, "Using freedesktop.org DBus interface for screen saver inhibition");
1506 return std::unique_ptr
<IOSScreenSaver
>(new KODI::WINDOWING::LINUX::COSScreenSaverFreedesktop
);
1510 CLog::LogF(LOGINFO
, "No supported method for screen saver inhibition found");
1511 return std::unique_ptr
<IOSScreenSaver
>(new CDummyOSScreenSaver
);
1514 std::string
CWinSystemWayland::GetClipboardText()
1516 std::unique_lock
<CCriticalSection
> lock(m_seatsMutex
);
1517 // Get text of first seat with non-empty selection
1518 // Actually, the value of the seat that received the Ctrl+V keypress should be used,
1519 // but this would need a workaround or proper multi-seat support in Kodi - it's
1520 // probably just not that relevant in practice
1521 for (auto const& seat
: m_seats
)
1523 auto text
= seat
.second
->GetSelectionText();
1532 void CWinSystemWayland::OnWindowMove(const wayland::seat_t
& seat
, std::uint32_t serial
)
1534 m_shellSurface
->StartMove(seat
, serial
);
1537 void CWinSystemWayland::OnWindowResize(const wayland::seat_t
& seat
, std::uint32_t serial
, wayland::shell_surface_resize edge
)
1539 m_shellSurface
->StartResize(seat
, serial
, edge
);
1542 void CWinSystemWayland::OnWindowShowContextMenu(const wayland::seat_t
& seat
, std::uint32_t serial
, CPointInt position
)
1544 m_shellSurface
->ShowShellContextMenu(seat
, serial
, position
);
1547 void CWinSystemWayland::OnWindowClose()
1549 CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT
);
1552 void CWinSystemWayland::OnWindowMinimize()
1554 m_shellSurface
->SetMinimized();
1557 void CWinSystemWayland::OnWindowMaximize()
1559 if (m_shellSurfaceState
.test(IShellSurface::STATE_MAXIMIZED
))
1561 m_shellSurface
->UnsetMaximized();
1565 m_shellSurface
->SetMaximized();
1569 void CWinSystemWayland::OnClose()
1571 CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT
);
1574 bool CWinSystemWayland::MessagePump()
1576 return m_winEvents
->MessagePump();