1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the plugins of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qxcbxsettings.h"
41
42#include <QtCore/QByteArray>
43#include <QtCore/QtEndian>
44
45#include <vector>
46#include <algorithm>
47
48QT_BEGIN_NAMESPACE
49/* Implementation of http://standards.freedesktop.org/xsettings-spec/xsettings-0.5.html */
50
51enum XSettingsType {
52 XSettingsTypeInteger = 0,
53 XSettingsTypeString = 1,
54 XSettingsTypeColor = 2
55};
56
57struct QXcbXSettingsCallback
58{
59 QXcbXSettings::PropertyChangeFunc func;
60 void *handle;
61};
62
63class QXcbXSettingsPropertyValue
64{
65public:
66 QXcbXSettingsPropertyValue()
67 : last_change_serial(-1)
68 {}
69
70 void updateValue(QXcbVirtualDesktop *screen, const QByteArray &name, const QVariant &value, int last_change_serial)
71 {
72 if (last_change_serial <= this->last_change_serial)
73 return;
74 this->value = value;
75 this->last_change_serial = last_change_serial;
76 for (const auto &callback : callback_links)
77 callback.func(screen, name, value, callback.handle);
78 }
79
80 void addCallback(QXcbXSettings::PropertyChangeFunc func, void *handle)
81 {
82 QXcbXSettingsCallback callback = { func, handle };
83 callback_links.push_back(callback);
84 }
85
86 QVariant value;
87 int last_change_serial;
88 std::vector<QXcbXSettingsCallback> callback_links;
89
90};
91
92class QXcbXSettingsPrivate
93{
94public:
95 QXcbXSettingsPrivate(QXcbVirtualDesktop *screen)
96 : screen(screen)
97 , initialized(false)
98 {
99 }
100
101 QByteArray getSettings()
102 {
103 QXcbConnectionGrabber connectionGrabber(screen->connection());
104
105 int offset = 0;
106 QByteArray settings;
107 xcb_atom_t _xsettings_atom = screen->connection()->atom(QXcbAtom::_XSETTINGS_SETTINGS);
108 while (1) {
109 auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property,
110 screen->xcb_connection(),
111 false,
112 x_settings_window,
113 _xsettings_atom,
114 _xsettings_atom,
115 offset/4,
116 8192);
117 bool more = false;
118 if (!reply)
119 return settings;
120
121 const auto property_value_length = xcb_get_property_value_length(reply.get());
122 settings.append(static_cast<const char *>(xcb_get_property_value(reply.get())), property_value_length);
123 offset += property_value_length;
124 more = reply->bytes_after != 0;
125
126 if (!more)
127 break;
128 }
129
130 return settings;
131 }
132
133 static int round_to_nearest_multiple_of_4(int value)
134 {
135 int remainder = value % 4;
136 if (!remainder)
137 return value;
138 return value + 4 - remainder;
139 }
140
141 void populateSettings(const QByteArray &xSettings)
142 {
143 if (xSettings.length() < 12)
144 return;
145 char byteOrder = xSettings.at(0);
146 if (byteOrder != XCB_IMAGE_ORDER_LSB_FIRST && byteOrder != XCB_IMAGE_ORDER_MSB_FIRST) {
147 qWarning("ByteOrder byte %d not 0 or 1", byteOrder);
148 return;
149 }
150
151#define ADJUST_BO(b, t, x) \
152 ((b == XCB_IMAGE_ORDER_LSB_FIRST) ? \
153 qFromLittleEndian<t>(x) : \
154 qFromBigEndian<t>(x))
155#define VALIDATE_LENGTH(x) \
156 if ((size_t)xSettings.length() < (offset + local_offset + 12 + x)) { \
157 qWarning("Length %d runs past end of data", x); \
158 return; \
159 }
160
161 uint number_of_settings = ADJUST_BO(byteOrder, quint32, xSettings.mid(8,4).constData());
162 const char *data = xSettings.constData() + 12;
163 size_t offset = 0;
164 for (uint i = 0; i < number_of_settings; i++) {
165 int local_offset = 0;
166 VALIDATE_LENGTH(2);
167 XSettingsType type = static_cast<XSettingsType>(*reinterpret_cast<const quint8 *>(data + offset));
168 local_offset += 2;
169
170 VALIDATE_LENGTH(2);
171 quint16 name_len = ADJUST_BO(byteOrder, quint16, data + offset + local_offset);
172 local_offset += 2;
173
174 VALIDATE_LENGTH(name_len);
175 QByteArray name(data + offset + local_offset, name_len);
176 local_offset += round_to_nearest_multiple_of_4(name_len);
177
178 VALIDATE_LENGTH(4);
179 int last_change_serial = ADJUST_BO(byteOrder, qint32, data + offset + local_offset);
180 Q_UNUSED(last_change_serial);
181 local_offset += 4;
182
183 QVariant value;
184 if (type == XSettingsTypeString) {
185 VALIDATE_LENGTH(4);
186 int value_length = ADJUST_BO(byteOrder, qint32, data + offset + local_offset);
187 local_offset+=4;
188 VALIDATE_LENGTH(value_length);
189 QByteArray value_string(data + offset + local_offset, value_length);
190 value.setValue(value_string);
191 local_offset += round_to_nearest_multiple_of_4(value_length);
192 } else if (type == XSettingsTypeInteger) {
193 VALIDATE_LENGTH(4);
194 int value_length = ADJUST_BO(byteOrder, qint32, data + offset + local_offset);
195 local_offset += 4;
196 value.setValue(value_length);
197 } else if (type == XSettingsTypeColor) {
198 VALIDATE_LENGTH(2*4);
199 quint16 red = ADJUST_BO(byteOrder, quint16, data + offset + local_offset);
200 local_offset += 2;
201 quint16 green = ADJUST_BO(byteOrder, quint16, data + offset + local_offset);
202 local_offset += 2;
203 quint16 blue = ADJUST_BO(byteOrder, quint16, data + offset + local_offset);
204 local_offset += 2;
205 quint16 alpha= ADJUST_BO(byteOrder, quint16, data + offset + local_offset);
206 local_offset += 2;
207 QColor color_value(red,green,blue,alpha);
208 value.setValue(color_value);
209 }
210 offset += local_offset;
211 settings[name].updateValue(screen,name,value,last_change_serial);
212 }
213
214 }
215
216 QXcbVirtualDesktop *screen;
217 xcb_window_t x_settings_window;
218 QMap<QByteArray, QXcbXSettingsPropertyValue> settings;
219 bool initialized;
220};
221
222
223QXcbXSettings::QXcbXSettings(QXcbVirtualDesktop *screen)
224 : d_ptr(new QXcbXSettingsPrivate(screen))
225{
226 QByteArray settings_atom_for_screen("_XSETTINGS_S");
227 settings_atom_for_screen.append(QByteArray::number(screen->number()));
228 auto atom_reply = Q_XCB_REPLY(xcb_intern_atom,
229 screen->xcb_connection(),
230 true,
231 settings_atom_for_screen.length(),
232 settings_atom_for_screen.constData());
233 if (!atom_reply)
234 return;
235
236 xcb_atom_t selection_owner_atom = atom_reply->atom;
237
238 auto selection_result = Q_XCB_REPLY(xcb_get_selection_owner,
239 screen->xcb_connection(), selection_owner_atom);
240 if (!selection_result)
241 return;
242
243 d_ptr->x_settings_window = selection_result->owner;
244 if (!d_ptr->x_settings_window)
245 return;
246
247 screen->connection()->addWindowEventListener(d_ptr->x_settings_window, this);
248 const uint32_t event = XCB_CW_EVENT_MASK;
249 const uint32_t event_mask[] = { XCB_EVENT_MASK_STRUCTURE_NOTIFY|XCB_EVENT_MASK_PROPERTY_CHANGE };
250 xcb_change_window_attributes(screen->xcb_connection(),d_ptr->x_settings_window,event,event_mask);
251
252 d_ptr->populateSettings(d_ptr->getSettings());
253 d_ptr->initialized = true;
254}
255
256QXcbXSettings::~QXcbXSettings()
257{
258 delete d_ptr;
259 d_ptr = 0;
260}
261
262bool QXcbXSettings::initialized() const
263{
264 Q_D(const QXcbXSettings);
265 return d->initialized;
266}
267
268void QXcbXSettings::handlePropertyNotifyEvent(const xcb_property_notify_event_t *event)
269{
270 Q_D(QXcbXSettings);
271 if (event->window != d->x_settings_window)
272 return;
273
274 d->populateSettings(d->getSettings());
275}
276
277void QXcbXSettings::registerCallbackForProperty(const QByteArray &property, QXcbXSettings::PropertyChangeFunc func, void *handle)
278{
279 Q_D(QXcbXSettings);
280 d->settings[property].addCallback(func,handle);
281}
282
283void QXcbXSettings::removeCallbackForHandle(const QByteArray &property, void *handle)
284{
285 Q_D(QXcbXSettings);
286 auto &callbacks = d->settings[property].callback_links;
287
288 auto isCallbackForHandle = [handle](const QXcbXSettingsCallback &cb) { return cb.handle == handle; };
289
290 callbacks.erase(std::remove_if(callbacks.begin(), callbacks.end(),
291 isCallbackForHandle),
292 callbacks.end());
293}
294
295void QXcbXSettings::removeCallbackForHandle(void *handle)
296{
297 Q_D(QXcbXSettings);
298 for (QMap<QByteArray, QXcbXSettingsPropertyValue>::const_iterator it = d->settings.cbegin();
299 it != d->settings.cend(); ++it) {
300 removeCallbackForHandle(it.key(),handle);
301 }
302}
303
304QVariant QXcbXSettings::setting(const QByteArray &property) const
305{
306 Q_D(const QXcbXSettings);
307 return d->settings.value(property).value;
308}
309
310QT_END_NAMESPACE
311