1 #include "xconnection.h"
2
3 #include <X11/Xatom.h>
4 #include <X11/Xlib.h>
5 #include <X11/Xproto.h>
6 #include <X11/Xutil.h>
7 #include <unistd.h>
8 #include <climits>
9 #include <iostream>
10
11 #include "globals.h"
12
13 using std::endl;
14 using std::make_pair;
15 using std::pair;
16 using std::string;
17 using std::vector;
18
19 bool XConnection::exitOnError_ = false;
20
setExitOnError(bool exitOnError)21 void XConnection::setExitOnError(bool exitOnError)
22 {
23 exitOnError_ = exitOnError;
24 }
25
XConnection(Display * disp)26 XConnection::XConnection(Display* disp)
27 : m_display(disp) {
28 m_screen = DefaultScreen(m_display);
29 m_screen_width = DisplayWidth(m_display, m_screen);
30 m_screen_height = DisplayHeight(m_display, m_screen);
31 m_root = RootWindow(m_display, m_screen);
32 utf8StringAtom_ = XInternAtom(m_display, "UTF8_STRING", False);
33 }
34
~XConnection()35 XConnection::~XConnection() {
36 HSDebug("Closing display\n");
37 XCloseDisplay(m_display);
38 }
39
connect(string display_name)40 XConnection* XConnection::connect(string display_name) {
41 const char* display_str = (!display_name.empty()) ? display_name.c_str() : nullptr;
42 Display* d = XOpenDisplay(display_str);
43 if (d == nullptr) {
44 std::cerr << "herbstluftwm: XOpenDisplay() failed" << endl;
45 exit(EXIT_FAILURE);
46 }
47 return new XConnection(d);
48 }
49
50 static bool g_other_wm_running = false;
51
52 // from dwm.c
53 /* Startup Error handler to check if another window manager
54 * is already running. */
xerrorstart(Display *,XErrorEvent *)55 static int xerrorstart(Display*, XErrorEvent*) {
56 g_other_wm_running = true;
57 return -1;
58 }
59
60 static int (*g_xerrorxlib)(Display *, XErrorEvent *);
61
62 // from dwm.c
63 /* There's no way to check accesses to destroyed windows, thus those cases are
64 * ignored (especially on UnmapNotify's). Other types of errors call Xlibs
65 * default error handler, which may call exit. */
xerror(Display * dpy,XErrorEvent * ee)66 int XConnection::xerror(Display *dpy, XErrorEvent *ee) {
67 if(ee->error_code == BadWindow
68 || ee->error_code == BadGC
69 || ee->error_code == BadPixmap
70 || (ee->request_code == X_SetInputFocus && ee->error_code == BadMatch)
71 || (ee->request_code == X_PolyText8 && ee->error_code == BadDrawable)
72 || (ee->request_code == X_PolyFillRectangle && ee->error_code == BadDrawable)
73 || (ee->request_code == X_PolySegment && ee->error_code == BadDrawable)
74 || (ee->request_code == X_ConfigureWindow && ee->error_code == BadMatch)
75 || (ee->request_code == X_GrabButton && ee->error_code == BadAccess)
76 || (ee->request_code == X_GrabKey && ee->error_code == BadAccess)
77 || (ee->request_code == X_CopyArea && ee->error_code == BadDrawable)) {
78 return 0;
79 }
80 char errorCodeString[100] = "unknown";
81 XGetErrorText(dpy, ee->error_code, errorCodeString, 100);
82 const char* requestCodeString = XConnection::requestCodeToString(ee->request_code);
83 if (!requestCodeString) {
84 requestCodeString = "unknown";
85 }
86 fprintf(stderr, "herbstluftwm: fatal error\n"
87 " resource id: 0x%lx\n"
88 " request code: %d \"%s\" (minor code: %d)\n"
89 " error code: %d \"%s\"\n"
90 " serial number: %ld\n",
91 ee->resourceid,
92 ee->request_code,
93 requestCodeString,
94 ee->minor_code,
95 ee->error_code,
96 errorCodeString,
97 ee->serial);
98 if (ee->error_code == BadDrawable) {
99 HSDebug("Warning: ignoring X_BadDrawable\n");
100 return 0;
101 }
102 if (exitOnError_) {
103 return g_xerrorxlib(dpy, ee); // may call exit()
104 }
105 // otherwise, just ignore this error and try to proceed.
106 return 0;
107 }
108
109
110 // from dwm.c
checkotherwm()111 bool XConnection::checkotherwm() {
112 g_other_wm_running = False;
113 g_xerrorxlib = XSetErrorHandler(xerrorstart);
114 /* this causes an error if some other window manager is running */
115 XSelectInput(m_display, DefaultRootWindow(m_display), SubstructureRedirectMask);
116 XSync(m_display, False);
117 if(g_other_wm_running) {
118 return true;
119 } else {
120 XSetErrorHandler(XConnection::xerror);
121 XSync(m_display, False);
122 return false;
123 }
124 }
125
windowSize(Window window)126 Rectangle XConnection::windowSize(Window window) {
127 unsigned int border, depth;
128 int x, y;
129 unsigned int w, h;
130 XGetGeometry(m_display, window, &m_root, &x, &y, &w, &h, &border, &depth);
131 // treat wanted coordinates as floating coords
132 return { x, y, (int)w, (int)h };
133 }
134
atom(const char * atom_name)135 Atom XConnection::atom(const char* atom_name) {
136 return XInternAtom(m_display, atom_name, False);
137 }
138
139
atomName(Atom atomIdentifier)140 string XConnection::atomName(Atom atomIdentifier) {
141 char* name = XGetAtomName(m_display, atomIdentifier);
142 string res = name;
143 XFree(name);
144 return res;
145 }
146
147 //! The pid of a window or -1 if the pid is not set
windowPid(Window window)148 int XConnection::windowPid(Window window) {
149 // TODO: move to Ewmh
150 auto res = getWindowPropertyCardinal(window, atom("_NET_WM_PID"));
151 if (!res.has_value() || res.value().empty()) {
152 return -1;
153 } else {
154 return res.value()[0];
155 }
156 }
157
158 //! The pgid of a window or -1 if the pid is not set
windowPgid(Window window)159 int XConnection::windowPgid(Window window) {
160 auto pid = windowPid(window);
161 if (pid == -1) {
162 return -1;
163 }
164 return getpgid(pid);
165 }
166
167 //! wrapper around XGetClassHint returning the window's instance and class name
getClassHint(Window window)168 pair<string, string> XConnection::getClassHint(Window window) {
169 XClassHint hint;
170 if (0 == XGetClassHint(m_display, window, &hint)) {
171 return {"", ""};
172 }
173 pair<string,string> result = {
174 hint.res_name ? hint.res_name : "",
175 hint.res_class ? hint.res_class : ""
176 };
177 if (hint.res_name) {
178 XFree(hint.res_name);
179 }
180 if (hint.res_class) {
181 XFree(hint.res_class);
182 }
183 return result;
184 }
185
186 //! from https://stackoverflow.com/a/39884120/4400896
iso_8859_1_to_utf8(const char * source)187 static string iso_8859_1_to_utf8(const char* source) {
188 string strOut;
189 for (int i = 0; source[i] != '\0'; i++) {
190 uint8_t ch = source[i];
191 if (ch < 0x80) {
192 strOut.push_back(ch);
193 }
194 else {
195 strOut.push_back(0xc0 | ch >> 6);
196 strOut.push_back(0x80 | (ch & 0x3f));
197 }
198 }
199 return strOut;
200 }
201
getWindowProperty(Window window,Atom atom)202 std::experimental::optional<string> XConnection::getWindowProperty(Window window, Atom atom) {
203 string result;
204 char** list = nullptr;
205 int n = 0;
206 XTextProperty prop;
207
208 if (0 == XGetTextProperty(m_display, window, &prop, atom)) {
209 return std::experimental::optional<string>();
210 }
211 // convert text property to a gstring
212 if (prop.encoding == XA_STRING) {
213 // a XA_STRING is always encoded in ISO 8859-1
214 result = iso_8859_1_to_utf8(reinterpret_cast<char *>(prop.value));
215 } else if (prop.encoding == utf8StringAtom_) {
216 result = reinterpret_cast<char *>(prop.value);
217 } else {
218 if (XmbTextPropertyToTextList(m_display, &prop, &list, &n) >= Success
219 && n > 0 && *list)
220 {
221 result = *list;
222 XFreeStringList(list);
223 }
224 }
225 XFree(prop.value);
226 return result;
227 }
228
229 //! implement XChangeProperty for type=ATOM('UTF8_STRING')
setPropertyString(Window w,Atom property,string value)230 void XConnection::setPropertyString(Window w, Atom property, string value) {
231 // according to the XChangeProperty-specification:
232 // if format = 8, then the data must be a char array.
233 XChangeProperty(m_display, w, property,
234 utf8StringAtom_, 8, PropModeReplace,
235 (unsigned char*)value.c_str(), value.size());
236 }
237
238 //! implement XSetTextProperty for an array of utf8-strings
setPropertyString(Window w,Atom property,const vector<string> & value)239 void XConnection::setPropertyString(Window w, Atom property, const vector<string>& value)
240 {
241 vector<const char*> value_c_str;
242 value_c_str.reserve(value.size());
243 for (const auto& s : value) {
244 value_c_str.push_back(s.c_str());
245 }
246 XTextProperty text_prop;
247 Xutf8TextListToTextProperty(
248 m_display, (char**) value_c_str.data(), value_c_str.size(),
249 XUTF8StringStyle, &text_prop);
250 XSetTextProperty(m_display, w, &text_prop, property);
251 XFree(text_prop.value);
252 }
253
254 //! implement XChangeProperty for type=XA_WINDOW
setPropertyWindow(Window w,Atom property,const vector<Window> & value)255 void XConnection::setPropertyWindow(Window w, Atom property, const vector<Window>& value) {
256 // according to the XChangeProperty-specification:
257 // if format = 32, then the data must be a long array.
258 XChangeProperty(m_display, w, property,
259 XA_WINDOW, 32, PropModeReplace,
260 (unsigned char*)(value.data()), value.size());
261 }
262
263 //! implement XChangeProperty for type=XA_CARDINAL
setPropertyCardinal(Window w,Atom property,const vector<long> & value)264 void XConnection::setPropertyCardinal(Window w, Atom property, const vector<long>& value) {
265 // according to the XChangeProperty-specification:
266 // if format = 32, then the data must be a long array.
267 XChangeProperty(m_display, w, property,
268 XA_CARDINAL, 32, PropModeReplace,
269 (unsigned char*)(value.data()), value.size());
270 }
271
getTransientForHint(Window win)272 std::experimental::optional<Window> XConnection::getTransientForHint(Window win)
273 {
274 Window master;
275 if (XGetTransientForHint(m_display, win, &master) != 0) {
276 return master;
277 }
278 return {};
279 }
280
281 /** a sincere wrapper around XGetWindowProperty():
282 * get a window property of format 32. If the property does not exist
283 * or is not of format 32, the return type is None (and the vector is empty).
284 * otherwise the content of the property together with its type is returned.
285 */
286 template<typename T> pair<Atom,vector<T>>
getWindowProperty32(Display * display,Window window,Atom property)287 getWindowProperty32(Display* display, Window window, Atom property)
288 {
289 Atom actual_type;
290 int format;
291 unsigned long bytes_left;
292 long* items_return;
293 unsigned long count;
294 int status = XGetWindowProperty(display, window,
295 property, 0, ULONG_MAX, False, AnyPropertyType,
296 &actual_type, &format, &count, &bytes_left,
297 (unsigned char**)&items_return);
298 if (Success != status || actual_type == None || format == 0) {
299 return make_pair(None, vector<T>());
300 }
301 if (format != 32) {
302 // if the property could be read, but is of the wrong format
303 XFree(items_return);
304 return make_pair(None, vector<T>());
305 }
306 vector<T> result;
307 result.reserve(count);
308 for (size_t i = 0; i < count; i++) {
309 result.push_back(items_return[i]);
310 }
311 XFree(items_return);
312 return make_pair(actual_type, result);
313 }
314
315 std::experimental::optional<vector<long>>
getWindowPropertyCardinal(Window window,Atom property)316 XConnection::getWindowPropertyCardinal(Window window, Atom property)
317 {
318 auto res = getWindowProperty32<long>(m_display, window, property);
319 if (res.first != XA_CARDINAL) {
320 return {};
321 }
322 return res.second;
323 }
324
325 std::experimental::optional<vector<Atom>>
getWindowPropertyAtom(Window window,Atom property)326 XConnection::getWindowPropertyAtom(Window window, Atom property)
327 {
328 auto res = getWindowProperty32<Atom>(m_display, window, property);
329 if (res.first != XA_ATOM) {
330 return {};
331 }
332 return res.second;
333 }
334
335
336 std::experimental::optional<vector<Window>>
getWindowPropertyWindow(Window window,Atom property)337 XConnection::getWindowPropertyWindow(Window window, Atom property)
338 {
339 auto res = getWindowProperty32<Window>(m_display, window, property);
340 if (res.first != XA_WINDOW) {
341 return {};
342 }
343 return res.second;
344 }
345
346 std::experimental::optional<vector<string>>
getWindowPropertyTextList(Window window,Atom property)347 XConnection::getWindowPropertyTextList(Window window, Atom property)
348 {
349 XTextProperty text_prop;
350 if (!XGetTextProperty(m_display, window, &text_prop, property)) {
351 return {};
352 }
353 char** list_return;
354 int count;
355 if (Success != Xutf8TextPropertyToTextList(m_display, &text_prop, &list_return, &count)) {
356 XFree(text_prop.value);
357 return {};
358 }
359 vector<string> arguments;
360 for (int i = 0; i < count; i++) {
361 arguments.push_back(list_return[i]);
362 }
363 XFreeStringList(list_return);
364 XFree(text_prop.value);
365 return { arguments };
366 }
367
368 //! query all children of the given window via XQueryTree()
queryTree(Window window)369 vector<Window> XConnection::queryTree(Window window) {
370 Window root, parent, *children = nullptr;
371 unsigned int count = 0;
372 Status status = XQueryTree(m_display, window,
373 &root, &parent, &children, &count);
374 if (status == 0) {
375 return {};
376 }
377 vector<Window> result;
378 result.reserve(count);
379 for (unsigned int i = 0; i < count; i++) {
380 result.push_back(children[i]);
381 }
382 XFree(children);
383 return result;
384 }
385
386 #define RequestCodeAndString(C) { C, #C }
requestCodeToString(int requestCode)387 const char* XConnection::requestCodeToString(int requestCode)
388 {
389 // all request codes from X11/Xproto.h
390 vector<pair<int, const char*>> requestCodeTable = {
391 RequestCodeAndString(X_CreateWindow ),
392 RequestCodeAndString(X_ChangeWindowAttributes ),
393 RequestCodeAndString(X_GetWindowAttributes ),
394 RequestCodeAndString(X_DestroyWindow ),
395 RequestCodeAndString(X_DestroySubwindows ),
396 RequestCodeAndString(X_ChangeSaveSet ),
397 RequestCodeAndString(X_ReparentWindow ),
398 RequestCodeAndString(X_MapWindow ),
399 RequestCodeAndString(X_MapSubwindows ),
400 RequestCodeAndString(X_UnmapWindow ),
401 RequestCodeAndString(X_UnmapSubwindows ),
402 RequestCodeAndString(X_ConfigureWindow ),
403 RequestCodeAndString(X_CirculateWindow ),
404 RequestCodeAndString(X_GetGeometry ),
405 RequestCodeAndString(X_QueryTree ),
406 RequestCodeAndString(X_InternAtom ),
407 RequestCodeAndString(X_GetAtomName ),
408 RequestCodeAndString(X_ChangeProperty ),
409 RequestCodeAndString(X_DeleteProperty ),
410 RequestCodeAndString(X_GetProperty ),
411 RequestCodeAndString(X_ListProperties ),
412 RequestCodeAndString(X_SetSelectionOwner ),
413 RequestCodeAndString(X_GetSelectionOwner ),
414 RequestCodeAndString(X_ConvertSelection ),
415 RequestCodeAndString(X_SendEvent ),
416 RequestCodeAndString(X_GrabPointer ),
417 RequestCodeAndString(X_UngrabPointer ),
418 RequestCodeAndString(X_GrabButton ),
419 RequestCodeAndString(X_UngrabButton ),
420 RequestCodeAndString(X_ChangeActivePointerGrab ),
421 RequestCodeAndString(X_GrabKeyboard ),
422 RequestCodeAndString(X_UngrabKeyboard ),
423 RequestCodeAndString(X_GrabKey ),
424 RequestCodeAndString(X_UngrabKey ),
425 RequestCodeAndString(X_AllowEvents ),
426 RequestCodeAndString(X_GrabServer ),
427 RequestCodeAndString(X_UngrabServer ),
428 RequestCodeAndString(X_QueryPointer ),
429 RequestCodeAndString(X_GetMotionEvents ),
430 RequestCodeAndString(X_TranslateCoords ),
431 RequestCodeAndString(X_WarpPointer ),
432 RequestCodeAndString(X_SetInputFocus ),
433 RequestCodeAndString(X_GetInputFocus ),
434 RequestCodeAndString(X_QueryKeymap ),
435 RequestCodeAndString(X_OpenFont ),
436 RequestCodeAndString(X_CloseFont ),
437 RequestCodeAndString(X_QueryFont ),
438 RequestCodeAndString(X_QueryTextExtents ),
439 RequestCodeAndString(X_ListFonts ),
440 RequestCodeAndString(X_ListFontsWithInfo ),
441 RequestCodeAndString(X_SetFontPath ),
442 RequestCodeAndString(X_GetFontPath ),
443 RequestCodeAndString(X_CreatePixmap ),
444 RequestCodeAndString(X_FreePixmap ),
445 RequestCodeAndString(X_CreateGC ),
446 RequestCodeAndString(X_ChangeGC ),
447 RequestCodeAndString(X_CopyGC ),
448 RequestCodeAndString(X_SetDashes ),
449 RequestCodeAndString(X_SetClipRectangles ),
450 RequestCodeAndString(X_FreeGC ),
451 RequestCodeAndString(X_ClearArea ),
452 RequestCodeAndString(X_CopyArea ),
453 RequestCodeAndString(X_CopyPlane ),
454 RequestCodeAndString(X_PolyPoint ),
455 RequestCodeAndString(X_PolyLine ),
456 RequestCodeAndString(X_PolySegment ),
457 RequestCodeAndString(X_PolyRectangle ),
458 RequestCodeAndString(X_PolyArc ),
459 RequestCodeAndString(X_FillPoly ),
460 RequestCodeAndString(X_PolyFillRectangle ),
461 RequestCodeAndString(X_PolyFillArc ),
462 RequestCodeAndString(X_PutImage ),
463 RequestCodeAndString(X_GetImage ),
464 RequestCodeAndString(X_PolyText8 ),
465 RequestCodeAndString(X_PolyText16 ),
466 RequestCodeAndString(X_ImageText8 ),
467 RequestCodeAndString(X_ImageText16 ),
468 RequestCodeAndString(X_CreateColormap ),
469 RequestCodeAndString(X_FreeColormap ),
470 RequestCodeAndString(X_CopyColormapAndFree ),
471 RequestCodeAndString(X_InstallColormap ),
472 RequestCodeAndString(X_UninstallColormap ),
473 RequestCodeAndString(X_ListInstalledColormaps ),
474 RequestCodeAndString(X_AllocColor ),
475 RequestCodeAndString(X_AllocNamedColor ),
476 RequestCodeAndString(X_AllocColorCells ),
477 RequestCodeAndString(X_AllocColorPlanes ),
478 RequestCodeAndString(X_FreeColors ),
479 RequestCodeAndString(X_StoreColors ),
480 RequestCodeAndString(X_StoreNamedColor ),
481 RequestCodeAndString(X_QueryColors ),
482 RequestCodeAndString(X_LookupColor ),
483 RequestCodeAndString(X_CreateCursor ),
484 RequestCodeAndString(X_CreateGlyphCursor ),
485 RequestCodeAndString(X_FreeCursor ),
486 RequestCodeAndString(X_RecolorCursor ),
487 RequestCodeAndString(X_QueryBestSize ),
488 RequestCodeAndString(X_QueryExtension ),
489 RequestCodeAndString(X_ListExtensions ),
490 RequestCodeAndString(X_ChangeKeyboardMapping ),
491 RequestCodeAndString(X_GetKeyboardMapping ),
492 RequestCodeAndString(X_ChangeKeyboardControl ),
493 RequestCodeAndString(X_GetKeyboardControl ),
494 RequestCodeAndString(X_Bell ),
495 RequestCodeAndString(X_ChangePointerControl ),
496 RequestCodeAndString(X_GetPointerControl ),
497 RequestCodeAndString(X_SetScreenSaver ),
498 RequestCodeAndString(X_GetScreenSaver ),
499 RequestCodeAndString(X_ChangeHosts ),
500 RequestCodeAndString(X_ListHosts ),
501 RequestCodeAndString(X_SetAccessControl ),
502 RequestCodeAndString(X_SetCloseDownMode ),
503 RequestCodeAndString(X_KillClient ),
504 RequestCodeAndString(X_RotateProperties ),
505 RequestCodeAndString(X_ForceScreenSaver ),
506 RequestCodeAndString(X_SetPointerMapping ),
507 RequestCodeAndString(X_GetPointerMapping ),
508 RequestCodeAndString(X_SetModifierMapping ),
509 RequestCodeAndString(X_GetModifierMapping ),
510 RequestCodeAndString(X_NoOperation ),
511 };
512 for (auto& e : requestCodeTable) {
513 if (e.first == requestCode) {
514 return e.second;
515 }
516 }
517 return nullptr;
518 }
519