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 QScreen* newscreen = nullptr;
347
348 // If geometry is overriden at the command line level, try and determine
349 // which screen that applies to (if any).
350 // N.B. So many potential issues here e.g. should the geometry override be
351 // ignored after first use? (as it will continue to override the screen
352 // regardless of changes to screen preference).
354 {
355 // this matches the check in MythMainWindow
356 bool windowed = GetMythDB()->GetBoolSetting("RunFrontendInWindow", false) &&
358 QRect override = MythMainWindow::GetGeometryOverride();
359 // When windowed, we use topleft as a best guess as to which screen we belong in.
360 // When fullscreen, Qt appears to use the reverse - though this may be
361 // the window manager rather than Qt. So could be wrong.
362 QPoint point = windowed ? override.topLeft() : override.bottomRight();
363 QList screens = QGuiApplication::screens();
364 for (QScreen *screen : std::as_const(screens))
365 {
366 if (screen->geometry().contains(point))
367 {
368 newscreen = screen;
369 LOG(VB_GENERAL, LOG_INFO, LOC + QString(
370 "Geometry override places window in screen '%1'").arg(newscreen->name()));
371 break;
372 }
373 }
374 }
375
376 // If spanning all screens, then always use the primary display
377 if (!newscreen && MythDisplay::SpanAllScreens())
378 {
379 LOG(VB_GENERAL, LOG_INFO, LOC + "Using primary screen for multiscreen");
380 newscreen = QGuiApplication::primaryScreen();
381 }
382
383 QString name = gCoreContext->GetSetting("XineramaScreen", nullptr);
384 // Lookup by name
385 if (!newscreen)
386 {
387 QList screens = QGuiApplication::screens();
388 for (QScreen *screen : std::as_const(screens))
389 {
390 if (!name.isEmpty() && name == screen->name())
391 {
392 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Found screen '%1'").arg(name));
393 newscreen = screen;
394 }
395 }
396 }
397
398 // No name match. These were previously numbers.
399 if (!newscreen)
400 {
401 bool ok = false;
402 int screen_num = name.toInt(&ok);
403 QList<QScreen *>screens = QGuiApplication::screens();
404 if (ok && (screen_num >= 0) && (screen_num < screens.size()))
405 {
406 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Found screen number %1 (%2)")
407 .arg(name, screens[screen_num]->name()));
408 newscreen = screens[screen_num];
409 }
410 }
411
412 // For anything else, return the primary screen.
413 if (!newscreen)
414 {
415 QScreen *primary = QGuiApplication::primaryScreen();
416 if (name.isEmpty() && primary)
417 {
418 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Defaulting to primary screen (%1)")
419 .arg(primary->name()));
420 }
421 else if (name != "-1" && primary)
422 {
423 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Screen '%1' not found, defaulting to primary screen (%2)")
424 .arg(name, primary->name()));
425 }
426 newscreen = primary;
427 }
428
429 return newscreen;
430}
431
434void MythDisplay::ScreenChanged(QScreen *qScreen)
435{
436 if (qScreen == m_screen)
437 return;
438 if (m_screen)
439 disconnect(m_screen, nullptr, this, nullptr);
440 DebugScreen(qScreen, "Changed to");
441 m_screen = qScreen;
442 connect(m_screen, &QScreen::geometryChanged, this, &MythDisplay::GeometryChanged);
443 connect(m_screen, &QScreen::physicalDotsPerInchChanged, this, &MythDisplay::PhysicalDPIChanged);
444 Initialise();
445 emit CurrentScreenChanged(qScreen);
446}
447
449{
450 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Qt screen pixel ratio changed to %1")
451 .arg(DPI, 2, 'f', 2, '0'));
452 emit CurrentDPIChanged(DPI);
453}
454
456{
457 DebugScreen(qScreen, "New primary");
458}
459
460void MythDisplay::ScreenAdded(QScreen* qScreen)
461{
462 DebugScreen(qScreen, "New");
463 emit ScreenCountChanged(QGuiApplication::screens().size());
464}
465
466void MythDisplay::ScreenRemoved(QScreen* qScreen)
467{
468 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Screen '%1' removed").arg(qScreen->name()));
469 emit ScreenCountChanged(QGuiApplication::screens().size());
470}
471
472void MythDisplay::GeometryChanged(const QRect Geo)
473{
474 LOG(VB_GENERAL, LOG_INFO, LOC + QString("New screen geometry: %1x%2+%3+%4")
475 .arg(Geo.width()).arg(Geo.height()).arg(Geo.left()).arg(Geo.top()));
476}
477
485{
486 // Certain platform implementations do not have a window to access at startup
487 // and hence use this implementation. Flag the status as incomplete to ensure
488 // we try to retrieve the full details at the first opportunity.
489 m_modeComplete = false;
490 m_edid = MythEDID();
491 QScreen *screen = GetCurrentScreen();
492 if (!screen)
493 {
494 m_refreshRate = 60.0;
495 m_physicalSize = QSize(0, 0);
496 m_resolution = QSize(1920, 1080);
497 return;
498 }
499 m_refreshRate = screen->refreshRate();
500 m_resolution = screen->size();
501 m_physicalSize = QSize(static_cast<int>(screen->physicalSize().width()),
502 static_cast<int>(screen->physicalSize().height()));
503}
504
507{
508 return gCoreContext->GetSetting("XineramaScreen", nullptr) == "-1";
509}
510
511QString MythDisplay::GetExtraScreenInfo(QScreen *qScreen)
512{
513 QString mfg = qScreen->manufacturer();
514 if (mfg.isEmpty())
515 mfg = "Unknown";
516 QString model = qScreen->model();
517 if (model.isEmpty())
518 model = "Unknown";
519 return QString("(Make: %1 Model: %2)").arg(mfg, model);
520}
521
522void MythDisplay::DebugScreen(QScreen *qScreen, const QString &Message)
523{
524 if (!qScreen)
525 return;
526
527 auto geom = qScreen->geometry();
528 LOG(VB_GENERAL, LOG_INFO, LOC + QString("%1 screen '%2' %3")
529 .arg(Message, qScreen->name(), GetExtraScreenInfo(qScreen)));
530 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Qt screen pixel ratio: %1")
531 .arg(qScreen->devicePixelRatio(), 2, 'f', 2, '0'));
532 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Geometry: %1x%2+%3+%4 Size(Qt): %5mmx%6mm")
533 .arg(geom.width()).arg(geom.height()).arg(geom.left()).arg(geom.top())
534 .arg(qScreen->physicalSize().width()).arg(qScreen->physicalSize().height()));
535
536 if (qScreen->virtualGeometry() != geom)
537 {
538 geom = qScreen->virtualGeometry();
539 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Total virtual geometry: %1x%2+%3+%4")
540 .arg(geom.width()).arg(geom.height()).arg(geom.left()).arg(geom.top()));
541 }
542}
543
545{
546 m_videoModes.clear();
547 m_overrideVideoModes.clear();
549 // Note: The EDID is retrieved in UpdateCurrentMode and we need the EDID to
550 // check for refresh rate range support.
553
554 // Set the desktop mode - which is the mode at startup. We must always return
555 // the screen to this mode.
556 if (!m_initialised)
557 {
558 // Only ever set this once or after a screen change
559 m_initialised = true;
561 LOG(VB_GENERAL, LOG_NOTICE, LOC + QString("Desktop video mode: %1x%2 %3Hz")
562 .arg(m_resolution.width()).arg(m_resolution.height()).arg(m_refreshRate, 0, 'f', 3));
563 if (m_edid.Valid())
564 {
565 if (m_edid.IsSRGB())
566 LOG(VB_GENERAL, LOG_NOTICE, LOC + "Display is using sRGB colourspace");
567 else
568 LOG(VB_GENERAL, LOG_NOTICE, LOC + "Display has custom colourspace");
569
570 InitHDR();
571 }
572 }
573
574 // Set the gui mode from database settings
575 int pixelwidth = m_resolution.width();
576 int pixelheight = m_resolution.height();
577 int mmwidth = m_physicalSize.width();
578 int mmheight = m_physicalSize.height();
579 double refreshrate = m_refreshRate;
580 double aspectratio = 0.0;
581 GetMythDB()->GetResolutionSetting("GuiVidMode", pixelwidth, pixelheight, aspectratio, refreshrate);
582 GetMythDB()->GetResolutionSetting("DisplaySize", mmwidth, mmheight);
583 m_guiMode = MythDisplayMode(pixelwidth, pixelheight, mmwidth, mmheight, -1.0, refreshrate);
584
585 // Set default video mode
586 pixelwidth = pixelheight = 0;
587 GetMythDB()->GetResolutionSetting("TVVidMode", pixelwidth, pixelheight, aspectratio, refreshrate);
588 m_videoMode = MythDisplayMode(pixelwidth, pixelheight, mmwidth, mmheight, aspectratio, refreshrate);
589
590 // Initialise video override modes
591 for (int i = 0; true; ++i)
592 {
593 int iw = 0;
594 int ih = 0;
595 int ow = 0;
596 int oh = 0;
597 double iaspect = 0.0;
598 double oaspect = 0.0;
599 double irate = 0.0;
600 double orate = 0.0;
601
602 GetMythDB()->GetResolutionSetting("VidMode", iw, ih, iaspect, irate, i);
603 GetMythDB()->GetResolutionSetting("TVVidMode", ow, oh, oaspect, orate, i);
604
605 if ((!iw && !ih && qFuzzyIsNull(irate)) || !(ih && ow && oh))
606 break;
607
608 uint64_t key = MythDisplayMode::CalcKey(QSize(iw, ih), irate);
609 MythDisplayMode scr(QSize(ow, oh), QSize(mmwidth, mmheight), oaspect, orate);
610 m_overrideVideoModes[key] = scr;
611 }
612}
613
614
623{
624 const auto screens = QGuiApplication::screens();
625 for (auto * screen : std::as_const(screens))
626 {
627 auto dim = screen->geometry();
628 auto extra = MythDisplay::GetExtraScreenInfo(screen);
629 LOG(VB_GUI, LOG_INFO, LOC + QString("Screen %1: %2x%3 %4")
630 .arg(screen->name()).arg(dim.width()).arg(dim.height()).arg(extra));
631 }
632
633 const auto * primary = QGuiApplication::primaryScreen();
634 if (!primary)
635 {
636 if (!screens.empty())
637 primary = screens.front();
638 if (!primary)
639 {
640 LOG(VB_GENERAL, LOG_ERR, LOC + "Qt has no screens!");
641 return;
642 }
643 }
644
645 LOG(VB_GUI, LOG_INFO, LOC +QString("Primary screen: %1.").arg(primary->name()));
646
647 auto numScreens = MythDisplay::GetScreenCount();
648 auto dim = primary->virtualSize();
649 LOG(VB_GUI, LOG_INFO, LOC + QString("Total desktop dim: %1x%2, over %3 screen[s].")
650 .arg(dim.width()).arg(dim.height()).arg(numScreens));
651
653 {
654 LOG(VB_GUI, LOG_INFO, LOC + QString("Using entire desktop."));
655 m_screenBounds = primary->virtualGeometry();
656 return;
657 }
658
659 if (!GetMythDB()->GetBoolSetting("ForceFullScreen", false) &&
660 GetMythDB()->GetBoolSetting("RunFrontendInWindow", false))
661 {
662 LOG(VB_GUI, LOG_INFO, LOC + "Running in a window");
663 // This doesn't include the area occupied by the
664 // Windows taskbar, or the Mac OS X menu bar and Dock
665 m_screenBounds = m_screen->availableGeometry();
666 }
667 else
668 {
669 m_screenBounds = m_screen->geometry();
670 }
671
672 LOG(VB_GUI, LOG_INFO, LOC + QString("Using screen %1: %2x%3 at %4+%5")
673 .arg(m_screen->name()).arg(m_screenBounds.width()).arg(m_screenBounds.height())
674 .arg(m_screenBounds.left()).arg(m_screenBounds.top()));
675}
676
684{
685 return Size.width() > m_resolution.width() || Size.height() > m_resolution.height();
686}
687
693{
695 if (current == m_desktopMode)
696 return;
698}
699
703bool MythDisplay::SwitchToVideo(QSize Size, double Rate)
704{
705 if (!m_modeComplete)
707
710 double targetrate = 0.0;
711 double aspectoverride = 0.0;
712
713 // try to find video override mode
715 Size.width(), Size.height(), Rate);
716
717 if (key != 0)
718 {
719 next = m_overrideVideoModes[key];
720 if (next.AspectRatio() > 0.0)
721 aspectoverride = next.AspectRatio();
722 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Found custom screen override %1x%2 Aspect %3")
723 .arg(next.Width()).arg(next.Height()).arg(aspectoverride));
724 }
725
726 // If requested refresh rate is 0, attempt to match video fps
727 if (qFuzzyIsNull(next.RefreshRate()))
728 {
729 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Trying to match best refresh rate %1Hz")
730 .arg(Rate, 0, 'f', 3));
731 next.SetRefreshRate(Rate);
732 }
733
734 // need to change video mode?
735 (void)MythDisplayMode::FindBestMatch(GetVideoModes(), next, targetrate);
736
737 // If GSync or FreeSync are enabled, ignore refresh rate only changes.
738 // N.B. This check is not used when switching to GUI (which already ignores
739 // rate only changes) or switching back to the desktop (where we must reset
740 // the display to the original state).
741 if (m_vrrState && m_vrrState->Enabled())
742 {
743 if (next.Resolution() == current.Resolution())
744 {
745 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Ignoring mode switch to %1Hz - VRR enabled")
746 .arg(Rate, 0, 'f', 3));
747 return true;
748 }
749 LOG(VB_GENERAL, LOG_INFO, LOC + "Allowing mode switch with VRR enabled for new resolution");
750 }
751
752 // No need for change
753 if ((next == current) && (MythDisplayMode::CompareRates(current.RefreshRate(), targetrate)))
754 {
755 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Using current mode %1x%2@%3Hz")
756 .arg(m_resolution.width()).arg(m_resolution.height()).arg(m_refreshRate));
757 return true;
758 }
759
760 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Trying mode %1x%2@%3Hz")
761 .arg(next.Width()).arg(next.Height()).arg(next.RefreshRate(), 0, 'f', 3));
762
763 if (!SwitchToVideoMode(next.Resolution(), targetrate))
764 {
765 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to change mode to %1x%2@%3Hz")
766 .arg(next.Width()).arg(next.Height()).arg(next.RefreshRate(), 0, 'f', 3));
767 return false;
768 }
769
770 if (next.Resolution() != m_resolution)
772
773 // N.B. We used a computed aspect ratio unless overridden
774 m_aspectRatioOverride = aspectoverride > 0.0 ? aspectoverride : 0.0;
776 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Switched to %1x%2@%3Hz for video %4x%5")
777 .arg(m_resolution.width()).arg(m_resolution.height())
778 .arg(m_refreshRate, 0, 'f', 3).arg(Size.width()).arg(Size.height()));
780 return true;
781}
782
786{
787 if (!m_modeComplete)
789
790 // If the current resolution is the same as the GUI resolution then do nothing
791 // as refresh rate should not be critical for the GUI.
793 {
794 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Using %1x%2@%3Hz for GUI")
795 .arg(m_resolution.width()).arg(m_resolution.height()).arg(m_refreshRate));
796 return true;
797 }
798
800 {
801 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to change mode to %1x%2@%3Hz")
802 .arg(m_guiMode.Width()).arg(m_guiMode.Height()).arg(m_guiMode.RefreshRate(), 0, 'f', 3));
803 return false;
804 }
805
806 if (Wait && (m_resolution != m_guiMode.Resolution()))
808
811 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Switched to %1x%2@%3Hz")
812 .arg(m_resolution.width()).arg(m_resolution.height()).arg(m_refreshRate, 0, 'f', 3));
813 return true;
814}
815
817{
818 return m_refreshRate;
819}
820
821std::chrono::microseconds MythDisplay::GetRefreshInterval(std::chrono::microseconds Fallback) const
822{
823 // If FreeSync or GSync are enabled, return the maximum refresh rate.
824 // N.B. This may need more work as the max may not be well defined - especially
825 // if the resolution is changing. Displays should however support at least 60Hz
826 // at all resolutions which should be fine in the vast majority of cases (as the
827 // only place the refresh interval is functionally important is in checking
828 // for double rate deinterlacing support).
829 if (m_vrrState && m_vrrState->Enabled())
830 {
831 const auto range = m_vrrState->GetRange();
832 auto max = std::get<1>(range) > 60 ? std::get<1>(range) : 60;
833 return microsecondsFromFloat(1000000.0 / max);
834 }
835
836 if (m_refreshRate > 20.0 && m_refreshRate < 200.0)
837 return microsecondsFromFloat(1000000.0 / m_refreshRate);
838 if (Fallback > 33ms) // ~30Hz
839 Fallback /= 2;
840 return Fallback;
841}
842
844{
845 auto targetrate = static_cast<double>(NAN);
846 const MythDisplayMode mode(Size, QSize(0, 0), -1.0, 0.0);
847 const auto & modes = GetVideoModes();
848 int match = MythDisplayMode::FindBestMatch(modes, mode, targetrate);
849 if (match < 0)
850 return {};
851 return modes[static_cast<size_t>(match)].RefreshRates();
852}
853
854bool MythDisplay::SwitchToVideoMode(QSize /*Size*/, double /*Framerate*/)
855{
856 return false;
857}
858
860{
861 return m_videoModes;
862}
863
882double MythDisplay::GetAspectRatio(QString &Source, bool IgnoreModeOverride)
883{
884 auto valid = [](double Aspect) { return (Aspect > 0.1 && Aspect < 10.0); };
885
886 // Override for this video mode
887 // Is this behaviour still needed?
888 if (!IgnoreModeOverride && valid(m_aspectRatioOverride))
889 {
890 Source = tr("Video mode override");
892 }
893
894 // General override for invalid/misleading EDIDs or multiscreen setups
895 // New default of -1.0 equates to square pixels for modern displays
896 bool multiscreen = MythDisplay::SpanAllScreens() && GetScreenCount() > 1;
897 double override = gCoreContext->GetFloatSettingOnHost("XineramaMonitorAspectRatio",
898 gCoreContext->GetHostName(), -1.0);
899
900 // Zero (not valid) indicates auto
901 if (valid(override))
902 {
903 Source = tr("Override");
904 return override;
905 }
906
907 // Auto for multiscreen is a best guess
908 if (multiscreen)
909 {
910 double aspect = EstimateVirtualAspectRatio();
911 if (valid(aspect))
912 {
913 Source = tr("Multiscreen estimate");
914 return aspect;
915 }
916 }
917
918 double calculated = m_resolution.isEmpty() ? 0.0 :
919 static_cast<double>(m_resolution.width()) / m_resolution.height();
920 double detected = m_physicalSize.isEmpty() ? 0.0 :
921 static_cast<double>(m_physicalSize.width()) / m_physicalSize.height();
922
923 // Assume pixel aspect ratio is 1 (square pixels)
924 if (valid(calculated))
925 {
926 if ((override < 0.0) || !valid(detected))
927 {
928 Source = tr("Square pixels");
929 return calculated;
930 }
931 }
932
933 // Based on actual physical size if available
934 if (valid(detected))
935 {
936 Source = tr("Detected");
937 return detected;
938 }
939
940 // the aspect ratio of last resort
941 Source = tr("Guessed");
942 return 16.0 / 9.0;
943}
944
946{
947 return m_edid;
948}
949
951{
952 return m_hdrState;
953}
954
956{
957 if (m_edid.Valid())
958 {
959 auto hdrdesc = m_edid.GetHDRSupport();
960 m_hdrState = MythHDR::Create(this, hdrdesc);
961 LOG(VB_GENERAL, LOG_NOTICE, LOC + QString("Supported HDR formats: %1")
962 .arg(m_hdrState->TypesToString().join(",")));
963 if (auto brightness = m_hdrState->GetMaxLuminance(); brightness > 1.0)
964 {
965 LOG(VB_GENERAL, LOG_NOTICE, LOC + QString("Display reports max brightness of %1 nits")
966 .arg(static_cast<int>(brightness)));
967 }
968 }
969}
970
981{
982 auto sortscreens = [](const QScreen* First, const QScreen* Second)
983 {
984 if (First->geometry().left() < Second->geometry().left())
985 return true;
986 if (First->geometry().top() < Second->geometry().top())
987 return true;
988 return false;
989 };
990
991 // default
992 double result = 16.0 / 9.0;
993
994 QList<QScreen*> screens;
995 if (m_screen)
996 screens = m_screen->virtualSiblings();
997 if (screens.empty())
998 return result;
999
1000 // N.B. This sorting may not be needed
1001 std::sort(screens.begin(), screens.end(), sortscreens);
1002 QList<double> aspectratios;
1003 QSize totalresolution;
1004 int lasttop = 0;
1005 int lastleft = 0;
1006 int rows = 1;
1007 int columns = 1;
1008 for (auto it = screens.constBegin() ; it != screens.constEnd(); ++it)
1009 {
1010 QRect geom = (*it)->geometry();
1011 totalresolution += geom.size();
1012 LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("%1x%2+%3+%4 %5")
1013 .arg(geom.width()).arg(geom.height()).arg(geom.left()).arg(geom.top())
1014 .arg((*it)->physicalSize().width() / (*it)->physicalSize().height()));
1015 if (lastleft < geom.left())
1016 {
1017 columns++;
1018 lastleft = geom.left();
1019 }
1020 if (lasttop < geom.top())
1021 {
1022 rows++;
1023 lasttop = geom.top();
1024 lastleft = 0;
1025 }
1026 aspectratios << (*it)->physicalSize().width() / (*it)->physicalSize().height();
1027 }
1028
1029 // If all else fails, use the total resolution and assume pixel aspect ratio
1030 // equals display aspect ratio
1031 if (!totalresolution.isEmpty())
1032 result = static_cast<double>(totalresolution.width()) / totalresolution.height();
1033
1034 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Screen layout: %1x%2").arg(rows).arg(columns));
1035 if (rows == columns)
1036 {
1037 LOG(VB_GENERAL, LOG_DEBUG, LOC + "Grid layout");
1038 }
1039 else if (rows == 1 && columns > 1)
1040 {
1041 LOG(VB_GENERAL, LOG_DEBUG, LOC + "Horizontal layout");
1042 }
1043 else if (columns == 1 && rows > 1)
1044 {
1045 LOG(VB_GENERAL, LOG_DEBUG, LOC + "Vertical layout");
1046 }
1047 else
1048 {
1049 LOG(VB_GENERAL, LOG_INFO,
1050 LOC + QString("Unsupported layout - defaulting to %1 (%2/%3)")
1051 .arg(result).arg(totalresolution.width()).arg(totalresolution.height()));
1052 return result;
1053 }
1054
1055 // validate aspect ratios - with a little fuzzyness
1056 double aspectratio = 0.0;
1057 double average = 0.0;
1058 int count = 1;
1059 for (auto it2 = aspectratios.constBegin() ; it2 != aspectratios.constEnd(); ++it2, ++count)
1060 {
1061 aspectratio += *it2;
1062 average = aspectratio / count;
1063 if (qAbs(*it2 - average) > 0.1)
1064 {
1065 LOG(VB_GENERAL, LOG_INFO, LOC +
1066 QString("Inconsistent aspect ratios - defaulting to %1 (%2/%3)")
1067 .arg(result).arg(totalresolution.width()).arg(totalresolution.height()));
1068 return result;
1069 }
1070 }
1071
1072 aspectratio = (average * columns) / rows;
1073 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Estimated aspect ratio: %1")
1074 .arg(aspectratio));
1075 return aspectratio;
1076}
1077
1079{
1080 return m_resolution;
1081}
1082
1084{
1085 return m_physicalSize;
1086}
1087
1089{
1090 // Some implementations may have their own mechanism for ensuring the mode
1091 // is updated before continuing
1093 return;
1094
1095 LOG(VB_GENERAL, LOG_INFO, LOC + "Waiting for resolution change");
1096 QEventLoop loop;
1097 QTimer timer;
1098 timer.setSingleShot(true);
1099 connect(&timer, &QTimer::timeout,
1100 &timer, [](){ LOG(VB_GENERAL, LOG_WARNING, LOC + "Timed out wating for screen change"); });
1101 QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
1102 QObject::connect(m_screen, &QScreen::geometryChanged, &loop, &QEventLoop::quit);
1103 // 500ms maximum wait
1104 timer.start(500ms);
1105 loop.exec();
1106}
1107
1109{
1110 // N.B. This isn't working as intended as it always times out rather than
1111 // exiting deliberately. It does however somehow filter out unwanted screenChanged
1112 // events that otherwise often put the widget in the wrong screen.
1113 // Needs more investigation - but for now it works:)
1114 if (!m_widget || !m_widget->windowHandle())
1115 return;
1116 LOG(VB_GENERAL, LOG_INFO, LOC + "Waiting for new screen");
1117 QEventLoop loop;
1118 QTimer timer;
1119 timer.setSingleShot(true);
1120 connect(&timer, &QTimer::timeout,
1121 &timer, [](){ LOG(VB_GENERAL, LOG_WARNING, LOC + "Timed out waiting for new screen"); });
1122 QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
1123 QObject::connect(m_widget->windowHandle(), &QWindow::screenChanged, &loop, &QEventLoop::quit);
1124 // 500ms maximum wait
1125 timer.start(500ms);
1126 loop.exec();
1127}
1128
1130{
1131 int pauselengthinms = gCoreContext->GetNumSetting("VideoModeChangePauseMS", 0);
1132 if (pauselengthinms)
1133 {
1134 LOG(VB_GENERAL, LOG_INFO, LOC +
1135 QString("Pausing %1ms for video mode switch").arg(pauselengthinms));
1136 QEventLoop loop;
1137 QTimer timer;
1138 timer.setSingleShot(true);
1139 QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
1140 // 500ms maximum wait
1141 timer.start(pauselengthinms);
1142 loop.exec();
1143 }
1144}
1145
1147{
1148 // This is intentionally formatted to match the output of xrandr for comparison
1149 if (VERBOSE_LEVEL_CHECK(VB_PLAYBACK, LOG_INFO))
1150 {
1151 LOG(VB_PLAYBACK, LOG_INFO, LOC + "Available modes:");
1152 for (auto it = m_videoModes.crbegin(); it != m_videoModes.crend(); ++it)
1153 {
1154 auto rates = (*it).RefreshRates();
1155 QStringList rateslist;
1156 for (auto it2 = rates.crbegin(); it2 != rates.crend(); ++it2)
1157 rateslist.append(QString("%1").arg(*it2, 2, 'f', 2, '0'));
1158 if (rateslist.empty())
1159 rateslist.append("Variable rate?");
1160 LOG(VB_PLAYBACK, LOG_INFO, QString("%1x%2\t%3")
1161 .arg((*it).Width()).arg((*it).Height()).arg(rateslist.join("\t")));
1162 }
1163 }
1164}
1165
1171void MythDisplay::ConfigureQtGUI(int SwapInterval, const MythCommandLineParser& CmdLine)
1172{
1173 auto forcevrr = CmdLine.toBool("vrr");
1174 bool gsyncchanged = false;
1175 bool freesyncchanged = false;
1176
1177#if CONFIG_QTWEBENGINE
1178 QApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
1179 QQuickWindow::setSceneGraphBackend("software");
1180 LOG(VB_GENERAL, LOG_INFO, LOC + "Using shared OpenGL Contexts");
1181#endif
1182
1183 // Set the default surface format. Explicitly required on some platforms.
1184 QSurfaceFormat format;
1185 // Allow overriding the default depth - use with caution as Qt will likely
1186 // crash if it cannot find a matching visual.
1187 if (qEnvironmentVariableIsSet("MYTHTV_DEPTH"))
1188 {
1189 // Note: Don't set depth and stencil to give Qt as much flexibility as possible
1190 int depth = std::clamp(qEnvironmentVariableIntValue("MYTHTV_DEPTH"), 6, 16);
1191 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Trying to force depth to '%1'").arg(depth));
1192 format.setRedBufferSize(depth);
1193 }
1194 else
1195 {
1196 format.setDepthBufferSize(0);
1197 format.setStencilBufferSize(0);
1198 }
1199 format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
1200 format.setProfile(QSurfaceFormat::CompatibilityProfile);
1201 format.setSwapInterval(SwapInterval);
1202 QSurfaceFormat::setDefaultFormat(format);
1203
1204#ifdef Q_OS_DARWIN
1205 // Without this, we can't set focus to any of the CheckBoxSetting, and most
1206 // of the MythPushButton widgets, and they don't use the themed background.
1207 QApplication::setDesktopSettingsAware(false);
1208#endif
1209
1210#if CONFIG_DRM && CONFIG_QTPRIVATEHEADERS
1211 // Avoid trying to setup DRM if we are definitely not going to use it.
1212#if CONFIG_X11
1214#endif
1215 {
1216#if CONFIG_WAYLANDEXTRAS
1217 // When vt switching this still detects wayland servers, so disabled for now
1218 //if (!MythWaylandDevice::IsAvailable())
1219#endif
1220 {
1221 MythDRMDevice::SetupDRM(CmdLine);
1222 freesyncchanged = MythDRMVRR::s_freeSyncResetOnExit;
1223 }
1224 }
1225#endif
1226
1227#if defined (Q_OS_LINUX) && CONFIG_EGL && CONFIG_X11
1228 // We want to use EGL for VAAPI/MMAL/DRMPRIME rendering to ensure we
1229 // can use zero copy video buffers for the best performance.
1230 // To force Qt to use EGL we must set 'QT_XCB_GL_INTEGRATION' to 'xcb_egl'
1231 // and this must be done before any GUI is created. If the platform plugin is
1232 // not xcb then this should have no effect.
1233 // This does however break when using NVIDIA drivers - which do not support
1234 // EGL like other drivers so we try to check the EGL vendor - and we currently
1235 // have no need for EGL with NVIDIA (that may change however).
1236 // NOTE force using EGL by setting MYTHTV_FORCE_EGL
1237 // NOTE disable using EGL by setting MYTHTV_NO_EGL
1238 // NOTE We have no Qt platform information, window/surface or logging when this is called.
1239 QString soft = qgetenv("LIBGL_ALWAYS_SOFTWARE");
1240 bool ignore = soft == "1" || soft.compare("true", Qt::CaseInsensitive) == 0;
1241 bool allow = qEnvironmentVariableIsEmpty("MYTHTV_NO_EGL") && !ignore;
1242 bool force = !qEnvironmentVariableIsEmpty("MYTHTV_FORCE_EGL");
1243 if ((force || allow) && MythDisplayX11::IsAvailable())
1244 {
1245 // N.B. By default, ignore EGL if vendor string is not returned
1246 QString vendor = MythEGL::GetEGLVendor();
1247 if (vendor.contains("nvidia", Qt::CaseInsensitive) && !force)
1248 {
1249 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Not requesting EGL for vendor '%1'").arg(vendor));
1250 }
1251 else if (!vendor.isEmpty() || force)
1252 {
1253 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Requesting EGL for vendor '%1'").arg(vendor));
1254 setenv("QT_XCB_GL_INTEGRATION", "xcb_egl", 0);
1255 }
1256 }
1257#endif
1258
1259#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
1260 // Ignore desktop scaling
1261 QApplication::setAttribute(Qt::AA_DisableHighDpiScaling);
1262#endif
1263
1264#if CONFIG_X11
1265 if (auto display = CmdLine.toString("display"); !display.isEmpty())
1267 // GSync support via libXNVCtrl
1268 // Note: FreeSync support is checked in MythDRMDevice::SetupDRM
1269 if (forcevrr)
1270 {
1271 MythGSync::ForceGSync(CmdLine.toUInt("vrr") > 0);
1272 gsyncchanged = MythGSync::s_gsyncResetOnExit;
1273 }
1274#endif
1275
1276 if (forcevrr && !(gsyncchanged || freesyncchanged))
1277 LOG(VB_GENERAL, LOG_INFO, LOC + "Variable refresh rate not adjusted");
1278}
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()
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
void CurrentScreenChanged(QScreen *qScreen)
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
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
void CurrentDPIChanged(qreal DPI)
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:30
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