GCC Code Coverage Report


Directory: ./
File: backends/xrandr/xrandr.cpp
Date: 2023-04-20 22:59:23
Exec Total Coverage
Lines: 0 132 0.0%
Branches: 0 62 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 "xrandr.h"
20
21 #include "xcbeventlistener.h"
22 #include "xcbwrapper.h"
23 #include "xrandrconfig.h"
24 #include "xrandrscreen.h"
25
26 #include "xrandr_logging.h"
27
28 #include "config.h"
29 #include "generator.h"
30 #include "output.h"
31
32 #include <QRect>
33 #include <QTime>
34 #include <QTimer>
35
36 #include <QtGui/private/qtx11extras_p.h>
37
38 xcb_screen_t* XRandR::s_screen = nullptr;
39 xcb_window_t XRandR::s_rootWindow = 0;
40 XRandRConfig* XRandR::s_internalConfig = nullptr;
41 int XRandR::s_randrBase = 0;
42 int XRandR::s_randrError = 0;
43 bool XRandR::s_monitorInitialized = false;
44 bool XRandR::s_has_1_3 = false;
45 bool XRandR::s_xorgCacheInitialized = false;
46
47 using namespace Disman;
48
49 XRandR::XRandR()
50 : Disman::BackendImpl()
51 , m_x11Helper(nullptr)
52 , m_valid(false)
53 , m_configChangeCompressor(nullptr)
54 {
55 qRegisterMetaType<xcb_randr_output_t>("xcb_randr_output_t");
56 qRegisterMetaType<xcb_randr_crtc_t>("xcb_randr_crtc_t");
57 qRegisterMetaType<xcb_randr_mode_t>("xcb_randr_mode_t");
58 qRegisterMetaType<xcb_randr_connection_t>("xcb_randr_connection_t");
59 qRegisterMetaType<xcb_randr_rotation_t>("xcb_randr_rotation_t");
60
61 // Use our own connection to make sure that we won't mess up Qt's connection
62 // if something goes wrong on our side.
63 xcb_generic_error_t* error = nullptr;
64 xcb_randr_query_version_reply_t* version;
65 XCB::connection();
66 version = xcb_randr_query_version_reply(XCB::connection(),
67 xcb_randr_query_version(XCB::connection(),
68 XCB_RANDR_MAJOR_VERSION,
69 XCB_RANDR_MINOR_VERSION),
70 &error);
71 if (!version || error) {
72 XCB::closeConnection();
73 free(error);
74 return;
75 }
76
77 if ((version->major_version > 1)
78 || ((version->major_version == 1) && (version->minor_version >= 2))) {
79 m_valid = true;
80 } else {
81 XCB::closeConnection();
82 qCWarning(DISMAN_XRANDR) << "XRandR extension not available or unsupported version";
83 return;
84 }
85
86 if (s_screen == nullptr) {
87 s_screen = XCB::screenOfDisplay(XCB::connection(), QX11Info::appScreen());
88 s_rootWindow = s_screen->root;
89
90 xcb_prefetch_extension_data(XCB::connection(), &xcb_randr_id);
91 auto reply = xcb_get_extension_data(XCB::connection(), &xcb_randr_id);
92 s_randrBase = reply->first_event;
93 s_randrError = reply->first_error;
94 }
95
96 XRandR::s_has_1_3 = (version->major_version > 1
97 || (version->major_version == 1 && version->minor_version >= 3));
98
99 if (s_internalConfig == nullptr) {
100 s_internalConfig = new XRandRConfig();
101 }
102
103 if (!s_monitorInitialized) {
104 m_x11Helper = new XCBEventListener();
105 connect(m_x11Helper,
106 &XCBEventListener::outputChanged,
107 this,
108 &XRandR::outputChanged,
109 Qt::QueuedConnection);
110 connect(m_x11Helper,
111 &XCBEventListener::crtcChanged,
112 this,
113 &XRandR::crtcChanged,
114 Qt::QueuedConnection);
115 connect(m_x11Helper,
116 &XCBEventListener::screenChanged,
117 this,
118 &XRandR::screenChanged,
119 Qt::QueuedConnection);
120
121 m_configChangeCompressor = new QTimer(this);
122 m_configChangeCompressor->setSingleShot(true);
123 m_configChangeCompressor->setInterval(500);
124 connect(m_configChangeCompressor, &QTimer::timeout, this, &XRandR::handle_config_change);
125
126 handle_config_change();
127 s_monitorInitialized = true;
128 }
129 }
130
131 XRandR::~XRandR()
132 {
133 delete m_x11Helper;
134 }
135
136 QString XRandR::name() const
137 {
138 return QStringLiteral("XRandR");
139 }
140
141 QString XRandR::service_name() const
142 {
143 return QStringLiteral("org.kde.Disman.Backend.XRandR");
144 }
145
146 void XRandR::outputChanged(xcb_randr_output_t output,
147 xcb_randr_crtc_t crtc,
148 xcb_randr_mode_t mode,
149 xcb_randr_connection_t connection)
150 {
151 m_configChangeCompressor->start();
152
153 auto xOutput = s_internalConfig->output(output);
154
155 if (connection == XCB_RANDR_CONNECTION_DISCONNECTED) {
156 if (xOutput) {
157 xOutput->disconnected();
158 s_internalConfig->removeOutput(output);
159 qCDebug(DISMAN_XRANDR) << "Output" << output << " removed";
160 }
161 return;
162 }
163
164 if (!xOutput) {
165 s_internalConfig->addNewOutput(output);
166 return;
167 }
168
169 XCB::PrimaryOutput primary(XRandR::rootWindow());
170 xOutput->update(crtc, mode, connection, (primary->output == output));
171 qCDebug(DISMAN_XRANDR) << "Output" << xOutput->id() << ": connected =" << xOutput->isConnected()
172 << ", enabled =" << xOutput->enabled();
173 }
174
175 void XRandR::crtcChanged(xcb_randr_crtc_t crtc,
176 xcb_randr_mode_t mode,
177 xcb_randr_rotation_t rotation,
178 const QRect& geom)
179 {
180 XRandRCrtc* xCrtc = s_internalConfig->crtc(crtc);
181 if (!xCrtc) {
182 s_internalConfig->addNewCrtc(crtc);
183 } else {
184 xCrtc->update(mode, rotation, geom);
185 }
186
187 m_configChangeCompressor->start();
188 }
189
190 void XRandR::screenChanged(xcb_randr_rotation_t rotation,
191 const QSize& sizePx,
192 const QSize& physical_size)
193 {
194 Q_UNUSED(physical_size);
195
196 QSize newSizePx = sizePx;
197 if (rotation == XCB_RANDR_ROTATION_ROTATE_90 || rotation == XCB_RANDR_ROTATION_ROTATE_270) {
198 newSizePx.transpose();
199 }
200
201 XRandRScreen* xScreen = s_internalConfig->screen();
202 Q_ASSERT(xScreen);
203 xScreen->update(newSizePx);
204
205 m_configChangeCompressor->start();
206 }
207
208 void XRandR::update_config(ConfigPtr& config) const
209 {
210 s_internalConfig->update_config(config);
211 }
212
213 bool XRandR::set_config_system(Disman::ConfigPtr const& config)
214 {
215 return s_internalConfig->applyDismanConfig(config);
216 }
217
218 bool XRandR::valid() const
219 {
220 return m_valid;
221 }
222
223 quint8* XRandR::getXProperty(xcb_randr_output_t output, xcb_atom_t atom, size_t& len)
224 {
225 quint8* result;
226
227 auto cookie = xcb_randr_get_output_property(
228 XCB::connection(), output, atom, XCB_ATOM_ANY, 0, 100, false, false);
229 auto reply = xcb_randr_get_output_property_reply(XCB::connection(), cookie, nullptr);
230
231 if (reply->type == XCB_ATOM_INTEGER && reply->format == 8) {
232 result = new quint8[reply->num_items];
233 memcpy(result, xcb_randr_get_output_property_data(reply), reply->num_items);
234 len = reply->num_items;
235 } else {
236 result = nullptr;
237 }
238
239 free(reply);
240 return result;
241 }
242
243 QByteArray XRandR::outputEdid(xcb_randr_output_t outputId)
244 {
245 size_t len = 0;
246 quint8* result;
247
248 auto edid_atom = XCB::InternAtom(false, 4, "EDID")->atom;
249 result = XRandR::getXProperty(outputId, edid_atom, len);
250 if (result == nullptr) {
251 auto edid_atom = XCB::InternAtom(false, 9, "EDID_DATA")->atom;
252 result = XRandR::getXProperty(outputId, edid_atom, len);
253 }
254 if (result == nullptr) {
255 auto edid_atom = XCB::InternAtom(false, 25, "XFree86_DDC_EDID1_RAWDATA")->atom;
256 result = XRandR::getXProperty(outputId, edid_atom, len);
257 }
258
259 QByteArray edid;
260 if (result != nullptr) {
261 if (len % 128 == 0) {
262 edid = QByteArray(reinterpret_cast<const char*>(result), len);
263 }
264 delete[] result;
265 }
266 return edid;
267 }
268
269 bool XRandR::hasProperty(xcb_randr_output_t output, const QByteArray& name)
270 {
271 xcb_generic_error_t* error = nullptr;
272 auto atom = XCB::InternAtom(false, name.length(), name.constData())->atom;
273
274 auto cookie = xcb_randr_get_output_property(
275 XCB::connection(), output, atom, XCB_ATOM_ANY, 0, 1, false, false);
276 auto prop_reply = xcb_randr_get_output_property_reply(XCB::connection(), cookie, &error);
277
278 const bool ret = prop_reply->num_items == 1;
279 free(prop_reply);
280 return ret;
281 }
282
283 xcb_randr_get_screen_resources_reply_t* XRandR::screenResources()
284 {
285 if (XRandR::s_has_1_3) {
286 if (XRandR::s_xorgCacheInitialized) {
287 // HACK: This abuses the fact that xcb_randr_get_screen_resources_reply_t
288 // and xcb_randr_get_screen_resources_current_reply_t are the same
289 return reinterpret_cast<xcb_randr_get_screen_resources_reply_t*>(
290 xcb_randr_get_screen_resources_current_reply(
291 XCB::connection(),
292 xcb_randr_get_screen_resources_current(XCB::connection(), XRandR::rootWindow()),
293 nullptr));
294 } else {
295 /* XRRGetScreenResourcesCurrent is faster then XRRGetScreenResources
296 * because it returns cached values. However the cached values are not
297 * available until someone calls XRRGetScreenResources first. In case
298 * we happen to be the first ones, we need to fill the cache first. */
299 XRandR::s_xorgCacheInitialized = true;
300 }
301 }
302
303 return xcb_randr_get_screen_resources_reply(
304 XCB::connection(),
305 xcb_randr_get_screen_resources(XCB::connection(), XRandR::rootWindow()),
306 nullptr);
307 }
308
309 xcb_window_t XRandR::rootWindow()
310 {
311 return s_rootWindow;
312 }
313
314 xcb_screen_t* XRandR::screen()
315 {
316 return s_screen;
317 }
318