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