MythTV master
mythdisplaymutter.cpp
Go to the documentation of this file.
1// MythTV
2#include "libmythbase/mythconfig.h"
6
7#if CONFIG_DRM
8extern "C" {
9#include <xf86drmMode.h>
10}
11#else
12#ifndef DRM_MODE_FLAG_INTERLACE
13#define DRM_MODE_FLAG_INTERLACE (1<<4)
14#endif
15#endif
16
17#define LOC QString("MutterDisp: ")
18
19// ApplyConfiguration CRTC output
21{
22 uint32_t id {};
23 int32_t new_mode {};
24 int32_t x {};
25 int32_t y {};
26 uint32_t transform {};
27 QList<uint32_t> outputs;
29};
30
31// ApplyConfiguration Outputs out
33{
34 uint32_t id {};
36};
37
38using MythMutterCRTCOutList = QList<MythMutterCRTCOut>;
41using MythMutterOutputOutList = QList<MythMutterOutputOut>;
44
45// NOLINTBEGIN(bugprone-return-const-ref-from-parameter)
46//
47// Detects return statements that return a constant reference
48// parameter as constant reference. This may cause use-after-free
49// errors if the caller uses xvalues as arguments.
50//
51// In these functions, if a temporary object is supplied to Argument
52// the application could crash. This shouldn't be a problem, but is
53// something to be aware of.
54
55static QDBusArgument &operator<<(QDBusArgument& Argument, const MythMutterOutputOut& Output)
56{
57 Argument.beginStructure();
58 Argument << Output.id << Output.properties;
59 Argument.endStructure();
60 return Argument;
61}
62
63static QDBusArgument &operator<<(QDBusArgument& Argument, const MythMutterOutputOutList& Outputs)
64{
65 Argument.beginArray(qMetaTypeId<MythMutterOutputOut>());
66 for (const auto & output : Outputs)
67 Argument << output;
68 Argument.endArray();
69 return Argument;
70}
71
72static const QDBusArgument &operator>>(const QDBusArgument& Argument, MythMutterOutputOut& Output)
73{
74 Argument.beginStructure();
75 Argument >> Output.id >> Output.properties;
76 Argument.endStructure();
77 return Argument;
78}
79
80static QDBusArgument &operator<<(QDBusArgument& Argument, const MythMutterCRTCOut& CRTC)
81{
82 Argument.beginStructure();
83 Argument << CRTC.id << CRTC.new_mode << CRTC.x << CRTC.y << CRTC.transform << CRTC.outputs << CRTC.properties;
84 Argument.endStructure();
85 return Argument;
86}
87
88static const QDBusArgument &operator>>(const QDBusArgument& Argument, MythMutterCRTCOut& CRTC)
89{
90 Argument.beginStructure();
91 Argument >> CRTC.id >> CRTC.new_mode >> CRTC.x >> CRTC.y >> CRTC.transform >> CRTC.outputs >> CRTC.properties;
92 Argument.endStructure();
93 return Argument;
94}
95
96static QDBusArgument &operator<<(QDBusArgument& Argument, const MythMutterCRTCOutList& CRTCS)
97{
98 Argument.beginArray(qMetaTypeId<MythMutterCRTCOut>());
99 for (const auto & crtc : CRTCS)
100 Argument << crtc;
101 Argument.endArray();
102 return Argument;
103}
104
105static const QDBusArgument &operator>>(const QDBusArgument& Argument, MythMutterCRTC& CRTC)
106{
107 Argument.beginStructure();
108 Argument >> CRTC.id >> CRTC.sys_id >> CRTC.x >> CRTC.y;
109 Argument >> CRTC.width >> CRTC.height >> CRTC.currentmode;
110 Argument >> CRTC.currenttransform >> CRTC.transforms >> CRTC.properties;
111 Argument.endStructure();
112 return Argument;
113}
114
115static const QDBusArgument &operator>>(const QDBusArgument& Argument, MythMutterCRTCList& CRTCS)
116{
117 Argument.beginArray();
118 CRTCS.clear();
119
120 while (!Argument.atEnd())
121 {
122 MythMutterCRTC crtc;
123 Argument >> crtc;
124 CRTCS.append(crtc);
125 }
126
127 Argument.endArray();
128 return Argument;
129}
130
131static const QDBusArgument &operator>>(const QDBusArgument& Argument, MythMutterOutput& Output)
132{
133 Argument.beginStructure();
134 Argument >> Output.id >> Output.sys_id >> Output.current_crtc >> Output.possible_crtcs;
135 Argument >> Output.name >> Output.modes >> Output.clones;
136 Argument >> Output.properties;
137 Argument.endStructure();
138 Output.serialnumber = QString();
139 Output.edid = QByteArray();
140 Output.widthmm = 0;
141 Output.heightmm = 0;
142 for (auto & property : Output.properties)
143 {
144 if (property.first == "serial")
145 Output.serialnumber = property.second.variant().toString();
146 if (property.first == "edid")
147 Output.edid = property.second.variant().toByteArray();
148 if (property.first == "width-mm")
149 Output.widthmm = property.second.variant().toInt();
150 if (property.first == "height-mm")
151 Output.heightmm = property.second.variant().toInt();
152 }
153 return Argument;
154}
155
156static const QDBusArgument &operator>>(const QDBusArgument& Argument, MythMutterOutputList& Outputs)
157{
158 Argument.beginArray();
159 Outputs.clear();
160
161 while (!Argument.atEnd())
162 {
164 Argument >> output;
165 Outputs.append(output);
166 }
167
168 Argument.endArray();
169 return Argument;
170}
171
172static const QDBusArgument &operator>>(const QDBusArgument& Argument, MythMutterMode& Mode)
173{
174 Argument.beginStructure();
175 Argument >> Mode.id >> Mode.sys_id >> Mode.width >> Mode.height;
176 Argument >> Mode.frequency >> Mode.flags;
177 Argument.endStructure();
178 return Argument;
179}
180
181static const QDBusArgument &operator>>(const QDBusArgument& Argument, MythMutterModeList& Modes)
182{
183 Argument.beginArray();
184 Modes.clear();
185
186 while (!Argument.atEnd())
187 {
188 MythMutterMode mode {};
189 Argument >> mode;
190 Modes.append(mode);
191 }
192
193 Argument.endArray();
194 return Argument;
195}
196
197// NOLINTEND(bugprone-return-const-ref-from-parameter)
198
205{
206 static bool s_checked(false);
207 static bool s_available(false);
208
209 if (!s_checked)
210 {
211 s_checked = true;
212 qDBusRegisterMetaType<MythMutterCRTCOut>();
213 qDBusRegisterMetaType<MythMutterOutputOut>();
214 auto mutter = QDBusInterface(DISP_CONFIG_SERVICE, DISP_CONFIG_PATH,
215 DISP_CONFIG_SERVICE, QDBusConnection::sessionBus());
216
217 if (mutter.isValid())
218 {
219 // Some implementations do not implement ApplyConfiguration and there
220 // is no point in using this class without it
221 // N.B. Use a bogus serial here to ensure that if it is implemented,
222 // it will fail with org.freedesktop.DBus.AccessDenied
223 QDBusMessage res = mutter.call("GetResources");
224 QList<QVariant> args = res.arguments();
225 if ((res.signature() == DISP_CONFIG_SIG) || (args.size() == 6))
226 {
227 uint serial = args[0].toUInt() + 100;
230 QDBusArgument crtcsarg;
231 QDBusArgument outarg;
232 QDBusReply<void> reply = mutter.call(QLatin1String("ApplyConfiguration"),
233 serial, false,
234 QVariant::fromValue(crtcsarg << crtcs),
235 QVariant::fromValue(outarg << outputs));
236 if (reply.error().type() == QDBusError::UnknownMethod)
237 {
238 LOG(VB_GENERAL, LOG_WARNING, LOC +
239 "org.gnome.Mutter.DisplayConfig.ApplyConfiguration not implemented");
240 }
241 else if (reply.error().type() == QDBusError::AccessDenied)
242 {
243 s_available = true;
244 }
245
246 }
247
248 if (!s_available)
249 LOG(VB_GENERAL, LOG_INFO, LOC + DISP_CONFIG_SERVICE + " not useable");
250 }
251 else
252 {
253 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Failed to find '%1'").arg(DISP_CONFIG_SERVICE));
254 }
255 }
256
257 if (s_available)
258 {
259 auto *result = new MythDisplayMutter();
260 if (result->IsValid())
261 return result;
262 delete result;
263 }
264
265 return nullptr;
266}
267
284{
286 Initialise();
287}
288
290{
291 delete m_interface;
292}
293
295{
296 return m_interface != nullptr;
297}
298
300{
301 LOG(VB_GENERAL, LOG_INFO, LOC + "Monitors changed");
303}
304
306{
307 if (gCoreContext)
308 return gCoreContext->GetBoolSetting("UseVideoModes", false);
309 return false;
310}
311
313{
314 if (!m_interface || m_outputIdx < 0 || !m_videoModes.empty())
315 return m_videoModes;
316
317 m_videoModes.clear();
318 m_modeMap.clear();
320 QSize physical(output.widthmm, output.heightmm);
321 DisplayModeMap screenmap;
322
323 for (auto & mode : output.modes)
324 {
325 MythMutterMode& mmode = m_modes[static_cast<int32_t>(mode)];
326
327 // the flags field will contain values dependant on enums for the
328 // underlying mechanism in use (i.e. XRandR or libdrm). We should not
329 // however be using this class if X11 is running and fortunately the
330 // values for the different enums match.
332 {
333 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Ignoring interlaced mode %1x%2 %3i")
334 .arg(mmode.width).arg(mmode.width).arg(mmode.frequency, 2, 'f', 2, '0'));
335 continue;
336 }
337
338 double rate = mmode.frequency;
339 QSize resolution(static_cast<int32_t>(mmode.width),
340 static_cast<int32_t>(mmode.height));
341
342 uint64_t key = MythDisplayMode::CalcKey(resolution, 0.0);
343 if (screenmap.find(key) == screenmap.end())
344 screenmap[key] = MythDisplayMode(resolution, physical, -1.0, rate);
345 else
346 screenmap[key].AddRefreshRate(rate);
347 m_modeMap.insert(MythDisplayMode::CalcKey(resolution, rate), mmode.id);
348 }
349
350 for (auto & it : screenmap)
351 m_videoModes.push_back(it.second);
352
353 DebugModes();
354 return m_videoModes;
355}
356
358{
359 if (!m_interface)
360 {
362 return;
363 }
364
366 m_modeComplete = true;
367}
368
370{
371 delete m_interface;
373 DISP_CONFIG_SERVICE, QDBusConnection::sessionBus());
374 if (m_interface->isValid())
375 {
376 QDBusMessage reply = m_interface->call("GetResources");
377
378 if (reply.signature() == DISP_CONFIG_SIG)
379 {
380 QList<QVariant> args = reply.arguments();
381 if (args.size() == 6)
382 {
383 if (!QDBusConnection::sessionBus().connect(DISP_CONFIG_SERVICE, DISP_CONFIG_PATH,
384 DISP_CONFIG_SERVICE, "MonitorsChanged", this,
385 SLOT(MonitorsChanged())))
386 {
387 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to register for MonitorsChanged");
388 }
389 return;
390 }
391 LOG(VB_GENERAL, LOG_ERR, LOC + "GetResources unexpected reply");
392 }
393 else
394 {
395 LOG(VB_GENERAL, LOG_ERR, LOC + "GetResources signature not recognised");
396 }
397 }
398
399 delete m_interface;
400 m_interface = nullptr;
401}
402
404bool MythDisplayMutter::SwitchToVideoMode(QSize Size, double DesiredRate)
405{
406 if (!m_interface)
407 return false;
408
409 auto rate = static_cast<double>(NAN);
410 QSize dummy(0, 0);
411 MythDisplayMode desired(Size, dummy, -1.0, DesiredRate);
412 int idx = MythDisplayMode::FindBestMatch(m_videoModes, desired, rate);
413
414 if (idx < 0)
415 {
416 LOG(VB_GENERAL, LOG_ERR, LOC + "Desired resolution and frame rate not found.");
417 return false;
418 }
419
420 auto mode = MythDisplayMode::CalcKey(Size, rate);
421 if (!m_modeMap.contains(mode))
422 {
423 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to find mode");
424 return false;
425 }
426
428 for (auto & crtc : m_crtcs)
429 {
430 // leave disabled CRTCs as disabled by ignoring
431 if ((crtc.currentmode < 0) || (crtc.width < 1) || (crtc.height < 1))
432 continue;
433
434 MythMutterCRTCOut crtcout;
435 crtcout.id = crtc.id;
436 crtcout.new_mode = crtc.currentmode;
437 crtcout.x = crtc.x;
438 crtcout.y = crtc.y;
439 crtcout.transform = crtc.currenttransform;
440 crtcout.outputs = QList<uint32_t>();
441 crtcout.properties = MythMutterMap();
442 crtcs.append(crtcout);
443 }
444
446 QDBusArgument crtcsarg;
447 QDBusArgument outarg;
448 QDBusReply<void> reply = m_interface->call(QLatin1String("ApplyConfiguration"),
449 m_serialVal, false,
450 QVariant::fromValue(crtcsarg << crtcs),
451 QVariant::fromValue(outarg << outputs));
452 if (!reply.isValid())
453 {
454 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Error applying new display configuration ('%1: %2')")
455 .arg(reply.error().type()).arg(reply.error().message()));
456 return false;
457 }
458
459 // If ApplyConfiguration is successful, then serial will have been updated
460 // and we need it for the next change
461 QDBusMessage resources = m_interface->call("GetResources");
462 QList<QVariant> args = resources.arguments();
463 if ((resources.signature() != DISP_CONFIG_SIG) || (args.size() != 6))
464 {
465 LOG(VB_GENERAL, LOG_WARNING, LOC + "Failed to get updated DisplayConfig serial"
466 " - further display changes may fail");
467 }
468 else
469 {
470 m_serialVal = args[0].toUInt();
471 // TODO Validate the new config against the expected result?
472 }
473
474 return true;
475}
476
478{
479 if (!m_interface)
480 return;
481
482 m_crtcs.clear();
483 m_outputs.clear();
484 m_modes.clear();
485 m_serialVal = 0;
486 m_outputIdx = -1;
487
488 QDBusMessage reply = m_interface->call("GetResources");
489 QList<QVariant> args = reply.arguments();
490 if ((reply.signature() != DISP_CONFIG_SIG) || (args.size() != 6))
491 return;
492
493 m_serialVal = args[0].toUInt();
494
495 args[1].value<QDBusArgument>() >> m_crtcs;
496 for (auto & crtc : m_crtcs)
497 {
498 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("CRTC %1/%2: %3x%4+%5+%6 Mode: %7")
499 .arg(crtc.id).arg(crtc.sys_id).arg(crtc.width)
500 .arg(crtc.height).arg(crtc.x).arg(crtc.y).arg(crtc.currentmode));
501 }
502
503 args[2].value<QDBusArgument>() >> m_outputs;
504 for (auto & output : m_outputs)
505 {
506 QStringList possiblecrtcs;
507 for (auto poss : std::as_const(output.possible_crtcs))
508 possiblecrtcs.append(QString::number(poss));
509 QStringList modes;
510 for (auto mode : std::as_const(output.modes))
511 modes.append(QString::number(mode));
512 QStringList props;
513 for (const auto& prop : std::as_const(output.properties))
514 props.append(QString("%1:%2").arg(prop.first, prop.second.variant().toString()));
515 LOG(VB_GENERAL, LOG_DEBUG, LOC +
516 QString("Output %1/%2: CRTC: %3 Possible CRTCs: %4 Name: '%5'")
517 .arg(output.id).arg(output.sys_id).arg(output.current_crtc)
518 .arg(possiblecrtcs.join(","), output.name));
519 LOG(VB_GENERAL, LOG_DEBUG, LOC +
520 QString("Output %1/%2: Modes: %3")
521 .arg(output.id).arg(output.sys_id).arg(modes.join(",")));
522 LOG(VB_GENERAL, LOG_DEBUG, LOC +
523 QString("Output %1/%2: Properties: %3")
524 .arg(output.id).arg(output.sys_id).arg(props.join(",")));
525 }
526
527 args[3].value<QDBusArgument>() >> m_modes;
528 for (auto & mode : m_modes)
529 {
530 LOG(VB_GENERAL, LOG_DEBUG, LOC +
531 QString("Mode %1/%2: %3x%4@%5 Flags: 0x%6")
532 .arg(mode.id).arg(mode.sys_id).arg(mode.width)
533 .arg(mode.height).arg(mode.frequency).arg(mode.flags, 0, 16));
534 }
535
536 if (m_outputs.empty())
537 {
538 LOG(VB_GENERAL, LOG_WARNING, LOC + "No connected outputs");
539 return;
540 }
541
542 // only one connected device - use it
543 if (m_outputs.size() == 1)
544 {
545 m_outputIdx = 0;
546 }
547 else
548 {
549 // TODO - we may be able to match based on name - but need to check with Wayland
550 // Use the serial number from the current QScreen to select a suitable device
551 auto serial = m_screen->serialNumber();
552 if (serial.isEmpty())
553 {
554 LOG(VB_GENERAL, LOG_INFO, LOC + "No serial number to search for - using first output");
555 m_outputIdx = 0;
556 }
557 // search for the best connected output
558 else
559 {
560 int idx = 0;
561 for (auto & output : m_outputs)
562 {
563 if (output.serialnumber == serial)
564 {
565 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Matched serial '%1' to device '%2'")
566 .arg(serial, output.name));
567 m_outputIdx = idx;
568 break;
569 }
570 ++idx;
571 }
572
573 if (m_outputIdx == -1)
574 {
575 LOG(VB_GENERAL, LOG_WARNING, LOC + "Failed to match display serial - using first device");
576 m_outputIdx = 0;
577 }
578 }
579 }
580
581 // retrieve details
582 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Using device '%1'").arg(m_outputs[m_outputIdx].name));
583
584 int32_t mode = m_crtcs[m_outputs[m_outputIdx].current_crtc].currentmode;
585 m_refreshRate = m_modes[mode].frequency;
586 m_resolution = QSize(static_cast<int>(m_modes[mode].width),
587 static_cast<int>(m_modes[mode].height));
588 m_physicalSize = QSize(m_outputs[m_outputIdx].widthmm, m_outputs[m_outputIdx].heightmm);
590}
bool GetBoolSetting(const QString &key, bool defaultval=false)
static int FindBestMatch(const MythDisplayModes &Modes, const MythDisplayMode &Mode, double &TargetRate)
static uint64_t CalcKey(QSize Size, double Rate)
A subclass of MythDisplay using the org.gnome.Mutter.DisplayConfig DBUS interface.
void UpdateCurrentMode() override
Retrieve screen details.
const MythDisplayModes & GetVideoModes(void) override
QMap< uint64_t, uint32_t > m_modeMap
bool UsingVideoModes() override
MythMutterCRTCList m_crtcs
MythMutterOutputList m_outputs
bool SwitchToVideoMode(QSize Size, double DesiredRate) override
static MythDisplayMutter * Create()
Create a valid instance.
MythMutterModeList m_modes
QDBusInterface * m_interface
QSize m_resolution
Definition: mythdisplay.h:92
virtual void UpdateCurrentMode()
Retrieve screen details.
QSize m_physicalSize
Definition: mythdisplay.h:93
void Initialise()
double m_refreshRate
Definition: mythdisplay.h:90
MythDisplayModes m_videoModes
Definition: mythdisplay.h:98
QScreen * m_screen
Definition: mythdisplay.h:97
MythEDID m_edid
Definition: mythdisplay.h:94
bool m_modeComplete
Definition: mythdisplay.h:89
void DebugModes() const
unsigned int uint
Definition: compat.h:68
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
std::map< uint64_t, MythDisplayMode > DisplayModeMap
std::vector< MythDisplayMode > MythDisplayModes
static QDBusArgument & operator<<(QDBusArgument &Argument, const MythMutterOutputOut &Output)
#define LOC
QList< MythMutterOutputOut > MythMutterOutputOutList
static const QDBusArgument & operator>>(const QDBusArgument &Argument, MythMutterOutputOut &Output)
Q_DECLARE_METATYPE(MythMutterCRTCOut)
#define DRM_MODE_FLAG_INTERLACE
QList< MythMutterCRTCOut > MythMutterCRTCOutList
QList< MythMutterCRTC > MythMutterCRTCList
QList< MythMutterOutput > MythMutterOutputList
#define DISP_CONFIG_SIG
QMap< QString, QDBusVariant > MythMutterMap
#define DISP_CONFIG_PATH
#define DISP_CONFIG_SERVICE
QList< MythMutterMode > MythMutterModeList
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
QList< uint32_t > outputs
MythMutterMap properties
MythMutterProperties properties
QList< uint32_t > transforms
uint32_t currenttransform
QList< uint32_t > modes
MythMutterProperties properties
QList< uint32_t > possible_crtcs
QList< uint32_t > clones
#define output
Mode
Definition: synaesthesia.h:20