GCC Code Coverage Report


Directory: ./
File: backends/xrandr/xrandroutput.cpp
Date: 2023-04-20 22:59:23
Exec Total Coverage
Lines: 0 242 0.0%
Branches: 0 150 0.0%

Line Branch Exec Source
1 /*************************************************************************************
2 * Copyright (C) 2012 by Alejandro Fiestas Olivares <afiestas@kde.org> *
3 * Copyright (C) 2012, 2013 by Daniel Vrátil <dvratil@redhat.com> *
4 * *
5 * This library is free software; you can redistribute it and/or *
6 * modify it under the terms of the GNU Lesser General Public *
7 * License as published by the Free Software Foundation; either *
8 * version 2.1 of the License, or (at your option) any later version. *
9 * *
10 * This library is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
13 * Lesser General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU Lesser General Public *
16 * License along with this library; if not, write to the Free Software *
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA *
18 *************************************************************************************/
19 #include "xrandroutput.h"
20
21 #include "config.h"
22 #include "edid.h"
23 #include "xrandr.h"
24 #include "xrandrconfig.h"
25 #include "xrandrmode.h"
26
27 #include "utils.h"
28 #include "xrandr_logging.h"
29
30 #include <xcb/render.h>
31
32 Q_DECLARE_METATYPE(QList<int>)
33
34 #define DOUBLE_TO_FIXED(d) ((xcb_render_fixed_t)((d)*65536))
35 #define FIXED_TO_DOUBLE(f) ((double)((f) / 65536.0))
36
37 xcb_render_fixed_t fOne = DOUBLE_TO_FIXED(1);
38 xcb_render_fixed_t fZero = DOUBLE_TO_FIXED(0);
39
40 XRandROutput::XRandROutput(xcb_randr_output_t id, XRandRConfig* config)
41 : QObject(config)
42 , m_config(config)
43 , m_id(id)
44 , m_primary(false)
45 , m_type(Disman::Output::Unknown)
46 , m_crtc(nullptr)
47 {
48 init();
49 }
50
51 XRandROutput::~XRandROutput()
52 {
53 }
54
55 void XRandROutput::disconnected()
56 {
57 if (m_crtc) {
58 xcb_randr_set_crtc_config(XCB::connection(),
59 m_crtc->crtc(),
60 XCB_CURRENT_TIME,
61 XCB_CURRENT_TIME,
62 0,
63 0,
64 XCB_NONE,
65 XCB_RANDR_ROTATION_ROTATE_0,
66 0,
67 nullptr);
68 m_crtc->disconectOutput(m_id);
69 m_crtc = nullptr;
70 }
71 }
72
73 xcb_randr_output_t XRandROutput::id() const
74 {
75 return m_id;
76 }
77
78 std::string XRandROutput::description() const
79 {
80 std::string ret;
81
82 Disman::Edid edid(this->edid());
83 if (edid.isValid()) {
84 if (auto vendor = edid.vendor(); vendor.size()) {
85 ret += vendor + " ";
86 }
87 if (auto name = edid.name(); name.size()) {
88 ret += name + " ";
89 }
90 }
91 if (!ret.size()) {
92 return m_name.toStdString();
93 }
94 return ret + "(" + m_name.toStdString() + ")";
95 }
96
97 std::string XRandROutput::hash() const
98 {
99 Disman::Edid edid(this->edid());
100 if (!edid.isValid()) {
101 return m_name.toStdString();
102 }
103 return edid.hash();
104 }
105
106 bool XRandROutput::isConnected() const
107 {
108 return m_connected == XCB_RANDR_CONNECTION_CONNECTED;
109 }
110
111 bool XRandROutput::enabled() const
112 {
113 return m_crtc != nullptr && m_crtc->mode() != XCB_NONE;
114 }
115
116 bool XRandROutput::isPrimary() const
117 {
118 return m_primary;
119 }
120
121 QPoint XRandROutput::position() const
122 {
123 return m_crtc ? m_crtc->geometry().topLeft() : QPoint();
124 }
125
126 QSize XRandROutput::size() const
127 {
128 return m_crtc ? m_crtc->geometry().size() : QSize();
129 }
130
131 XRandRMode::Map XRandROutput::modes() const
132 {
133 return m_modes;
134 }
135
136 std::string XRandROutput::currentModeId() const
137 {
138 return m_crtc ? std::to_string(m_crtc->mode()) : std::string();
139 }
140
141 XRandRMode* XRandROutput::currentMode() const
142 {
143 if (!m_crtc) {
144 return nullptr;
145 }
146
147 unsigned int modeId = m_crtc->mode();
148 if (auto mode = m_modes.find(modeId); mode != m_modes.end()) {
149 return mode->second;
150 }
151 return nullptr;
152 }
153
154 Disman::Output::Rotation XRandROutput::rotation() const
155 {
156 return static_cast<Disman::Output::Rotation>(m_crtc ? m_crtc->rotation()
157 : XCB_RANDR_ROTATION_ROTATE_0);
158 }
159
160 bool XRandROutput::isHorizontal() const
161 {
162 const auto rot = rotation();
163 return rot == Disman::Output::Rotation::None || rot == Disman::Output::Rotation::Inverted;
164 }
165
166 QByteArray XRandROutput::edid() const
167 {
168 if (m_edid.isNull()) {
169 m_edid = XRandR::outputEdid(m_id);
170 }
171 return m_edid;
172 }
173
174 XRandRCrtc* XRandROutput::crtc() const
175 {
176 return m_crtc;
177 }
178
179 void XRandROutput::update()
180 {
181 init();
182 }
183
184 void XRandROutput::update(xcb_randr_crtc_t crtc,
185 xcb_randr_mode_t mode,
186 xcb_randr_connection_t conn,
187 bool primary)
188 {
189 qCDebug(DISMAN_XRANDR) << "XRandROutput" << m_id << "update"
190 << "\n"
191 << "\tm_connected:" << m_connected << "\n"
192 << "\tm_crtc" << m_crtc << "\n"
193 << "\tCRTC:" << crtc << "\n"
194 << "\tMODE:" << mode << "\n"
195 << "\tConnection:" << conn << "\n"
196 << "\tPrimary:" << primary;
197
198 // Connected or disconnected
199 if (isConnected() != (conn == XCB_RANDR_CONNECTION_CONNECTED)) {
200 if (conn == XCB_RANDR_CONNECTION_CONNECTED) {
201 // New monitor has been connected, refresh everything
202 init();
203 } else {
204 // Disconnected
205 m_connected = conn;
206 m_heightMm = 0;
207 m_widthMm = 0;
208 m_type = Disman::Output::Unknown;
209
210 for (auto& [key, mode] : m_modes) {
211 delete mode;
212 }
213 m_modes.clear();
214
215 m_preferredModes.clear();
216 m_edid.clear();
217 }
218 } else if (conn == XCB_RANDR_CONNECTION_CONNECTED) {
219 // the output changed in some way, let's update the internal
220 // list of modes, as it may have changed
221 XCB::OutputInfo outputInfo(m_id, XCB_TIME_CURRENT_TIME);
222 if (outputInfo) {
223 updateModes(outputInfo);
224 }
225
226 m_hotplugModeUpdate = XRandR::hasProperty(m_id, "hotplug_mode_update");
227 }
228
229 // A monitor has been enabled or disabled
230 // We don't use enabled(), because it checks for crtc && crtc->mode(), however
231 // crtc->mode may already be unset due to xcb_randr_crtc_tChangeNotify coming before
232 // xcb_randr_output_tChangeNotify and reseting the CRTC mode
233
234 if ((m_crtc == nullptr) != (crtc == XCB_NONE)) {
235 if (crtc == XCB_NONE && mode == XCB_NONE) {
236 // Monitor has been disabled
237 m_crtc->disconectOutput(m_id);
238 m_crtc = nullptr;
239 } else {
240 m_crtc = m_config->crtc(crtc);
241 m_crtc->connectOutput(m_id);
242 }
243 }
244
245 // Primary has changed
246 m_primary = primary;
247 }
248
249 void XRandROutput::setIsPrimary(bool primary)
250 {
251 m_primary = primary;
252 }
253
254 void XRandROutput::init()
255 {
256 XCB::OutputInfo outputInfo(m_id, XCB_TIME_CURRENT_TIME);
257 Q_ASSERT(outputInfo);
258 if (!outputInfo) {
259 return;
260 }
261
262 XCB::PrimaryOutput primary(XRandR::rootWindow());
263
264 m_name = QString::fromUtf8((const char*)xcb_randr_get_output_info_name(outputInfo.data()),
265 outputInfo->name_len);
266 m_type = fetchOutputType(m_id, m_name);
267 m_connected = (xcb_randr_connection_t)outputInfo->connection;
268 m_primary = (primary->output == m_id);
269
270 m_widthMm = outputInfo->mm_width;
271 m_heightMm = outputInfo->mm_height;
272
273 m_crtc = m_config->crtc(outputInfo->crtc);
274 if (m_crtc) {
275 m_crtc->connectOutput(m_id);
276 }
277 m_hotplugModeUpdate = XRandR::hasProperty(m_id, "hotplug_mode_update");
278
279 updateModes(outputInfo);
280 }
281
282 void XRandROutput::updateModes(const XCB::OutputInfo& outputInfo)
283 {
284 /* Init modes */
285 XCB::ScopedPointer<xcb_randr_get_screen_resources_reply_t> screenResources(
286 XRandR::screenResources());
287
288 Q_ASSERT(screenResources);
289 if (!screenResources) {
290 return;
291 }
292 xcb_randr_mode_info_t* modes = xcb_randr_get_screen_resources_modes(screenResources.data());
293 xcb_randr_mode_t* outputModes = xcb_randr_get_output_info_modes(outputInfo.data());
294
295 m_preferredModes.clear();
296
297 for (auto& [key, mode] : m_modes) {
298 delete mode;
299 }
300 m_modes.clear();
301
302 for (int i = 0; i < outputInfo->num_modes; ++i) {
303 /* Resources->modes contains all possible modes, we are only interested
304 * in those listed in outputInfo->modes. */
305 for (int j = 0; j < screenResources->num_modes; ++j) {
306 if (modes[j].id != outputModes[i]) {
307 continue;
308 }
309
310 XRandRMode* mode = new XRandRMode(modes[j], this);
311 if (mode->doubleScan()) {
312 delete mode;
313 continue;
314 }
315
316 m_modes.insert({mode->id(), mode});
317
318 if (i < outputInfo->num_preferred) {
319 m_preferredModes.push_back(std::to_string(mode->id()));
320 }
321 break;
322 }
323 }
324 }
325
326 Disman::Output::Type XRandROutput::fetchOutputType(xcb_randr_output_t outputId, const QString& name)
327 {
328 QString type = QString::fromUtf8(typeFromProperty(outputId));
329 if (type.isEmpty()) {
330 type = name;
331 }
332
333 return Utils::guessOutputType(type, name);
334 }
335
336 QByteArray XRandROutput::typeFromProperty(xcb_randr_output_t outputId)
337 {
338 QByteArray type;
339
340 XCB::InternAtom atomType(true, 13, "ConnectorType");
341 if (!atomType) {
342 return type;
343 }
344
345 auto cookie = xcb_randr_get_output_property(
346 XCB::connection(), outputId, atomType->atom, XCB_ATOM_ANY, 0, 100, false, false);
347 XCB::ScopedPointer<xcb_randr_get_output_property_reply_t> reply(
348 xcb_randr_get_output_property_reply(XCB::connection(), cookie, nullptr));
349 if (!reply) {
350 return type;
351 }
352
353 if (!(reply->type == XCB_ATOM_ATOM && reply->format == 32 && reply->num_items == 1)) {
354 return type;
355 }
356
357 const uint8_t* prop = xcb_randr_get_output_property_data(reply.data());
358 XCB::AtomName atomName(*reinterpret_cast<const xcb_atom_t*>(prop));
359 if (!atomName) {
360 return type;
361 }
362
363 char* connectorType = xcb_get_atom_name_name(atomName);
364 if (!connectorType) {
365 return type;
366 }
367
368 type = connectorType;
369 return type;
370 }
371
372 bool isScaling(const xcb_render_transform_t& tr)
373 {
374 return tr.matrix11 != fZero && tr.matrix12 == fZero && tr.matrix13 == fZero
375 && tr.matrix21 == fZero && tr.matrix22 != fZero && tr.matrix23 == fZero
376 && tr.matrix31 == fZero && tr.matrix32 == fZero && tr.matrix33 == fOne;
377 }
378
379 xcb_render_transform_t zeroTransform()
380 {
381 return {DOUBLE_TO_FIXED(0),
382 DOUBLE_TO_FIXED(0),
383 DOUBLE_TO_FIXED(0),
384 DOUBLE_TO_FIXED(0),
385 DOUBLE_TO_FIXED(0),
386 DOUBLE_TO_FIXED(0),
387 DOUBLE_TO_FIXED(0),
388 DOUBLE_TO_FIXED(0),
389 DOUBLE_TO_FIXED(0)};
390 }
391
392 xcb_render_transform_t unityTransform()
393 {
394 return {DOUBLE_TO_FIXED(1),
395 DOUBLE_TO_FIXED(0),
396 DOUBLE_TO_FIXED(0),
397 DOUBLE_TO_FIXED(0),
398 DOUBLE_TO_FIXED(1),
399 DOUBLE_TO_FIXED(0),
400 DOUBLE_TO_FIXED(0),
401 DOUBLE_TO_FIXED(0),
402 DOUBLE_TO_FIXED(1)};
403 }
404
405 xcb_render_transform_t XRandROutput::currentTransform() const
406 {
407 auto cookie = xcb_randr_get_crtc_transform(XCB::connection(), m_crtc->crtc());
408 xcb_generic_error_t* error = nullptr;
409 auto* reply = xcb_randr_get_crtc_transform_reply(XCB::connection(), cookie, &error);
410 if (error) {
411 return zeroTransform();
412 }
413
414 const xcb_render_transform_t transform = reply->pending_transform;
415 free(reply);
416 return transform;
417 }
418
419 QSizeF XRandROutput::logicalSize() const
420 {
421 const QSize modeSize = size();
422 if (!modeSize.isValid()) {
423 return QSize();
424 }
425
426 const xcb_render_transform_t transform = currentTransform();
427 const qreal width = FIXED_TO_DOUBLE(transform.matrix11) * modeSize.width();
428 const qreal height = FIXED_TO_DOUBLE(transform.matrix22) * modeSize.height();
429
430 return QSizeF(width, height);
431 }
432
433 void XRandROutput::updateLogicalSize(const Disman::OutputPtr& output, XRandRCrtc* crtc)
434 {
435 if (!crtc) {
436 // TODO: This is a workaround for now when updateLogicalSize is called on enabling the
437 // output. At this point m_crtc is not yet set. Change the order in the future so
438 // that the additional argument is not necessary anymore.
439 crtc = m_crtc;
440 }
441 auto const logicalSize = output->geometry().size();
442 xcb_render_transform_t transform = unityTransform();
443
444 auto mode = output->auto_mode() ? output->auto_mode() : output->preferred_mode();
445 if (mode && logicalSize.isValid()) {
446 QSize modeSize = mode->size();
447 if (!output->horizontal()) {
448 modeSize.transpose();
449 }
450
451 const qreal widthFactor = logicalSize.width() / (qreal)modeSize.width();
452 const qreal heightFactor = logicalSize.height() / (qreal)modeSize.height();
453 transform.matrix11 = DOUBLE_TO_FIXED(widthFactor);
454 transform.matrix22 = DOUBLE_TO_FIXED(heightFactor);
455 }
456
457 QByteArray filterName(isScaling(transform) ? "bilinear" : "nearest");
458
459 auto cookie = xcb_randr_set_crtc_transform_checked(XCB::connection(),
460 crtc->crtc(),
461 transform,
462 filterName.size(),
463 filterName.data(),
464 0,
465 nullptr);
466 xcb_generic_error_t* error = xcb_request_check(XCB::connection(), cookie);
467 if (error) {
468 qCDebug(DISMAN_XRANDR) << "Error on logical size transformation!";
469 free(error);
470 }
471 }
472
473 void XRandROutput::updateDismanOutput(Disman::OutputPtr& dismanOutput) const
474 {
475 const bool signalsBlocked = dismanOutput->signalsBlocked();
476 dismanOutput->blockSignals(true);
477 dismanOutput->set_id(m_id);
478 dismanOutput->setType(m_type);
479 dismanOutput->set_physical_size(QSize(m_widthMm, m_heightMm));
480 dismanOutput->set_name(m_name.toStdString());
481 dismanOutput->set_description(description());
482 dismanOutput->set_hash(this->hash());
483
484 // Currently we do not set the edid since it messes with our control files.
485 // TODO: Decide on a common principle for identifying outputs in Wayland and X11. EDID, name,
486 // with or without connector?
487 #if 0
488 dismanOutput->setEdid(m_edid);
489 #endif
490
491 // See https://bugzilla.redhat.com/show_bug.cgi?id=1290586
492 // QXL will be creating a new mode we need to jump to every time the display is resized
493 dismanOutput->set_follow_preferred_mode(m_hotplugModeUpdate);
494
495 if (isConnected()) {
496 Disman::ModeMap dismanModes;
497 for (auto const& [key, mode] : m_modes) {
498 dismanModes.insert({std::to_string(key), mode->toDismanMode()});
499 }
500 dismanOutput->set_modes(dismanModes);
501 dismanOutput->set_preferred_modes(m_preferredModes);
502 dismanOutput->set_enabled(enabled());
503 if (enabled()) {
504 dismanOutput->set_position(position());
505 dismanOutput->set_rotation(rotation());
506
507 auto cur_mode = currentMode();
508 if (cur_mode) {
509 dismanOutput->set_mode(dismanOutput->mode(std::to_string(cur_mode->id())));
510 dismanOutput->set_resolution(cur_mode->size());
511 dismanOutput->set_refresh_rate(cur_mode->refreshRate() * 1000);
512 }
513 }
514 // TODO: set logical size?
515 }
516
517 dismanOutput->blockSignals(signalsBlocked);
518 }
519