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 = qGuiApp->screens();
145  bool first = true;
146  for (auto it = screens.cbegin(); it != screens.cend(); ++it)
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((*it)->manufacturer());
153 #else
154  QString id;
155 #endif
156  if ((*it) == current && !spanall)
157  result.append(tr("Current screen %1 %2:").arg((*it)->name()).arg(id));
158  else
159  result.append(tr("Screen %1 %2:").arg((*it)->name()).arg(id));
160  result.append(tr("Size") + QString("\t\t: %1mmx%2mm")
161  .arg((*it)->physicalSize().width()).arg((*it)->physicalSize().height()));
162  if ((*it) == 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  connect(qGuiApp, &QGuiApplication::screenRemoved, this, &MythDisplay::ScreenRemoved);
189  connect(qGuiApp, &QGuiApplication::screenAdded, this, &MythDisplay::ScreenAdded);
190 #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
191  connect(qGuiApp, &QGuiApplication::primaryScreenChanged, this, &MythDisplay::PrimaryScreenChanged);
192 #endif
193 }
194 
196 {
197  LOG(VB_GENERAL, LOG_INFO, LOC + "Deleting");
198 }
199 
213 void MythDisplay::SetWidget(QWidget *MainWindow)
214 {
215  QWidget* oldwidget = m_widget;
216  m_widget = MainWindow;
217  if (!m_modeComplete)
219 
220  QWindow* oldwindow = m_window;
221  if (m_widget)
222  m_window = m_widget->windowHandle();
223  else
224  m_window = nullptr;
225 
226  if (m_widget && (m_widget != oldwidget))
227  LOG(VB_GENERAL, LOG_INFO, LOC + "Have main widget");
228 
229  if (m_window && (m_window != oldwindow))
230  {
231  LOG(VB_GENERAL, LOG_INFO, LOC + "Have main window");
232 
233  connect(m_window, &QWindow::screenChanged, this, &MythDisplay::ScreenChanged, Qt::UniqueConnection);
234  QScreen *desired = GetDesiredScreen();
235  // If we have changed the video mode for the old screen then reset
236  // it to the default/desktop mode
237  SwitchToDesktop();
238  // Ensure we completely re-initialise when the new screen is set
239  m_initialised = false;
240  if (desired != m_screen)
241  DebugScreen(desired, "Moving to");
242  m_window->setScreen(desired);
243  // WaitForNewScreen doesn't work as intended. It successfully filters
244  // out unwanted screenChanged signals after moving screens - but always
245  //times out. This just delays startup by 500ms - so ignore on startup as it isn't needed.
246  if (!m_firstScreenChange)
248  m_firstScreenChange = false;
250  return;
251  }
252 }
253 
255 {
256  return qGuiApp->screens().size();
257 }
258 
260 {
261  if (m_physicalSize.isEmpty() || m_resolution.isEmpty())
262  return 1.0;
263 
264  return (m_physicalSize.width() / static_cast<double>(m_resolution.width())) /
265  (m_physicalSize.height() / static_cast<double>(m_resolution.height()));
266 }
267 
269 {
270  return m_guiMode.Resolution();
271 }
272 
274 {
275  return m_screenBounds;
276 }
277 
291 {
292  return m_screen;
293 }
294 
296 {
297  QScreen* newscreen = nullptr;
298 
299  // If geometry is overriden at the command line level, try and determine
300  // which screen that applies to (if any).
301  // N.B. So many potential issues here e.g. should the geometry override be
302  // ignored after first use? (as it will continue to override the screen
303  // regardless of changes to screen preference).
305  {
306  // this matches the check in MythMainWindow
307  bool windowed = GetMythDB()->GetBoolSetting("RunFrontendInWindow", false) &&
309  QRect override = GetMythUI()->GetGeometryOverride();
310  // When windowed, we use topleft as a best guess as to which screen we belong in.
311  // When fullscreen, Qt appears to use the reverse - though this may be
312  // the window manager rather than Qt. So could be wrong.
313  QPoint point = windowed ? override.topLeft() : override.bottomRight();
314  foreach (QScreen *screen, qGuiApp->screens())
315  {
316  if (screen->geometry().contains(point))
317  {
318  newscreen = screen;
319  LOG(VB_GENERAL, LOG_INFO, LOC + QString(
320  "Geometry override places window in screen '%1'").arg(newscreen->name()));
321  break;
322  }
323  }
324  }
325 
326  // If spanning all screens, then always use the primary display
327  if (!newscreen && MythDisplay::SpanAllScreens())
328  {
329  LOG(VB_GENERAL, LOG_INFO, LOC + "Using primary screen for multiscreen");
330  newscreen = qGuiApp->primaryScreen();
331  }
332 
333  QString name = gCoreContext->GetSetting("XineramaScreen", nullptr);
334  // Lookup by name
335  if (!newscreen)
336  {
337  foreach (QScreen *screen, qGuiApp->screens())
338  {
339  if (!name.isEmpty() && name == screen->name())
340  {
341  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Found screen '%1'").arg(name));
342  newscreen = screen;
343  }
344  }
345  }
346 
347  // No name match. These were previously numbers.
348  if (!newscreen)
349  {
350  bool ok = false;
351  int screen_num = name.toInt(&ok);
352  QList<QScreen *>screens = qGuiApp->screens();
353  if (ok && (screen_num >= 0) && (screen_num < screens.size()))
354  {
355  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Found screen number %1 (%2)")
356  .arg(name).arg(screens[screen_num]->name()));
357  newscreen = screens[screen_num];
358  }
359  }
360 
361  // For anything else, return the primary screen.
362  if (!newscreen)
363  {
364  QScreen *primary = qGuiApp->primaryScreen();
365  if (name.isEmpty() && primary)
366  {
367  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Defaulting to primary screen (%1)")
368  .arg(primary->name()));
369  }
370  else if (name != "-1" && primary)
371  {
372  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Screen '%1' not found, defaulting to primary screen (%2)")
373  .arg(name).arg(primary->name()));
374  }
375  newscreen = primary;
376  }
377 
378  return newscreen;
379 }
380 
383 void MythDisplay::ScreenChanged(QScreen *qScreen)
384 {
385  if (qScreen == m_screen)
386  return;
387  if (m_screen)
388  disconnect(m_screen, nullptr, this, nullptr);
389  DebugScreen(qScreen, "Changed to");
390  m_screen = qScreen;
391  connect(m_screen, &QScreen::geometryChanged, this, &MythDisplay::GeometryChanged);
392  Initialise();
393  emit CurrentScreenChanged(qScreen);
394 }
395 
396 void MythDisplay::PrimaryScreenChanged(QScreen* qScreen)
397 {
398  DebugScreen(qScreen, "New primary");
399 }
400 
401 void MythDisplay::ScreenAdded(QScreen* qScreen)
402 {
403  DebugScreen(qScreen, "New");
404  emit ScreenCountChanged(qGuiApp->screens().size());
405 }
406 
407 void MythDisplay::ScreenRemoved(QScreen* qScreen)
408 {
409  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Screen '%1' removed").arg(qScreen->name()));
410  emit ScreenCountChanged(qGuiApp->screens().size());
411 }
412 
413 void MythDisplay::GeometryChanged(const QRect &Geo)
414 {
415  LOG(VB_GENERAL, LOG_INFO, LOC + QString("New screen geometry: %1x%2+%3+%4")
416  .arg(Geo.width()).arg(Geo.height()).arg(Geo.left()).arg(Geo.top()));
417 }
418 
426 {
427  // Certain platform implementations do not have a window to access at startup
428  // and hence use this implementation. Flag the status as incomplete to ensure
429  // we try to retrieve the full details at the first opportunity.
430  m_modeComplete = false;
431  m_edid = MythEDID();
432  QScreen *screen = GetCurrentScreen();
433  if (!screen)
434  {
435  m_refreshRate = 60.0;
436  m_physicalSize = QSize(0, 0);
437  m_resolution = QSize(1920, 1080);
438  return;
439  }
440  m_refreshRate = screen->refreshRate();
441  m_resolution = screen->size();
442  m_physicalSize = QSize(static_cast<int>(screen->physicalSize().width()),
443  static_cast<int>(screen->physicalSize().height()));
444 }
445 
448 {
449  return gCoreContext->GetSetting("XineramaScreen", nullptr) == "-1";
450 }
451 
452 QString MythDisplay::GetExtraScreenInfo(QScreen *qScreen)
453 {
454 #if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
455  QString mfg = qScreen->manufacturer();
456  if (mfg.isEmpty())
457  mfg = "Unknown";
458  QString model = qScreen->model();
459  if (model.isEmpty())
460  model = "Unknown";
461  return QString("(Make: %1 Model: %2)").arg(mfg).arg(model);
462 #else
463  Q_UNUSED(qScreen);
464  return QString();
465 #endif
466 }
467 
468 void MythDisplay::DebugScreen(QScreen *qScreen, const QString &Message)
469 {
470  if (!qScreen)
471  return;
472 
473  QRect geom = qScreen->geometry();
474  QString extra = GetExtraScreenInfo(qScreen);
475 
476  LOG(VB_GENERAL, LOG_INFO, LOC + QString("%1 screen '%2' %3")
477  .arg(Message).arg(qScreen->name()).arg(extra));
478 
479  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Geometry: %1x%2+%3+%4 Size(Qt): %5mmx%6mm")
480  .arg(geom.width()).arg(geom.height()).arg(geom.left()).arg(geom.top())
481  .arg(qScreen->physicalSize().width()).arg(qScreen->physicalSize().height()));
482 
483  if (qScreen->virtualGeometry() != geom)
484  {
485  geom = qScreen->virtualGeometry();
486  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Total virtual geometry: %1x%2+%3+%4")
487  .arg(geom.width()).arg(geom.height()).arg(geom.left()).arg(geom.top()));
488  }
489 }
490 
492 {
493  m_videoModes.clear();
494  m_overrideVideoModes.clear();
497 
498  // Set the desktop mode - which is the mode at startup. We must always return
499  // the screen to this mode.
500  if (!m_initialised)
501  {
502  // Only ever set this once or after a screen change
503  m_initialised = true;
505  LOG(VB_GENERAL, LOG_NOTICE, LOC + QString("Desktop video mode: %1x%2 %3Hz")
506  .arg(m_resolution.width()).arg(m_resolution.height()).arg(m_refreshRate, 0, 'f', 3));
507  if (m_edid.Valid())
508  {
509  if (m_edid.IsSRGB())
510  LOG(VB_GENERAL, LOG_NOTICE, LOC + "Display is using sRGB colourspace");
511  else
512  LOG(VB_GENERAL, LOG_NOTICE, LOC + "Display has custom colourspace");
513  }
514  }
515 
516  // Set the gui mode from database settings
517  int pixelwidth = m_resolution.width();
518  int pixelheight = m_resolution.height();
519  int mmwidth = m_physicalSize.width();
520  int mmheight = m_physicalSize.height();
521  double refreshrate = m_refreshRate;
522  double aspectratio = 0.0;
523  GetMythDB()->GetResolutionSetting("GuiVidMode", pixelwidth, pixelheight, aspectratio, refreshrate);
524  GetMythDB()->GetResolutionSetting("DisplaySize", mmwidth, mmheight);
525  m_guiMode = MythDisplayMode(pixelwidth, pixelheight, mmwidth, mmheight, -1.0, refreshrate);
526 
527  // Set default video mode
528  pixelwidth = pixelheight = 0;
529  GetMythDB()->GetResolutionSetting("TVVidMode", pixelwidth, pixelheight, aspectratio, refreshrate);
530  m_videoMode = MythDisplayMode(pixelwidth, pixelheight, mmwidth, mmheight, aspectratio, refreshrate);
531 
532  // Initialise video override modes
533  for (int i = 0; true; ++i)
534  {
535  int iw = 0;
536  int ih = 0;
537  int ow = 0;
538  int oh = 0;
539  double iaspect = 0.0;
540  double oaspect = 0.0;
541  double irate = 0.0;
542  double orate = 0.0;
543 
544  GetMythDB()->GetResolutionSetting("VidMode", iw, ih, iaspect, irate, i);
545  GetMythDB()->GetResolutionSetting("TVVidMode", ow, oh, oaspect, orate, i);
546 
547  if (!(iw || ih || !qFuzzyIsNull(irate)) || !(ih && ow && oh))
548  break;
549 
550  uint64_t key = MythDisplayMode::CalcKey(QSize(iw, ih), irate);
551  MythDisplayMode scr(QSize(ow, oh), QSize(mmwidth, mmheight), oaspect, orate);
552  m_overrideVideoModes[key] = scr;
553  }
554 }
555 
556 
565 {
566  QList<QScreen*> screens = qGuiApp->screens();
567  for (auto it = screens.cbegin(); it != screens.cend(); ++it)
568  {
569  QRect dim = (*it)->geometry();
570  QString extra = MythDisplay::GetExtraScreenInfo(*it);
571  LOG(VB_GUI, LOG_INFO, LOC + QString("Screen %1: %2x%3 %4")
572  .arg((*it)->name()).arg(dim.width()).arg(dim.height()).arg(extra));
573  }
574 
575  QScreen *primary = qGuiApp->primaryScreen();
576  LOG(VB_GUI, LOG_INFO, LOC +QString("Primary screen: %1.").arg(primary->name()));
577 
578  int numScreens = MythDisplay::GetScreenCount();
579  QSize dim = primary->virtualSize();
580  LOG(VB_GUI, LOG_INFO, LOC + QString("Total desktop dim: %1x%2, over %3 screen[s].")
581  .arg(dim.width()).arg(dim.height()).arg(numScreens));
582 
584  {
585  LOG(VB_GUI, LOG_INFO, LOC + QString("Using entire desktop."));
586  m_screenBounds = primary->virtualGeometry();
587  return;
588  }
589 
590  if (GetMythDB()->GetBoolSetting("RunFrontendInWindow", false))
591  {
592  LOG(VB_GUI, LOG_INFO, LOC + "Running in a window");
593  // This doesn't include the area occupied by the
594  // Windows taskbar, or the Mac OS X menu bar and Dock
595  m_screenBounds = m_screen->availableGeometry();
596  }
597  else
598  {
599  m_screenBounds = m_screen->geometry();
600  }
601 
602  LOG(VB_GUI, LOG_INFO, LOC + QString("Using screen %1: %2x%3 at %4+%5")
603  .arg(m_screen->name()).arg(m_screenBounds.width()).arg(m_screenBounds.height())
604  .arg(m_screenBounds.left()).arg(m_screenBounds.top()));
605 }
606 
614 {
615  return Size.width() > m_resolution.width() || Size.height() > m_resolution.height();
616 }
617 
623 {
625  if (current == m_desktopMode)
626  return;
628 }
629 
633 bool MythDisplay::SwitchToVideo(QSize Size, double Rate)
634 {
635  if (!m_modeComplete)
637 
640  double targetrate = 0.0;
641  double aspectoverride = 0.0;
642 
643  // try to find video override mode
645  Size.width(), Size.height(), Rate);
646 
647  if (key != 0)
648  {
649  next = m_overrideVideoModes[key];
650  if (next.AspectRatio() > 0.0)
651  aspectoverride = next.AspectRatio();
652  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Found custom screen override %1x%2 Aspect %3")
653  .arg(next.Width()).arg(next.Height()).arg(aspectoverride));
654  }
655 
656  // If requested refresh rate is 0, attempt to match video fps
657  if (qFuzzyIsNull(next.RefreshRate()))
658  {
659  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Trying to match best refresh rate %1Hz")
660  .arg(Rate, 0, 'f', 3));
661  next.SetRefreshRate(Rate);
662  }
663 
664  // need to change video mode?
666  if ((next == current) && (MythDisplayMode::CompareRates(current.RefreshRate(), targetrate)))
667  {
668  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Using current mode %1x%2@%3Hz")
669  .arg(m_resolution.width()).arg(m_resolution.height()).arg(m_refreshRate));
670  return true;
671  }
672 
673  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Trying mode %1x%2@%3Hz")
674  .arg(next.Width()).arg(next.Height()).arg(next.RefreshRate(), 0, 'f', 3));
675 
676  if (!SwitchToVideoMode(next.Resolution(), targetrate))
677  {
678  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to change mode to %1x%2@%3Hz")
679  .arg(next.Width()).arg(next.Height()).arg(next.RefreshRate(), 0, 'f', 3));
680  return false;
681  }
682 
683  if (next.Resolution() != m_resolution)
685 
686  // N.B. We used a computed aspect ratio unless overridden
687  m_aspectRatioOverride = aspectoverride > 0.0 ? aspectoverride : 0.0;
689  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Switched to %1x%2@%3Hz for video %4x%5")
690  .arg(m_resolution.width()).arg(m_resolution.height())
691  .arg(m_refreshRate, 0, 'f', 3).arg(Size.width()).arg(Size.height()));
693  return true;
694 }
695 
699 {
700  if (!m_modeComplete)
702 
703  // If the current resolution is the same as the GUI resolution then do nothing
704  // as refresh rate should not be critical for the GUI.
706  {
707  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Using %1x%2@%3Hz for GUI")
708  .arg(m_resolution.width()).arg(m_resolution.height()).arg(m_refreshRate));
709  return true;
710  }
711 
713  {
714  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to change mode to %1x%2@%3Hz")
715  .arg(m_guiMode.Width()).arg(m_guiMode.Height()).arg(m_guiMode.RefreshRate(), 0, 'f', 3));
716  return false;
717  }
718 
719  if (Wait && (m_resolution != m_guiMode.Resolution()))
721 
723  m_aspectRatioOverride = 0.0;
724  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Switched to %1x%2@%3Hz")
725  .arg(m_resolution.width()).arg(m_resolution.height()).arg(m_refreshRate, 0, 'f', 3));
726  return true;
727 }
728 
730 {
731  return m_refreshRate;
732 }
733 
735 {
736  if (m_refreshRate > 20.0 && m_refreshRate < 200.0)
737  return static_cast<int>(lround(1000000.0 / m_refreshRate));
738  if (Fallback > 33000) // ~30Hz
739  Fallback /= 2;
740  return Fallback;
741 }
742 
743 std::vector<double> MythDisplay::GetRefreshRates(QSize Size)
744 {
745  auto targetrate = static_cast<double>(NAN);
746  const MythDisplayMode mode(Size, QSize(0, 0), -1.0, 0.0);
747  const vector<MythDisplayMode>& modes = GetVideoModes();
748  int match = MythDisplayMode::FindBestMatch(modes, mode, targetrate);
749  if (match < 0)
750  return std::vector<double>();
751  return modes[static_cast<size_t>(match)].RefreshRates();
752 }
753 
754 bool MythDisplay::SwitchToVideoMode(QSize /*Size*/, double /*Framerate*/)
755 {
756  return false;
757 }
758 
759 const vector<MythDisplayMode> &MythDisplay::GetVideoModes(void)
760 {
761  return m_videoModes;
762 }
763 
782 double MythDisplay::GetAspectRatio(QString &Source, bool IgnoreModeOverride)
783 {
784  auto valid = [](double Aspect) { return (Aspect > 0.1 && Aspect < 10.0); };
785 
786  // Override for this video mode
787  // Is this behaviour still needed?
788  if (!IgnoreModeOverride && valid(m_aspectRatioOverride))
789  {
790  Source = tr("Video mode override");
791  return m_aspectRatioOverride;
792  }
793 
794  // General override for invalid/misleading EDIDs or multiscreen setups
795  bool multiscreen = MythDisplay::SpanAllScreens() && GetScreenCount() > 1;
796  double override = gCoreContext->GetFloatSettingOnHost("XineramaMonitorAspectRatio",
797  gCoreContext->GetHostName(), 0.0);
798 
799  // Zero (not valid) indicates auto
800  if (valid(override))
801  {
802  Source = tr("Override");
803  return override;
804  }
805  // Auto for multiscreen is a best guess
806  else if (multiscreen)
807  {
808  double aspect = EstimateVirtualAspectRatio();
809  if (valid(aspect))
810  {
811  Source = tr("Multiscreen estimate");
812  return aspect;
813  }
814  }
815 
816  // Based on actual physical size if available
817  if (!m_physicalSize.isEmpty())
818  {
819  double aspect = static_cast<double>(m_physicalSize.width()) / m_physicalSize.height();
820  if (valid(aspect))
821  {
822  Source = tr("Detected");
823  return aspect;
824  }
825  }
826 
827  // Assume pixel aspect ratio is 1 (square pixels)
828  if (!m_resolution.isEmpty())
829  {
830  double aspect = static_cast<double>(m_resolution.width()) / m_resolution.height();
831  if (valid(aspect))
832  {
833  Source = tr("Fallback");
834  return aspect;
835  }
836  }
837 
838  // the aspect ratio of last resort
839  Source = tr("Guessed");
840  return 16.0 / 9.0;
841 }
842 
844 {
845  return m_edid;
846 }
847 
858 {
859  auto sortscreens = [](const QScreen* First, const QScreen* Second)
860  {
861  if (First->geometry().left() < Second->geometry().left())
862  return true;
863  if (First->geometry().top() < Second->geometry().top())
864  return true;
865  return false;
866  };
867 
868  // default
869  double result = 16.0 / 9.0;
870 
871  QList<QScreen*> screens;
872  if (m_screen)
873  screens = m_screen->virtualSiblings();
874  if (screens.empty())
875  return result;
876 
877  // N.B. This sorting may not be needed
878  std::sort(screens.begin(), screens.end(), sortscreens);
879  QList<double> aspectratios;
880  QSize totalresolution;
881  int lasttop = 0;
882  int lastleft = 0;
883  int rows = 1;
884  int columns = 1;
885  for (auto it = screens.constBegin() ; it != screens.constEnd(); ++it)
886  {
887  QRect geom = (*it)->geometry();
888  totalresolution += geom.size();
889  LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("%1x%2+%3+%4 %5")
890  .arg(geom.width()).arg(geom.height()).arg(geom.left()).arg(geom.top())
891  .arg((*it)->physicalSize().width() / (*it)->physicalSize().height()));
892  if (lastleft < geom.left())
893  {
894  columns++;
895  lastleft = geom.left();
896  }
897  if (lasttop < geom.top())
898  {
899  rows++;
900  lasttop = geom.top();
901  lastleft = 0;
902  }
903  aspectratios << (*it)->physicalSize().width() / (*it)->physicalSize().height();
904  }
905 
906  // If all else fails, use the total resolution and assume pixel aspect ratio
907  // equals display aspect ratio
908  if (!totalresolution.isEmpty())
909  result = totalresolution.width() / totalresolution.height();
910 
911  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Screen layout: %1x%2").arg(rows).arg(columns));
912  if (rows == columns)
913  {
914  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Grid layout");
915  }
916  else if (rows == 1 && columns > 1)
917  {
918  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Horizontal layout");
919  }
920  else if (columns == 1 && rows > 1)
921  {
922  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Vertical layout");
923  }
924  else
925  {
926  LOG(VB_GENERAL, LOG_INFO,
927  LOC + QString("Unsupported layout - defaulting to %1 (%2/%3)")
928  .arg(result).arg(totalresolution.width()).arg(totalresolution.height()));
929  return result;
930  }
931 
932  // validate aspect ratios - with a little fuzzyness
933  double aspectratio = 0.0;
934  double average = 0.0;
935  int count = 1;
936  for (auto it2 = aspectratios.constBegin() ; it2 != aspectratios.constEnd(); ++it2, ++count)
937  {
938  aspectratio += *it2;
939  average = aspectratio / count;
940  if (qAbs(*it2 - average) > 0.1)
941  {
942  LOG(VB_GENERAL, LOG_INFO, LOC +
943  QString("Inconsistent aspect ratios - defaulting to %1 (%2/%3)")
944  .arg(result).arg(totalresolution.width()).arg(totalresolution.height()));
945  return result;
946  }
947  }
948 
949  aspectratio = (average * columns) / rows;
950  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Estimated aspect ratio: %1")
951  .arg(aspectratio));
952  return aspectratio;
953 }
954 
956 {
957  return m_resolution;
958 }
959 
961 {
962  return m_physicalSize;
963 }
964 
966 {
967  // Some implementations may have their own mechanism for ensuring the mode
968  // is updated before continuing
970  return;
971 
972  LOG(VB_GENERAL, LOG_INFO, LOC + "Waiting for resolution change");
973  QEventLoop loop;
974  QTimer timer;
975  timer.setSingleShot(true);
976  connect(&timer, &QTimer::timeout, [](){ LOG(VB_GENERAL, LOG_WARNING, LOC + "Timed out wating for screen change"); });
977  QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
978  QObject::connect(m_screen, &QScreen::geometryChanged, &loop, &QEventLoop::quit);
979  // 500ms maximum wait
980  timer.start(500);
981  loop.exec();
982 }
983 
985 {
986  // N.B. This isn't working as intended as it always times out rather than
987  // exiting deliberately. It does however somehow filter out unwanted screenChanged
988  // events that otherwise often put the widget in the wrong screen.
989  // Needs more investigation - but for now it works:)
990  if (!m_widget || (m_widget && !m_widget->windowHandle()))
991  return;
992  LOG(VB_GENERAL, LOG_INFO, LOC + "Waiting for new screen");
993  QEventLoop loop;
994  QTimer timer;
995  timer.setSingleShot(true);
996  connect(&timer, &QTimer::timeout, [](){ LOG(VB_GENERAL, LOG_WARNING, LOC + "Timed out waiting for new screen"); });
997  QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
998  QObject::connect(m_widget->windowHandle(), &QWindow::screenChanged, &loop, &QEventLoop::quit);
999  // 500ms maximum wait
1000  timer.start(500);
1001  loop.exec();
1002 }
1003 
1005 {
1006  int pauselengthinms = gCoreContext->GetNumSetting("VideoModeChangePauseMS", 0);
1007  if (pauselengthinms)
1008  {
1009  LOG(VB_GENERAL, LOG_INFO, LOC +
1010  QString("Pausing %1ms for video mode switch").arg(pauselengthinms));
1011  QEventLoop loop;
1012  QTimer timer;
1013  timer.setSingleShot(true);
1014  QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
1015  // 500ms maximum wait
1016  timer.start(pauselengthinms);
1017  loop.exec();
1018  }
1019 }
1020 
1021 void MythDisplay::DebugModes(void) const
1022 {
1023  // This is intentionally formatted to match the output of xrandr for comparison
1024  if (VERBOSE_LEVEL_CHECK(VB_PLAYBACK, LOG_INFO))
1025  {
1026  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Available modes:");
1027  for (auto it = m_videoModes.crbegin(); it != m_videoModes.crend(); ++it)
1028  {
1029  auto rates = (*it).RefreshRates();
1030  QStringList rateslist;
1031  for (auto it2 = rates.crbegin(); it2 != rates.crend(); ++it2)
1032  rateslist.append(QString("%1").arg(*it2, 2, 'f', 2, '0'));
1033  if (rateslist.empty())
1034  rateslist.append("Variable rate?");
1035  LOG(VB_PLAYBACK, LOG_INFO, QString("%1x%2\t%3")
1036  .arg((*it).Width()).arg((*it).Height()).arg(rateslist.join("\t")));
1037  }
1038  }
1039 }
1040 
1046 void MythDisplay::ConfigureQtGUI(int SwapInterval)
1047 {
1048  // Set the default surface format. Explicitly required on some platforms.
1049  QSurfaceFormat format;
1050  format.setDepthBufferSize(0);
1051  format.setStencilBufferSize(0);
1052  format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
1053  format.setProfile(QSurfaceFormat::CompatibilityProfile);
1054  format.setSwapInterval(SwapInterval);
1055  QSurfaceFormat::setDefaultFormat(format);
1056 
1057 #ifdef Q_OS_MAC
1058  // Without this, we can't set focus to any of the CheckBoxSetting, and most
1059  // of the MythPushButton widgets, and they don't use the themed background.
1060  QApplication::setDesktopSettingsAware(false);
1061 #endif
1062 #if defined (Q_OS_LINUX) && defined (USING_EGL)
1063  // We want to use EGL for VAAPI/MMAL/DRMPRIME rendering to ensure we
1064  // can use zero copy video buffers for the best performance (N.B. not tested
1065  // on AMD desktops). To force Qt to use EGL we must set 'QT_XCB_GL_INTEGRATION'
1066  // to 'xcb_egl' and this must be done before any GUI is created. If the platform
1067  // plugin is not xcb then this should have no effect.
1068  // This does however break when using NVIDIA drivers - which do not support
1069  // EGL like other drivers so we try to check the EGL vendor - and we currently
1070  // have no need for EGL with NVIDIA (that may change however).
1071  // NOTE force using EGL by setting MYTHTV_FORCE_EGL
1072  // NOTE disable using EGL by setting MYTHTV_NO_EGL
1073  // NOTE We have no Qt platform information, window/surface or logging when this is called.
1074  QString soft = qgetenv("LIBGL_ALWAYS_SOFTWARE");
1075  bool ignore = soft == "1" || soft.compare("true", Qt::CaseInsensitive) == 0;
1076  bool allow = qgetenv("MYTHTV_NO_EGL").isEmpty() && !ignore;
1077  bool force = !qgetenv("MYTHTV_FORCE_EGL").isEmpty();
1078  if (force || allow)
1079  {
1080  // N.B. By default, ignore EGL if vendor string is not returned
1081  QString vendor = MythEGL::GetEGLVendor();
1082  if (vendor.contains("nvidia", Qt::CaseInsensitive) && !force)
1083  {
1084  qInfo() << LOC + QString("Not requesting EGL for vendor '%1'").arg(vendor);
1085  }
1086  else if (!vendor.isEmpty() || force)
1087  {
1088  qInfo() << LOC + QString("Requesting EGL for '%1'").arg(vendor);
1089  setenv("QT_XCB_GL_INTEGRATION", "xcb_egl", 0);
1090  }
1091  }
1092 
1093  // This makes Xlib calls thread-safe which seems to be required for hardware
1094  // accelerated Flash playback to work without causing mythfrontend to abort.
1095  QApplication::setAttribute(Qt::AA_X11InitThreads);
1096 #endif
1097 #ifdef Q_OS_ANDROID
1098  //QApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
1099 #endif
1100 
1101 #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
1102  // Ignore desktop scaling
1103  QApplication::setAttribute(Qt::AA_DisableHighDpiScaling);
1104 #endif
1105 }
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.
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)
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)
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.
int GetRefreshInterval(int Fallback)
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:24
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)
MythUIHelper * GetMythUI()
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
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
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.
void GeometryChanged(const QRect &Geometry)
static QScreen * GetDesiredScreen(void)