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