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