MythTV master
mythdisplay.cpp
Go to the documentation of this file.
1// Std
2#include <algorithm>
3
4//Qt
5#include <QtGlobal>
6#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
7#include <QtSystemDetection>
8#endif
9#include <QTimer>
10#include <QThread>
11#include <QApplication>
12#include <QElapsedTimer>
13#include <QWindow>
14
15#include "libmythbase/mythconfig.h"
16
17#if CONFIG_QTWEBENGINE
18#include <QQuickWindow>
19#endif
20
21// MythTV
22#include "libmythbase/compat.h"
25#include "mythuihelper.h"
26#include "mythdisplay.h"
27#include "opengl/mythegl.h"
28#include "mythmainwindow.h"
29
30#if CONFIG_QTDBUS
32#endif
33#if CONFIG_WAYLANDEXTRAS
35#endif
36#ifdef Q_OS_ANDROID
38#endif
39#ifdef Q_OS_DARWIN
41#endif
42#if CONFIG_X11
46#endif
47#if CONFIG_DRM
50#endif
51#ifdef Q_OS_WINDOWS
53#endif
54#if CONFIG_MMAL
56#endif
57
58#define LOC QString("Display: ")
59
94MythDisplay* MythDisplay::Create([[maybe_unused]] MythMainWindow* MainWindow)
95{
96 MythDisplay* result = nullptr;
97#if CONFIG_X11
99 result = new MythDisplayX11();
100#endif
101#if CONFIG_QTDBUS
102 // Disabled for now as org.gnome.Mutter.DisplayConfig.ApplyConfiguration does
103 // not seem to be actually implemented by anyone.
104#if CONFIG_WAYLANDEXTRAS
105 //if (MythWaylandDevice::IsAvailable())
106#endif
107 //{
108 // if (!result)
109 // result = MythDisplayMutter::Create();
110 //}
111#endif
112#if CONFIG_DRM
113 if (!result)
114 {
115 result = new MythDisplayDRM(MainWindow);
116 // On the Pi, use MythDisplayRPI if mode switching is not available via DRM
117#if CONFIG_MMAL
118 if (!result->VideoModesAvailable())
119 {
120 delete result;
121 result = nullptr;
122 }
123#endif
124 }
125#endif
126#if CONFIG_MMAL
127 if (!result)
128 result = new MythDisplayRPI();
129#endif
130#ifdef Q_OS_DARWIN
131 if (!result)
132 result = new MythDisplayOSX();
133#endif
134#ifdef Q_OS_ANDROID
135 if (!result)
136 result = new MythDisplayAndroid();
137#endif
138#ifdef Q_OS_WINDOWS
139 if (!result)
140 result = new MythDisplayWindows();
141#endif
142 if (!result)
143 result = new MythDisplay();
144 return result;
145}
146
148{
149 QStringList result;
150 bool spanall = false;
151 int screencount = MythDisplay::GetScreenCount();
152 if (MythDisplay::SpanAllScreens() && screencount > 1)
153 {
154 spanall = true;
155 result.append(tr("Spanning %1 screens").arg(screencount));
156 result.append(tr("Total bounds") + QString("\t: %1x%2")
157 .arg(GetScreenBounds().width()).arg(GetScreenBounds().height()));
158 result.append("");
159 }
160
161 if (m_hdrState)
162 {
163 auto types = m_hdrState->m_supportedTypes;
164 auto hdr = m_hdrState->TypesToString();
165 result.append(tr("Supported HDR formats\t: %1").arg(hdr.join(",")));
166 if (types && !m_hdrState->IsControllable())
167 result.append(tr("HDR mode switching is not available"));
168 if (auto brightness = m_hdrState->GetMaxLuminance(); brightness > 1.0)
169 result.append(tr("Max display brightness\t: %1 nits").arg(static_cast<int>(brightness)));
170 }
171
172 if (m_vrrState)
173 {
174 result.append(tr("Variable refresh rate '%1': %2 %3")
175 .arg(m_vrrState->TypeToString(),
176 m_vrrState->Enabled() ? tr("Enabled") : tr("Disabled"),
177 m_vrrState->RangeDescription()));
178 }
179
180 auto * current = GetCurrentScreen();
181 const auto screens = QGuiApplication::screens();
182 bool first = true;
183 for (auto *screen : std::as_const(screens))
184 {
185 if (!first)
186 result.append("");
187 first = false;
188 auto id = QString("(%1)").arg(screen->manufacturer());
189 if (screen == current && !spanall)
190 result.append(tr("Current screen\t: %1 %2").arg(screen->name(), id));
191 else
192 result.append(tr("Screen\t\t: %1 %2").arg(screen->name(), id));
193 result.append(tr("Size") + QString("\t\t: %1mmx%2mm")
194 .arg(screen->physicalSize().width()).arg(screen->physicalSize().height()));
195 if (screen == current)
196 {
197 QString source;
198 auto aspect = GetAspectRatio(source);
199 result.append(tr("Aspect ratio") + QString("\t: %1 (%2)")
200 .arg(aspect, 0, 'f', 3).arg(source));
201 if (!spanall)
202 {
203 result.append(tr("Current mode") + QString("\t: %1x%2@%3Hz")
204 .arg(GetResolution().width()).arg(GetResolution().height())
205 .arg(GetRefreshRate(), 0, 'f', 2));
206 const auto & modes = GetVideoModes();
207 if (!modes.empty())
208 {
209 result.append(tr("Available modes:"));
210 for (auto it = modes.crbegin(); it != modes.crend(); ++it)
211 result.append(" " + it->ToString());
212 }
213 }
214 }
215 }
216
217 return result;
218}
219
221 : m_screen(GetDesiredScreen())
222{
223 DebugScreen(m_screen, "Using");
224 if (m_screen)
225 {
226 connect(m_screen, &QScreen::geometryChanged, this, &MythDisplay::GeometryChanged);
227 connect(m_screen, &QScreen::physicalDotsPerInchChanged, this, &MythDisplay::PhysicalDPIChanged);
228 }
229
230 auto *guiapp = qobject_cast<QGuiApplication *>(QCoreApplication::instance());
231 if (guiapp == nullptr)
232 return;
233
234 connect(guiapp, &QGuiApplication::screenRemoved, this, &MythDisplay::ScreenRemoved);
235 connect(guiapp, &QGuiApplication::screenAdded, this, &MythDisplay::ScreenAdded);
236 connect(guiapp, &QGuiApplication::primaryScreenChanged, this, &MythDisplay::PrimaryScreenChanged);
237}
238
240{
241 LOG(VB_GENERAL, LOG_INFO, LOC + "Deleting");
242}
243
257void MythDisplay::SetWidget(QWidget *MainWindow)
258{
259 QWidget* oldwidget = m_widget;
260 m_widget = MainWindow;
261 if (!m_modeComplete)
263
264 QWindow* oldwindow = m_window;
265 if (m_widget)
266 m_window = m_widget->windowHandle();
267 else
268 m_window = nullptr;
269
270 if (m_widget && (m_widget != oldwidget))
271 LOG(VB_GENERAL, LOG_INFO, LOC + "Have main widget");
272
273 if (m_window && (m_window != oldwindow))
274 {
275 LOG(VB_GENERAL, LOG_INFO, LOC + "Have main window");
276
277 connect(m_window, &QWindow::screenChanged, this, &MythDisplay::ScreenChanged, Qt::UniqueConnection);
278 QScreen *desired = GetDesiredScreen();
279 // If we have changed the video mode for the old screen then reset
280 // it to the default/desktop mode
281 if (oldwindow)
283 // Ensure we completely re-initialise when the new screen is set
284 m_initialised = false;
285 if (desired != m_screen)
286 DebugScreen(desired, "Moving to");
287 m_window->setScreen(desired);
288 // WaitForNewScreen doesn't work as intended. It successfully filters
289 // out unwanted screenChanged signals after moving screens - but always
290 //times out. This just delays startup by 500ms - so ignore on startup as it isn't needed.
293 m_firstScreenChange = false;
295 return;
296 }
297}
298
300{
301 return QGuiApplication::screens().size();
302}
303
305{
306 if (m_physicalSize.isEmpty() || m_resolution.isEmpty())
307 return 1.0;
308
309 // HD-Ready or better displays always have square pixels
310 if (m_resolution.height() >= 720)
311 return 1.0;
312
313 return (m_physicalSize.width() / static_cast<double>(m_resolution.width())) /
314 (m_physicalSize.height() / static_cast<double>(m_resolution.height()));
315}
316
318{
319 return m_guiMode.Resolution();
320}
321
323{
324 return m_screenBounds;
325}
326
340{
341 return m_screen;
342}
343
345{
346 return m_window;
347}
348
350{
351 QScreen* newscreen = nullptr;
352
353 // If geometry is overriden at the command line level, try and determine
354 // which screen that applies to (if any).
355 // N.B. So many potential issues here e.g. should the geometry override be
356 // ignored after first use? (as it will continue to override the screen
357 // regardless of changes to screen preference).
359 {
360 // this matches the check in MythMainWindow
361 bool windowed = GetMythDB()->GetBoolSetting("RunFrontendInWindow", false) &&
363 QRect override = MythMainWindow::GetGeometryOverride();
364 // When windowed, we use topleft as a best guess as to which screen we belong in.
365 // When fullscreen, Qt appears to use the reverse - though this may be
366 // the window manager rather than Qt. So could be wrong.
367 QPoint point = windowed ? override.topLeft() : override.bottomRight();
368 QList screens = QGuiApplication::screens();
369 for (QScreen *screen : std::as_const(screens))
370 {
371 if (screen->geometry().contains(point))
372 {
373 newscreen = screen;
374 LOG(VB_GENERAL, LOG_INFO, LOC + QString(
375 "Geometry override places window in screen '%1'").arg(newscreen->name()));
376 break;
377 }
378 }
379 }
380
381 // If spanning all screens, then always use the primary display
382 if (!newscreen && MythDisplay::SpanAllScreens())
383 {
384 LOG(VB_GENERAL, LOG_INFO, LOC + "Using primary screen for multiscreen");
385 newscreen = QGuiApplication::primaryScreen();
386 }
387
388 QString name = gCoreContext->GetSetting("XineramaScreen", nullptr);
389 // Lookup by name
390 if (!newscreen)
391 {
392 QList screens = QGuiApplication::screens();
393 for (QScreen *screen : std::as_const(screens))
394 {
395 if (!name.isEmpty() && name == screen->name())
396 {
397 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Found screen '%1'").arg(name));
398 newscreen = screen;
399 }
400 }
401 }
402
403 // No name match. These were previously numbers.
404 if (!newscreen)
405 {
406 bool ok = false;
407 int screen_num = name.toInt(&ok);
408 QList<QScreen *>screens = QGuiApplication::screens();
409 if (ok && (screen_num >= 0) && (screen_num < screens.size()))
410 {
411 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Found screen number %1 (%2)")
412 .arg(name, screens[screen_num]->name()));
413 newscreen = screens[screen_num];
414 }
415 }
416
417 // For anything else, return the primary screen.
418 if (!newscreen)
419 {
420 QScreen *primary = QGuiApplication::primaryScreen();
421 if (name.isEmpty() && primary)
422 {
423 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Defaulting to primary screen (%1)")
424 .arg(primary->name()));
425 }
426 else if (name != "-1" && primary)
427 {
428 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Screen '%1' not found, defaulting to primary screen (%2)")
429 .arg(name, primary->name()));
430 }
431 newscreen = primary;
432 }
433
434 return newscreen;
435}
436
439void MythDisplay::ScreenChanged(QScreen *qScreen)
440{
441 if (qScreen == m_screen)
442 return;
443 if (m_screen)
444 disconnect(m_screen, nullptr, this, nullptr);
445 DebugScreen(qScreen, "Changed to");
446 m_screen = qScreen;
447 connect(m_screen, &QScreen::geometryChanged, this, &MythDisplay::GeometryChanged);
448 connect(m_screen, &QScreen::physicalDotsPerInchChanged, this, &MythDisplay::PhysicalDPIChanged);
449 Initialise();
450 emit DisplayChanged();
451}
452
454{
455 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Qt screen pixel ratio changed to %1")
456 .arg(DPI, 2, 'f', 2, '0'));
457 emit DisplayChanged();
458}
459
461{
462 DebugScreen(qScreen, "New primary");
463}
464
465void MythDisplay::ScreenAdded(QScreen* qScreen)
466{
467 DebugScreen(qScreen, "New");
468 emit ScreenCountChanged(QGuiApplication::screens().size());
469}
470
471void MythDisplay::ScreenRemoved(QScreen* qScreen)
472{
473 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Screen '%1' removed").arg(qScreen->name()));
474 emit ScreenCountChanged(QGuiApplication::screens().size());
475}
476
477void MythDisplay::GeometryChanged(const QRect Geo)
478{
479 LOG(VB_GENERAL, LOG_INFO, LOC + QString("New screen geometry: %1x%2+%3+%4")
480 .arg(Geo.width()).arg(Geo.height()).arg(Geo.left()).arg(Geo.top()));
481}
482
490{
491 // Certain platform implementations do not have a window to access at startup
492 // and hence use this implementation. Flag the status as incomplete to ensure
493 // we try to retrieve the full details at the first opportunity.
494 m_modeComplete = false;
495 m_edid = MythEDID();
496 QScreen *screen = GetCurrentScreen();
497 if (!screen)
498 {
499 m_refreshRate = 60.0;
500 m_physicalSize = QSize(0, 0);
501 m_resolution = QSize(1920, 1080);
502 return;
503 }
504 m_refreshRate = screen->refreshRate();
505 m_resolution = screen->size();
506 m_physicalSize = QSize(static_cast<int>(screen->physicalSize().width()),
507 static_cast<int>(screen->physicalSize().height()));
508}
509
512{
513 return gCoreContext->GetSetting("XineramaScreen", nullptr) == "-1";
514}
515
516QString MythDisplay::GetExtraScreenInfo(QScreen *qScreen)
517{
518 QString mfg = qScreen->manufacturer();
519 if (mfg.isEmpty())
520 mfg = "Unknown";
521 QString model = qScreen->model();
522 if (model.isEmpty())
523 model = "Unknown";
524 return QString("(Make: %1 Model: %2)").arg(mfg, model);
525}
526
527void MythDisplay::DebugScreen(QScreen *qScreen, const QString &Message)
528{
529 if (!qScreen)
530 return;
531
532 auto geom = qScreen->geometry();
533 LOG(VB_GENERAL, LOG_INFO, LOC + QString("%1 screen '%2' %3")
534 .arg(Message, qScreen->name(), GetExtraScreenInfo(qScreen)));
535 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Qt screen pixel ratio: %1")
536 .arg(qScreen->devicePixelRatio(), 2, 'f', 2, '0'));
537 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Geometry: %1x%2+%3+%4 Size(Qt): %5mmx%6mm")
538 .arg(geom.width()).arg(geom.height()).arg(geom.left()).arg(geom.top())
539 .arg(qScreen->physicalSize().width()).arg(qScreen->physicalSize().height()));
540
541 if (qScreen->virtualGeometry() != geom)
542 {
543 geom = qScreen->virtualGeometry();
544 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Total virtual geometry: %1x%2+%3+%4")
545 .arg(geom.width()).arg(geom.height()).arg(geom.left()).arg(geom.top()));
546 }
547}
548
550{
551 m_videoModes.clear();
552 m_overrideVideoModes.clear();
554 // Note: The EDID is retrieved in UpdateCurrentMode and we need the EDID to
555 // check for refresh rate range support.
558
559 // Set the desktop mode - which is the mode at startup. We must always return
560 // the screen to this mode.
561 if (!m_initialised)
562 {
563 // Only ever set this once or after a screen change
564 m_initialised = true;
566 LOG(VB_GENERAL, LOG_NOTICE, LOC + QString("Desktop video mode: %1x%2 %3Hz")
567 .arg(m_resolution.width()).arg(m_resolution.height()).arg(m_refreshRate, 0, 'f', 3));
568 if (m_edid.Valid())
569 {
570 if (m_edid.IsSRGB())
571 LOG(VB_GENERAL, LOG_NOTICE, LOC + "Display is using sRGB colourspace");
572 else
573 LOG(VB_GENERAL, LOG_NOTICE, LOC + "Display has custom colourspace");
574
575 InitHDR();
576 }
577 }
578
579 // Set the gui mode from database settings
580 int pixelwidth = m_resolution.width();
581 int pixelheight = m_resolution.height();
582 int mmwidth = m_physicalSize.width();
583 int mmheight = m_physicalSize.height();
584 double refreshrate = m_refreshRate;
585 double aspectratio = 0.0;
586 GetMythDB()->GetResolutionSetting("GuiVidMode", pixelwidth, pixelheight, aspectratio, refreshrate);
587 GetMythDB()->GetResolutionSetting("DisplaySize", mmwidth, mmheight);
588 m_guiMode = MythDisplayMode(pixelwidth, pixelheight, mmwidth, mmheight, -1.0, refreshrate);
589
590 // Set default video mode
591 pixelwidth = pixelheight = 0;
592 GetMythDB()->GetResolutionSetting("TVVidMode", pixelwidth, pixelheight, aspectratio, refreshrate);
593 m_videoMode = MythDisplayMode(pixelwidth, pixelheight, mmwidth, mmheight, aspectratio, refreshrate);
594
595 // Initialise video override modes
596 for (int i = 0; true; ++i)
597 {
598 int iw = 0;
599 int ih = 0;
600 int ow = 0;
601 int oh = 0;
602 double iaspect = 0.0;
603 double oaspect = 0.0;
604 double irate = 0.0;
605 double orate = 0.0;
606
607 GetMythDB()->GetResolutionSetting("VidMode", iw, ih, iaspect, irate, i);
608 GetMythDB()->GetResolutionSetting("TVVidMode", ow, oh, oaspect, orate, i);
609
610 if ((!iw && !ih && qFuzzyIsNull(irate)) || !(ih && ow && oh))
611 break;
612
613 uint64_t key = MythDisplayMode::CalcKey(QSize(iw, ih), irate);
614 MythDisplayMode scr(QSize(ow, oh), QSize(mmwidth, mmheight), oaspect, orate);
615 m_overrideVideoModes[key] = scr;
616 }
617}
618
619
628{
629 const auto screens = QGuiApplication::screens();
630 for (auto * screen : std::as_const(screens))
631 {
632 auto dim = screen->geometry();
633 auto extra = MythDisplay::GetExtraScreenInfo(screen);
634 LOG(VB_GUI, LOG_INFO, LOC + QString("Screen %1: %2x%3 %4")
635 .arg(screen->name()).arg(dim.width()).arg(dim.height()).arg(extra));
636 }
637
638 const auto * primary = QGuiApplication::primaryScreen();
639 if (!primary)
640 {
641 if (!screens.empty())
642 primary = screens.front();
643 if (!primary)
644 {
645 LOG(VB_GENERAL, LOG_ERR, LOC + "Qt has no screens!");
646 return;
647 }
648 }
649
650 LOG(VB_GUI, LOG_INFO, LOC +QString("Primary screen: %1.").arg(primary->name()));
651
652 auto numScreens = MythDisplay::GetScreenCount();
653 auto dim = primary->virtualSize();
654 LOG(VB_GUI, LOG_INFO, LOC + QString("Total desktop dim: %1x%2, over %3 screen[s].")
655 .arg(dim.width()).arg(dim.height()).arg(numScreens));
656
658 {
659 LOG(VB_GUI, LOG_INFO, LOC + QString("Using entire desktop."));
660 m_screenBounds = primary->virtualGeometry();
661 return;
662 }
663
664 if (!GetMythDB()->GetBoolSetting("ForceFullScreen", false) &&
665 GetMythDB()->GetBoolSetting("RunFrontendInWindow", false))
666 {
667 LOG(VB_GUI, LOG_INFO, LOC + "Running in a window");
668 // This doesn't include the area occupied by the
669 // Windows taskbar, or the Mac OS X menu bar and Dock
670 m_screenBounds = m_screen->availableGeometry();
671 }
672 else
673 {
674 m_screenBounds = m_screen->geometry();
675 }
676
677 LOG(VB_GUI, LOG_INFO, LOC + QString("Using screen %1: %2x%3 at %4+%5")
678 .arg(m_screen->name()).arg(m_screenBounds.width()).arg(m_screenBounds.height())
679 .arg(m_screenBounds.left()).arg(m_screenBounds.top()));
680}
681
689{
690 return Size.width() > m_resolution.width() || Size.height() > m_resolution.height();
691}
692
698{
700 if (current == m_desktopMode)
701 return;
703}
704
708bool MythDisplay::SwitchToVideo(QSize Size, double Rate)
709{
710 if (!m_modeComplete)
712
715 double targetrate = 0.0;
716 double aspectoverride = 0.0;
717
718 // try to find video override mode
720 Size.width(), Size.height(), Rate);
721
722 if (key != 0)
723 {
724 next = m_overrideVideoModes[key];
725 if (next.AspectRatio() > 0.0)
726 aspectoverride = next.AspectRatio();
727 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Found custom screen override %1x%2 Aspect %3")
728 .arg(next.Width()).arg(next.Height()).arg(aspectoverride));
729 }
730
731 // If requested refresh rate is 0, attempt to match video fps
732 if (qFuzzyIsNull(next.RefreshRate()))
733 {
734 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Trying to match best refresh rate %1Hz")
735 .arg(Rate, 0, 'f', 3));
736 next.SetRefreshRate(Rate);
737 }
738
739 // need to change video mode?
740 (void)MythDisplayMode::FindBestMatch(GetVideoModes(), next, targetrate);
741
742 // If GSync or FreeSync are enabled, ignore refresh rate only changes.
743 // N.B. This check is not used when switching to GUI (which already ignores
744 // rate only changes) or switching back to the desktop (where we must reset
745 // the display to the original state).
746 if (m_vrrState && m_vrrState->Enabled())
747 {
748 if (next.Resolution() == current.Resolution())
749 {
750 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Ignoring mode switch to %1Hz - VRR enabled")
751 .arg(Rate, 0, 'f', 3));
752 return true;
753 }
754 LOG(VB_GENERAL, LOG_INFO, LOC + "Allowing mode switch with VRR enabled for new resolution");
755 }
756
757 // No need for change
758 if ((next == current) && (MythDisplayMode::CompareRates(current.RefreshRate(), targetrate)))
759 {
760 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Using current mode %1x%2@%3Hz")
761 .arg(m_resolution.width()).arg(m_resolution.height()).arg(m_refreshRate));
762 return true;
763 }
764
765 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Trying mode %1x%2@%3Hz")
766 .arg(next.Width()).arg(next.Height()).arg(next.RefreshRate(), 0, 'f', 3));
767
768 if (!SwitchToVideoMode(next.Resolution(), targetrate))
769 {
770 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to change mode to %1x%2@%3Hz")
771 .arg(next.Width()).arg(next.Height()).arg(next.RefreshRate(), 0, 'f', 3));
772 return false;
773 }
774
775 if (next.Resolution() != m_resolution)
777
778 // N.B. We used a computed aspect ratio unless overridden
779 m_aspectRatioOverride = aspectoverride > 0.0 ? aspectoverride : 0.0;
781 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Switched to %1x%2@%3Hz for video %4x%5")
782 .arg(m_resolution.width()).arg(m_resolution.height())
783 .arg(m_refreshRate, 0, 'f', 3).arg(Size.width()).arg(Size.height()));
785 return true;
786}
787
791{
792 if (!m_modeComplete)
794
795 // If the current resolution is the same as the GUI resolution then do nothing
796 // as refresh rate should not be critical for the GUI.
798 {
799 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Using %1x%2@%3Hz for GUI")
800 .arg(m_resolution.width()).arg(m_resolution.height()).arg(m_refreshRate));
801 return true;
802 }
803
805 {
806 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to change mode to %1x%2@%3Hz")
807 .arg(m_guiMode.Width()).arg(m_guiMode.Height()).arg(m_guiMode.RefreshRate(), 0, 'f', 3));
808 return false;
809 }
810
811 if (Wait && (m_resolution != m_guiMode.Resolution()))
813
816 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Switched to %1x%2@%3Hz")
817 .arg(m_resolution.width()).arg(m_resolution.height()).arg(m_refreshRate, 0, 'f', 3));
818 return true;
819}
820
822{
823 return m_refreshRate;
824}
825
826std::chrono::microseconds MythDisplay::GetRefreshInterval(std::chrono::microseconds Fallback) const
827{
828 // If FreeSync or GSync are enabled, return the maximum refresh rate.
829 // N.B. This may need more work as the max may not be well defined - especially
830 // if the resolution is changing. Displays should however support at least 60Hz
831 // at all resolutions which should be fine in the vast majority of cases (as the
832 // only place the refresh interval is functionally important is in checking
833 // for double rate deinterlacing support).
834 if (m_vrrState && m_vrrState->Enabled())
835 {
836 const auto range = m_vrrState->GetRange();
837 auto max = std::get<1>(range) > 60 ? std::get<1>(range) : 60;
838 return microsecondsFromFloat(1000000.0 / max);
839 }
840
841 if (m_refreshRate > 20.0 && m_refreshRate < 200.0)
842 return microsecondsFromFloat(1000000.0 / m_refreshRate);
843 if (Fallback > 33ms) // ~30Hz
844 Fallback /= 2;
845 return Fallback;
846}
847
849{
850 auto targetrate = static_cast<double>(NAN);
851 const MythDisplayMode mode(Size, QSize(0, 0), -1.0, 0.0);
852 const auto & modes = GetVideoModes();
853 int match = MythDisplayMode::FindBestMatch(modes, mode, targetrate);
854 if (match < 0)
855 return {};
856 return modes[static_cast<size_t>(match)].RefreshRates();
857}
858
859bool MythDisplay::SwitchToVideoMode(QSize /*Size*/, double /*Framerate*/)
860{
861 return false;
862}
863
865{
866 return m_videoModes;
867}
868
887double MythDisplay::GetAspectRatio(QString &Source, bool IgnoreModeOverride)
888{
889 auto valid = [](double Aspect) { return (Aspect > 0.1 && Aspect < 10.0); };
890
891 // Override for this video mode
892 // Is this behaviour still needed?
893 if (!IgnoreModeOverride && valid(m_aspectRatioOverride))
894 {
895 Source = tr("Video mode override");
897 }
898
899 // General override for invalid/misleading EDIDs or multiscreen setups
900 // New default of -1.0 equates to square pixels for modern displays
901 bool multiscreen = MythDisplay::SpanAllScreens() && GetScreenCount() > 1;
902 double override = gCoreContext->GetFloatSettingOnHost("XineramaMonitorAspectRatio",
903 gCoreContext->GetHostName(), -1.0);
904
905 // Zero (not valid) indicates auto
906 if (valid(override))
907 {
908 Source = tr("Override");
909 return override;
910 }
911
912 // Auto for multiscreen is a best guess
913 if (multiscreen)
914 {
915 double aspect = EstimateVirtualAspectRatio();
916 if (valid(aspect))
917 {
918 Source = tr("Multiscreen estimate");
919 return aspect;
920 }
921 }
922
923 double calculated = m_resolution.isEmpty() ? 0.0 :
924 static_cast<double>(m_resolution.width()) / m_resolution.height();
925 double detected = m_physicalSize.isEmpty() ? 0.0 :
926 static_cast<double>(m_physicalSize.width()) / m_physicalSize.height();
927
928 // Assume pixel aspect ratio is 1 (square pixels)
929 if (valid(calculated))
930 {
931 if ((override < 0.0) || !valid(detected))
932 {
933 Source = tr("Square pixels");
934 return calculated;
935 }
936 }
937
938 // Based on actual physical size if available
939 if (valid(detected))
940 {
941 Source = tr("Detected");
942 return detected;
943 }
944
945 // the aspect ratio of last resort
946 Source = tr("Guessed");
947 return 16.0 / 9.0;
948}
949
951{
952 return m_edid;
953}
954
956{
957 return m_hdrState;
958}
959
961{
962 if (m_edid.Valid())
963 {
964 auto hdrdesc = m_edid.GetHDRSupport();
965 m_hdrState = MythHDR::Create(this, hdrdesc);
966 LOG(VB_GENERAL, LOG_NOTICE, LOC + QString("Supported HDR formats: %1")
967 .arg(m_hdrState->TypesToString().join(",")));
968 if (auto brightness = m_hdrState->GetMaxLuminance(); brightness > 1.0)
969 {
970 LOG(VB_GENERAL, LOG_NOTICE, LOC + QString("Display reports max brightness of %1 nits")
971 .arg(static_cast<int>(brightness)));
972 }
973 }
974}
975
986{
987 auto sortscreens = [](const QScreen* First, const QScreen* Second)
988 {
989 if (First->geometry().left() < Second->geometry().left())
990 return true;
991 if (First->geometry().top() < Second->geometry().top())
992 return true;
993 return false;
994 };
995
996 // default
997 double result = 16.0 / 9.0;
998
999 QList<QScreen*> screens;
1000 if (m_screen)
1001 screens = m_screen->virtualSiblings();
1002 if (screens.empty())
1003 return result;
1004
1005 // N.B. This sorting may not be needed
1006 std::sort(screens.begin(), screens.end(), sortscreens);
1007 QList<double> aspectratios;
1008 QSize totalresolution;
1009 int lasttop = 0;
1010 int lastleft = 0;
1011 int rows = 1;
1012 int columns = 1;
1013 for (auto it = screens.constBegin() ; it != screens.constEnd(); ++it)
1014 {
1015 QRect geom = (*it)->geometry();
1016 totalresolution += geom.size();
1017 LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("%1x%2+%3+%4 %5")
1018 .arg(geom.width()).arg(geom.height()).arg(geom.left()).arg(geom.top())
1019 .arg((*it)->physicalSize().width() / (*it)->physicalSize().height()));
1020 if (lastleft < geom.left())
1021 {
1022 columns++;
1023 lastleft = geom.left();
1024 }
1025 if (lasttop < geom.top())
1026 {
1027 rows++;
1028 lasttop = geom.top();
1029 lastleft = 0;
1030 }
1031 aspectratios << (*it)->physicalSize().width() / (*it)->physicalSize().height();
1032 }
1033
1034 // If all else fails, use the total resolution and assume pixel aspect ratio
1035 // equals display aspect ratio
1036 if (!totalresolution.isEmpty())
1037 result = static_cast<double>(totalresolution.width()) / totalresolution.height();
1038
1039 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Screen layout: %1x%2").arg(rows).arg(columns));
1040 if (rows == columns)
1041 {
1042 LOG(VB_GENERAL, LOG_DEBUG, LOC + "Grid layout");
1043 }
1044 else if (rows == 1 && columns > 1)
1045 {
1046 LOG(VB_GENERAL, LOG_DEBUG, LOC + "Horizontal layout");
1047 }
1048 else if (columns == 1 && rows > 1)
1049 {
1050 LOG(VB_GENERAL, LOG_DEBUG, LOC + "Vertical layout");
1051 }
1052 else
1053 {
1054 LOG(VB_GENERAL, LOG_INFO,
1055 LOC + QString("Unsupported layout - defaulting to %1 (%2/%3)")
1056 .arg(result).arg(totalresolution.width()).arg(totalresolution.height()));
1057 return result;
1058 }
1059
1060 // validate aspect ratios - with a little fuzzyness
1061 double aspectratio = 0.0;
1062 double average = 0.0;
1063 int count = 1;
1064 for (auto it2 = aspectratios.constBegin() ; it2 != aspectratios.constEnd(); ++it2, ++count)
1065 {
1066 aspectratio += *it2;
1067 average = aspectratio / count;
1068 if (qAbs(*it2 - average) > 0.1)
1069 {
1070 LOG(VB_GENERAL, LOG_INFO, LOC +
1071 QString("Inconsistent aspect ratios - defaulting to %1 (%2/%3)")
1072 .arg(result).arg(totalresolution.width()).arg(totalresolution.height()));
1073 return result;
1074 }
1075 }
1076
1077 aspectratio = (average * columns) / rows;
1078 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Estimated aspect ratio: %1")
1079 .arg(aspectratio));
1080 return aspectratio;
1081}
1082
1084{
1085 return m_resolution;
1086}
1087
1089{
1090 return m_physicalSize;
1091}
1092
1094{
1095 // Some implementations may have their own mechanism for ensuring the mode
1096 // is updated before continuing
1098 return;
1099
1100 LOG(VB_GENERAL, LOG_INFO, LOC + "Waiting for resolution change");
1101 QEventLoop loop;
1102 QTimer timer;
1103 timer.setSingleShot(true);
1104 connect(&timer, &QTimer::timeout,
1105 &timer, [](){ LOG(VB_GENERAL, LOG_WARNING, LOC + "Timed out waiting for screen change"); });
1106 QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
1107 QObject::connect(m_screen, &QScreen::geometryChanged, &loop, &QEventLoop::quit);
1108 // 500ms maximum wait
1109 timer.start(500ms);
1110 loop.exec();
1111}
1112
1114{
1115 // N.B. This isn't working as intended as it always times out rather than
1116 // exiting deliberately. It does however somehow filter out unwanted screenChanged
1117 // events that otherwise often put the widget in the wrong screen.
1118 // Needs more investigation - but for now it works:)
1119 if (!m_widget || !m_widget->windowHandle())
1120 return;
1121 LOG(VB_GENERAL, LOG_INFO, LOC + "Waiting for new screen");
1122 QEventLoop loop;
1123 QTimer timer;
1124 timer.setSingleShot(true);
1125 connect(&timer, &QTimer::timeout,
1126 &timer, [](){ LOG(VB_GENERAL, LOG_WARNING, LOC + "Timed out waiting for new screen"); });
1127 QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
1128 QObject::connect(m_widget->windowHandle(), &QWindow::screenChanged, &loop, &QEventLoop::quit);
1129 // 500ms maximum wait
1130 timer.start(500ms);
1131 loop.exec();
1132}
1133
1135{
1136 int pauselengthinms = gCoreContext->GetNumSetting("VideoModeChangePauseMS", 0);
1137 if (pauselengthinms)
1138 {
1139 LOG(VB_GENERAL, LOG_INFO, LOC +
1140 QString("Pausing %1ms for video mode switch").arg(pauselengthinms));
1141 QEventLoop loop;
1142 QTimer timer;
1143 timer.setSingleShot(true);
1144 QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
1145 // 500ms maximum wait
1146 timer.start(pauselengthinms);
1147 loop.exec();
1148 }
1149}
1150
1152{
1153 // This is intentionally formatted to match the output of xrandr for comparison
1154 if (VERBOSE_LEVEL_CHECK(VB_PLAYBACK, LOG_INFO))
1155 {
1156 LOG(VB_PLAYBACK, LOG_INFO, LOC + "Available modes:");
1157 for (auto it = m_videoModes.crbegin(); it != m_videoModes.crend(); ++it)
1158 {
1159 auto rates = (*it).RefreshRates();
1160 QStringList rateslist;
1161 for (auto it2 = rates.crbegin(); it2 != rates.crend(); ++it2)
1162 rateslist.append(QString("%1").arg(*it2, 2, 'f', 2, '0'));
1163 if (rateslist.empty())
1164 rateslist.append("Variable rate?");
1165 LOG(VB_PLAYBACK, LOG_INFO, QString("%1x%2\t%3")
1166 .arg((*it).Width()).arg((*it).Height()).arg(rateslist.join("\t")));
1167 }
1168 }
1169}
1170
1176void MythDisplay::ConfigureQtGUI(int SwapInterval, const MythCommandLineParser& CmdLine)
1177{
1178 auto forcevrr = CmdLine.toBool("vrr");
1179 bool gsyncchanged = false;
1180 bool freesyncchanged = false;
1181
1182#if CONFIG_QTWEBENGINE
1183 QApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
1184 QQuickWindow::setSceneGraphBackend("software");
1185 LOG(VB_GENERAL, LOG_INFO, LOC + "Using shared OpenGL Contexts");
1186#endif
1187
1188 // Set the default surface format. Explicitly required on some platforms.
1189 QSurfaceFormat format;
1190 // Allow overriding the default depth - use with caution as Qt will likely
1191 // crash if it cannot find a matching visual.
1192 if (qEnvironmentVariableIsSet("MYTHTV_DEPTH"))
1193 {
1194 // Note: Don't set depth and stencil to give Qt as much flexibility as possible
1195 int depth = std::clamp(qEnvironmentVariableIntValue("MYTHTV_DEPTH"), 6, 16);
1196 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Trying to force depth to '%1'").arg(depth));
1197 format.setRedBufferSize(depth);
1198 }
1199 else
1200 {
1201 format.setDepthBufferSize(0);
1202 format.setStencilBufferSize(0);
1203 }
1204 format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
1205 format.setProfile(QSurfaceFormat::CompatibilityProfile);
1206 format.setSwapInterval(SwapInterval);
1207 QSurfaceFormat::setDefaultFormat(format);
1208
1209#ifdef Q_OS_DARWIN
1210 // Without this, we can't set focus to any of the CheckBoxSetting, and most
1211 // of the MythPushButton widgets, and they don't use the themed background.
1212 QApplication::setDesktopSettingsAware(false);
1213#endif
1214
1215#if CONFIG_DRM && CONFIG_QTPRIVATEHEADERS
1216 // Avoid trying to setup DRM if we are definitely not going to use it.
1217#if CONFIG_X11
1219#endif
1220 {
1221#if CONFIG_WAYLANDEXTRAS
1222 // When vt switching this still detects wayland servers, so disabled for now
1223 //if (!MythWaylandDevice::IsAvailable())
1224#endif
1225 {
1226 MythDRMDevice::SetupDRM(CmdLine);
1227 freesyncchanged = MythDRMVRR::s_freeSyncResetOnExit;
1228 }
1229 }
1230#endif
1231
1232#if defined (Q_OS_LINUX) && CONFIG_EGL && CONFIG_X11
1233 // We want to use EGL for VAAPI/MMAL/DRMPRIME rendering to ensure we
1234 // can use zero copy video buffers for the best performance.
1235 // To force Qt to use EGL we must set 'QT_XCB_GL_INTEGRATION' to 'xcb_egl'
1236 // and this must be done before any GUI is created. If the platform plugin is
1237 // not xcb then this should have no effect.
1238 // This does however break when using NVIDIA drivers - which do not support
1239 // EGL like other drivers so we try to check the EGL vendor - and we currently
1240 // have no need for EGL with NVIDIA (that may change however).
1241 // NOTE force using EGL by setting MYTHTV_FORCE_EGL
1242 // NOTE disable using EGL by setting MYTHTV_NO_EGL
1243 // NOTE We have no Qt platform information, window/surface or logging when this is called.
1244 QString soft = qgetenv("LIBGL_ALWAYS_SOFTWARE");
1245 bool ignore = soft == "1" || soft.compare("true", Qt::CaseInsensitive) == 0;
1246 bool allow = qEnvironmentVariableIsEmpty("MYTHTV_NO_EGL") && !ignore;
1247 bool force = !qEnvironmentVariableIsEmpty("MYTHTV_FORCE_EGL");
1248 if ((force || allow) && MythDisplayX11::IsAvailable())
1249 {
1250 // N.B. By default, ignore EGL if vendor string is not returned
1251 QString vendor = MythEGL::GetEGLVendor();
1252 if (vendor.contains("nvidia", Qt::CaseInsensitive) && !force)
1253 {
1254 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Not requesting EGL for vendor '%1'").arg(vendor));
1255 }
1256 else if (!vendor.isEmpty() || force)
1257 {
1258 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Requesting EGL for vendor '%1'").arg(vendor));
1259 setenv("QT_XCB_GL_INTEGRATION", "xcb_egl", 0);
1260 }
1261 }
1262#endif
1263
1264#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
1265 // Ignore desktop scaling
1266 QApplication::setAttribute(Qt::AA_DisableHighDpiScaling);
1267#endif
1268
1269#if CONFIG_X11
1270 if (auto display = CmdLine.toString("display"); !display.isEmpty())
1272 // GSync support via libXNVCtrl
1273 // Note: FreeSync support is checked in MythDRMDevice::SetupDRM
1274 if (forcevrr)
1275 {
1276 MythGSync::ForceGSync(CmdLine.toUInt("vrr") > 0);
1277 gsyncchanged = MythGSync::s_gsyncResetOnExit;
1278 }
1279#endif
1280
1281 if (forcevrr && !(gsyncchanged || freesyncchanged))
1282 LOG(VB_GENERAL, LOG_INFO, LOC + "Variable refresh rate not adjusted");
1283}
Parent class for defining application command line parsers.
bool toBool(const QString &key) const
Returns stored QVariant as a boolean.
QString toString(const QString &key) const
Returns stored QVariant as a QString, falling to default if not provided.
uint toUInt(const QString &key) const
Returns stored QVariant as an unsigned integer, falling to default if not provided.
QString GetHostName(void)
QString GetSetting(const QString &key, const QString &defaultval="")
int GetNumSetting(const QString &key, int defaultval=0)
double GetFloatSettingOnHost(const QString &key, const QString &host, double defaultval=0.0)
static bool s_freeSyncResetOnExit
Definition: mythdrmvrr.h:11
int Height() const
static bool CompareRates(double First, double Second, double Precision=0.01)
Determine whether two rates are considered equal with the given precision.
static int FindBestMatch(const MythDisplayModes &Modes, const MythDisplayMode &Mode, double &TargetRate)
void SetRefreshRate(double Rate)
double AspectRatio() const
double RefreshRate() const
static uint64_t FindBestScreen(const DisplayModeMap &Map, int Width, int Height, double Rate)
static uint64_t CalcKey(QSize Size, double Rate)
QSize Resolution() const
static bool IsAvailable()
MythEDID & GetEDID()
QWidget * m_widget
Definition: mythdisplay.h:95
double GetPixelAspectRatio()
virtual bool SwitchToVideoMode(QSize Size, double Framerate)
QSize m_resolution
Definition: mythdisplay.h:92
void InitScreenBounds()
Get screen size from Qt while respecting the user's multiscreen settings.
MythHDRPtr GetHDRState()
QStringList GetDescription()
static void PauseForModeSwitch()
QWindow * GetCurrentWindow()
double EstimateVirtualAspectRatio()
Estimate the overall display aspect ratio for multi screen setups.
double GetAspectRatio(QString &Source, bool IgnoreModeOverride=false)
Returns current screen aspect ratio.
void WaitForNewScreen()
void SwitchToDesktop()
Return the screen to the original desktop video mode.
virtual void UpdateCurrentMode()
Retrieve screen details.
static void GeometryChanged(QRect Geometry)
MythDisplayRates GetRefreshRates(QSize Size)
static void ConfigureQtGUI(int SwapInterval, const MythCommandLineParser &CmdLine)
Shared static initialisation code for all MythTV GUI applications.
QSize GetPhysicalSize()
MythDisplayMode m_guiMode
Definition: mythdisplay.h:110
QWindow * m_window
Definition: mythdisplay.h:96
bool m_firstScreenChange
Definition: mythdisplay.h:107
double GetRefreshRate() const
bool m_initialised
Definition: mythdisplay.h:106
bool SwitchToVideo(QSize Size, double Rate=0.0)
Switches to the resolution and refresh rate defined in the database for the specified video resolutio...
QSize m_physicalSize
Definition: mythdisplay.h:93
void PhysicalDPIChanged(qreal DPI)
static QScreen * GetDesiredScreen()
QSize GetGUIResolution()
static void DebugScreen(QScreen *qScreen, const QString &Message)
virtual bool VideoModesAvailable()
Definition: mythdisplay.h:29
static MythDisplay * Create(MythMainWindow *MainWindow)
Create a MythDisplay object appropriate for the current platform.
Definition: mythdisplay.cpp:94
bool SwitchToGUI(bool Wait=false)
Switches to the GUI resolution.
void Initialise()
double m_refreshRate
Definition: mythdisplay.h:90
virtual void ScreenChanged(QScreen *qScreen)
The actual screen in use has changed. We must use it.
MythDisplayModes m_videoModes
Definition: mythdisplay.h:98
bool m_waitForModeChanges
Definition: mythdisplay.h:88
void ScreenRemoved(QScreen *qScreen)
MythDisplayMode m_desktopMode
Definition: mythdisplay.h:109
QScreen * GetCurrentScreen()
Return a pointer to the screen to use.
static void PrimaryScreenChanged(QScreen *qScreen)
QRect GetScreenBounds()
~MythDisplay() override
void ScreenCountChanged(int Screens)
void ScreenAdded(QScreen *qScreen)
DisplayModeMap m_overrideVideoModes
Definition: mythdisplay.h:112
MythHDRPtr m_hdrState
Definition: mythdisplay.h:99
double m_aspectRatioOverride
Definition: mythdisplay.h:91
QScreen * m_screen
Definition: mythdisplay.h:97
MythEDID m_edid
Definition: mythdisplay.h:94
void WaitForScreenChange()
virtual const MythDisplayModes & GetVideoModes()
static QString GetExtraScreenInfo(QScreen *qScreen)
void SetWidget(QWidget *MainWindow)
Set the QWidget and QWindow in use.
void InitHDR()
MythVRRPtr m_vrrState
Definition: mythdisplay.h:100
void DisplayChanged()
QRect m_screenBounds
Definition: mythdisplay.h:108
std::chrono::microseconds GetRefreshInterval(std::chrono::microseconds Fallback) const
MythDisplayMode m_videoMode
Definition: mythdisplay.h:111
QSize GetResolution()
bool NextModeIsLarger(QSize Size)
Check whether the next mode is larger in size than the current mode.
bool m_modeComplete
Definition: mythdisplay.h:89
static bool SpanAllScreens()
Return true if the MythTV windows should span all screens.
static int GetScreenCount()
void DebugModes() const
MythHDRDesc GetHDRSupport() const
Definition: mythedid.cpp:99
bool IsSRGB() const
Definition: mythedid.cpp:74
bool Valid() const
Definition: mythedid.cpp:39
static QString GetEGLVendor(void)
Definition: mythegl.cpp:83
static bool s_gsyncResetOnExit
Definition: mythnvcontrol.h:13
static void ForceGSync(bool Enable)
Enable or disable GSync before the main window is created.
static MythHDRPtr Create(class MythDisplay *MDisplay, const MythHDRDesc &Desc)
Definition: mythhdr.cpp:31
static QRect GetGeometryOverride()
static bool WindowIsAlwaysFullscreen()
Return true if the current platform only supports fullscreen windows.
static bool GeometryIsOverridden()
static MythVRRPtr Create(class MythDisplay *MDisplay)
Create a concrete implementation of MythVRR suitable for the given Display.
Definition: mythvrr.cpp:56
static void SetQtX11Display(const QString &DisplayStr)
#define setenv(x, y, z)
Definition: compat.h:62
static const struct wl_interface * types[]
@ quit
Definition: lirc_client.h:31
std::enable_if_t< std::is_floating_point_v< T >, std::chrono::microseconds > microsecondsFromFloat(T value)
Helper function for convert a floating point number to a duration.
Definition: mythchrono.h:102
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
MythDB * GetMythDB(void)
Definition: mythdb.cpp:50
#define LOC
Definition: mythdisplay.cpp:58
std::vector< MythDisplayMode > MythDisplayModes
std::vector< double > MythDisplayRates
std::shared_ptr< class MythHDR > MythHDRPtr
Definition: mythhdr.h:30
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
Definition: mythlogging.h:29
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15
static eu8 clamp(eu8 value, eu8 low, eu8 high)
Definition: pxsup2dast.c:206