1 /*
2  *  Copyright (C) 2017-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "WinSystemWayland.h"
10 
11 #include "Application.h"
12 #include "CompileInfo.h"
13 #include "Connection.h"
14 #include "OSScreenSaverIdleInhibitUnstableV1.h"
15 #include "OptionalsReg.h"
16 #include "Registry.h"
17 #include "ServiceBroker.h"
18 #include "ShellSurfaceWlShell.h"
19 #include "ShellSurfaceXdgShell.h"
20 #include "ShellSurfaceXdgShellUnstableV6.h"
21 #include "Util.h"
22 #include "VideoSyncWpPresentation.h"
23 #include "WinEventsWayland.h"
24 #include "WindowDecorator.h"
25 #include "cores/RetroPlayer/process/wayland/RPProcessInfoWayland.h"
26 #include "cores/VideoPlayer/Process/wayland/ProcessInfoWayland.h"
27 #include "guilib/DispResource.h"
28 #include "guilib/LocalizeStrings.h"
29 #include "input/InputManager.h"
30 #include "input/touch/generic/GenericTouchActionHandler.h"
31 #include "input/touch/generic/GenericTouchInputHandler.h"
32 #include "messaging/ApplicationMessenger.h"
33 #include "settings/AdvancedSettings.h"
34 #include "settings/DisplaySettings.h"
35 #include "settings/Settings.h"
36 #include "settings/SettingsComponent.h"
37 #include "settings/lib/Setting.h"
38 #include "threads/SingleLock.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"
45 
46 #include "platform/linux/TimeUtils.h"
47 
48 #include <algorithm>
49 #include <limits>
50 #include <numeric>
51 
52 #if defined(HAS_DBUS)
53 # include "windowing/linux/OSScreenSaverFreedesktop.h"
54 #endif
55 
56 using namespace KODI::WINDOWING;
57 using namespace KODI::WINDOWING::WAYLAND;
58 using namespace std::placeholders;
59 
60 namespace
61 {
62 
FindMatchingCustomResolution(CSizeInt size,float refreshRate)63 RESOLUTION FindMatchingCustomResolution(CSizeInt size, float refreshRate)
64 {
65   for (size_t res{RES_DESKTOP}; res < CDisplaySettings::GetInstance().ResolutionInfoSize(); ++res)
66   {
67     auto const& resInfo = CDisplaySettings::GetInstance().GetResolutionInfo(res);
68     if (resInfo.iWidth == size.Width() && resInfo.iHeight == size.Height() && MathUtils::FloatEquals(resInfo.fRefreshRate, refreshRate, 0.0005f))
69     {
70       return static_cast<RESOLUTION> (res);
71     }
72   }
73   return RES_INVALID;
74 }
75 
76 struct OutputScaleComparer
77 {
operator ()__anon7814f5de0111::OutputScaleComparer78   bool operator()(std::shared_ptr<COutput> const& output1, std::shared_ptr<COutput> const& output2)
79   {
80     return output1->GetScale() < output2->GetScale();
81   }
82 };
83 
84 struct OutputCurrentRefreshRateComparer
85 {
operator ()__anon7814f5de0111::OutputCurrentRefreshRateComparer86   bool operator()(std::shared_ptr<COutput> const& output1, std::shared_ptr<COutput> const& output2)
87   {
88     return output1->GetCurrentMode().refreshMilliHz < output2->GetCurrentMode().refreshMilliHz;
89   }
90 };
91 
92 /// Scope guard for Actor::Message
93 class MessageHandle : public KODI::UTILS::CScopeGuard<Actor::Message*, nullptr, void(Actor::Message*)>
94 {
95 public:
MessageHandle()96   MessageHandle() : CScopeGuard{std::bind(&Actor::Message::Release, std::placeholders::_1), nullptr} {}
MessageHandle(Actor::Message * message)97   explicit MessageHandle(Actor::Message* message) : CScopeGuard{std::bind(&Actor::Message::Release, std::placeholders::_1), message} {}
Get()98   Actor::Message* Get() { return static_cast<Actor::Message*> (*this); }
99 };
100 
101 /**
102  * Protocol for communication between Wayland event thread and main thread
103  *
104  * Many messages received from the Wayland compositor must be processed at a
105  * defined time between frame rendering, such as resolution switches. Thus
106  * they are pushed to the main thread for processing.
107  *
108  * The protocol is strictly uni-directional from event to main thread at the moment,
109  * so \ref Actor::Protocol is mainly used as an event queue.
110  */
111 namespace WinSystemWaylandProtocol
112 {
113 
114 enum OutMessage
115 {
116   CONFIGURE,
117   OUTPUT_HOTPLUG,
118   BUFFER_SCALE
119 };
120 
121 struct MsgConfigure
122 {
123   std::uint32_t serial;
124   CSizeInt surfaceSize;
125   IShellSurface::StateBitset state;
126 };
127 
128 struct MsgBufferScale
129 {
130   int scale;
131 };
132 
133 };
134 
135 }
136 
CWinSystemWayland()137 CWinSystemWayland::CWinSystemWayland()
138 : CWinSystemBase{}, m_protocol{"WinSystemWaylandInternal"}
139 {
140   m_winEvents.reset(new CWinEventsWayland());
141 }
142 
~CWinSystemWayland()143 CWinSystemWayland::~CWinSystemWayland() noexcept
144 {
145   DestroyWindowSystem();
146 }
147 
InitWindowSystem()148 bool CWinSystemWayland::InitWindowSystem()
149 {
150   const char* env = getenv("WAYLAND_DISPLAY");
151   if (!env)
152   {
153     CLog::Log(LOGDEBUG, "CWinSystemWayland::{} - WAYLAND_DISPLAY env not set", __FUNCTION__);
154     return false;
155   }
156 
157   wayland::set_log_handler([](const std::string& message) {
158     CLog::Log(LOGWARNING, "wayland-client log message: %s", message.c_str());
159   });
160 
161   VIDEOPLAYER::CProcessInfoWayland::Register();
162   RETRO::CRPProcessInfoWayland::Register();
163 
164   CLog::LogF(LOGINFO, "Connecting to Wayland server");
165   m_connection.reset(new CConnection);
166   m_registry.reset(new CRegistry{*m_connection});
167 
168   m_registry->RequestSingleton(m_compositor, 1, 4);
169   m_registry->RequestSingleton(m_shm, 1, 1);
170   m_registry->RequestSingleton(m_presentation, 1, 1, false);
171   // version 2 adds done() -> required
172   // version 3 adds destructor -> optional
173   m_registry->Request<wayland::output_t>(2, 3, std::bind(&CWinSystemWayland::OnOutputAdded, this, _1, _2), std::bind(&CWinSystemWayland::OnOutputRemoved, this, _1));
174 
175   m_registry->Bind();
176 
177   if (m_presentation)
178   {
179     m_presentation.on_clock_id() = [this](std::uint32_t clockId)
180     {
181       CLog::Log(LOGINFO, "Wayland presentation clock: %" PRIu32, clockId);
182       m_presentationClock = static_cast<clockid_t> (clockId);
183     };
184   }
185 
186   // Do another roundtrip to get initial wl_output information
187   m_connection->GetDisplay().roundtrip();
188   if (m_outputs.empty())
189   {
190     throw std::runtime_error("No outputs received from compositor");
191   }
192 
193   // Event loop is started in CreateWindow
194 
195   // pointer is by default not on this window, will be immediately rectified
196   // by the enter() events if it is
197   CServiceBroker::GetInputManager().SetMouseActive(false);
198   // Always use the generic touch action handler
199   CGenericTouchInputHandler::GetInstance().RegisterHandler(&CGenericTouchActionHandler::GetInstance());
200 
201   CServiceBroker::GetSettingsComponent()
202       ->GetSettings()
203       ->GetSetting(CSettings::SETTING_VIDEOSCREEN_LIMITEDRANGE)
204       ->SetVisible(true);
205 
206   return CWinSystemBase::InitWindowSystem();
207 }
208 
DestroyWindowSystem()209 bool CWinSystemWayland::DestroyWindowSystem()
210 {
211   DestroyWindow();
212   // wl_display_disconnect frees all proxy objects, so we have to make sure
213   // all stuff is gone on the C++ side before that
214   m_cursorSurface = wayland::surface_t{};
215   m_cursorBuffer = wayland::buffer_t{};
216   m_cursorImage = wayland::cursor_image_t{};
217   m_cursorTheme = wayland::cursor_theme_t{};
218   m_outputsInPreparation.clear();
219   m_outputs.clear();
220   m_frameCallback = wayland::callback_t{};
221   m_screenSaverManager.reset();
222 
223   m_seatInputProcessing.reset();
224 
225   if (m_registry)
226   {
227     m_registry->UnbindSingletons();
228   }
229   m_registry.reset();
230   m_connection.reset();
231 
232   CGenericTouchInputHandler::GetInstance().UnregisterHandler();
233 
234   return CWinSystemBase::DestroyWindowSystem();
235 }
236 
CreateNewWindow(const std::string & name,bool fullScreen,RESOLUTION_INFO & res)237 bool CWinSystemWayland::CreateNewWindow(const std::string& name,
238                                         bool fullScreen,
239                                         RESOLUTION_INFO& res)
240 {
241   CLog::LogF(LOGINFO, "Starting %s size %dx%d", fullScreen ? "full screen" : "windowed", res.iWidth, res.iHeight);
242 
243   m_surface = m_compositor.create_surface();
244   m_surface.on_enter() = [this](const wayland::output_t& wloutput) {
245     if (auto output = FindOutputByWaylandOutput(wloutput))
246     {
247       CLog::Log(LOGDEBUG, "Entering output \"%s\" with scale %d and %.3f dpi", UserFriendlyOutputName(output).c_str(), output->GetScale(), output->GetCurrentDpi());
248       CSingleLock lock(m_surfaceOutputsMutex);
249       m_surfaceOutputs.emplace(output);
250       lock.Leave();
251       UpdateBufferScale();
252       UpdateTouchDpi();
253     }
254     else
255     {
256       CLog::Log(LOGWARNING, "Entering output that was not configured yet, ignoring");
257     }
258   };
259   m_surface.on_leave() = [this](const wayland::output_t& wloutput) {
260     if (auto output = FindOutputByWaylandOutput(wloutput))
261     {
262       CLog::Log(LOGDEBUG, "Leaving output \"%s\" with scale %d", UserFriendlyOutputName(output).c_str(), output->GetScale());
263       CSingleLock lock(m_surfaceOutputsMutex);
264       m_surfaceOutputs.erase(output);
265       lock.Leave();
266       UpdateBufferScale();
267       UpdateTouchDpi();
268     }
269     else
270     {
271       CLog::Log(LOGWARNING, "Leaving output that was not configured yet, ignoring");
272     }
273   };
274 
275   m_windowDecorator.reset(new CWindowDecorator(*this, *m_connection, m_surface));
276 
277   m_seatInputProcessing.reset(new CSeatInputProcessing(m_surface, *this));
278   m_seatRegistry.reset(new CRegistry{*m_connection});
279   // version 2 adds name event -> optional
280   // version 4 adds wl_keyboard repeat_info -> optional
281   // version 5 adds discrete axis events in wl_pointer -> unused
282   m_seatRegistry->Request<wayland::seat_t>(1, 5, std::bind(&CWinSystemWayland::OnSeatAdded, this, _1, _2), std::bind(&CWinSystemWayland::OnSeatRemoved, this, _1));
283   m_seatRegistry->Bind();
284 
285   if (m_seats.empty())
286   {
287     CLog::Log(LOGWARNING, "Wayland compositor did not announce a wl_seat - you will not have any input devices for the time being");
288   }
289 
290   if (fullScreen)
291   {
292     m_shellSurfaceState.set(IShellSurface::STATE_FULLSCREEN);
293   }
294   // Assume we're active on startup until someone tells us otherwise
295   m_shellSurfaceState.set(IShellSurface::STATE_ACTIVATED);
296   // Try with this resolution if compositor does not say otherwise
297   UpdateSizeVariables({res.iWidth, res.iHeight}, m_scale, m_shellSurfaceState, false);
298 
299   // Use AppName as the desktop file name. This is required to lookup the app icon of the same name.
300   m_shellSurface.reset(CShellSurfaceXdgShell::TryCreate(*this, *m_connection, m_surface, name,
301                                                         std::string(CCompileInfo::GetAppName())));
302   if (!m_shellSurface)
303   {
304     m_shellSurface.reset(CShellSurfaceXdgShellUnstableV6::TryCreate(
305         *this, *m_connection, m_surface, name, std::string(CCompileInfo::GetAppName())));
306   }
307   if (!m_shellSurface)
308   {
309     CLog::LogF(LOGWARNING, "Compositor does not support xdg_shell protocol (stable or unstable v6) - falling back to wl_shell, not all features might work");
310     m_shellSurface.reset(new CShellSurfaceWlShell(*this, *m_connection, m_surface, name,
311                                                   std::string(CCompileInfo::GetAppName())));
312   }
313 
314   if (fullScreen)
315   {
316     // Try to start on correct monitor and with correct buffer scale
317     auto output = FindOutputByUserFriendlyName(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR));
318     auto wlOutput = output ? output->GetWaylandOutput() : wayland::output_t{};
319     m_lastSetOutput = wlOutput;
320     m_shellSurface->SetFullScreen(wlOutput, res.fRefreshRate);
321     if (output && m_surface.can_set_buffer_scale())
322     {
323       m_scale = output->GetScale();
324       ApplyBufferScale();
325     }
326   }
327 
328   // Just remember initial width/height for context creation in OnConfigure
329   // This is used for sizing the EGLSurface
330   m_shellSurfaceInitializing = true;
331   m_shellSurface->Initialize();
332   m_shellSurfaceInitializing = false;
333 
334   // Apply window decorations if necessary
335   m_windowDecorator->SetState(m_configuredSize, m_scale, m_shellSurfaceState);
336 
337   // Set initial opaque region and window geometry
338   ApplyOpaqueRegion();
339   ApplyWindowGeometry();
340 
341   // Update resolution with real size as it could have changed due to configure()
342   UpdateDesktopResolution(res, res.strOutput, m_bufferSize.Width(), m_bufferSize.Height(), res.fRefreshRate, 0);
343   res.bFullScreen = fullScreen;
344 
345   // Now start processing events
346   //
347   // There are two stages to the event handling:
348   // * Initialization (which ends here): Everything runs synchronously and init
349   //   code that needs events processed must call roundtrip().
350   //   This is done for simplicity because it is a lot easier than to make
351   //   everything event-based and thread-safe everywhere in the startup code,
352   //   which is also not really necessary.
353   // * Runtime (which starts here): Every object creation from now on
354   //   needs to take great care to be thread-safe:
355   //   Since the event pump is always running now, there is a tiny window between
356   //   creating an object and attaching the C++ event handlers during which
357   //   events can get queued and dispatched for the object but the handlers have
358   //   not been set yet. Consequently, the events would get lost.
359   //   However, this does not apply to objects that are created in response to
360   //   compositor events. Since the callbacks are called from the event processing
361   //   thread and ran strictly sequentially, no other events are dispatched during
362   //   the runtime of a callback. Luckily this applies to global binding like
363   //   wl_output and wl_seat and thus to most if not all runtime object creation
364   //   cases we have to support.
365   //   There is another problem when Wayland objects are destructed from the main
366   //   thread: An event handler could be running in parallel, resulting in certain
367   //   doom. So objects should only be deleted in response to compositor events, too.
368   //   They might be hiding behind class member variables, so be wary.
369   //   Note that this does not apply to global teardown since the event pump is
370   //   stopped then.
371   CWinEventsWayland::SetDisplay(&m_connection->GetDisplay());
372 
373   return true;
374 }
375 
DestroyWindow()376 bool CWinSystemWayland::DestroyWindow()
377 {
378   // Make sure no more events get processed when we kill the instances
379   CWinEventsWayland::SetDisplay(nullptr);
380 
381   m_shellSurface.reset();
382   // waylandpp automatically calls wl_surface_destroy when the last reference is removed
383   m_surface = wayland::surface_t();
384   m_windowDecorator.reset();
385   m_seats.clear();
386   m_lastSetOutput.proxy_release();
387   m_surfaceOutputs.clear();
388   m_surfaceSubmissions.clear();
389   m_seatRegistry.reset();
390 
391   return true;
392 }
393 
CanDoWindowed()394 bool CWinSystemWayland::CanDoWindowed()
395 {
396   return true;
397 }
398 
GetConnectedOutputs(std::vector<std::string> * outputs)399 void CWinSystemWayland::GetConnectedOutputs(std::vector<std::string>* outputs)
400 {
401   CSingleLock lock(m_outputsMutex);
402   std::transform(m_outputs.cbegin(), m_outputs.cend(), std::back_inserter(*outputs),
403                  [this](decltype(m_outputs)::value_type const& pair)
404                  {
405                    return UserFriendlyOutputName(pair.second); });
406 }
407 
UseLimitedColor()408 bool CWinSystemWayland::UseLimitedColor()
409 {
410   return CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOSCREEN_LIMITEDRANGE);
411 }
412 
UpdateResolutions()413 void CWinSystemWayland::UpdateResolutions()
414 {
415   CWinSystemBase::UpdateResolutions();
416 
417   CDisplaySettings::GetInstance().ClearCustomResolutions();
418 
419   // Mimic X11:
420   // Only show resolutions for the currently selected output
421   std::string userOutput = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR);
422 
423   CSingleLock lock(m_outputsMutex);
424 
425   if (m_outputs.empty())
426   {
427     // *Usually* this should not happen - just give up
428     return;
429   }
430 
431   auto output = FindOutputByUserFriendlyName(userOutput);
432   if (!output && m_lastSetOutput)
433   {
434     // Fallback to current output
435     output = FindOutputByWaylandOutput(m_lastSetOutput);
436   }
437   if (!output)
438   {
439     // Well just use the first one
440     output = m_outputs.begin()->second;
441   }
442 
443   std::string outputName = UserFriendlyOutputName(output);
444 
445   auto const& modes = output->GetModes();
446   auto const& currentMode = output->GetCurrentMode();
447   auto physicalSize = output->GetPhysicalSize();
448   CLog::LogF(LOGINFO, "User wanted output \"%s\", we now have \"%s\" size %dx%d mm with %zu mode(s):", userOutput.c_str(), outputName.c_str(), physicalSize.Width(), physicalSize.Height(), modes.size());
449 
450   for (auto const& mode : modes)
451   {
452     bool isCurrent = (mode == currentMode);
453     float pixelRatio = output->GetPixelRatioForMode(mode);
454     CLog::LogF(LOGINFO, "- %dx%d @%.3f Hz pixel ratio %.3f%s", mode.size.Width(), mode.size.Height(), mode.refreshMilliHz / 1000.0f, pixelRatio, isCurrent ? " current" : "");
455 
456     RESOLUTION_INFO res;
457     UpdateDesktopResolution(res, outputName, mode.size.Width(), mode.size.Height(), mode.GetRefreshInHz(), 0);
458     res.fPixelRatio = pixelRatio;
459 
460     if (isCurrent)
461     {
462       CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP) = res;
463     }
464     else
465     {
466       CDisplaySettings::GetInstance().AddResolutionInfo(res);
467     }
468   }
469 
470   CDisplaySettings::GetInstance().ApplyCalibrations();
471 }
472 
FindOutputByUserFriendlyName(const std::string & name)473 std::shared_ptr<COutput> CWinSystemWayland::FindOutputByUserFriendlyName(const std::string& name)
474 {
475   CSingleLock lock(m_outputsMutex);
476   auto outputIt = std::find_if(m_outputs.begin(), m_outputs.end(),
477                                [this, &name](decltype(m_outputs)::value_type const& entry)
478                                {
479                                  return (name == UserFriendlyOutputName(entry.second));
480                                });
481 
482   return (outputIt == m_outputs.end() ? nullptr : outputIt->second);
483 }
484 
FindOutputByWaylandOutput(wayland::output_t const & output)485 std::shared_ptr<COutput> CWinSystemWayland::FindOutputByWaylandOutput(wayland::output_t const& output)
486 {
487   CSingleLock lock(m_outputsMutex);
488   auto outputIt = std::find_if(m_outputs.begin(), m_outputs.end(),
489                                [&output](decltype(m_outputs)::value_type const& entry)
490                                {
491                                  return (output == entry.second->GetWaylandOutput());
492                                });
493 
494   return (outputIt == m_outputs.end() ? nullptr : outputIt->second);
495 }
496 
497 /**
498  * Change resolution and window state on Kodi request
499  *
500  * This function is used for updating resolution when Kodi initiates a resolution
501  * change, such as when changing between full screen and windowed mode or when
502  * selecting a different monitor or resolution in the settings.
503  *
504  * Size updates originating from compositor events (such as configure or buffer
505  * scale changes) should not use this function, but \ref SetResolutionInternal
506  * instead.
507  *
508  * \param fullScreen whether to go full screen or windowed
509  * \param res resolution to set
510  * \return whether the requested resolution was actually set - is false e.g.
511  *         when already in full screen mode since the application cannot
512  *         set the size then
513  */
SetResolutionExternal(bool fullScreen,RESOLUTION_INFO const & res)514 bool CWinSystemWayland::SetResolutionExternal(bool fullScreen, RESOLUTION_INFO const& res)
515 {
516   // In fullscreen modes, we never change the surface size on Kodi's request,
517   // but only when the compositor tells us to. At least xdg_shell specifies
518   // that with state fullscreen the dimensions given in configure() must
519   // always be observed.
520   // This does mean that the compositor has no way of knowing which resolution
521   // we would (in theory) want. Since no compositor implements dynamic resolution
522   // switching at the moment, this is not a problem. If it is some day implemented
523   // in compositors, this code must be changed to match the behavior that is
524   // expected then anyway.
525 
526   // We can honor the Kodi-requested size only if we are not bound by configure rules,
527   // which applies for maximized and fullscreen states.
528   // Also, setting an unconfigured size when just going fullscreen makes no sense.
529   // Give precedence to the size we have still pending, if any.
530   bool mustHonorSize{m_waitingForApply || m_shellSurfaceState.test(IShellSurface::STATE_MAXIMIZED) || m_shellSurfaceState.test(IShellSurface::STATE_FULLSCREEN) || fullScreen};
531 
532   CLog::LogF(LOGINFO, "Kodi asked to switch mode to %dx%d @%.3f Hz on output \"%s\" %s", res.iWidth, res.iHeight, res.fRefreshRate, res.strOutput.c_str(), fullScreen ? "full screen" : "windowed");
533 
534   if (fullScreen)
535   {
536     // Try to match output
537     auto output = FindOutputByUserFriendlyName(res.strOutput);
538     auto wlOutput = output ? output->GetWaylandOutput() : wayland::output_t{};
539     if (!m_shellSurfaceState.test(IShellSurface::STATE_FULLSCREEN) || (m_lastSetOutput != wlOutput))
540     {
541       // Remember the output we set last so we don't set it again until we
542       // either go windowed or were on a different output
543       m_lastSetOutput = wlOutput;
544 
545       if (output)
546       {
547         CLog::LogF(LOGDEBUG, "Resolved output \"%s\" to bound Wayland global %u", res.strOutput.c_str(), output->GetGlobalName());
548       }
549       else
550       {
551         CLog::LogF(LOGINFO, "Could not match output \"%s\" to a currently available Wayland output, falling back to default output", res.strOutput.c_str());
552       }
553 
554       CLog::LogF(LOGDEBUG, "Setting full-screen with refresh rate %.3f", res.fRefreshRate);
555       m_shellSurface->SetFullScreen(wlOutput, res.fRefreshRate);
556     }
557     else
558     {
559       CLog::LogF(LOGDEBUG, "Not setting full screen: already full screen on requested output");
560     }
561   }
562   else
563   {
564     if (m_shellSurfaceState.test(IShellSurface::STATE_FULLSCREEN))
565     {
566       CLog::LogF(LOGDEBUG, "Setting windowed");
567       m_shellSurface->SetWindowed();
568     }
569     else
570     {
571       CLog::LogF(LOGDEBUG, "Not setting windowed: already windowed");
572     }
573   }
574 
575   // Set Kodi-provided size only if we are free to choose any size, otherwise
576   // wait for the compositor configure
577   if (!mustHonorSize)
578   {
579     CLog::LogF(LOGDEBUG, "Directly setting windowed size %dx%d on Kodi request", res.iWidth, res.iHeight);
580     // Kodi is directly setting window size, apply
581     auto updateResult = UpdateSizeVariables({res.iWidth, res.iHeight}, m_scale, m_shellSurfaceState, false);
582     ApplySizeUpdate(updateResult);
583   }
584 
585   bool wasInitialSetFullScreen{m_isInitialSetFullScreen};
586   m_isInitialSetFullScreen = false;
587 
588   // Need to return true
589   // * when this SetFullScreen() call was free to change the context size (and possibly did so)
590   // * on first SetFullScreen so GraphicsContext gets resolution
591   // Otherwise, Kodi must keep the old resolution.
592   return !mustHonorSize || wasInitialSetFullScreen;
593 }
594 
ResizeWindow(int,int,int,int)595 bool CWinSystemWayland::ResizeWindow(int, int, int, int)
596 {
597   // CGraphicContext is "smart" and calls ResizeWindow or SetFullScreen depending
598   // on some state like wheter we were already full screen. But actually the processing
599   // here is always identical, so we are using a common function to handle both.
600   auto& res = CDisplaySettings::GetInstance().GetResolutionInfo(RES_WINDOW);
601   // The newWidth/newHeight parameters are taken from RES_WINDOW anyway, so we can just
602   // ignore them
603   return SetResolutionExternal(false, res);
604 }
605 
SetFullScreen(bool fullScreen,RESOLUTION_INFO & res,bool)606 bool CWinSystemWayland::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool)
607 {
608   return SetResolutionExternal(fullScreen, res);
609 }
610 
ApplySizeUpdate(SizeUpdateInformation update)611 void CWinSystemWayland::ApplySizeUpdate(SizeUpdateInformation update)
612 {
613   if (update.bufferScaleChanged)
614   {
615     // Buffer scale must also match egl size configuration
616     ApplyBufferScale();
617   }
618   if (update.surfaceSizeChanged)
619   {
620     // Update opaque region here so size always matches the configured egl surface
621     ApplyOpaqueRegion();
622   }
623   if (update.configuredSizeChanged)
624   {
625     // Update window decoration state
626     m_windowDecorator->SetState(m_configuredSize, m_scale, m_shellSurfaceState);
627     ApplyWindowGeometry();
628   }
629   // Set always, because of initialization order GL context has to keep track of
630   // whether the size changed. If we skip based on update.bufferSizeChanged here,
631   // GL context will never get its initial size set.
632   SetContextSize(m_bufferSize);
633 }
634 
ApplyOpaqueRegion()635 void CWinSystemWayland::ApplyOpaqueRegion()
636 {
637   // Mark everything opaque so the compositor can render it faster
638   CLog::LogF(LOGDEBUG, "Setting opaque region size %dx%d", m_surfaceSize.Width(), m_surfaceSize.Height());
639   wayland::region_t opaqueRegion{m_compositor.create_region()};
640   opaqueRegion.add(0, 0, m_surfaceSize.Width(), m_surfaceSize.Height());
641   m_surface.set_opaque_region(opaqueRegion);
642 }
643 
ApplyWindowGeometry()644 void CWinSystemWayland::ApplyWindowGeometry()
645 {
646   m_shellSurface->SetWindowGeometry(m_windowDecorator->GetWindowGeometry());
647 }
648 
ProcessMessages()649 void CWinSystemWayland::ProcessMessages()
650 {
651   if (m_waitingForApply)
652   {
653     // Do not put multiple size updates into the pipeline, this would only make
654     // it more complicated without any real benefit. Wait until the size was reconfigured,
655     // then process events again.
656     return;
657   }
658 
659   Actor::Message* message{};
660   MessageHandle lastConfigureMessage;
661   int skippedConfigures{-1};
662   int newScale{m_scale};
663 
664   while (m_protocol.ReceiveOutMessage(&message))
665   {
666     MessageHandle guard{message};
667     switch (message->signal)
668     {
669       case WinSystemWaylandProtocol::CONFIGURE:
670         // Do not directly process configures, get the last one queued:
671         // While resizing, the compositor will usually send a configure event
672         // each time the mouse moves without any throttling (i.e. multiple times
673         // per rendered frame).
674         // Going through all those and applying them would waste a lot of time when
675         // we already know that the size is not final and will change again anyway.
676         skippedConfigures++;
677         lastConfigureMessage = std::move(guard);
678         break;
679       case WinSystemWaylandProtocol::OUTPUT_HOTPLUG:
680       {
681         CLog::LogF(LOGDEBUG, "Output hotplug, re-reading resolutions");
682         UpdateResolutions();
683         CSingleLock lock(m_outputsMutex);
684         auto const& desktopRes = CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP);
685         auto output = FindOutputByUserFriendlyName(desktopRes.strOutput);
686         auto const& wlOutput = output->GetWaylandOutput();
687         // Maybe the output that was added was the one we should be on?
688         if (m_bFullScreen && m_lastSetOutput != wlOutput)
689         {
690           CLog::LogF(LOGDEBUG, "Output hotplug resulted in monitor set in settings appearing, switching");
691           // Switch to this output
692           m_lastSetOutput = wlOutput;
693           m_shellSurface->SetFullScreen(wlOutput, desktopRes.fRefreshRate);
694           // SetOutput will result in a configure that updates the actual context size
695         }
696       }
697         break;
698       case WinSystemWaylandProtocol::BUFFER_SCALE:
699         // Never update buffer scale if not possible to set it
700         if (m_surface.can_set_buffer_scale())
701         {
702           newScale = (reinterpret_cast<WinSystemWaylandProtocol::MsgBufferScale*> (message->data))->scale;
703         }
704         break;
705     }
706   }
707 
708   if (lastConfigureMessage)
709   {
710     if (skippedConfigures > 0)
711     {
712       CLog::LogF(LOGDEBUG, "Skipped %d configures", skippedConfigures);
713     }
714     // Wayland will tell us here the size of the surface that was actually created,
715     // which might be different from what we expected e.g. when fullscreening
716     // on an output we chose - the compositor might have decided to use a different
717     // output for example
718     // It is very important that the EGL native module and the rendering system use the
719     // Wayland-announced size for rendering or corrupted graphics output will result.
720     auto configure = reinterpret_cast<WinSystemWaylandProtocol::MsgConfigure*> (lastConfigureMessage.Get()->data);
721     CLog::LogF(LOGDEBUG, "Configure serial %u: size %dx%d state %s", configure->serial, configure->surfaceSize.Width(), configure->surfaceSize.Height(), IShellSurface::StateToString(configure->state).c_str());
722 
723 
724     CSizeInt size = configure->surfaceSize;
725     bool sizeIncludesDecoration = true;
726 
727     if (size.IsZero())
728     {
729       if (configure->state.test(IShellSurface::STATE_FULLSCREEN))
730       {
731         // Do not change current size - UpdateWithConfiguredSize must be called regardless in case
732         // scale or something else changed
733         size = m_configuredSize;
734       }
735       else
736       {
737         // Compositor has no preference and we're windowed
738         // -> adopt windowed size that Kodi wants
739         auto const& windowed = CDisplaySettings::GetInstance().GetResolutionInfo(RES_WINDOW);
740         // Kodi resolution is buffer size, but SetResolutionInternal expects
741         // surface size, so divide by m_scale
742         size = CSizeInt{windowed.iWidth, windowed.iHeight} / newScale;
743         CLog::LogF(LOGDEBUG, "Adapting Kodi windowed size %dx%d", size.Width(), size.Height());
744         sizeIncludesDecoration = false;
745       }
746     }
747 
748     SetResolutionInternal(size, newScale, configure->state, sizeIncludesDecoration, true, configure->serial);
749   }
750   // If we were also configured, scale is already taken care of. But it could
751   // also be a scale change without configure, so apply that.
752   else if (m_scale != newScale)
753   {
754     SetResolutionInternal(m_configuredSize, newScale, m_shellSurfaceState, true, false);
755   }
756 }
757 
ApplyShellSurfaceState(IShellSurface::StateBitset state)758 void CWinSystemWayland::ApplyShellSurfaceState(IShellSurface::StateBitset state)
759 {
760   m_windowDecorator->SetState(m_configuredSize, m_scale, state);
761   m_shellSurfaceState = state;
762 }
763 
OnConfigure(std::uint32_t serial,CSizeInt size,IShellSurface::StateBitset state)764 void CWinSystemWayland::OnConfigure(std::uint32_t serial, CSizeInt size, IShellSurface::StateBitset state)
765 {
766   if (m_shellSurfaceInitializing)
767   {
768     CLog::LogF(LOGDEBUG, "Initial configure serial %u: size %dx%d state %s", serial, size.Width(), size.Height(), IShellSurface::StateToString(state).c_str());
769     m_shellSurfaceState = state;
770     if (!size.IsZero())
771     {
772       UpdateSizeVariables(size, m_scale, m_shellSurfaceState, true);
773     }
774     AckConfigure(serial);
775   }
776   else
777   {
778     WinSystemWaylandProtocol::MsgConfigure msg{serial, size, state};
779     m_protocol.SendOutMessage(WinSystemWaylandProtocol::CONFIGURE, &msg, sizeof(msg));
780   }
781 }
782 
AckConfigure(std::uint32_t serial)783 void CWinSystemWayland::AckConfigure(std::uint32_t serial)
784 {
785   // Send ack if we have a new serial number or this is the first time
786   // this function is called
787   if (serial != m_lastAckedSerial || !m_firstSerialAcked)
788   {
789     CLog::LogF(LOGDEBUG, "Acking serial %u", serial);
790     m_shellSurface->AckConfigure(serial);
791     m_lastAckedSerial = serial;
792     m_firstSerialAcked = true;
793   }
794 }
795 
796 /**
797  * Recalculate sizes from given parameters, apply them and update Kodi CDisplaySettings
798  * resolution if necessary
799  *
800  * This function should be called when events internal to the windowing system
801  * such as a compositor configure lead to a size change.
802  *
803  * Call only from main thread.
804  *
805  * \param size configured size, can be zero if compositor does not have a preference
806  * \param scale new buffer scale
807  * \param sizeIncludesDecoration whether size includes the size of the window decorations if present
808  */
SetResolutionInternal(CSizeInt size,std::int32_t scale,IShellSurface::StateBitset state,bool sizeIncludesDecoration,bool mustAck,std::uint32_t configureSerial)809 void CWinSystemWayland::SetResolutionInternal(CSizeInt size, std::int32_t scale, IShellSurface::StateBitset state, bool sizeIncludesDecoration, bool mustAck, std::uint32_t configureSerial)
810 {
811   // This should never be called while a size set is pending
812   assert(!m_waitingForApply);
813 
814   bool fullScreen{state.test(IShellSurface::STATE_FULLSCREEN)};
815   auto sizes = CalculateSizes(size, scale, state, sizeIncludesDecoration);
816 
817   CLog::LogF(LOGDEBUG, "Set size for serial %" PRIu32 ": %dx%d %s decoration at scale %d state %s", configureSerial, size.Width(), size.Height(), sizeIncludesDecoration ? "including" : "excluding", scale, IShellSurface::StateToString(state).c_str());
818 
819   // Get actual frame rate from monitor, take highest frame rate if multiple
820   float refreshRate{m_fRefreshRate};
821   {
822     CSingleLock lock(m_surfaceOutputsMutex);
823     auto maxRefreshIt = std::max_element(m_surfaceOutputs.cbegin(), m_surfaceOutputs.cend(), OutputCurrentRefreshRateComparer());
824     if (maxRefreshIt != m_surfaceOutputs.cend())
825     {
826       refreshRate = (*maxRefreshIt)->GetCurrentMode().GetRefreshInHz();
827       CLog::LogF(LOGDEBUG, "Resolved actual (maximum) refresh rate to %.3f Hz on output \"%s\"", refreshRate, UserFriendlyOutputName(*maxRefreshIt).c_str());
828     }
829   }
830 
831   m_next.mustBeAcked = mustAck;
832   m_next.configureSerial = configureSerial;
833   m_next.configuredSize = sizes.configuredSize;
834   m_next.scale = scale;
835   m_next.shellSurfaceState = state;
836 
837   // Check if any parameters of the Kodi resolution configuration changed
838   if (refreshRate != m_fRefreshRate || sizes.bufferSize != m_bufferSize || m_bFullScreen != fullScreen)
839   {
840     if (!fullScreen)
841     {
842       if (m_bFullScreen)
843       {
844         XBMC_Event msg{XBMC_MODECHANGE};
845         msg.mode.res = RES_WINDOW;
846         SetWindowResolution(sizes.bufferSize.Width(), sizes.bufferSize.Height());
847         // FIXME
848         dynamic_cast<CWinEventsWayland&>(*m_winEvents).MessagePush(&msg);
849         m_waitingForApply = true;
850         CLog::LogF(LOGDEBUG, "Queued change to windowed mode size %dx%d", sizes.bufferSize.Width(), sizes.bufferSize.Height());
851       }
852       else
853       {
854         XBMC_Event msg{XBMC_VIDEORESIZE};
855         msg.resize = {sizes.bufferSize.Width(), sizes.bufferSize.Height()};
856         // FIXME
857         dynamic_cast<CWinEventsWayland&>(*m_winEvents).MessagePush(&msg);
858         m_waitingForApply = true;
859         CLog::LogF(LOGDEBUG, "Queued change to windowed buffer size %dx%d", sizes.bufferSize.Width(), sizes.bufferSize.Height());
860       }
861     }
862     else
863     {
864       // Find matching Kodi resolution member
865       RESOLUTION res{FindMatchingCustomResolution(sizes.bufferSize, refreshRate)};
866       if (res == RES_INVALID)
867       {
868         // Add new resolution if none found
869         RESOLUTION_INFO newResInfo;
870         // we just assume the compositor put us on the right output
871         UpdateDesktopResolution(newResInfo, CDisplaySettings::GetInstance().GetCurrentResolutionInfo().strOutput, sizes.bufferSize.Width(), sizes.bufferSize.Height(), refreshRate, 0);
872         CDisplaySettings::GetInstance().AddResolutionInfo(newResInfo);
873         CDisplaySettings::GetInstance().ApplyCalibrations();
874         res = static_cast<RESOLUTION> (CDisplaySettings::GetInstance().ResolutionInfoSize() - 1);
875       }
876 
877       XBMC_Event msg{XBMC_MODECHANGE};
878       msg.mode.res = res;
879       // FIXME
880       dynamic_cast<CWinEventsWayland&>(*m_winEvents).MessagePush(&msg);
881       m_waitingForApply = true;
882       CLog::LogF(LOGDEBUG, "Queued change to resolution %d surface size %dx%d scale %d state %s", res, sizes.surfaceSize.Width(), sizes.surfaceSize.Height(), scale, IShellSurface::StateToString(state).c_str());
883     }
884   }
885   else
886   {
887     // Apply directly, Kodi resolution does not change
888     ApplyNextState();
889   }
890 }
891 
FinishModeChange(RESOLUTION res)892 void CWinSystemWayland::FinishModeChange(RESOLUTION res)
893 {
894   auto& resInfo = CDisplaySettings::GetInstance().GetResolutionInfo(res);
895 
896   ApplyNextState();
897 
898   m_fRefreshRate = resInfo.fRefreshRate;
899   m_bFullScreen = resInfo.bFullScreen;
900   m_waitingForApply = false;
901 }
902 
FinishWindowResize(int,int)903 void CWinSystemWayland::FinishWindowResize(int, int)
904 {
905   ApplyNextState();
906   m_waitingForApply = false;
907 }
908 
ApplyNextState()909 void CWinSystemWayland::ApplyNextState()
910 {
911   CLog::LogF(LOGDEBUG, "Applying next state: serial %" PRIu32 " configured size %dx%d scale %d state %s", m_next.configureSerial, m_next.configuredSize.Width(), m_next.configuredSize.Height(), m_next.scale, IShellSurface::StateToString(m_next.shellSurfaceState).c_str());
912 
913   ApplyShellSurfaceState(m_next.shellSurfaceState);
914   auto updateResult = UpdateSizeVariables(m_next.configuredSize, m_next.scale, m_next.shellSurfaceState, true);
915   ApplySizeUpdate(updateResult);
916 
917   if (m_next.mustBeAcked)
918   {
919     AckConfigure(m_next.configureSerial);
920   }
921 }
922 
CalculateSizes(CSizeInt size,int scale,IShellSurface::StateBitset state,bool sizeIncludesDecoration)923 CWinSystemWayland::Sizes CWinSystemWayland::CalculateSizes(CSizeInt size, int scale, IShellSurface::StateBitset state, bool sizeIncludesDecoration)
924 {
925   Sizes result;
926 
927   // Clamp to a sensible range
928   constexpr int MIN_WIDTH{300};
929   constexpr int MIN_HEIGHT{200};
930   if (size.Width() < MIN_WIDTH)
931   {
932     CLog::LogF(LOGWARNING, "Width %d is very small, clamping to %d", size.Width(), MIN_WIDTH);
933     size.SetWidth(MIN_WIDTH);
934   }
935   if (size.Height() < MIN_HEIGHT)
936   {
937     CLog::LogF(LOGWARNING, "Height %d is very small, clamping to %d", size.Height(), MIN_HEIGHT);
938     size.SetHeight(MIN_HEIGHT);
939   }
940 
941   // Depending on whether the size has decorations included (i.e. comes from the
942   // compositor or from Kodi), we need to calculate differently
943   if (sizeIncludesDecoration)
944   {
945     result.configuredSize = size;
946     result.surfaceSize = m_windowDecorator->CalculateMainSurfaceSize(size, state);
947   }
948   else
949   {
950     result.surfaceSize = size;
951     result.configuredSize = m_windowDecorator->CalculateFullSurfaceSize(size, state);
952   }
953 
954   result.bufferSize = result.surfaceSize * scale;
955 
956   return result;
957 }
958 
959 
960 /**
961  * Calculate internal resolution from surface size and set variables
962  *
963  * \param next surface size
964  * \param scale new buffer scale
965  * \param state window state to determine whether decorations are enabled at all
966  * \param sizeIncludesDecoration if true, given size includes potential window decorations
967  * \return whether main buffer (not surface) size changed
968  */
UpdateSizeVariables(CSizeInt size,int scale,IShellSurface::StateBitset state,bool sizeIncludesDecoration)969 CWinSystemWayland::SizeUpdateInformation CWinSystemWayland::UpdateSizeVariables(CSizeInt size, int scale, IShellSurface::StateBitset state, bool sizeIncludesDecoration)
970 {
971   CLog::LogF(LOGDEBUG, "Set size %dx%d scale %d %s decorations with state %s", size.Width(), size.Height(), scale, sizeIncludesDecoration ? "including" : "excluding", IShellSurface::StateToString(state).c_str());
972 
973   auto oldSurfaceSize = m_surfaceSize;
974   auto oldBufferSize = m_bufferSize;
975   auto oldConfiguredSize = m_configuredSize;
976   auto oldBufferScale = m_scale;
977 
978   m_scale = scale;
979   auto sizes = CalculateSizes(size, scale, state, sizeIncludesDecoration);
980   m_surfaceSize = sizes.surfaceSize;
981   m_bufferSize = sizes.bufferSize;
982   m_configuredSize = sizes.configuredSize;
983 
984   SizeUpdateInformation changes{m_surfaceSize != oldSurfaceSize, m_bufferSize != oldBufferSize, m_configuredSize != oldConfiguredSize, m_scale != oldBufferScale};
985 
986   if (changes.surfaceSizeChanged)
987   {
988     CLog::LogF(LOGINFO, "Surface size changed: %dx%d -> %dx%d", oldSurfaceSize.Width(), oldSurfaceSize.Height(), m_surfaceSize.Width(), m_surfaceSize.Height());
989   }
990   if (changes.bufferSizeChanged)
991   {
992     CLog::LogF(LOGINFO, "Buffer size changed: %dx%d -> %dx%d", oldBufferSize.Width(), oldBufferSize.Height(), m_bufferSize.Width(), m_bufferSize.Height());
993   }
994   if (changes.configuredSizeChanged)
995   {
996     CLog::LogF(LOGINFO, "Configured size changed: %dx%d -> %dx%d", oldConfiguredSize.Width(), oldConfiguredSize.Height(), m_configuredSize.Width(), m_configuredSize.Height());
997   }
998   if (changes.bufferScaleChanged)
999   {
1000     CLog::LogF(LOGINFO, "Buffer scale changed: %d -> %d", oldBufferScale, m_scale);
1001   }
1002 
1003   return changes;
1004 }
1005 
UserFriendlyOutputName(std::shared_ptr<COutput> const & output)1006 std::string CWinSystemWayland::UserFriendlyOutputName(std::shared_ptr<COutput> const& output)
1007 {
1008   std::vector<std::string> parts;
1009   if (!output->GetMake().empty())
1010   {
1011     parts.emplace_back(output->GetMake());
1012   }
1013   if (!output->GetModel().empty())
1014   {
1015     parts.emplace_back(output->GetModel());
1016   }
1017   if (parts.empty())
1018   {
1019     // Fallback to "unknown" if no name received from compositor
1020     parts.emplace_back(g_localizeStrings.Get(13205));
1021   }
1022 
1023   // Add position
1024   auto pos = output->GetPosition();
1025   if (pos.x != 0 || pos.y != 0)
1026   {
1027     parts.emplace_back(StringUtils::Format("@{}x{}", pos.x, pos.y));
1028   }
1029 
1030   return StringUtils::Join(parts, " ");
1031 }
1032 
Minimize()1033 bool CWinSystemWayland::Minimize()
1034 {
1035   m_shellSurface->SetMinimized();
1036   return true;
1037 }
1038 
HasCursor()1039 bool CWinSystemWayland::HasCursor()
1040 {
1041   CSingleLock lock(m_seatsMutex);
1042   return std::any_of(m_seats.cbegin(), m_seats.cend(),
1043                      [](decltype(m_seats)::value_type const& entry)
1044                      {
1045                        return entry.second.HasPointerCapability();
1046                      });
1047 }
1048 
ShowOSMouse(bool show)1049 void CWinSystemWayland::ShowOSMouse(bool show)
1050 {
1051   m_osCursorVisible = show;
1052 }
1053 
LoadDefaultCursor()1054 void CWinSystemWayland::LoadDefaultCursor()
1055 {
1056   if (!m_cursorSurface)
1057   {
1058     // Load default cursor theme and default cursor
1059     // Size of 24px is what most themes seem to have
1060     m_cursorTheme = wayland::cursor_theme_t("", 24, m_shm);
1061     wayland::cursor_t cursor;
1062     try
1063     {
1064       cursor = CCursorUtil::LoadFromTheme(m_cursorTheme, "default");
1065     }
1066     catch (std::exception const& e)
1067     {
1068       CLog::Log(LOGWARNING, "Could not load default cursor from theme, continuing without OS cursor");
1069     }
1070     // Just use the first image, do not handle animation
1071     m_cursorImage = cursor.image(0);
1072     m_cursorBuffer = m_cursorImage.get_buffer();
1073     m_cursorSurface = m_compositor.create_surface();
1074   }
1075   // Attach buffer to a surface - it seems that the compositor may change
1076   // the cursor surface when the pointer leaves our surface, so we reattach the
1077   // buffer each time
1078   m_cursorSurface.attach(m_cursorBuffer, 0, 0);
1079   m_cursorSurface.damage(0, 0, m_cursorImage.width(), m_cursorImage.height());
1080   m_cursorSurface.commit();
1081 }
1082 
Register(IDispResource * resource)1083 void CWinSystemWayland::Register(IDispResource* resource)
1084 {
1085   CSingleLock lock(m_dispResourcesMutex);
1086   m_dispResources.emplace(resource);
1087 }
1088 
Unregister(IDispResource * resource)1089 void CWinSystemWayland::Unregister(IDispResource* resource)
1090 {
1091   CSingleLock lock(m_dispResourcesMutex);
1092   m_dispResources.erase(resource);
1093 }
1094 
OnSeatAdded(std::uint32_t name,wayland::proxy_t && proxy)1095 void CWinSystemWayland::OnSeatAdded(std::uint32_t name, wayland::proxy_t&& proxy)
1096 {
1097   CSingleLock lock(m_seatsMutex);
1098 
1099   wayland::seat_t seat(proxy);
1100   auto newSeatEmplace = m_seats.emplace(std::piecewise_construct,
1101                                            std::forward_as_tuple(name),
1102                                                  std::forward_as_tuple(name, seat, *m_connection));
1103 
1104   auto& seatInst = newSeatEmplace.first->second;
1105   m_seatInputProcessing->AddSeat(&seatInst);
1106   m_windowDecorator->AddSeat(&seatInst);
1107 }
1108 
OnSeatRemoved(std::uint32_t name)1109 void CWinSystemWayland::OnSeatRemoved(std::uint32_t name)
1110 {
1111   CSingleLock lock(m_seatsMutex);
1112 
1113   auto seatI = m_seats.find(name);
1114   if (seatI != m_seats.end())
1115   {
1116     m_seatInputProcessing->RemoveSeat(&seatI->second);
1117     m_windowDecorator->RemoveSeat(&seatI->second);
1118     m_seats.erase(name);
1119   }
1120 }
1121 
OnOutputAdded(std::uint32_t name,wayland::proxy_t && proxy)1122 void CWinSystemWayland::OnOutputAdded(std::uint32_t name, wayland::proxy_t&& proxy)
1123 {
1124   wayland::output_t output(proxy);
1125   // This is not accessed from multiple threads
1126   m_outputsInPreparation.emplace(name, std::make_shared<COutput>(name, output, std::bind(&CWinSystemWayland::OnOutputDone, this, name)));
1127 }
1128 
OnOutputDone(std::uint32_t name)1129 void CWinSystemWayland::OnOutputDone(std::uint32_t name)
1130 {
1131   auto it = m_outputsInPreparation.find(name);
1132   if (it != m_outputsInPreparation.end())
1133   {
1134     // This output was added for the first time - done is also sent when
1135     // output parameters change later
1136 
1137     {
1138       CSingleLock lock(m_outputsMutex);
1139       // Move from m_outputsInPreparation to m_outputs
1140       m_outputs.emplace(std::move(*it));
1141       m_outputsInPreparation.erase(it);
1142     }
1143 
1144     m_protocol.SendOutMessage(WinSystemWaylandProtocol::OUTPUT_HOTPLUG);
1145   }
1146 
1147   UpdateBufferScale();
1148 }
1149 
OnOutputRemoved(std::uint32_t name)1150 void CWinSystemWayland::OnOutputRemoved(std::uint32_t name)
1151 {
1152   m_outputsInPreparation.erase(name);
1153 
1154   CSingleLock lock(m_outputsMutex);
1155   if (m_outputs.erase(name) != 0)
1156   {
1157     // Theoretically, the compositor should automatically put us on another
1158     // (visible and connected) output if the output we were on is lost,
1159     // so there is nothing in particular to do here
1160   }
1161 }
1162 
SendFocusChange(bool focus)1163 void CWinSystemWayland::SendFocusChange(bool focus)
1164 {
1165   g_application.m_AppFocused = focus;
1166   CSingleLock lock(m_dispResourcesMutex);
1167   for (auto dispResource : m_dispResources)
1168   {
1169     dispResource->OnAppFocusChange(focus);
1170   }
1171 }
1172 
OnEnter(InputType type)1173 void CWinSystemWayland::OnEnter(InputType type)
1174 {
1175   // Couple to keyboard focus
1176   if (type == InputType::KEYBOARD)
1177   {
1178     SendFocusChange(true);
1179   }
1180   if (type == InputType::POINTER)
1181   {
1182     CServiceBroker::GetInputManager().SetMouseActive(true);
1183   }
1184 }
1185 
OnLeave(InputType type)1186 void CWinSystemWayland::OnLeave(InputType type)
1187 {
1188   // Couple to keyboard focus
1189   if (type == InputType::KEYBOARD)
1190   {
1191     SendFocusChange(false);
1192   }
1193   if (type == InputType::POINTER)
1194   {
1195     CServiceBroker::GetInputManager().SetMouseActive(false);
1196   }
1197 }
1198 
OnEvent(InputType type,XBMC_Event & event)1199 void CWinSystemWayland::OnEvent(InputType type, XBMC_Event& event)
1200 {
1201   // FIXME
1202   dynamic_cast<CWinEventsWayland&>(*m_winEvents).MessagePush(&event);
1203 }
1204 
OnSetCursor(std::uint32_t seatGlobalName,std::uint32_t serial)1205 void CWinSystemWayland::OnSetCursor(std::uint32_t seatGlobalName, std::uint32_t serial)
1206 {
1207   auto seatI = m_seats.find(seatGlobalName);
1208   if (seatI == m_seats.end())
1209   {
1210     return;
1211   }
1212 
1213   if (m_osCursorVisible)
1214   {
1215     LoadDefaultCursor();
1216     if (m_cursorSurface) // Cursor loading could have failed
1217     {
1218       seatI->second.SetCursor(serial, m_cursorSurface, m_cursorImage.hotspot_x(), m_cursorImage.hotspot_y());
1219     }
1220   }
1221   else
1222   {
1223     seatI->second.SetCursor(serial, wayland::surface_t{}, 0, 0);
1224   }
1225 }
1226 
UpdateBufferScale()1227 void CWinSystemWayland::UpdateBufferScale()
1228 {
1229   // Adjust our surface size to the output with the biggest scale in order
1230   // to get the best quality
1231   auto const maxBufferScaleIt = std::max_element(m_surfaceOutputs.cbegin(), m_surfaceOutputs.cend(), OutputScaleComparer());
1232   if (maxBufferScaleIt != m_surfaceOutputs.cend())
1233   {
1234     WinSystemWaylandProtocol::MsgBufferScale msg{(*maxBufferScaleIt)->GetScale()};
1235     m_protocol.SendOutMessage(WinSystemWaylandProtocol::BUFFER_SCALE, &msg, sizeof(msg));
1236   }
1237 }
1238 
ApplyBufferScale()1239 void CWinSystemWayland::ApplyBufferScale()
1240 {
1241   CLog::LogF(LOGINFO, "Setting Wayland buffer scale to %d", m_scale);
1242   m_surface.set_buffer_scale(m_scale);
1243   m_windowDecorator->SetState(m_configuredSize, m_scale, m_shellSurfaceState);
1244   m_seatInputProcessing->SetCoordinateScale(m_scale);
1245 }
1246 
UpdateTouchDpi()1247 void CWinSystemWayland::UpdateTouchDpi()
1248 {
1249   // If we have multiple outputs with wildly different DPI, this is really just
1250   // guesswork to get a halfway reasonable value. min/max would probably also be OK.
1251   float dpiSum = std::accumulate(m_surfaceOutputs.cbegin(), m_surfaceOutputs.cend(),  0.0f,
1252                                  [](float acc, std::shared_ptr<COutput> const& output)
1253                                  {
1254                                    return acc + output->GetCurrentDpi();
1255                                  });
1256   float dpi = dpiSum / m_surfaceOutputs.size();
1257   CLog::LogF(LOGDEBUG, "Computed average dpi of %.3f for touch handler", dpi);
1258   CGenericTouchInputHandler::GetInstance().SetScreenDPI(dpi);
1259 }
1260 
SurfaceSubmission(timespec const & submissionTime,wayland::presentation_feedback_t const & feedback)1261 CWinSystemWayland::SurfaceSubmission::SurfaceSubmission(timespec const& submissionTime, wayland::presentation_feedback_t const& feedback)
1262 : submissionTime{submissionTime}, feedback{feedback}
1263 {
1264 }
1265 
GetPresentationClockTime()1266 timespec CWinSystemWayland::GetPresentationClockTime()
1267 {
1268   timespec time;
1269   if (clock_gettime(m_presentationClock, &time) != 0)
1270   {
1271     throw std::system_error(errno, std::generic_category(), "Error getting time from Wayland presentation clock with clock_gettime");
1272   }
1273   return time;
1274 }
1275 
PrepareFramePresentation()1276 void CWinSystemWayland::PrepareFramePresentation()
1277 {
1278   // Continuously measure display latency (i.e. time between when the frame was rendered
1279   // and when it becomes visible to the user) to correct AV sync
1280   if (m_presentation)
1281   {
1282     auto tStart = GetPresentationClockTime();
1283     // wp_presentation_feedback creation is coupled to the surface's commit().
1284     // eglSwapBuffers() (which will be called after this) will call commit().
1285     // This creates a new Wayland protocol object in the main thread, but this
1286     // will not result in a race since the corresponding events are never sent
1287     // before commit() on the surface, which only occurs afterwards.
1288     auto feedback = m_presentation.feedback(m_surface);
1289     // Save feedback objects in list so they don't get destroyed upon exit of this function
1290     // Hand iterator to lambdas so they do not hold a (then circular) reference
1291     // to the actual object
1292     decltype(m_surfaceSubmissions)::iterator iter;
1293     {
1294       CSingleLock lock(m_surfaceSubmissionsMutex);
1295       iter = m_surfaceSubmissions.emplace(m_surfaceSubmissions.end(), tStart, feedback);
1296     }
1297 
1298     feedback.on_sync_output() = [this](const wayland::output_t& wloutput) {
1299       m_syncOutputID = wloutput.get_id();
1300       auto output = FindOutputByWaylandOutput(wloutput);
1301       if (output)
1302       {
1303         m_syncOutputRefreshRate = output->GetCurrentMode().GetRefreshInHz();
1304       }
1305       else
1306       {
1307         CLog::Log(LOGWARNING, "Could not find Wayland output that is supposedly the sync output");
1308       }
1309     };
1310     feedback.on_presented() = [this, iter](std::uint32_t tvSecHi, std::uint32_t tvSecLo,
1311                                            std::uint32_t tvNsec, std::uint32_t refresh,
1312                                            std::uint32_t seqHi, std::uint32_t seqLo,
1313                                            const wayland::presentation_feedback_kind& flags) {
1314       timespec tv = { .tv_sec = static_cast<std::time_t> ((static_cast<std::uint64_t>(tvSecHi) << 32) + tvSecLo), .tv_nsec = static_cast<long>(tvNsec) };
1315       std::int64_t latency{KODI::LINUX::TimespecDifference(iter->submissionTime, tv)};
1316       std::uint64_t msc{(static_cast<std::uint64_t>(seqHi) << 32) + seqLo};
1317       m_presentationFeedbackHandlers.Invoke(tv, refresh, m_syncOutputID, m_syncOutputRefreshRate, msc);
1318 
1319       iter->latency = latency / 1e9f; // nanoseconds to seconds
1320       float adjust{};
1321       {
1322         CSingleLock lock(m_surfaceSubmissionsMutex);
1323         if (m_surfaceSubmissions.size() > LATENCY_MOVING_AVERAGE_SIZE)
1324         {
1325           adjust = - m_surfaceSubmissions.front().latency / LATENCY_MOVING_AVERAGE_SIZE;
1326           m_surfaceSubmissions.pop_front();
1327         }
1328       }
1329       m_latencyMovingAverage = m_latencyMovingAverage + iter->latency / LATENCY_MOVING_AVERAGE_SIZE + adjust;
1330 
1331       CLog::Log(LOGDEBUG, LOGAVTIMING, "Presentation feedback: %" PRIi64 " ns -> moving average %f s", latency, static_cast<double> (m_latencyMovingAverage));
1332     };
1333     feedback.on_discarded() = [this,iter]()
1334     {
1335       CLog::Log(LOGDEBUG, "Presentation: Frame was discarded by compositor");
1336       CSingleLock lock(m_surfaceSubmissionsMutex);
1337       m_surfaceSubmissions.erase(iter);
1338     };
1339   }
1340 
1341   // Now wait for the frame callback that tells us that it is a good time to start drawing
1342   //
1343   // To sum up, we:
1344   // 1. wait until a frame() drawing hint from the compositor arrives,
1345   // 2. request a new frame() hint for the next presentation
1346   // 2. then commit the backbuffer to the surface and immediately
1347   //    return, i.e. drawing can start again
1348   // This means that rendering is optimized for maximum time available for
1349   // our repaint and reliable timing rather than latency. With weston, latency
1350   // will usually be on the order of two frames plus a few milliseconds.
1351   // The frame timings become irregular though when nothing is rendered because
1352   // kodi then sleeps for a fixed time without swapping buffers. This makes us
1353   // immediately attach the next buffer because the frame callback has already arrived when
1354   // this function is called and step 1. above is skipped. As we render with full
1355   // FPS during video playback anyway and the timing is otherwise not relevant,
1356   // this should not be a problem.
1357   if (m_frameCallback)
1358   {
1359     // If the window is e.g. minimized, chances are that we will *never* get frame
1360     // callbacks from the compositor for optimization reasons.
1361     // Still, the app should remain functional, which means that we can't
1362     // just block forever here - if the render thread is blocked, Kodi will not
1363     // function normally. It would also be impossible to close the application
1364     // while it is minimized (since the wait needs to be interrupted for that).
1365     // -> Use Wait with timeout here so we can maintain a reasonable frame rate
1366     //    even when the window is not visible and we do not get any frame callbacks.
1367     if (m_frameCallbackEvent.WaitMSec(50))
1368     {
1369       // Only reset frame callback object a callback was received so a
1370       // new one is not requested continuously
1371       m_frameCallback = {};
1372       m_frameCallbackEvent.Reset();
1373     }
1374   }
1375 
1376   if (!m_frameCallback)
1377   {
1378     // Get frame callback event for checking in the next call to this function
1379     m_frameCallback = m_surface.frame();
1380     m_frameCallback.on_done() = [this](std::uint32_t)
1381     {
1382       m_frameCallbackEvent.Set();
1383     };
1384   }
1385 }
1386 
FinishFramePresentation()1387 void CWinSystemWayland::FinishFramePresentation()
1388 {
1389   ProcessMessages();
1390 
1391   m_frameStartTime = CurrentHostCounter();
1392 }
1393 
GetFrameLatencyAdjustment()1394 float CWinSystemWayland::GetFrameLatencyAdjustment()
1395 {
1396   std::int64_t now{CurrentHostCounter()};
1397   return static_cast<float> (now - m_frameStartTime) / CurrentHostFrequency() * 1000.0f;
1398 }
1399 
GetDisplayLatency()1400 float CWinSystemWayland::GetDisplayLatency()
1401 {
1402   if (m_presentation)
1403   {
1404     return m_latencyMovingAverage * 1000.0f;
1405   }
1406   else
1407   {
1408     return CWinSystemBase::GetDisplayLatency();
1409   }
1410 }
1411 
GetSyncOutputRefreshRate()1412 float CWinSystemWayland::GetSyncOutputRefreshRate()
1413 {
1414   return m_syncOutputRefreshRate;
1415 }
1416 
RegisterOnPresentationFeedback(const PresentationFeedbackHandler & handler)1417 KODI::CSignalRegistration CWinSystemWayland::RegisterOnPresentationFeedback(
1418     const PresentationFeedbackHandler& handler)
1419 {
1420   return m_presentationFeedbackHandlers.Register(handler);
1421 }
1422 
GetVideoSync(void * clock)1423 std::unique_ptr<CVideoSync> CWinSystemWayland::GetVideoSync(void* clock)
1424 {
1425   if (m_surface && m_presentation)
1426   {
1427     CLog::LogF(LOGINFO, "Using presentation protocol for video sync");
1428     return std::unique_ptr<CVideoSync>(new CVideoSyncWpPresentation(clock, *this));
1429   }
1430   else
1431   {
1432     CLog::LogF(LOGINFO, "No supported method for video sync found");
1433     return nullptr;
1434   }
1435 }
1436 
GetOSScreenSaverImpl()1437 std::unique_ptr<IOSScreenSaver> CWinSystemWayland::GetOSScreenSaverImpl()
1438 {
1439   if (m_surface)
1440   {
1441     std::unique_ptr<IOSScreenSaver> ptr;
1442     ptr.reset(COSScreenSaverIdleInhibitUnstableV1::TryCreate(*m_connection, m_surface));
1443     if (ptr)
1444     {
1445       CLog::LogF(LOGINFO, "Using idle-inhibit-unstable-v1 protocol for screen saver inhibition");
1446       return ptr;
1447     }
1448   }
1449 
1450 #if defined(HAS_DBUS)
1451   if (KODI::WINDOWING::LINUX::COSScreenSaverFreedesktop::IsAvailable())
1452   {
1453     CLog::LogF(LOGINFO, "Using freedesktop.org DBus interface for screen saver inhibition");
1454     return std::unique_ptr<IOSScreenSaver>(new KODI::WINDOWING::LINUX::COSScreenSaverFreedesktop);
1455   }
1456 #endif
1457 
1458   CLog::LogF(LOGINFO, "No supported method for screen saver inhibition found");
1459   return std::unique_ptr<IOSScreenSaver>(new CDummyOSScreenSaver);
1460 }
1461 
GetClipboardText()1462 std::string CWinSystemWayland::GetClipboardText()
1463 {
1464   CSingleLock lock(m_seatsMutex);
1465   // Get text of first seat with non-empty selection
1466   // Actually, the value of the seat that received the Ctrl+V keypress should be used,
1467   // but this would need a workaround or proper multi-seat support in Kodi - it's
1468   // probably just not that relevant in practice
1469   for (auto const& seat : m_seats)
1470   {
1471     auto text = seat.second.GetSelectionText();
1472     if (text != "")
1473     {
1474       return text;
1475     }
1476   }
1477   return "";
1478 }
1479 
OnWindowMove(const wayland::seat_t & seat,std::uint32_t serial)1480 void CWinSystemWayland::OnWindowMove(const wayland::seat_t& seat, std::uint32_t serial)
1481 {
1482   m_shellSurface->StartMove(seat, serial);
1483 }
1484 
OnWindowResize(const wayland::seat_t & seat,std::uint32_t serial,wayland::shell_surface_resize edge)1485 void CWinSystemWayland::OnWindowResize(const wayland::seat_t& seat, std::uint32_t serial, wayland::shell_surface_resize edge)
1486 {
1487   m_shellSurface->StartResize(seat, serial, edge);
1488 }
1489 
OnWindowShowContextMenu(const wayland::seat_t & seat,std::uint32_t serial,CPointInt position)1490 void CWinSystemWayland::OnWindowShowContextMenu(const wayland::seat_t& seat, std::uint32_t serial, CPointInt position)
1491 {
1492   m_shellSurface->ShowShellContextMenu(seat, serial, position);
1493 }
1494 
OnWindowClose()1495 void CWinSystemWayland::OnWindowClose()
1496 {
1497   KODI::MESSAGING::CApplicationMessenger::GetInstance().PostMsg(TMSG_QUIT);
1498 }
1499 
OnWindowMinimize()1500 void CWinSystemWayland::OnWindowMinimize()
1501 {
1502   m_shellSurface->SetMinimized();
1503 }
1504 
OnWindowMaximize()1505 void CWinSystemWayland::OnWindowMaximize()
1506 {
1507   if (m_shellSurfaceState.test(IShellSurface::STATE_MAXIMIZED))
1508   {
1509     m_shellSurface->UnsetMaximized();
1510   }
1511   else
1512   {
1513     m_shellSurface->SetMaximized();
1514   }
1515 }
1516 
OnClose()1517 void CWinSystemWayland::OnClose()
1518 {
1519   KODI::MESSAGING::CApplicationMessenger::GetInstance().PostMsg(TMSG_QUIT);
1520 }
1521 
MessagePump()1522 bool CWinSystemWayland::MessagePump()
1523 {
1524   return m_winEvents->MessagePump();
1525 }
1526