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