MythTV  master
mythdisplay.cpp
Go to the documentation of this file.
1 //Qt
2 #include <QTimer>
3 #include <QThread>
4 #include <QApplication>
5 #include <QElapsedTimer>
6 #include <QWindow>
7 
8 // MythTV
9 #include "mythlogging.h"
10 #include "compat.h"
11 #include "mythcorecontext.h"
12 #include "mythuihelper.h"
13 #include "mythdisplay.h"
14 #include "mythegl.h"
15 #include "mythmainwindow.h"
16 
17 #ifdef Q_OS_ANDROID
18 #include "mythdisplayandroid.h"
19 #endif
20 #if defined(Q_OS_MAC)
21 #include "mythdisplayosx.h"
22 #endif
23 #ifdef USING_X11
24 #include "mythdisplayx11.h"
25 #endif
26 #ifdef USING_DRM
27 #include "mythdisplaydrm.h"
28 #endif
29 #if defined(Q_OS_WIN)
30 #include "mythdisplaywindows.h"
31 #endif
32 #ifdef USING_MMAL
33 #include "mythdisplayrpi.h"
34 #endif
35 
36 #define LOC QString("Display: ")
37 
71 {
72  static QMutex s_lock(QMutex::Recursive);
73  static MythDisplay* s_display = nullptr;
74 
75  QMutexLocker locker(&s_lock);
76 
77  if (Acquire)
78  {
79  if (s_display)
80  {
81  s_display->IncrRef();
82  }
83  else
84  {
85 #ifdef USING_X11
87  s_display = new MythDisplayX11();
88 #endif
89 #ifdef USING_MMAL
90  if (!s_display)
91  s_display = new MythDisplayRPI();
92 #endif
93 #ifdef USING_DRM
94 #if QT_VERSION >= QT_VERSION_CHECK(5,9,0)
95  // this will only work by validating the screen's serial number
96  // - which is only available with Qt 5.9
97  if (!s_display)
98  s_display = new MythDisplayDRM();
99 #endif
100 #endif
101 #if defined(Q_OS_MAC)
102  if (!s_display)
103  s_display = new MythDisplayOSX();
104 #endif
105 #ifdef Q_OS_ANDROID
106  if (!s_display)
107  s_display = new MythDisplayAndroid();
108 #endif
109 #if defined(Q_OS_WIN)
110  if (!s_display)
111  s_display = new MythDisplayWindows();
112 #endif
113  if (!s_display)
114  s_display = new MythDisplay();
115  }
116  }
117  else
118  {
119  if (s_display)
120  if (s_display->DecrRef() == 0)
121  s_display = nullptr;
122  }
123  return s_display;
124 }
125 
127 {
128  QStringList result;
129  bool spanall = false;
130  int screencount = MythDisplay::GetScreenCount();
132 
133  if (MythDisplay::SpanAllScreens() && screencount > 1)
134  {
135  spanall = true;
136  result.append(tr("Spanning %1 screens").arg(screencount));
137  result.append(tr("Total bounds") + QString("\t: %1x%2")
138  .arg(display->GetScreenBounds().width())
139  .arg(display->GetScreenBounds().height()));
140  result.append("");
141  }
142 
143  QScreen *current = display->GetCurrentScreen();
144  QList<QScreen*> screens = QGuiApplication::screens();
145  bool first = true;
146  for (auto *screen : qAsConst(screens))
147  {
148  if (!first)
149  result.append("");
150  first = false;
151 #if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
152  QString id = QString("(%1)").arg(screen->manufacturer());
153 #else
154  QString id;
155 #endif
156  if (screen == current && !spanall)
157  result.append(tr("Current screen %1 %2:").arg(screen->name()).arg(id));
158  else
159  result.append(tr("Screen %1 %2:").arg(screen->name()).arg(id));
160  result.append(tr("Size") + QString("\t\t: %1mmx%2mm")
161  .arg(screen->physicalSize().width()).arg(screen->physicalSize().height()));
162  if (screen == current)
163  {
164  QString source;
165  double aspect = display->GetAspectRatio(source);
166  result.append(tr("Aspect ratio") + QString("\t: %1 (%2)")
167  .arg(aspect, 0, 'f', 3).arg(source));
168  if (!spanall)
169  {
170  result.append(tr("Current mode") + QString("\t: %1x%2@%3Hz")
171  .arg(display->GetResolution().width()).arg(display->GetResolution().height())
172  .arg(display->GetRefreshRate(), 0, 'f', 2));
173  }
174  }
175  }
177  return result;
178 }
179 
181  : ReferenceCounter("Display")
182 {
184  DebugScreen(m_screen, "Using");
185  if (m_screen)
186  connect(m_screen, &QScreen::geometryChanged, this, &MythDisplay::GeometryChanged);
187 
188  auto *guiapp = qobject_cast<QGuiApplication *>(QCoreApplication::instance());
189  if (guiapp == nullptr)
190  return;
191 
192  connect(guiapp, &QGuiApplication::screenRemoved, this, &MythDisplay::ScreenRemoved);
193  connect(guiapp, &QGuiApplication::screenAdded, this, &MythDisplay::ScreenAdded);
194 #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
195  connect(guiapp, &QGuiApplication::primaryScreenChanged, this, &MythDisplay::PrimaryScreenChanged);
196 #endif
197 }
198 
200 {
201  LOG(VB_GENERAL, LOG_INFO, LOC + "Deleting");
202 }
203 
217 void MythDisplay::SetWidget(QWidget *MainWindow)
218 {
219  QWidget* oldwidget = m_widget;
220  m_widget = MainWindow;
221  if (!m_modeComplete)
223 
224  QWindow* oldwindow = m_window;
225  if (m_widget)
226  m_window = m_widget->windowHandle();
227  else
228  m_window = nullptr;
229 
230  if (m_widget && (m_widget != oldwidget))
231  LOG(VB_GENERAL, LOG_INFO, LOC + "Have main widget");
232 
233  if (m_window && (m_window != oldwindow))
234  {
235  LOG(VB_GENERAL, LOG_INFO, LOC + "Have main window");
236 
237  connect(m_window, &QWindow::screenChanged, this, &MythDisplay::ScreenChanged, Qt::UniqueConnection);
238  QScreen *desired = GetDesiredScreen();
239  // If we have changed the video mode for the old screen then reset
240  // it to the default/desktop mode
241  SwitchToDesktop();
242  // Ensure we completely re-initialise when the new screen is set
243  m_initialised = false;
244  if (desired != m_screen)
245  DebugScreen(desired, "Moving to");
246  m_window->setScreen(desired);
247  // WaitForNewScreen doesn't work as intended. It successfully filters
248  // out unwanted screenChanged signals after moving screens - but always
249  //times out. This just delays startup by 500ms - so ignore on startup as it isn't needed.
250  if (!m_firstScreenChange)
252  m_firstScreenChange = false;
254  return;
255  }
256 }
257 
259 {
260  return QGuiApplication::screens().size();
261 }
262 
264 {
265  if (m_physicalSize.isEmpty() || m_resolution.isEmpty())
266  return 1.0;
267 
268  return (m_physicalSize.width() / static_cast<double>(m_resolution.width())) /
269  (m_physicalSize.height() / static_cast<double>(m_resolution.height()));
270 }
271 
273 {
274  return m_guiMode.Resolution();
275 }
276 
278 {
279  return m_screenBounds;
280 }
281 
295 {
296  return m_screen;
297 }
298 
300 {
301  QScreen* newscreen = nullptr;
302 
303  // If geometry is overriden at the command line level, try and determine
304  // which screen that applies to (if any).
305  // N.B. So many potential issues here e.g. should the geometry override be
306  // ignored after first use? (as it will continue to override the screen
307  // regardless of changes to screen preference).
309  {
310  // this matches the check in MythMainWindow
311  bool windowed = GetMythDB()->GetBoolSetting("RunFrontendInWindow", false) &&
313  QRect override = MythUIHelper::GetGeometryOverride();
314  // When windowed, we use topleft as a best guess as to which screen we belong in.
315  // When fullscreen, Qt appears to use the reverse - though this may be
316  // the window manager rather than Qt. So could be wrong.
317  QPoint point = windowed ? override.topLeft() : override.bottomRight();
318  for (QScreen *screen : QGuiApplication::screens())
319  {
320  if (screen->geometry().contains(point))
321  {
322  newscreen = screen;
323  LOG(VB_GENERAL, LOG_INFO, LOC + QString(
324  "Geometry override places window in screen '%1'").arg(newscreen->name()));
325  break;
326  }
327  }
328  }
329 
330  // If spanning all screens, then always use the primary display
331  if (!newscreen && MythDisplay::SpanAllScreens())
332  {
333  LOG(VB_GENERAL, LOG_INFO, LOC + "Using primary screen for multiscreen");
334  newscreen = QGuiApplication::primaryScreen();
335  }
336 
337  QString name = gCoreContext->GetSetting("XineramaScreen", nullptr);
338  // Lookup by name
339  if (!newscreen)
340  {
341  for (QScreen *screen : QGuiApplication::screens())
342  {
343  if (!name.isEmpty() && name == screen->name())
344  {
345  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Found screen '%1'").arg(name));
346  newscreen = screen;
347  }
348  }
349  }
350 
351  // No name match. These were previously numbers.
352  if (!newscreen)
353  {
354  bool ok = false;
355  int screen_num = name.toInt(&ok);
356  QList<QScreen *>screens = QGuiApplication::screens();
357  if (ok && (screen_num >= 0) && (screen_num < screens.size()))
358  {
359  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Found screen number %1 (%2)")
360  .arg(name).arg(screens[screen_num]->name()));
361  newscreen = screens[screen_num];
362  }
363  }
364 
365  // For anything else, return the primary screen.
366  if (!newscreen)
367  {
368  QScreen *primary = QGuiApplication::primaryScreen();
369  if (name.isEmpty() && primary)
370  {
371  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Defaulting to primary screen (%1)")
372  .arg(primary->name()));
373  }
374  else if (name != "-1" && primary)
375  {
376  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Screen '%1' not found, defaulting to primary screen (%2)")
377  .arg(name).arg(primary->name()));
378  }
379  newscreen = primary;
380  }
381 
382  return newscreen;
383 }
384 
387 void MythDisplay::ScreenChanged(QScreen *qScreen)
388 {
389  if (qScreen == m_screen)
390  return;
391  if (m_screen)
392  disconnect(m_screen, nullptr, this, nullptr);
393  DebugScreen(qScreen, "Changed to");
394  m_screen = qScreen;
395  connect(m_screen, &QScreen::geometryChanged, this, &MythDisplay::GeometryChanged);
396  Initialise();
397  emit CurrentScreenChanged(qScreen);
398 }
399 
400 void MythDisplay::PrimaryScreenChanged(QScreen* qScreen)
401 {
402  DebugScreen(qScreen, "New primary");
403 }
404 
405 void MythDisplay::ScreenAdded(QScreen* qScreen)
406 {
407  DebugScreen(qScreen, "New");
408  emit ScreenCountChanged(QGuiApplication::screens().size());
409 }
410 
411 void MythDisplay::ScreenRemoved(QScreen* qScreen)
412 {
413  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Screen '%1' removed").arg(qScreen->name()));
414  emit ScreenCountChanged(QGuiApplication::screens().size());
415 }
416 
417 void MythDisplay::GeometryChanged(const QRect &Geo)
418 {
419  LOG(VB_GENERAL, LOG_INFO, LOC + QString("New screen geometry: %1x%2+%3+%4")
420  .arg(Geo.width()).arg(Geo.height()).arg(Geo.left()).arg(Geo.top()));
421 }
422 
430 {
431  // Certain platform implementations do not have a window to access at startup
432  // and hence use this implementation. Flag the status as incomplete to ensure
433  // we try to retrieve the full details at the first opportunity.
434  m_modeComplete = false;
435  m_edid = MythEDID();
436  QScreen *screen = GetCurrentScreen();
437  if (!screen)
438  {
439  m_refreshRate = 60.0;
440  m_physicalSize = QSize(0, 0);
441  m_resolution = QSize(1920, 1080);
442  return;
443  }
444  m_refreshRate = screen->refreshRate();
445  m_resolution = screen->size();
446  m_physicalSize = QSize(static_cast<int>(screen->physicalSize().width()),
447  static_cast<int>(screen->physicalSize().height()));
448 }
449 
452 {
453  return gCoreContext->GetSetting("XineramaScreen", nullptr) == "-1";
454 }
455 
456 QString MythDisplay::GetExtraScreenInfo(QScreen *qScreen)
457 {
458 #if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
459  QString mfg = qScreen->manufacturer();
460  if (mfg.isEmpty())
461  mfg = "Unknown";
462  QString model = qScreen->model();
463  if (model.isEmpty())
464  model = "Unknown";
465  return QString("(Make: %1 Model: %2)").arg(mfg).arg(model);
466 #else
467  Q_UNUSED(qScreen);
468  return QString();
469 #endif
470 }
471 
472 void MythDisplay::DebugScreen(QScreen *qScreen, const QString &Message)
473 {
474  if (!qScreen)
475  return;
476 
477  QRect geom = qScreen->geometry();
478  QString extra = GetExtraScreenInfo(qScreen);
479 
480  LOG(VB_GENERAL, LOG_INFO, LOC + QString("%1 screen '%2' %3")
481  .arg(Message).arg(qScreen->name()).arg(extra));
482 
483  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Geometry: %1x%2+%3+%4 Size(Qt): %5mmx%6mm")
484  .arg(geom.width()).arg(geom.height()).arg(geom.left()).arg(geom.top())
485  .arg(qScreen->physicalSize().width()).arg(qScreen->physicalSize().height()));
486 
487  if (qScreen->virtualGeometry() != geom)
488  {
489  geom = qScreen->virtualGeometry();
490  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Total virtual geometry: %1x%2+%3+%4")
491  .arg(geom.width()).arg(geom.height()).arg(geom.left()).arg(geom.top()));
492  }
493 }
494 
496 {
497  m_videoModes.clear();
498  m_overrideVideoModes.clear();
501 
502  // Set the desktop mode - which is the mode at startup. We must always return
503  // the screen to this mode.
504  if (!m_initialised)
505  {
506  // Only ever set this once or after a screen change
507  m_initialised = true;
509  LOG(VB_GENERAL, LOG_NOTICE, LOC + QString("Desktop video mode: %1x%2 %3Hz")
510  .arg(m_resolution.width()).arg(m_resolution.height()).arg(m_refreshRate, 0, 'f', 3));
511  if (m_edid.Valid())
512  {
513  if (m_edid.IsSRGB())
514  LOG(VB_GENERAL, LOG_NOTICE, LOC + "Display is using sRGB colourspace");
515  else
516  LOG(VB_GENERAL, LOG_NOTICE, LOC + "Display has custom colourspace");
517  }
518  }
519 
520  // Set the gui mode from database settings
521  int pixelwidth = m_resolution.width();
522  int pixelheight = m_resolution.height();
523  int mmwidth = m_physicalSize.width();
524  int mmheight = m_physicalSize.height();
525  double refreshrate = m_refreshRate;
526  double aspectratio = 0.0;
527  GetMythDB()->GetResolutionSetting("GuiVidMode", pixelwidth, pixelheight, aspectratio, refreshrate);
528  GetMythDB()->GetResolutionSetting("DisplaySize", mmwidth, mmheight);
529  m_guiMode = MythDisplayMode(pixelwidth, pixelheight, mmwidth, mmheight, -1.0, refreshrate);
530 
531  // Set default video mode
532  pixelwidth = pixelheight = 0;
533  GetMythDB()->GetResolutionSetting("TVVidMode", pixelwidth, pixelheight, aspectratio, refreshrate);
534  m_videoMode = MythDisplayMode(pixelwidth, pixelheight, mmwidth, mmheight, aspectratio, refreshrate);
535 
536  // Initialise video override modes
537  for (int i = 0; true; ++i)
538  {
539  int iw = 0;
540  int ih = 0;
541  int ow = 0;
542  int oh = 0;
543  double iaspect = 0.0;
544  double oaspect = 0.0;
545  double irate = 0.0;
546  double orate = 0.0;
547 
548  GetMythDB()->GetResolutionSetting("VidMode", iw, ih, iaspect, irate, i);
549  GetMythDB()->GetResolutionSetting("TVVidMode", ow, oh, oaspect, orate, i);
550 
551  if (!(iw || ih || !qFuzzyIsNull(irate)) || !(ih && ow && oh))
552  break;
553 
554  uint64_t key = MythDisplayMode::CalcKey(QSize(iw, ih), irate);
555  MythDisplayMode scr(QSize(ow, oh), QSize(mmwidth, mmheight), oaspect, orate);
556  m_overrideVideoModes[key] = scr;
557  }
558 }
559 
560 
569 {
570  QList<QScreen*> screens = QGuiApplication::screens();
571  for (auto *screen : screens)
572  {
573  QRect dim = screen->geometry();
574  QString extra = MythDisplay::GetExtraScreenInfo(screen);
575  LOG(VB_GUI, LOG_INFO, LOC + QString("Screen %1: %2x%3 %4")
576  .arg(screen->name()).arg(dim.width()).arg(dim.height()).arg(extra));
577  }
578 
579  QScreen *primary = QGuiApplication::primaryScreen();
580  LOG(VB_GUI, LOG_INFO, LOC +QString("Primary screen: %1.").arg(primary->name()));
581 
582  int numScreens = MythDisplay::GetScreenCount();
583  QSize dim = primary->virtualSize();
584  LOG(VB_GUI, LOG_INFO, LOC + QString("Total desktop dim: %1x%2, over %3 screen[s].")
585  .arg(dim.width()).arg(dim.height()).arg(numScreens));
586 
588  {
589  LOG(VB_GUI, LOG_INFO, LOC + QString("Using entire desktop."));
590  m_screenBounds = primary->virtualGeometry();
591  return;
592  }
593 
594  if (GetMythDB()->GetBoolSetting("RunFrontendInWindow", false))
595  {
596  LOG(VB_GUI, LOG_INFO, LOC + "Running in a window");
597  // This doesn't include the area occupied by the
598  // Windows taskbar, or the Mac OS X menu bar and Dock
599  m_screenBounds = m_screen->availableGeometry();
600  }
601  else
602  {
603  m_screenBounds = m_screen->geometry();
604  }
605 
606  LOG(VB_GUI, LOG_INFO, LOC + QString("Using screen %1: %2x%3 at %4+%5")
607  .arg(m_screen->name()).arg(m_screenBounds.width()).arg(m_screenBounds.height())
608  .arg(m_screenBounds.left()).arg(m_screenBounds.top()));
609 }
610 
618 {
619  return Size.width() > m_resolution.width() || Size.height() > m_resolution.height();
620 }
621 
627 {
629  if (current == m_desktopMode)
630  return;
632 }
633 
637 bool MythDisplay::SwitchToVideo(QSize Size, double Rate)
638 {
639  if (!m_modeComplete)
641 
644  double targetrate = 0.0;
645  double aspectoverride = 0.0;
646 
647  // try to find video override mode
649  Size.width(), Size.height(), Rate);
650 
651  if (key != 0)
652  {
653  next = m_overrideVideoModes[key];
654  if (next.AspectRatio() > 0.0)
655  aspectoverride = next.AspectRatio();
656  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Found custom screen override %1x%2 Aspect %3")
657  .arg(next.Width()).arg(next.Height()).arg(aspectoverride));
658  }
659 
660  // If requested refresh rate is 0, attempt to match video fps
661  if (qFuzzyIsNull(next.RefreshRate()))
662  {
663  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Trying to match best refresh rate %1Hz")
664  .arg(Rate, 0, 'f', 3));
665  next.SetRefreshRate(Rate);
666  }
667 
668  // need to change video mode?
670  if ((next == current) && (MythDisplayMode::CompareRates(current.RefreshRate(), targetrate)))
671  {
672  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Using current mode %1x%2@%3Hz")
673  .arg(m_resolution.width()).arg(m_resolution.height()).arg(m_refreshRate));
674  return true;
675  }
676 
677  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Trying mode %1x%2@%3Hz")
678  .arg(next.Width()).arg(next.Height()).arg(next.RefreshRate(), 0, 'f', 3));
679 
680  if (!SwitchToVideoMode(next.Resolution(), targetrate))
681  {
682  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to change mode to %1x%2@%3Hz")
683  .arg(next.Width()).arg(next.Height()).arg(next.RefreshRate(), 0, 'f', 3));
684  return false;
685  }
686 
687  if (next.Resolution() != m_resolution)
689 
690  // N.B. We used a computed aspect ratio unless overridden
691  m_aspectRatioOverride = aspectoverride > 0.0 ? aspectoverride : 0.0;
693  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Switched to %1x%2@%3Hz for video %4x%5")
694  .arg(m_resolution.width()).arg(m_resolution.height())
695  .arg(m_refreshRate, 0, 'f', 3).arg(Size.width()).arg(Size.height()));
697  return true;
698 }
699 
703 {
704  if (!m_modeComplete)
706 
707  // If the current resolution is the same as the GUI resolution then do nothing
708  // as refresh rate should not be critical for the GUI.
710  {
711  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Using %1x%2@%3Hz for GUI")
712  .arg(m_resolution.width()).arg(m_resolution.height()).arg(m_refreshRate));
713  return true;
714  }
715 
717  {
718  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to change mode to %1x%2@%3Hz")
719  .arg(m_guiMode.Width()).arg(m_guiMode.Height()).arg(m_guiMode.RefreshRate(), 0, 'f', 3));
720  return false;
721  }
722 
723  if (Wait && (m_resolution != m_guiMode.Resolution()))
725 
727  m_aspectRatioOverride = 0.0;
728  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Switched to %1x%2@%3Hz")
729  .arg(m_resolution.width()).arg(m_resolution.height()).arg(m_refreshRate, 0, 'f', 3));
730  return true;
731 }
732 
733 double MythDisplay::GetRefreshRate(void) const
734 {
735  return m_refreshRate;
736 }
737 
738 int MythDisplay::GetRefreshInterval(int Fallback) const
739 {
740  if (m_refreshRate > 20.0 && m_refreshRate < 200.0)
741  return static_cast<int>(lround(1000000.0 / m_refreshRate));
742  if (Fallback > 33000) // ~30Hz
743  Fallback /= 2;
744  return Fallback;
745 }
746 
747 std::vector<double> MythDisplay::GetRefreshRates(QSize Size)
748 {
749  auto targetrate = static_cast<double>(NAN);
750  const MythDisplayMode mode(Size, QSize(0, 0), -1.0, 0.0);
751  const vector<MythDisplayMode>& modes = GetVideoModes();
752  int match = MythDisplayMode::FindBestMatch(modes, mode, targetrate);
753  if (match < 0)
754  return std::vector<double>();
755  return modes[static_cast<size_t>(match)].RefreshRates();
756 }
757 
758 bool MythDisplay::SwitchToVideoMode(QSize /*Size*/, double /*Framerate*/)
759 {
760  return false;
761 }
762 
763 const vector<MythDisplayMode> &MythDisplay::GetVideoModes(void)
764 {
765  return m_videoModes;
766 }
767 
786 double MythDisplay::GetAspectRatio(QString &Source, bool IgnoreModeOverride)
787 {
788  auto valid = [](double Aspect) { return (Aspect > 0.1 && Aspect < 10.0); };
789 
790  // Override for this video mode
791  // Is this behaviour still needed?
792  if (!IgnoreModeOverride && valid(m_aspectRatioOverride))
793  {
794  Source = tr("Video mode override");
795  return m_aspectRatioOverride;
796  }
797 
798  // General override for invalid/misleading EDIDs or multiscreen setups
799  bool multiscreen = MythDisplay::SpanAllScreens() && GetScreenCount() > 1;
800  double override = gCoreContext->GetFloatSettingOnHost("XineramaMonitorAspectRatio",
801  gCoreContext->GetHostName(), 0.0);
802 
803  // Zero (not valid) indicates auto
804  if (valid(override))
805  {
806  Source = tr("Override");
807  return override;
808  }
809 
810  // Auto for multiscreen is a best guess
811  if (multiscreen)
812  {
813  double aspect = EstimateVirtualAspectRatio();
814  if (valid(aspect))
815  {
816  Source = tr("Multiscreen estimate");
817  return aspect;
818  }
819  }
820 
821  // Based on actual physical size if available
822  if (!m_physicalSize.isEmpty())
823  {
824  double aspect = static_cast<double>(m_physicalSize.width()) / m_physicalSize.height();
825  if (valid(aspect))
826  {
827  Source = tr("Detected");
828  return aspect;
829  }
830  }
831 
832  // Assume pixel aspect ratio is 1 (square pixels)
833  if (!m_resolution.isEmpty())
834  {
835  double aspect = static_cast<double>(m_resolution.width()) / m_resolution.height();
836  if (valid(aspect))
837  {
838  Source = tr("Fallback");
839  return aspect;
840  }
841  }
842 
843  // the aspect ratio of last resort
844  Source = tr("Guessed");
845  return 16.0 / 9.0;
846 }
847 
849 {
850  return m_edid;
851 }
852 
863 {
864  auto sortscreens = [](const QScreen* First, const QScreen* Second)
865  {
866  if (First->geometry().left() < Second->geometry().left())
867  return true;
868  if (First->geometry().top() < Second->geometry().top())
869  return true;
870  return false;
871  };
872 
873  // default
874  double result = 16.0 / 9.0;
875 
876  QList<QScreen*> screens;
877  if (m_screen)
878  screens = m_screen->virtualSiblings();
879  if (screens.empty())
880  return result;
881 
882  // N.B. This sorting may not be needed
883  std::sort(screens.begin(), screens.end(), sortscreens);
884  QList<double> aspectratios;
885  QSize totalresolution;
886  int lasttop = 0;
887  int lastleft = 0;
888  int rows = 1;
889  int columns = 1;
890  for (auto it = screens.constBegin() ; it != screens.constEnd(); ++it)
891  {
892  QRect geom = (*it)->geometry();
893  totalresolution += geom.size();
894  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("%1x%2+%3+%4 %5")
895  .arg(geom.width()).arg(geom.height()).arg(geom.left()).arg(geom.top())
896  .arg((*it)->physicalSize().width() / (*it)->physicalSize().height()));
897  if (lastleft < geom.left())
898  {
899  columns++;
900  lastleft = geom.left();
901  }
902  if (lasttop < geom.top())
903  {
904  rows++;
905  lasttop = geom.top();
906  lastleft = 0;
907  }
908  aspectratios << (*it)->physicalSize().width() / (*it)->physicalSize().height();
909  }
910 
911  // If all else fails, use the total resolution and assume pixel aspect ratio
912  // equals display aspect ratio
913  if (!totalresolution.isEmpty())
914  result = static_cast<double>(totalresolution.width()) / totalresolution.height();
915 
916  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Screen layout: %1x%2").arg(rows).arg(columns));
917  if (rows == columns)
918  {
919  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Grid layout");
920  }
921  else if (rows == 1 && columns > 1)
922  {
923  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Horizontal layout");
924  }
925  else if (columns == 1 && rows > 1)
926  {
927  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Vertical layout");
928  }
929  else
930  {
931  LOG(VB_GENERAL, LOG_INFO,
932  LOC + QString("Unsupported layout - defaulting to %1 (%2/%3)")
933  .arg(result).arg(totalresolution.width()).arg(totalresolution.height()));
934  return result;
935  }
936 
937  // validate aspect ratios - with a little fuzzyness
938  double aspectratio = 0.0;
939  double average = 0.0;
940  int count = 1;
941  for (auto it2 = aspectratios.constBegin() ; it2 != aspectratios.constEnd(); ++it2, ++count)
942  {
943  aspectratio += *it2;
944  average = aspectratio / count;
945  if (qAbs(*it2 - average) > 0.1)
946  {
947  LOG(VB_GENERAL, LOG_INFO, LOC +
948  QString("Inconsistent aspect ratios - defaulting to %1 (%2/%3)")
949  .arg(result).arg(totalresolution.width()).arg(totalresolution.height()));
950  return result;
951  }
952  }
953 
954  aspectratio = (average * columns) / rows;
955  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Estimated aspect ratio: %1")
956  .arg(aspectratio));
957  return aspectratio;
958 }
959 
961 {
962  return m_resolution;
963 }
964 
966 {
967  return m_physicalSize;
968 }
969 
971 {
972  // Some implementations may have their own mechanism for ensuring the mode
973  // is updated before continuing
975  return;
976 
977  LOG(VB_GENERAL, LOG_INFO, LOC + "Waiting for resolution change");
978  QEventLoop loop;
979  QTimer timer;
980  timer.setSingleShot(true);
981  connect(&timer, &QTimer::timeout, [](){ LOG(VB_GENERAL, LOG_WARNING, LOC + "Timed out wating for screen change"); });
982  QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
983  QObject::connect(m_screen, &QScreen::geometryChanged, &loop, &QEventLoop::quit);
984  // 500ms maximum wait
985  timer.start(500);
986  loop.exec();
987 }
988 
990 {
991  // N.B. This isn't working as intended as it always times out rather than
992  // exiting deliberately. It does however somehow filter out unwanted screenChanged
993  // events that otherwise often put the widget in the wrong screen.
994  // Needs more investigation - but for now it works:)
995  if (!m_widget || (m_widget && !m_widget->windowHandle()))
996  return;
997  LOG(VB_GENERAL, LOG_INFO, LOC + "Waiting for new screen");
998  QEventLoop loop;
999  QTimer timer;
1000  timer.setSingleShot(true);
1001  connect(&timer, &QTimer::timeout, [](){ LOG(VB_GENERAL, LOG_WARNING, LOC + "Timed out waiting for new screen"); });
1002  QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
1003  QObject::connect(m_widget->windowHandle(), &QWindow::screenChanged, &loop, &QEventLoop::quit);
1004  // 500ms maximum wait
1005  timer.start(500);
1006  loop.exec();
1007 }
1008 
1010 {
1011  int pauselengthinms = gCoreContext->GetNumSetting("VideoModeChangePauseMS", 0);
1012  if (pauselengthinms)
1013  {
1014  LOG(VB_GENERAL, LOG_INFO, LOC +
1015  QString("Pausing %1ms for video mode switch").arg(pauselengthinms));
1016  QEventLoop loop;
1017  QTimer timer;
1018  timer.setSingleShot(true);
1019  QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
1020  // 500ms maximum wait
1021  timer.start(pauselengthinms);
1022  loop.exec();
1023  }
1024 }
1025 
1026 void MythDisplay::DebugModes(void) const
1027 {
1028  // This is intentionally formatted to match the output of xrandr for comparison
1029  if (VERBOSE_LEVEL_CHECK(VB_PLAYBACK, LOG_INFO))
1030  {
1031  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Available modes:");
1032  for (auto it = m_videoModes.crbegin(); it != m_videoModes.crend(); ++it)
1033  {
1034  auto rates = (*it).RefreshRates();
1035  QStringList rateslist;
1036  for (auto it2 = rates.crbegin(); it2 != rates.crend(); ++it2)
1037  rateslist.append(QString("%1").arg(*it2, 2, 'f', 2, '0'));
1038  if (rateslist.empty())
1039  rateslist.append("Variable rate?");
1040  LOG(VB_PLAYBACK, LOG_INFO, QString("%1x%2\t%3")
1041  .arg((*it).Width()).arg((*it).Height()).arg(rateslist.join("\t")));
1042  }
1043  }
1044 }
1045 
1051 void MythDisplay::ConfigureQtGUI(int SwapInterval)
1052 {
1053  // Set the default surface format. Explicitly required on some platforms.
1054  QSurfaceFormat format;
1055  format.setDepthBufferSize(0);
1056  format.setStencilBufferSize(0);
1057  format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
1058  format.setProfile(QSurfaceFormat::CompatibilityProfile);
1059  format.setSwapInterval(SwapInterval);
1060  QSurfaceFormat::setDefaultFormat(format);
1061 
1062 #ifdef Q_OS_MAC
1063  // Without this, we can't set focus to any of the CheckBoxSetting, and most
1064  // of the MythPushButton widgets, and they don't use the themed background.
1065  QApplication::setDesktopSettingsAware(false);
1066 #endif
1067 #if defined (Q_OS_LINUX) && defined (USING_EGL)
1068  // We want to use EGL for VAAPI/MMAL/DRMPRIME rendering to ensure we
1069  // can use zero copy video buffers for the best performance (N.B. not tested
1070  // on AMD desktops). To force Qt to use EGL we must set 'QT_XCB_GL_INTEGRATION'
1071  // to 'xcb_egl' and this must be done before any GUI is created. If the platform
1072  // plugin is not xcb then this should have no effect.
1073  // This does however break when using NVIDIA drivers - which do not support
1074  // EGL like other drivers so we try to check the EGL vendor - and we currently
1075  // have no need for EGL with NVIDIA (that may change however).
1076  // NOTE force using EGL by setting MYTHTV_FORCE_EGL
1077  // NOTE disable using EGL by setting MYTHTV_NO_EGL
1078  // NOTE We have no Qt platform information, window/surface or logging when this is called.
1079  QString soft = qgetenv("LIBGL_ALWAYS_SOFTWARE");
1080  bool ignore = soft == "1" || soft.compare("true", Qt::CaseInsensitive) == 0;
1081  bool allow = qgetenv("MYTHTV_NO_EGL").isEmpty() && !ignore;
1082  bool force = !qgetenv("MYTHTV_FORCE_EGL").isEmpty();
1083  if (force || allow)
1084  {
1085  // N.B. By default, ignore EGL if vendor string is not returned
1086  QString vendor = MythEGL::GetEGLVendor();
1087  if (vendor.contains("nvidia", Qt::CaseInsensitive) && !force)
1088  {
1089  qInfo() << LOC + QString("Not requesting EGL for vendor '%1'").arg(vendor);
1090  }
1091  else if (!vendor.isEmpty() || force)
1092  {
1093  qInfo() << LOC + QString("Requesting EGL for '%1'").arg(vendor);
1094  setenv("QT_XCB_GL_INTEGRATION", "xcb_egl", 0);
1095  }
1096  }
1097 
1098  // This makes Xlib calls thread-safe which seems to be required for hardware
1099  // accelerated Flash playback to work without causing mythfrontend to abort.
1100  QApplication::setAttribute(Qt::AA_X11InitThreads);
1101 #endif
1102 #ifdef Q_OS_ANDROID
1103  //QApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
1104 #endif
1105 
1106  // Ignore desktop scaling
1107  QApplication::setAttribute(Qt::AA_DisableHighDpiScaling);
1108 }
bool IsSRGB(void) const
Definition: mythedid.cpp:67
QRect GetScreenBounds(void)
static void ConfigureQtGUI(int SwapInterval=1)
Shared static initialistaion code for all MythTV GUI applications.
int GetRefreshInterval(int Fallback) const
virtual void UpdateCurrentMode(void)
Retrieve screen details.
static void PrimaryScreenChanged(QScreen *qScreen)
QScreen * m_screen
Definition: mythdisplay.h:91
double GetPixelAspectRatio(void)
static QStringList GetDescription(void)
QSize GetGUIResolution(void)
channel setAttribute("chanid", chanID)
vector< MythDisplayMode > m_videoModes
Definition: mythdisplay.h:92
static uint64_t CalcKey(QSize Size, double Rate)
static QString GetEGLVendor(void)
Definition: mythegl.cpp:85
bool m_firstScreenChange
Definition: mythdisplay.h:99
General purpose reference counter.
double GetRefreshRate(void) const
static bool WindowIsAlwaysFullscreen(void)
Return true if the current platform only supports fullscreen windows.
MythEDID m_edid
Definition: mythdisplay.h:88
double GetFloatSettingOnHost(const QString &key, const QString &host, double defaultval=0.0)
void SetWidget(QWidget *MainWindow)
Set the QWidget and QWindow in use.
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
std::vector< double > GetRefreshRates(QSize Size)
QSize m_resolution
Definition: mythdisplay.h:86
#define LOC
Definition: mythdisplay.cpp:36
int Width(void) const
bool SwitchToVideo(QSize Size, double Rate=0.0)
Switches to the resolution and refresh rate defined in the database for the specified video resolutio...
static QString GetExtraScreenInfo(QScreen *qScreen)
static QRect GetGeometryOverride(void)
Return the raw geometry override rectangle.
static bool CompareRates(double First, double Second, double Precision=0.01)
Determine whether two rates are considered equal with the given precision.
void Initialise(void)
QScreen * GetCurrentScreen(void)
Return a pointer to the screen to use.
QSize Resolution(void) const
QWindow * m_window
Definition: mythdisplay.h:90
bool m_waitForModeChanges
Definition: mythdisplay.h:82
double EstimateVirtualAspectRatio(void)
Estimate the overall display aspect ratio for multi screen setups.
double RefreshRate(void) const
double GetAspectRatio(QString &Source, bool IgnoreModeOverride=false)
Returns current screen aspect ratio.
static MythDisplay * AcquireRelease(bool Acquire=true)
Definition: mythdisplay.cpp:70
virtual int IncrRef(void)
Increments reference count.
MythDisplayMode m_videoMode
Definition: mythdisplay.h:103
bool m_modeComplete
Definition: mythdisplay.h:83
#define VERBOSE_LEVEL_CHECK(_MASK_, _LEVEL_)
Definition: mythlogging.h:14
QSize GetPhysicalSize(void)
void CurrentScreenChanged(QScreen *qScreen)
bool SwitchToGUI(bool Wait=false)
Switches to the GUI resolution.
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
QString GetSetting(const QString &key, const QString &defaultval="")
void ScreenCountChanged(int Screens)
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
void ScreenRemoved(QScreen *qScreen)
MythEDID & GetEDID(void)
bool NextModeIsLarger(QSize Size)
Check whether the next mode is larger in size than the current mode.
MythDisplayMode m_desktopMode
Definition: mythdisplay.h:101
QSize GetResolution(void)
void WaitForNewScreen(void)
DisplayModeMap m_overrideVideoModes
Definition: mythdisplay.h:104
MythDisplayMode m_guiMode
Definition: mythdisplay.h:102
static uint64_t FindBestScreen(const DisplayModeMap &Map, int Width, int Height, double Rate)
void SwitchToDesktop(void)
Return the screen to the original desktop video mode.
bool m_initialised
Definition: mythdisplay.h:98
static int FindBestMatch(const vector< MythDisplayMode > &Modes, const MythDisplayMode &Mode, double &TargetRate)
void InitScreenBounds(void)
Get screen size from Qt while respecting the user's multiscreen settings.
virtual bool SwitchToVideoMode(QSize Size, double Framerate)
double m_aspectRatioOverride
Definition: mythdisplay.h:85
static void PauseForModeSwitch(void)
int GetNumSetting(const QString &key, int defaultval=0)
~MythDisplay() override
PictureAttribute next(PictureAttributeSupported Supported, PictureAttribute Attribute)
void WaitForScreenChange(void)
static int GetScreenCount(void)
bool Valid(void) const
Definition: mythedid.cpp:32
void DebugModes(void) const
virtual const vector< MythDisplayMode > & GetVideoModes(void)
void ScreenAdded(QScreen *qScreen)
#define setenv(x, y, z)
Definition: compat.h:156
QSize m_physicalSize
Definition: mythdisplay.h:87
MythDB * GetMythDB(void)
Definition: mythdb.cpp:46
QWidget * m_widget
Definition: mythdisplay.h:89
int Height(void) const
static bool IsGeometryOverridden(void)
QString GetHostName(void)
QRect m_screenBounds
Definition: mythdisplay.h:100
double m_refreshRate
Definition: mythdisplay.h:84
static bool IsAvailable(void)
static bool SpanAllScreens(void)
Return true if the MythTV windows should span all screens.
static void DebugScreen(QScreen *qScreen, const QString &Message)
virtual void ScreenChanged(QScreen *qScreen)
The actual screen in use has changed. We must use it.
static void GeometryChanged(const QRect &Geometry)
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
static QScreen * GetDesiredScreen(void)