forked from derat/xsettingsd
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsettings_manager.cc
347 lines (298 loc) · 10.8 KB
/
settings_manager.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
// Copyright 2009 Daniel Erat <[email protected]>
// All rights reserved.
#include "settings_manager.h"
#include <cassert>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/types.h>
#include <unistd.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include "config_parser.h"
#include "data_writer.h"
#include "setting.h"
using std::make_pair;
using std::map;
using std::max;
using std::string;
using std::vector;
namespace xsettingsd {
// Arbitrarily big number.
static const int kMaxPropertySize = (2 << 15);
SettingsManager::SettingsManager(const string& config_filename)
: config_filename_(config_filename),
serial_(0),
display_(NULL),
prop_atom_(None) {
}
SettingsManager::~SettingsManager() {
if (display_) {
if (!windows_.empty())
DestroyWindows();
XCloseDisplay(display_);
display_ = NULL;
}
}
bool SettingsManager::LoadConfig() {
ConfigParser parser(new ConfigParser::FileCharStream(config_filename_));
SettingsMap new_settings;
if (!parser.Parse(&new_settings, &settings_, serial_ + 1)) {
fprintf(stderr, "%s: Unable to parse %s: %s\n",
kProgName, config_filename_.c_str(), parser.FormatError().c_str());
return false;
}
serial_++;
fprintf(stderr, "%s: Loaded %zu setting%s from %s\n",
kProgName, new_settings.map().size(),
(new_settings.map().size() == 1) ? "" : "s",
config_filename_.c_str());
settings_.swap(&new_settings);
return true;
}
bool SettingsManager::InitX11(int screen, bool replace_existing_manager) {
assert(!display_);
display_ = XOpenDisplay(NULL);
if (!display_) {
fprintf(stderr, "%s: Unable to open connection to X server\n", kProgName);
return false;
}
prop_atom_ = XInternAtom(display_, "_XSETTINGS_SETTINGS", False);
char data[kMaxPropertySize];
DataWriter writer(data, kMaxPropertySize);
if (!WriteProperty(&writer))
return false;
int min_screen = 0;
int max_screen = ScreenCount(display_) - 1;
if (screen >= 0)
min_screen = max_screen = screen;
for (screen = min_screen; screen <= max_screen; ++screen) {
Window win = None;
Time timestamp = 0;
if (!CreateWindow(screen, &win, ×tamp)) {
fprintf(stderr, "%s: Unable to create window on screen %d\n",
kProgName, screen);
return false;
}
fprintf(stderr, "%s: Created window 0x%x on screen %d with timestamp %lu\n",
kProgName, static_cast<unsigned int>(win), screen, timestamp);
SetPropertyOnWindow(win, data, writer.bytes_written());
if (!ManageScreen(screen, win, timestamp, replace_existing_manager))
return false;
windows_.push_back(win);
}
return true;
}
void SettingsManager::RunEventLoop() {
int x11_fd = XConnectionNumber(display_);
// TODO: Need to also use XAddConnectionWatch()?
while (true) {
// Rather than blocking in XNextEvent(), we just read all the available
// events here. We block in select() instead, so that we'll get EINTR
// if a SIGHUP came in to ask us to reload the config.
while (XPending(display_)) {
XEvent event;
XNextEvent(display_, &event);
switch (event.type) {
case MappingNotify:
// Doesn't really mean anything to us, but might as well handle it.
XRefreshKeyboardMapping(&(event.xmapping));
break;
case SelectionClear: {
// If someone else took the selection, that's our sign to leave.
fprintf(stderr, "%s: 0x%x took a selection from us; exiting\n",
kProgName,
static_cast<unsigned int>(event.xselectionclear.window));
DestroyWindows();
return;
}
default:
fprintf(stderr, "%s: Ignoring event of type %d\n",
kProgName, event.type);
}
}
// TODO: There's a small race condition here, in that SIGHUP can come
// in while we're outside of the select() call, but it's probably not
// worth trying to work around.
fd_set fds;
FD_ZERO(&fds);
FD_SET(x11_fd, &fds);
if (select(x11_fd + 1, &fds, NULL, NULL, NULL) == -1) {
if (errno != EINTR) {
fprintf(stderr, "%s: select() failed: %s\n",
kProgName, strerror(errno));
return;
}
fprintf(stderr, "%s: Reloading configuration\n", kProgName);
if (!LoadConfig())
continue;
char data[kMaxPropertySize];
DataWriter writer(data, kMaxPropertySize);
if (!WriteProperty(&writer))
continue;
for (vector<Window>::const_iterator it = windows_.begin();
it != windows_.end(); ++it) {
SetPropertyOnWindow(*it, data, writer.bytes_written());
}
}
}
}
void SettingsManager::DestroyWindows() {
assert(display_);
for (vector<Window>::iterator it = windows_.begin();
it != windows_.end(); ++it) {
XDestroyWindow(display_, *it);
}
windows_.clear();
}
bool SettingsManager::CreateWindow(int screen,
Window* win_out,
Time* timestamp_out) {
assert(win_out);
assert(timestamp_out);
if (screen < 0 || screen >= ScreenCount(display_))
return false;
XSetWindowAttributes attr;
attr.override_redirect = True;
Window win = XCreateWindow(display_,
RootWindow(display_, screen), // parent
-1, -1, // x, y
1, 1, // width, height
0, // border_width
CopyFromParent, // depth
InputOutput, // class
CopyFromParent, // visual
CWOverrideRedirect, // attr_mask
&attr);
if (win == None)
return false;
*win_out = win;
// This sets a few properties for us, including WM_CLIENT_MACHINE.
XSetWMProperties(display_,
win,
NULL, // window_name
NULL, // icon_name
NULL, // argv
0, // argc
NULL, // normal_hints
NULL, // wm_hints
NULL); // class_hints
XStoreName(display_, win, kProgName);
XChangeProperty(display_,
win,
XInternAtom(display_, "_NET_WM_NAME", False), // property
XInternAtom(display_, "UTF8_STRING", False), // type
8, // format (bits per element)
PropModeReplace,
reinterpret_cast<const unsigned char*>(kProgName),
strlen(kProgName));
// Grab a timestamp from our final property change; we'll need it later
// when announcing that we've taken the manager selection.
pid_t pid = getpid();
XSelectInput(display_, win, PropertyChangeMask);
XChangeProperty(display_,
win,
XInternAtom(display_, "_NET_WM_PID", False), // property
XA_CARDINAL, // type
32, // format (bits per element)
PropModeReplace,
reinterpret_cast<const unsigned char*>(&pid), // value
1); // num elements
XSelectInput(display_, win, NoEventMask);
XEvent event;
while (true) {
XWindowEvent(display_, win, PropertyChangeMask, &event);
if (event.type == PropertyNotify) {
*timestamp_out = event.xproperty.time;
break;
}
}
return true;
}
bool SettingsManager::WriteProperty(DataWriter* writer) {
assert(writer);
int byte_order = IsLittleEndian() ? LSBFirst : MSBFirst;
if (!writer->WriteInt8(byte_order)) return false;
if (!writer->WriteZeros(3)) return false;
if (!writer->WriteInt32(serial_)) return false;
if (!writer->WriteInt32(settings_.map().size())) return false;
for (SettingsMap::Map::const_iterator it = settings_.map().begin();
it != settings_.map().end(); ++it) {
if (!it->second->Write(it->first, writer))
return false;
}
return true;
}
void SettingsManager::SetPropertyOnWindow(
Window win, const char* data, size_t size) {
XChangeProperty(display_,
win,
prop_atom_, // property
prop_atom_, // type
8, // format (bits per element)
PropModeReplace,
reinterpret_cast<const unsigned char*>(data),
size);
}
bool SettingsManager::ManageScreen(int screen,
Window win,
Time timestamp,
bool replace_existing_manager) {
assert(display_);
assert(win != None);
assert(screen < ScreenCount(display_));
Window root = RootWindow(display_, screen);
string sel_atom_name = StringPrintf("_XSETTINGS_S%d", screen);
Atom sel_atom = XInternAtom(display_, sel_atom_name.c_str(), False);
XGrabServer(display_);
Window prev_win = XGetSelectionOwner(display_, sel_atom);
fprintf(stderr, "%s: Selection %s is owned by 0x%x\n",
kProgName, sel_atom_name.c_str(),
static_cast<unsigned int>(prev_win));
if (prev_win != None && !replace_existing_manager) {
fprintf(stderr, "%s: Someone else already owns the %s selection "
"and we weren't asked to replace them\n",
kProgName, sel_atom_name.c_str());
XUngrabServer(display_);
return false;
}
if (prev_win)
XSelectInput(display_, prev_win, StructureNotifyMask);
XSetSelectionOwner(display_, sel_atom, win, CurrentTime);
fprintf(stderr, "%s: Took ownership of selection %s\n",
kProgName, sel_atom_name.c_str());
XUngrabServer(display_);
if (prev_win) {
// Wait for the previous owner to go away.
XEvent event;
while (true) {
XWindowEvent(display_, prev_win, StructureNotifyMask, &event);
if (event.type == DestroyNotify)
break;
}
}
// Make sure that no one else took the selection while we were waiting.
if (XGetSelectionOwner(display_, sel_atom) != win) {
fprintf(stderr, "%s: Someone else took ownership of the %s selection\n",
kProgName, sel_atom_name.c_str());
return false;
}
XEvent ev;
ev.xclient.type = ClientMessage;
ev.xclient.window = root;
ev.xclient.message_type = XInternAtom(display_, "MANAGER", False);
ev.xclient.format = 32;
ev.xclient.data.l[0] = timestamp;
ev.xclient.data.l[1] = sel_atom;
ev.xclient.data.l[2] = win;
ev.xclient.data.l[3] = 0;
XSendEvent(display_,
root,
False, // propagate
StructureNotifyMask, // event_mask
&ev);
return true;
}
} // namespace xsettingsd