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