1 #include "ewmh.h"
2 
3 #include <X11/Xatom.h>
4 #include <X11/Xlib.h>
5 #include <algorithm>
6 #include <cstdio>
7 
8 #include "client.h"
9 #include "globals.h"
10 #include "hlwmcommon.h"
11 #include "layout.h"
12 #include "monitor.h"
13 #include "monitormanager.h"
14 #include "mousemanager.h"
15 #include "root.h"
16 #include "settings.h"
17 #include "stack.h"
18 #include "tagmanager.h"
19 #include "utils.h"
20 #include "xconnection.h"
21 
22 using std::pair;
23 using std::string;
24 using std::vector;
25 
26 /* list of names of all _NET-atoms */
27 const std::array<const char*,NetCOUNT> Ewmh::netatomNames_ =
28   ArrayInitializer<const char*,NetCOUNT>({
29     { NetSupported                   , "_NET_SUPPORTED"                    },
30     { NetClientList                  , "_NET_CLIENT_LIST"                  },
31     { NetClientListStacking          , "_NET_CLIENT_LIST_STACKING"         },
32     { NetCloseWindow                 , "_NET_CLOSE_WINDOW"                 },
33     { NetNumberOfDesktops            , "_NET_NUMBER_OF_DESKTOPS"           },
34     { NetCurrentDesktop              , "_NET_CURRENT_DESKTOP"              },
35     { NetDesktopNames                , "_NET_DESKTOP_NAMES"                },
36     { NetWmDesktop                   , "_NET_WM_DESKTOP"                   },
37     { NetDesktopViewport             , "_NET_DESKTOP_VIEWPORT"             },
38     { NetActiveWindow                , "_NET_ACTIVE_WINDOW"                },
39     { NetWmName                      , "_NET_WM_NAME"                      },
40     { NetSupportingWmCheck           , "_NET_SUPPORTING_WM_CHECK"          },
41     { NetWmWindowType                , "_NET_WM_WINDOW_TYPE"               },
42     { NetWmState                     , "_NET_WM_STATE"                     },
43     { NetWmWindowOpacity             , "_NET_WM_WINDOW_OPACITY"            },
44     { NetMoveresizeWindow            , "_NET_MOVERESIZE_WINDOW"            },
45     { NetWmMoveresize                , "_NET_WM_MOVERESIZE"                },
46     { NetFrameExtents                , "_NET_FRAME_EXTENTS"                },
47     /* window states */
48     { NetWmStateFullscreen           , "_NET_WM_STATE_FULLSCREEN"          },
49     { NetWmStateHidden               , "_NET_WM_STATE_HIDDEN"              },
50     { NetWmStateDemandsAttention     , "_NET_WM_STATE_DEMANDS_ATTENTION"   },
51     /* window types */
52     { NetWmWindowTypeDesktop         , "_NET_WM_WINDOW_TYPE_DESKTOP"       },
53     { NetWmWindowTypeDock            , "_NET_WM_WINDOW_TYPE_DOCK"          },
54     { NetWmWindowTypeToolbar         , "_NET_WM_WINDOW_TYPE_TOOLBAR"       },
55     { NetWmWindowTypeMenu            , "_NET_WM_WINDOW_TYPE_MENU"          },
56     { NetWmWindowTypeUtility         , "_NET_WM_WINDOW_TYPE_UTILITY"       },
57     { NetWmWindowTypeSplash          , "_NET_WM_WINDOW_TYPE_SPLASH"        },
58     { NetWmWindowTypeDialog          , "_NET_WM_WINDOW_TYPE_DIALOG"        },
59     { NetWmWindowTypeDropdownMenu    , "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU" },
60     { NetWmWindowTypePopupMenu       , "_NET_WM_WINDOW_TYPE_POPUP_MENU"    },
61     { NetWmWindowTypeTooltip         , "_NET_WM_WINDOW_TYPE_TOOLTIP"       },
62     { NetWmWindowTypeNotification    , "_NET_WM_WINDOW_TYPE_NOTIFICATION"  },
63     { NetWmWindowTypeCombo           , "_NET_WM_WINDOW_TYPE_COMBO"         },
64     { NetWmWindowTypeDnd             , "_NET_WM_WINDOW_TYPE_DND"           },
65     { NetWmWindowTypeNormal          , "_NET_WM_WINDOW_TYPE_NORMAL"        },
66 }).a;
67 
Ewmh(XConnection & xconnection)68 Ewmh::Ewmh(XConnection& xconnection)
69     : X_(xconnection)
70 {
71     /* init ewmh net atoms */
72     for (int i = 0; i < NetCOUNT; i++) {
73         if (!netatomNames_[i]) {
74             HSWarning("no name specified in g_netatom_names "
75                       "for atom number %d\n", i);
76             continue;
77         }
78         netatom_[i] = XInternAtom(X_.display(), netatomNames_[i], False);
79     }
80 
81     vector<pair<WM,const char*>> wm2name = {
82         { WM::Name,         "WM_NAME" },
83         { WM::Protocols,    "WM_PROTOCOLS" },
84         { WM::Delete,       "WM_DELETE_WINDOW" },
85         { WM::State,        "WM_STATE" },
86         { WM::ChangeState,  "WM_CHANGE_STATE" },
87         { WM::TakeFocus,    "WM_TAKE_FOCUS" },
88     };
89     for (const auto& init : wm2name) {
90         auto atom = XInternAtom(X_.display(), init.second, False);
91         wmatom_[static_cast<size_t>(init.first)] = atom;
92     }
93 
94     /* tell which ewmh atoms are supported */
95     XChangeProperty(X_.display(), X_.root(), netatom_[NetSupported], XA_ATOM, 32,
96         PropModeReplace, (unsigned char *) netatom_, NetCOUNT);
97 
98     readInitialEwmhState();
99 
100     /* init for the supporting wm check */
101     windowManagerWindow_ = XCreateSimpleWindow(X_.display(), X_.root(),
102                                       -100, -100, 1, 1, 0, 0, CWOverrideRedirect | CWEventMask);
103     X_.setPropertyWindow(X_.root(), netatom_[NetSupportingWmCheck], { windowManagerWindow_ });
104     X_.setPropertyWindow(windowManagerWindow_, netatom_[NetSupportingWmCheck], { windowManagerWindow_ });
105     XMapWindow(X_.display(), windowManagerWindow_);
106 
107     /* init atoms that never change */
108     X_.setPropertyCardinal(X_.root(), netatom_[NetDesktopViewport], {0, 0});
109 }
110 
111 //! read the current ewmh properties from the root window
readInitialEwmhState()112 void Ewmh::readInitialEwmhState()
113 {
114     // list of desktops
115     auto number = X_.getWindowPropertyCardinal(X_.root(), netatom_[NetNumberOfDesktops]);
116     if (number.has_value() && !number.value().empty()) {
117         auto val = number.value()[0];
118         initialState_.numberOfDesktops = (val >= 0) ? ((size_t)(val)) : 0;
119     }
120     auto maybe_names = X_.getWindowPropertyTextList(X_.root(), netatom_[NetDesktopNames]);
121     if (maybe_names.has_value()) {
122         initialState_.desktopNames = maybe_names.value();
123     }
124     // list of managed clients
125     auto maybe_clients =
126         X_.getWindowPropertyWindow(X_.root(), netatom_[NetClientList]);
127     initialState_.original_client_list_ =
128         maybe_clients.has_value() ? maybe_clients.value() : vector<Window>();
129     if (g_verbose) {
130         initialState_.print(stderr);
131     }
132 }
133 
windowGetInitialDesktop(Window win)134 long Ewmh::windowGetInitialDesktop(Window win)
135 {
136     auto maybe_idx = X_.getWindowPropertyCardinal(win, netatom_[NetWmDesktop]);
137     if (maybe_idx.has_value() && !maybe_idx.value().empty()) {
138         return maybe_idx.value()[0];
139     }
140     return -1;
141 }
142 
print(FILE * file)143 void Ewmh::InitialState::print(FILE *file)
144 {
145     fprintf(file, "EWMH: %zu desktops:", numberOfDesktops);
146     for (const auto& n : desktopNames) {
147         fprintf(file, " \'%s\'", n.c_str());
148     }
149     fprintf(file, "\n");
150     fprintf(file, "%zu managed clients: ", original_client_list_.size());
151     for (auto win : original_client_list_) {
152         fprintf(file, "  window 0x%lx", win);
153     }
154     fprintf(file, "\n");
155 }
156 
157 
injectDependencies(Root * root)158 void Ewmh::injectDependencies(Root* root) {
159     root_ = root;
160     tags_ = root->tags();
161 }
162 
updateAll()163 void Ewmh::updateAll() {
164     /* init many properties */
165     updateWmName();
166     updateClientList();
167     updateClientListStacking();
168     updateDesktops();
169     updateCurrentDesktop();
170     updateDesktopNames();
171 }
172 
~Ewmh()173 Ewmh::~Ewmh() {
174     XDeleteProperty(X_.display(), X_.root(), netatom_[NetSupportingWmCheck]);
175     XDestroyWindow(X_.display(), windowManagerWindow_);
176 }
177 
updateWmName()178 void Ewmh::updateWmName() {
179     string name = root_->settings->wmname();
180     X_.setPropertyString(windowManagerWindow_, netatom_[NetWmName], name);
181     X_.setPropertyString(X_.root(), netatom_[NetWmName], name);
182 }
183 
updateClientList()184 void Ewmh::updateClientList() {
185     X_.setPropertyWindow(X_.root(), netatom_[NetClientList], netClientList_);
186 }
187 
initialState()188 const Ewmh::InitialState &Ewmh::initialState()
189 {
190     return initialState_;
191 }
192 
updateClientListStacking()193 void Ewmh::updateClientListStacking() {
194     // First: get the windows currently visible
195     vector<Window> buf;
196     auto addToVector = [&buf](Window w) { buf.push_back(w); };
197     g_monitors->extractWindowStack(true, addToVector);
198 
199     // Then add all the invisible windows at the end
200     for (auto tag : *tags_) {
201         if (find_monitor_with_tag(tag)) {
202         // do not add tags because they are already added
203             continue;
204         }
205         tag->stack->extractWindows(true, addToVector);
206     }
207 
208     // reverse stacking order, because ewmh requires bottom to top order
209     std::reverse(buf.begin(), buf.end());
210 
211     X_.setPropertyWindow(X_.root(), netatom_[NetClientListStacking], buf);
212 }
213 
addClient(Window win)214 void Ewmh::addClient(Window win) {
215     netClientList_.push_back(win);
216     updateClientList();
217     updateClientListStacking();
218 }
219 
removeClient(Window win)220 void Ewmh::removeClient(Window win) {
221     netClientList_.erase(std::remove(netClientList_.begin(), netClientList_.end(), win), netClientList_.end());
222     updateClientList();
223     updateClientListStacking();
224 }
225 
updateDesktops()226 void Ewmh::updateDesktops() {
227     X_.setPropertyCardinal(X_.root(), netatom_[NetNumberOfDesktops],
228                            { (long) root_->tags->size() });
229 }
230 
updateDesktopNames()231 void Ewmh::updateDesktopNames() {
232     vector<string> names;
233     for (auto tag : *tags_) {
234         names.push_back(tag->name);
235     }
236     X_.setPropertyString(X_.root(), netatom_[NetDesktopNames], names);
237 }
238 
updateCurrentDesktop()239 void Ewmh::updateCurrentDesktop() {
240     HSTag* tag = get_current_monitor()->tag;
241     int index = tags_->index_of(tag);
242     if (index < 0) {
243         HSWarning("tag %s not found in internal list\n", tag->name->c_str());
244         return;
245     }
246     X_.setPropertyCardinal(X_.root(), netatom_[NetCurrentDesktop], { index });
247 }
248 
windowUpdateTag(Window win,HSTag * tag)249 void Ewmh::windowUpdateTag(Window win, HSTag* tag) {
250     if (!tag) {
251         return;
252     }
253     int index = tag->index();
254     X_.setPropertyCardinal(win, netatom_[NetWmDesktop], { index });
255 }
256 
updateActiveWindow(Window win)257 void Ewmh::updateActiveWindow(Window win) {
258     X_.setPropertyWindow(X_.root(), netatom_[NetActiveWindow], { win });
259 }
260 
focusStealingAllowed(long source)261 bool Ewmh::focusStealingAllowed(long source) {
262     if (root_->settings->focus_stealing_prevention()) {
263         /* only allow it to pagers/taskbars */
264         return (source == 2);
265     } else {
266         /* no prevention */
267         return true;
268     }
269 }
270 
handleClientMessage(XClientMessageEvent * me)271 void Ewmh::handleClientMessage(XClientMessageEvent* me) {
272     HSDebug("Received event: ClientMessage: \"%s\" for %lx\n",
273             X_.atomName(me->message_type).c_str(),
274             me->window);
275     if (me->message_type == wmatom(WM::ChangeState)) {
276         if (me->data.l[0] == static_cast<long>(WmState::WSIconicState)) {
277             Client* client = Root::common().client(me->window);
278             if (client) {
279                 client->minimized_ = true;
280             }
281         }
282         return;
283     }
284     int index;
285     for (index = 0; index < NetCOUNT; index++) {
286         if (me->message_type == netatom_[index]) {
287             break;
288         }
289     }
290     if (index >= NetCOUNT) {
291         HSDebug("received unknown client message\n");
292         return;
293     }
294 
295     int desktop_index;
296     switch (index) {
297         case NetActiveWindow: {
298             // only steal focus if allowed to the current source
299             // (i.e. me->data.l[0] in this case as specified by EWMH)
300             if (focusStealingAllowed(me->data.l[0])) {
301                 auto client = Root::common().client(me->window);
302                 if (client) {
303                     focus_client(client, true, true, true);
304                 }
305             } else {
306                 // Focus stealing is not allowed, at least mark the client urgent
307                 auto client = Root::common().client(me->window);
308                 if (client) {
309                     client->set_urgent(true);
310                 }
311             }
312             break;
313         }
314 
315         case NetCurrentDesktop: {
316             desktop_index = me->data.l[0];
317             if (desktop_index < 0 || desktop_index >= tag_get_count()) {
318                 HSDebug("_NET_CURRENT_DESKTOP: invalid index \"%d\"\n",
319                         desktop_index);
320                 break;
321             }
322             HSTag* tag = get_tag_by_index(desktop_index);
323             monitor_set_tag(get_current_monitor(), tag);
324             break;
325         }
326 
327         case NetWmDesktop: {
328             desktop_index = me->data.l[0];
329             if (!focusStealingAllowed(me->data.l[1])) {
330                 break;
331             }
332             HSTag* target = get_tag_by_index(desktop_index);
333             auto client = Root::common().client(me->window);
334             if (client && target) {
335                 tags_->moveClient(client, target);
336             }
337             break;
338         }
339 
340         case NetWmState: {
341             auto client = Root::common().client(me->window);
342             /* ignore requests for unmanaged windows */
343             if (!client || !client->ewmhrequests_) {
344                 break;
345             }
346 
347             /* mapping between EWMH atoms and client struct members */
348             struct {
349                 int     atom_index;
350                 bool    enabled;
351                 void    (*callback)(Client*, bool);
352             } client_atoms[] = {
353                 { NetWmStateFullscreen,
354                     client->fullscreen_,     [](Client* c, bool state){ c->fullscreen_ = state; } },
355                 { NetWmStateDemandsAttention,
356                     client->urgent_,         [](Client* c, bool state){ c->set_urgent(state); } },
357             };
358 
359             /* me->data.l[1] and [2] describe the properties to alter */
360             for (int prop = 1; prop <= 2; prop++) {
361                 if (me->data.l[prop] == 0) {
362                     /* skip if no property is specified */
363                     continue;
364                 }
365                 /* check if we support the property data[prop] */
366                 size_t i;
367                 for (i = 0; i < LENGTH(client_atoms); i++) {
368                     if (netatom_[client_atoms[i].atom_index]
369                         == static_cast<unsigned int>(me->data.l[prop])) {
370                         break;
371                     }
372                 }
373                 if (i >= LENGTH(client_atoms)) {
374                     /* property will not be handled */
375                     continue;
376                 }
377                 auto new_value = ArrayInitializer<bool,3>({
378                     { _NET_WM_STATE_REMOVE  , false },
379                     { _NET_WM_STATE_ADD     , true },
380                     { _NET_WM_STATE_TOGGLE  , !client_atoms[i].enabled },
381                 }).a;
382                 int action = me->data.l[0];
383                 if (action >= static_cast<int>(new_value.size())) {
384                     HSDebug("_NET_WM_STATE: invalid action %d\n", action);
385                 }
386                 /* change the value */
387                 client_atoms[i].callback(client, new_value[action]);
388             }
389             break;
390         }
391 
392         case NetWmMoveresize: {
393             auto client = Root::common().client(me->window);
394             if (!client) {
395                 break;
396             }
397             int direction = me->data.l[2];
398             if (direction == _NET_WM_MOVERESIZE_MOVE
399                 || direction == _NET_WM_MOVERESIZE_MOVE_KEYBOARD) {
400                 root_->mouse->mouse_initiate_move(client, {});
401             } else if (direction == _NET_WM_MOVERESIZE_CANCEL) {
402                 if (root_->mouse->mouse_is_dragging()) {
403                     root_->mouse->mouse_stop_drag();
404                 }
405             } else {
406                 // anything else is a resize
407                 root_->mouse->mouse_initiate_resize(client, {});
408             }
409             break;
410         }
411 
412         case NetCloseWindow: {
413             windowClose(me->window);
414             break;
415         }
416 
417         default:
418             HSDebug("no handler for the client message \"%s\"\n",
419                     netatomNames_[index]);
420             break;
421     }
422 }
423 
updateWindowState(Client * client)424 void Ewmh::updateWindowState(Client* client) {
425     /* mapping between EWMH atoms and client struct members */
426     struct {
427         int     atom_index;
428         bool    enabled;
429     } client_atoms[] = {
430         { NetWmStateFullscreen,         client->ewmhfullscreen_  },
431         { NetWmStateDemandsAttention,   client->urgent_          },
432         { NetWmStateHidden,             client->minimized_       },
433     };
434 
435     /* find out which flags are set */
436     Atom window_state[LENGTH(client_atoms)];
437     size_t count_enabled = 0;
438     for (size_t i = 0; i < LENGTH(client_atoms); i++) {
439         if (client_atoms[i].enabled) {
440             window_state[count_enabled] = netatom_[client_atoms[i].atom_index];
441             count_enabled++;
442         }
443     }
444 
445     /* write it to the window */
446     XChangeProperty(X_.display(), client->window_, netatom_[NetWmState], XA_ATOM,
447         32, PropModeReplace, (unsigned char *) window_state, count_enabled);
448 }
449 
clearClientProperties(Window win)450 void Ewmh::clearClientProperties(Window win) {
451     // delete ewmh-properties and ICCCM-Properties such that the client knows
452     // that he has been unmanaged and now the client is allowed to be mapped
453     // again (e.g. if it is some dialog)
454     XDeleteProperty(X_.display(), win, netatom_[NetWmState]);
455     XDeleteProperty(X_.display(), win, wmatom(WM::State));
456 }
457 
isWindowStateSet(Window win,Atom hint)458 bool Ewmh::isWindowStateSet(Window win, Atom hint) {
459     auto res = X_.getWindowPropertyAtom(win, netatom_[NetWmState]);
460     if (!res.has_value()) {
461         return false;
462     }
463     for (auto& h : res.value()) {
464         if (hint == h) {
465             return true;
466         }
467     }
468     return false;
469 }
470 
isFullscreenSet(Window win)471 bool Ewmh::isFullscreenSet(Window win) {
472     return isWindowStateSet(win, netatom_[NetWmStateFullscreen]);
473 }
474 
setWindowOpacity(Window win,double opacity)475 void Ewmh::setWindowOpacity(Window win, double opacity) {
476     /* Based on the EWMH proposal
477      * https://mail.gnome.org/archives/wm-spec-list/2003-December/msg00035.html
478      */
479     long long_opacity = 0xffffffff * CLAMP(opacity, 0, 1);
480     X_.setPropertyCardinal(win, netatom_[NetWmWindowOpacity], { long_opacity });
481 }
482 
updateFrameExtents(Window win,int left,int right,int top,int bottom)483 void Ewmh::updateFrameExtents(Window win, int left, int right, int top, int bottom) {
484     X_.setPropertyCardinal(win, netatom_[NetFrameExtents],
485                            { left, right, top, bottom });
486 }
487 
windowUpdateWmState(Window win,WmState state)488 void Ewmh::windowUpdateWmState(Window win, WmState state) {
489     /* set full WM_STATE according to
490      * http://www.x.org/releases/X11R7.7/doc/xorg-docs/icccm/icccm.html#WM_STATE_Property
491      *
492      * It's crucial that the property has the type WM_STATE!
493      */
494     long wmstate[] = { static_cast<long>(state), None };
495     XChangeProperty(X_.display(), win,  wmatom(WM::State), wmatom(WM::State),
496                     32, PropModeReplace,
497                     reinterpret_cast<unsigned char*>(wmstate), LENGTH(wmstate));
498 }
499 
isOwnWindow(Window win)500 bool Ewmh::isOwnWindow(Window win) {
501     return windowManagerWindow_ == win;
502 }
503 
clearInputFocus()504 void Ewmh::clearInputFocus() {
505     XSetInputFocus(X_.display(), windowManagerWindow_, RevertToPointerRoot, CurrentTime);
506 }
507 
get()508 Ewmh& Ewmh::get() {
509     return *(Root::get()->ewmh);
510 }
511 
512 /** send the given proto atom to the given window via XSendEvent(). If
513  * checkProtocols = true, this is done only if proto is present in the window's
514  * WM protocols. The return value tells whether the event was actually sent.
515  */
sendEvent(Window window,Ewmh::WM proto,bool checkProtocols)516 bool Ewmh::sendEvent(Window window, Ewmh::WM proto, bool checkProtocols) {
517     bool exists = false;
518     Atom protoAtom = wmatom(proto);
519     if (!checkProtocols) {
520         exists = true;
521     } else {
522         int n;
523         Atom *protocols;
524 
525         if (XGetWMProtocols(X_.display(), window, &protocols, &n)) {
526             while (!exists && n--) {
527                 exists = protocols[n] == protoAtom;
528             }
529             XFree(protocols);
530         }
531     }
532     if (exists) {
533         XEvent ev;
534         ev.type = ClientMessage;
535         ev.xclient.window = window;
536         ev.xclient.message_type = wmatom(WM::Protocols);
537         ev.xclient.format = 32;
538         ev.xclient.data.l[0] = protoAtom;
539         ev.xclient.data.l[1] = CurrentTime;
540         XSendEvent(X_.display(), window, False, NoEventMask, &ev);
541     }
542     return exists;
543 }
544 
windowClose(Window window)545 void Ewmh::windowClose(Window window) {
546     sendEvent(window, WM::Delete, false);
547 }
548 
netatom(int netatomEnum)549 Atom Ewmh::netatom(int netatomEnum)
550 {
551     return netatom_[netatomEnum];
552 }
553 
netatomName(int netatomEnum)554 const char* Ewmh::netatomName(int netatomEnum)
555 {
556     return netatomNames_[static_cast<unsigned long>(netatomEnum)];
557 }
558 
559 //! convenience wrapper around wmatom_
wmatom(WM proto)560 Atom Ewmh::wmatom(WM proto) {
561     return wmatom_[(int)proto];
562 }
563 
getWindowTitle(Window win)564 string Ewmh::getWindowTitle(Window win) {
565     auto newName = X_.getWindowProperty(win, netatom_[NetWmName]);
566     if (newName.has_value()) {
567         return newName.value();
568     }
569     newName = X_.getWindowProperty(win, wmatom(WM::Name));
570     if (newName.has_value()) {
571         return newName.value();
572     }
573     return "";
574 }
575 
576 /** Return the window type of the given window. If there are multiple entries, then
577  * only the first window type entry is returned. The return value is an enum value between
578  * NetWmWindowTypeFIRST and NetWmWindowTypeLAST (inclusive). Any other window
579  * type is not recognized and leads to -1 being returned.
580  */
getWindowType(Window win)581 int Ewmh::getWindowType(Window win) {
582     auto atoms = X_.getWindowPropertyAtom(win, netatom_[NetWmWindowType]);
583     if (!atoms.has_value() || atoms.value().empty()) {
584         return -1;
585     }
586     Atom windowtype = atoms.value()[0];
587     for (int i = NetWmWindowTypeFIRST; i <= NetWmWindowTypeLAST; i++) {
588         // try to find the window type
589         if (windowtype == netatom_[i]) {
590             return i;
591         }
592     }
593     return -1;
594 }
595