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