[Windows] Fix driver version detection of AMD RDNA+ GPU on Windows 10
[xbmc.git] / xbmc / windowing / wayland / WinSystemWayland.cpp
blob01d7c8b7eacb7c02680e806517805266434250d7
1 /*
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.
7 */
9 #include "WinSystemWayland.h"
11 #include "CompileInfo.h"
12 #include "Connection.h"
13 #include "OSScreenSaverIdleInhibitUnstableV1.h"
14 #include "OptionalsReg.h"
15 #include "Registry.h"
16 #include "ServiceBroker.h"
17 #include "ShellSurfaceWlShell.h"
18 #include "ShellSurfaceXdgShell.h"
19 #include "ShellSurfaceXdgShellUnstableV6.h"
20 #include "Util.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"
48 #include <algorithm>
49 #include <limits>
50 #include <memory>
51 #include <mutex>
52 #include <numeric>
54 using namespace KODI::WINDOWING;
55 using namespace KODI::WINDOWING::WAYLAND;
56 using namespace std::placeholders;
57 using namespace std::chrono_literals;
59 namespace
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);
72 return RES_INVALID;
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*)>
94 public:
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
113 enum OutMessage
115 CONFIGURE,
116 OUTPUT_HOTPLUG,
117 BUFFER_SCALE
120 struct MsgConfigure
122 std::uint32_t serial;
123 CSizeInt surfaceSize;
124 IShellSurface::StateBitset state;
127 struct MsgBufferScale
129 int scale;
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");
150 if (!env)
152 CLog::Log(LOGDEBUG, "CWinSystemWayland::{} - WAYLAND_DISPLAY env not set", __FUNCTION__);
153 return false;
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())
162 return false;
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));
176 m_registry->Bind();
178 if (m_presentation)
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()
203 ->GetSettings()
204 ->GetSetting(CSettings::SETTING_VIDEOSCREEN_LIMITEDRANGE)
205 ->SetVisible(true);
207 return CWinSystemBase::InitWindowSystem();
210 bool CWinSystemWayland::DestroyWindowSystem()
212 DestroyWindow();
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();
220 m_outputs.clear();
221 m_frameCallback = wayland::callback_t{};
222 m_screenSaverManager.reset();
224 m_seatInputProcessing.reset();
226 if (m_registry)
228 m_registry->UnbindSingletons();
230 m_registry.reset();
231 m_connection.reset();
233 CGenericTouchInputHandler::GetInstance().UnregisterHandler();
235 return CWinSystemBase::DestroyWindowSystem();
238 bool CWinSystemWayland::CreateNewWindow(const std::string& name,
239 bool fullScreen,
240 RESOLUTION_INFO& res)
242 CLog::LogF(LOGINFO, "Starting {} size {}x{}", fullScreen ? "full screen" : "windowed", res.iWidth,
243 res.iHeight);
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);
253 lock.unlock();
254 UpdateBufferScale();
255 UpdateTouchDpi();
257 else
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),
266 output->GetScale());
267 std::unique_lock<CCriticalSection> lock(m_surfaceOutputsMutex);
268 m_surfaceOutputs.erase(output);
269 lock.unlock();
270 UpdateBufferScale();
271 UpdateTouchDpi();
273 else
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();
289 if (m_seats.empty())
291 CLog::Log(LOGWARNING, "Wayland compositor did not announce a wl_seat - you will not have any input devices for the time being");
294 if (fullScreen)
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));
306 if (fullScreen)
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();
316 ApplyBufferScale();
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
330 ApplyOpaqueRegion();
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
362 // stopped then.
363 CWinEventsWayland::SetDisplay(&m_connection->GetDisplay());
365 return true;
368 IShellSurface* CWinSystemWayland::CreateShellSurface(const std::string& name)
370 IShellSurface* shell = CShellSurfaceXdgShell::TryCreate(*this, *m_connection, m_surface, name,
371 std::string(CCompileInfo::GetAppName()));
372 if (!shell)
374 shell = CShellSurfaceXdgShellUnstableV6::TryCreate(*this, *m_connection, m_surface, name,
375 std::string(CCompileInfo::GetAppName()));
377 if (!shell)
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()));
385 return shell;
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();
397 m_seats.clear();
398 m_lastSetOutput.proxy_release();
399 m_surfaceOutputs.clear();
400 m_surfaceSubmissions.clear();
401 m_seatRegistry.reset();
403 return true;
406 bool CWinSystemWayland::CanDoWindowed()
408 return true;
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); });
419 return outputs;
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();
433 // Mimic X11:
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
442 return;
445 auto output = FindOutputByUserFriendlyName(userOutput);
446 if (!output && m_lastSetOutput)
448 // Fallback to current output
449 output = FindOutputByWaylandOutput(m_lastSetOutput);
451 if (!output)
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();
462 CLog::LogF(LOGINFO,
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" : "");
474 RESOLUTION_INFO res;
475 UpdateDesktopResolution(res, outputName, mode.size.Width(), mode.size.Height(), mode.GetRefreshInHz(), 0);
476 res.fPixelRatio = pixelRatio;
478 if (isCurrent)
480 CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP) = res;
482 else
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
524 * instead.
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
530 * set the size then
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");
554 if (fullScreen)
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;
565 if (output)
567 CLog::LogF(LOGDEBUG, "Resolved output \"{}\" to bound Wayland global {}", res.strOutput,
568 output->GetGlobalName());
570 else
572 CLog::LogF(LOGINFO,
573 "Could not match output \"{}\" to a currently available Wayland output, falling "
574 "back to default output",
575 res.strOutput);
578 CLog::LogF(LOGDEBUG, "Setting full-screen with refresh rate {:.3f}", res.fRefreshRate);
579 m_shellSurface->SetFullScreen(wlOutput, res.fRefreshRate);
581 else
583 CLog::LogF(LOGDEBUG, "Not setting full screen: already full screen on requested output");
586 else
588 if (m_shellSurfaceState.test(IShellSurface::STATE_FULLSCREEN))
590 CLog::LogF(LOGDEBUG, "Setting windowed");
591 m_shellSurface->SetWindowed();
593 else
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
601 if (!mustHonorSize)
603 CLog::LogF(LOGDEBUG, "Directly setting windowed size {}x{} on Kodi request", res.iWidth,
604 res.iHeight);
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
627 // ignore them
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
641 ApplyBufferScale();
643 if (update.surfaceSizeChanged)
645 // Update opaque region here so size always matches the configured egl surface
646 ApplyOpaqueRegion();
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.
682 return;
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.
702 skippedConfigures++;
703 lastConfigureMessage = std::move(guard);
704 break;
705 case WinSystemWaylandProtocol::OUTPUT_HOTPLUG:
707 CLog::LogF(LOGDEBUG, "Output hotplug, re-reading resolutions");
708 UpdateResolutions();
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
723 break;
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;
730 break;
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;
755 if (size.IsZero())
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;
763 else
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;
799 if (!size.IsZero())
801 UpdateSizeVariables(size, m_scale, m_shellSurfaceState, true);
803 AckConfigure(serial);
805 else
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)
873 if (!fullScreen)
875 if (m_bFullScreen)
877 XBMC_Event msg{};
878 msg.type = XBMC_MODECHANGE;
879 msg.mode.res = RES_WINDOW;
880 SetWindowResolution(sizes.bufferSize.Width(), sizes.bufferSize.Height());
881 // FIXME
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());
887 else
889 XBMC_Event msg{};
890 msg.type = XBMC_VIDEORESIZE;
891 msg.resize = {sizes.bufferSize.Width(), sizes.bufferSize.Height()};
892 // FIXME
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());
899 else
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);
914 XBMC_Event msg{};
915 msg.type = XBMC_MODECHANGE;
916 msg.mode.res = res;
917 // FIXME
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));
925 else
927 // Apply directly, Kodi resolution does not change
928 ApplyNextState();
932 void CWinSystemWayland::FinishModeChange(RESOLUTION res)
934 const auto& resInfo = CDisplaySettings::GetInstance().GetResolutionInfo(res);
936 ApplyNextState();
938 m_fRefreshRate = resInfo.fRefreshRate;
939 m_bFullScreen = resInfo.bFullScreen;
940 m_waitingForApply = false;
943 void CWinSystemWayland::FinishWindowResize(int, int)
945 ApplyNextState();
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)
967 Sizes result;
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);
990 else
992 result.surfaceSize = size;
993 result.configuredSize = m_windowDecorator->CalculateFullSurfaceSize(size, state);
996 result.bufferSize = result.surfaceSize * scale;
998 return result;
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;
1022 m_scale = 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);
1050 return changes;
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());
1064 if (parts.empty())
1066 // Fallback to "unknown" if no name received from compositor
1067 parts.emplace_back(g_localizeStrings.Get(13205));
1070 // Add position
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();
1083 return true;
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
1122 // buffer each time
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)
1250 // FIXME
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())
1259 return;
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());
1271 else
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()
1318 timespec time;
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");
1323 return time;
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
1330 if (m_presentation)
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);
1351 if (output)
1353 m_syncOutputRefreshRate = output->GetCurrentMode().GetRefreshInHz();
1355 else
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
1370 float adjust{};
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
1394 // To sum up, we:
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()
1440 ProcessMessages();
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()
1454 if (m_presentation)
1456 return m_latencyMovingAverage * 1000.0f;
1458 else
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));
1482 else
1484 CLog::LogF(LOGINFO, "No supported method for video sync found");
1485 return nullptr;
1489 std::unique_ptr<IOSScreenSaver> CWinSystemWayland::GetOSScreenSaverImpl()
1491 if (m_surface)
1493 std::unique_ptr<IOSScreenSaver> ptr;
1494 ptr.reset(COSScreenSaverIdleInhibitUnstableV1::TryCreate(*m_connection, m_surface));
1495 if (ptr)
1497 CLog::LogF(LOGINFO, "Using idle-inhibit-unstable-v1 protocol for screen saver inhibition");
1498 return ptr;
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);
1508 #endif
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();
1524 if (text != "")
1526 return text;
1529 return "";
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();
1563 else
1565 m_shellSurface->SetMaximized();
1569 void CWinSystemWayland::OnClose()
1571 CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT);
1574 bool CWinSystemWayland::MessagePump()
1576 return m_winEvents->MessagePump();