1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qxcbxsettings.h"
5
6#include <QtCore/QByteArray>
7#include <QtCore/QtEndian>
8
9#include <vector>
10#include <algorithm>
11
12QT_BEGIN_NAMESPACE
13/* Implementation of http://standards.freedesktop.org/xsettings-spec/xsettings-0.5.html */
14
15enum XSettingsType {
16 XSettingsTypeInteger = 0,
17 XSettingsTypeString = 1,
18 XSettingsTypeColor = 2
19};
20
21struct QXcbXSettingsCallback
22{
23 QXcbXSettings::PropertyChangeFunc func;
24 void *handle;
25};
26
27class QXcbXSettingsPropertyValue
28{
29public:
30 QXcbXSettingsPropertyValue()
31 : last_change_serial(-1)
32 {}
33
34 void updateValue(QXcbVirtualDesktop *screen, const QByteArray &name, const QVariant &value, int last_change_serial)
35 {
36 if (last_change_serial <= this->last_change_serial)
37 return;
38 this->value = value;
39 this->last_change_serial = last_change_serial;
40 for (const auto &callback : callback_links)
41 callback.func(screen, name, value, callback.handle);
42 }
43
44 void addCallback(QXcbXSettings::PropertyChangeFunc func, void *handle)
45 {
46 QXcbXSettingsCallback callback = { .func: func, .handle: handle };
47 callback_links.push_back(x: callback);
48 }
49
50 QVariant value;
51 int last_change_serial;
52 std::vector<QXcbXSettingsCallback> callback_links;
53
54};
55
56class QXcbXSettingsPrivate
57{
58public:
59 QXcbXSettingsPrivate(QXcbVirtualDesktop *screen)
60 : screen(screen)
61 , initialized(false)
62 {
63 }
64
65 QByteArray getSettings()
66 {
67 QXcbConnectionGrabber connectionGrabber(screen->connection());
68
69 int offset = 0;
70 QByteArray settings;
71 xcb_atom_t _xsettings_atom = screen->connection()->atom(qatom: QXcbAtom::Atom_XSETTINGS_SETTINGS);
72 while (1) {
73 auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property,
74 screen->xcb_connection(),
75 false,
76 x_settings_window,
77 _xsettings_atom,
78 _xsettings_atom,
79 offset/4,
80 8192);
81 bool more = false;
82 if (!reply)
83 return settings;
84
85 const auto property_value_length = xcb_get_property_value_length(R: reply.get());
86 settings.append(s: static_cast<const char *>(xcb_get_property_value(R: reply.get())), len: property_value_length);
87 offset += property_value_length;
88 more = reply->bytes_after != 0;
89
90 if (!more)
91 break;
92 }
93
94 return settings;
95 }
96
97 static int round_to_nearest_multiple_of_4(int value)
98 {
99 int remainder = value % 4;
100 if (!remainder)
101 return value;
102 return value + 4 - remainder;
103 }
104
105 void populateSettings(const QByteArray &xSettings)
106 {
107 if (xSettings.size() < 12)
108 return;
109 char byteOrder = xSettings.at(i: 0);
110 if (byteOrder != XCB_IMAGE_ORDER_LSB_FIRST && byteOrder != XCB_IMAGE_ORDER_MSB_FIRST) {
111 qWarning(msg: "ByteOrder byte %d not 0 or 1", byteOrder);
112 return;
113 }
114
115#define ADJUST_BO(b, t, x) \
116 ((b == XCB_IMAGE_ORDER_LSB_FIRST) ? \
117 qFromLittleEndian<t>(x) : \
118 qFromBigEndian<t>(x))
119#define VALIDATE_LENGTH(x) \
120 if ((size_t)xSettings.length() < (offset + local_offset + 12 + x)) { \
121 qWarning("Length %d runs past end of data", x); \
122 return; \
123 }
124
125 uint number_of_settings = ADJUST_BO(byteOrder, quint32, xSettings.mid(8,4).constData());
126 const char *data = xSettings.constData() + 12;
127 size_t offset = 0;
128 for (uint i = 0; i < number_of_settings; i++) {
129 int local_offset = 0;
130 VALIDATE_LENGTH(2);
131 XSettingsType type = static_cast<XSettingsType>(*reinterpret_cast<const quint8 *>(data + offset));
132 local_offset += 2;
133
134 VALIDATE_LENGTH(2);
135 quint16 name_len = ADJUST_BO(byteOrder, quint16, data + offset + local_offset);
136 local_offset += 2;
137
138 VALIDATE_LENGTH(name_len);
139 QByteArray name(data + offset + local_offset, name_len);
140 local_offset += round_to_nearest_multiple_of_4(value: name_len);
141
142 VALIDATE_LENGTH(4);
143 int last_change_serial = ADJUST_BO(byteOrder, qint32, data + offset + local_offset);
144 Q_UNUSED(last_change_serial);
145 local_offset += 4;
146
147 QVariant value;
148 if (type == XSettingsTypeString) {
149 VALIDATE_LENGTH(4);
150 int value_length = ADJUST_BO(byteOrder, qint32, data + offset + local_offset);
151 local_offset+=4;
152 VALIDATE_LENGTH(value_length);
153 QByteArray value_string(data + offset + local_offset, value_length);
154 value.setValue(value_string);
155 local_offset += round_to_nearest_multiple_of_4(value: value_length);
156 } else if (type == XSettingsTypeInteger) {
157 VALIDATE_LENGTH(4);
158 int value_length = ADJUST_BO(byteOrder, qint32, data + offset + local_offset);
159 local_offset += 4;
160 value.setValue(value_length);
161 } else if (type == XSettingsTypeColor) {
162 VALIDATE_LENGTH(2*4);
163 quint16 red = ADJUST_BO(byteOrder, quint16, data + offset + local_offset);
164 local_offset += 2;
165 quint16 green = ADJUST_BO(byteOrder, quint16, data + offset + local_offset);
166 local_offset += 2;
167 quint16 blue = ADJUST_BO(byteOrder, quint16, data + offset + local_offset);
168 local_offset += 2;
169 quint16 alpha= ADJUST_BO(byteOrder, quint16, data + offset + local_offset);
170 local_offset += 2;
171 QColor color_value(red,green,blue,alpha);
172 value.setValue(color_value);
173 }
174 offset += local_offset;
175 settings[name].updateValue(screen,name,value,last_change_serial);
176 }
177
178 }
179
180 QXcbVirtualDesktop *screen;
181 xcb_window_t x_settings_window;
182 QMap<QByteArray, QXcbXSettingsPropertyValue> settings;
183 bool initialized;
184};
185
186
187QXcbXSettings::QXcbXSettings(QXcbVirtualDesktop *screen)
188 : d_ptr(new QXcbXSettingsPrivate(screen))
189{
190 QByteArray settings_atom_for_screen("_XSETTINGS_S");
191 settings_atom_for_screen.append(a: QByteArray::number(screen->number()));
192 auto atom_reply = Q_XCB_REPLY(xcb_intern_atom,
193 screen->xcb_connection(),
194 true,
195 settings_atom_for_screen.size(),
196 settings_atom_for_screen.constData());
197 if (!atom_reply)
198 return;
199
200 xcb_atom_t selection_owner_atom = atom_reply->atom;
201
202 xcb_window_t owner = screen->connection()->selectionOwner(atom: selection_owner_atom);
203 if (owner == XCB_NONE)
204 return;
205
206 d_ptr->x_settings_window = owner;
207 if (!d_ptr->x_settings_window)
208 return;
209
210 screen->connection()->addWindowEventListener(id: d_ptr->x_settings_window, eventListener: this);
211 const uint32_t event = XCB_CW_EVENT_MASK;
212 const uint32_t event_mask[] = { XCB_EVENT_MASK_STRUCTURE_NOTIFY|XCB_EVENT_MASK_PROPERTY_CHANGE };
213 xcb_change_window_attributes(c: screen->xcb_connection(),window: d_ptr->x_settings_window,value_mask: event,value_list: event_mask);
214
215 d_ptr->populateSettings(xSettings: d_ptr->getSettings());
216 d_ptr->initialized = true;
217}
218
219QXcbXSettings::~QXcbXSettings()
220{
221 delete d_ptr;
222 d_ptr = nullptr;
223}
224
225bool QXcbXSettings::initialized() const
226{
227 Q_D(const QXcbXSettings);
228 return d->initialized;
229}
230
231void QXcbXSettings::handlePropertyNotifyEvent(const xcb_property_notify_event_t *event)
232{
233 Q_D(QXcbXSettings);
234 if (event->window != d->x_settings_window)
235 return;
236
237 d->populateSettings(xSettings: d->getSettings());
238}
239
240void QXcbXSettings::registerCallbackForProperty(const QByteArray &property, QXcbXSettings::PropertyChangeFunc func, void *handle)
241{
242 Q_D(QXcbXSettings);
243 d->settings[property].addCallback(func,handle);
244}
245
246void QXcbXSettings::removeCallbackForHandle(const QByteArray &property, void *handle)
247{
248 Q_D(QXcbXSettings);
249 auto &callbacks = d->settings[property].callback_links;
250
251 auto isCallbackForHandle = [handle](const QXcbXSettingsCallback &cb) { return cb.handle == handle; };
252
253 callbacks.erase(first: std::remove_if(first: callbacks.begin(), last: callbacks.end(),
254 pred: isCallbackForHandle),
255 last: callbacks.end());
256}
257
258void QXcbXSettings::removeCallbackForHandle(void *handle)
259{
260 Q_D(QXcbXSettings);
261 for (QMap<QByteArray, QXcbXSettingsPropertyValue>::const_iterator it = d->settings.cbegin();
262 it != d->settings.cend(); ++it) {
263 removeCallbackForHandle(property: it.key(),handle);
264 }
265}
266
267QVariant QXcbXSettings::setting(const QByteArray &property) const
268{
269 Q_D(const QXcbXSettings);
270 return d->settings.value(key: property).value;
271}
272
273QT_END_NAMESPACE
274

source code of qtbase/src/plugins/platforms/xcb/qxcbxsettings.cpp