1 #include "clientmanager.h"
2 
3 #include <X11/Xlib.h>
4 #include <algorithm>
5 #include <iostream>
6 #include <string>
7 
8 #include "attribute.h"
9 #include "client.h"
10 #include "completion.h"
11 #include "decoration.h"
12 #include "ewmh.h"
13 #include "globals.h"
14 #include "ipc-protocol.h"
15 #include "monitor.h"
16 #include "monitormanager.h"
17 #include "mousemanager.h"
18 #include "root.h"
19 #include "rulemanager.h"
20 #include "stack.h"
21 #include "tag.h"
22 #include "tagmanager.h"
23 #include "utils.h"
24 #include "xconnection.h"
25 
26 using std::endl;
27 using std::function;
28 using std::string;
29 using std::vector;
30 
ClientManager()31 ClientManager::ClientManager()
32     : focus(*this, "focus")
33     , dragged(*this, "dragged")
34     , theme(nullptr)
35     , settings(nullptr)
36     , ewmh(nullptr)
37 {
38     setDoc("The managed windows. For every (managed) window id there "
39            "is an entry here.");
40     focus.setDoc("the focused client (only exists if a client is focused)");
41     dragged.setDoc("the object of a client which is currently dragged"
42                    " by the mouse, if any. See the documentation of the"
43                    "  mousebind command for examples.");
44 }
45 
~ClientManager()46 ClientManager::~ClientManager()
47 {
48     // make all clients visible at their original floating position
49     for (auto c : clients_) {
50         auto r = c.second->float_size_;
51         auto window = c.second->x11Window();
52         XMoveResizeWindow(g_display, window, r.x, r.y, r.width, r.height);
53         XReparentWindow(g_display, window, g_root, r.x, r.y);
54         ewmh->updateFrameExtents(window, 0,0,0,0);
55         XMapWindow(g_display, window);
56         delete c.second;
57     }
58 }
59 
injectDependencies(Settings * s,Theme * t,Ewmh * e)60 void ClientManager::injectDependencies(Settings* s, Theme* t, Ewmh* e) {
61     settings = s;
62     theme = t;
63     ewmh = e;
64 }
65 
client(Window window)66 Client* ClientManager::client(Window window)
67 {
68     auto entry = clients_.find(window);
69     if (entry != clients_.end()) {
70         return entry->second;
71     }
72     return {};
73 }
74 
75 /**
76  * \brief   Resolve a window description to a client
77  *
78  * \param   str     Describes the window: "" means the focused one, "urgent"
79  *                  resolves to a arbitrary urgent window, "0x..." just
80  *                  resolves to the given window given its hexadecimal window id,
81  *                  a decimal number its decimal window id.
82  * \return          Pointer to the resolved client, or null, if client not found
83  */
client(const string & identifier)84 Client* ClientManager::client(const string &identifier)
85 {
86     if (identifier.empty()) {
87         return focus();
88     }
89     if (identifier == "urgent") {
90         for (auto c : clients_) {
91             if (c.second->urgent_) {
92                 return c.second;
93             }
94         }
95         return {}; // no urgent client found
96     }
97     try {
98         Window win = Converter<WindowID>::parse(identifier);
99         return client(win);
100     } catch (...) {
101         return nullptr;
102     }
103 }
104 
105 //! the completion-counterpart of ClientManager::client()
completeClients(Completion & complete)106 void ClientManager::completeClients(Completion& complete)
107 {
108     complete.full("urgent");
109     for (const auto& it : clients_) {
110         complete.full(Converter<WindowID>::str(it.first));
111     }
112 }
113 
add(Client * client)114 void ClientManager::add(Client* client)
115 {
116     clients_[client->window_] = client;
117     client->needsRelayout.connect(needsRelayout);
118     client->floating_.changed().connect([this,client]() {
119         this->clientStateChanged.emit(client);
120     });
121     client->minimized_.changed().connect([this,client]() {
122         this->clientStateChanged.emit(client);
123     });
124     addChild(client, client->window_id_str);
125 }
126 
setDragged(Client * client)127 void ClientManager::setDragged(Client* client) {
128     if (dragged()) {
129         dragged()->dragged_ = false;
130     }
131     dragged = client;
132     if (dragged()) {
133         dragged()->dragged_ = true;
134     }
135 }
136 
remove(Window window)137 void ClientManager::remove(Window window)
138 {
139     removeChild(*clients_[window]->window_id_str);
140     clients_.erase(window);
141 }
142 
manage_client(Window win,bool visible_already,bool force_unmanage,function<void (ClientChanges &)> additionalRules)143 Client* ClientManager::manage_client(Window win, bool visible_already, bool force_unmanage,
144                                      function<void(ClientChanges&)> additionalRules) {
145     if (is_herbstluft_window(g_display, win)) {
146         // ignore our own window
147         return nullptr;
148     }
149 
150     if (client(win)) { // if the client is managed already
151         return nullptr;
152     }
153 
154     // init client
155     auto client = new Client(win, visible_already, *this);
156     client->listen_for_events();
157     Monitor* m = get_current_monitor();
158 
159     // apply rules
160     ClientChanges changes = applyDefaultRules(client->window_);
161     if (additionalRules) {
162         additionalRules(changes);
163     }
164     changes = Root::get()->rules()->evaluateRules(client, std::cerr, changes);
165     if (!changes.manage || force_unmanage) {
166         // map it... just to be sure
167         XMapWindow(g_display, win);
168         delete client;
169         return {};
170     }
171 
172     if (!changes.tag_name.empty()) {
173         HSTag* tag = find_tag(changes.tag_name.c_str());
174         if (tag) {
175             client->setTag(tag);
176         }
177     }
178     if (!changes.monitor_name.empty()) {
179         Monitor *monitor = string_to_monitor(changes.monitor_name.c_str());
180         if (monitor) {
181             // a valid tag was not already found, use the target monitor's tag
182             if (!client->tag()) { client->setTag(monitor->tag); }
183             // a tag was already found, display it on the target monitor, but
184             // only if switchtag is set
185             else if (changes.switchtag) {
186                 monitor_set_tag(monitor, client->tag());
187             }
188         }
189     }
190 
191     // important that this happens befor the insertion to a tag
192     setSimpleClientAttributes(client, changes);
193 
194     // actually manage it
195     client->dec->createWindow();
196     client->fuzzy_fix_initial_position();
197     add(client);
198     // insert to layout
199     if (!client->tag()) {
200         client->setTag(m->tag);
201     }
202     // insert window to the stack
203     client->slice = Slice::makeClientSlice(client);
204     client->tag()->insertClientSlice(client);
205     // insert window to the tag
206     client->tag()->insertClient(client, changes.tree_index, changes.focus);
207 
208     tag_set_flags_dirty();
209     if (changes.fullscreen.has_value()) {
210         client->fullscreen_ = changes.fullscreen.value();
211     } else {
212         client->fullscreen_ = ewmh->isFullscreenSet(client->window_);
213     }
214     ewmh->updateWindowState(client);
215     // add client after setting the correct tag for the new client
216     // this ensures a panel can read the tag property correctly at this point
217     ewmh->addClient(client->window_);
218 
219     client->make_full_client();
220 
221     Monitor* monitor = find_monitor_with_tag(client->tag());
222     if (monitor) {
223         if (monitor != get_current_monitor()
224             && changes.focus && changes.switchtag) {
225             monitor_set_tag(get_current_monitor(), client->tag());
226         }
227         monitor->evaluateClientPlacement(client, changes.floatplacement);
228         // TODO: monitor_apply_layout() maybe is called twice here if it
229         // already is called by monitor_set_tag()
230         monitor->applyLayout();
231         client->set_visible(true);
232     } else {
233         if (changes.focus && changes.switchtag) {
234             monitor_set_tag(get_current_monitor(), client->tag());
235             get_current_monitor()->evaluateClientPlacement(client, changes.floatplacement);
236             client->set_visible(true);
237         } else {
238             // if the client is not directly displayed on any monitor,
239             // take the current monitor
240             get_current_monitor()->evaluateClientPlacement(client, changes.floatplacement);
241             // mark the client as hidden
242             ewmh->windowUpdateWmState(client->window_, WmState::WSIconicState);
243         }
244     }
245     client->send_configure();
246 
247     // TODO: make this better
248     Root::get()->mouse->grab_client_buttons(client, false);
249 
250     return client;
251 }
252 
253 //! apply some built in rules that reflect the EWMH specification
254 //! and regarding sensible single-window floating settings
applyDefaultRules(Window win)255 ClientChanges ClientManager::applyDefaultRules(Window win)
256 {
257     ClientChanges changes;
258     const int windowType = ewmh->getWindowType(win);
259     vector<int> unmanaged= {
260         NetWmWindowTypeDesktop,
261         NetWmWindowTypeDock,
262     };
263     if (std::find(unmanaged.begin(), unmanaged.end(), windowType)
264             != unmanaged.end())
265     {
266         changes.manage = False;
267     }
268     vector<int> floated = {
269         NetWmWindowTypeToolbar,
270         NetWmWindowTypeMenu,
271         NetWmWindowTypeUtility,
272         NetWmWindowTypeSplash,
273         NetWmWindowTypeDialog,
274         NetWmWindowTypeDropdownMenu,
275         NetWmWindowTypePopupMenu,
276         NetWmWindowTypeTooltip,
277         NetWmWindowTypeNotification,
278         NetWmWindowTypeCombo,
279         NetWmWindowTypeDnd,
280     };
281     if (std::find(floated.begin(), floated.end(), windowType) != floated.end())
282     {
283         changes.floating = True;
284     }
285     if (ewmh->X().getTransientForHint(win).has_value()) {
286         changes.floating = true;
287     }
288     return changes;
289 }
290 
291 /** apply simple attribute based client changes. We do not apply 'fullscreen' here because
292  * its value defaults to the client's ewmh property and is handled in applyRulesCmd() and manage_client() differently.
293  */
setSimpleClientAttributes(Client * client,const ClientChanges & changes)294 void ClientManager::setSimpleClientAttributes(Client* client, const ClientChanges& changes)
295 {
296     if (changes.floating.has_value()) {
297         client->floating_ = changes.floating.value();
298     }
299     if (changes.pseudotile.has_value()) {
300         client->pseudotile_ = changes.pseudotile.value();
301     }
302 
303     if (changes.ewmhNotify.has_value()) {
304         client->ewmhnotify_ = changes.ewmhNotify.value();
305     }
306 
307     if (changes.ewmhRequests.has_value()) {
308         client->ewmhrequests_ = changes.ewmhRequests.value();
309     }
310 
311     if (changes.keyMask.has_value()) {
312         client->keyMask_ = changes.keyMask.value();
313     }
314     if (changes.keysInactive.has_value()) {
315         client->keysInactive_ = changes.keysInactive.value();
316     }
317 }
318 
applyRulesCmd(Input input,Output output)319 int ClientManager::applyRulesCmd(Input input, Output output) {
320     string winid;
321     if (!(input >> winid)) {
322         return HERBST_NEED_MORE_ARGS;
323     }
324     if (winid == "--all") {
325         MonitorManager* monitors = Root::get()->monitors();
326         monitors->lock(); // avoid unnecessary redraws
327         int status = 0;
328         for (const auto& it : clients_) {
329             status = std::max(status, applyRules(it.second, output, false));
330         }
331         monitors->unlock();
332         return status;
333     } else {
334         Client* client = this->client(winid);
335         if (!client) {
336             output << "No such (managed) client: " << winid << "\n";
337             return HERBST_INVALID_ARGUMENT;
338         }
339         return applyRules(client, output);
340     }
341 }
342 
343 //! apply all rules for the given client. if focus=on and changeFocus=true,
344 //! then the client is focused
applyRules(Client * client,Output output,bool changeFocus)345 int ClientManager::applyRules(Client* client, Output output, bool changeFocus)
346 {
347     ClientChanges changes;
348     changes.focus = client == focus();
349     changes = Root::get()->rules()->evaluateRules(client, output, changes);
350     if (!changeFocus) {
351         changes.focus = false;
352     }
353     return applyChanges(client, changes, output);
354 }
355 
applyChanges(Client * client,ClientChanges changes,Output output)356 int ClientManager::applyChanges(Client* client, ClientChanges changes, Output output)
357 {
358     if (changes.manage == false) {
359         // only make unmanaging clients possible as soon as it is
360         // possible to make them managed again
361         output << "Unmanaging clients not yet possible.\n";
362         return HERBST_INVALID_ARGUMENT;
363     }
364     // do the simple attributes first
365     setSimpleClientAttributes(client, changes);
366     if (changes.fullscreen.has_value()) {
367         client->fullscreen_ = changes.fullscreen.value();
368     }
369     HSTag* tag = nullptr;
370     Monitor* monitor = nullptr;
371     bool switch_tag = false;
372     // in the following, we do the same decisions in the same order as in manage_client();
373     // the only difference is, that we only set the above variables and execute the decisions
374     // later
375     if (!changes.tag_name.empty()) {
376         tag = find_tag(changes.tag_name.c_str());
377     }
378     if (!changes.monitor_name.empty()) {
379         monitor = string_to_monitor(changes.monitor_name.c_str());
380         if (monitor) {
381             // a valid tag was not already found, use the target monitor's tag
382             if (!tag) { tag = monitor->tag; }
383             // a tag was already found, display it on the target monitor, but
384             // only if switchtag is set
385             else if (changes.switchtag) {
386                 switch_tag = true;
387             }
388         }
389     }
390     if (tag || !changes.tree_index.empty()) {
391         if (!tag) {
392             tag = client->tag();
393         }
394         TagManager* tagman = Root::get()->tags();
395         tagman->moveClient(client, tag, changes.tree_index, changes.focus);
396     } else if (changes.focus && (client != focus())) {
397         // focus the client
398         client->tag()->focusClient(client);
399         Root::get()->monitors->relayoutTag(client->tag());
400     }
401     if (monitor && switch_tag && tag) {
402         monitor_set_tag(monitor, tag);
403     }
404     return 0;
405 }
406 
applyRulesCompletion(Completion & complete)407 void ClientManager::applyRulesCompletion(Completion& complete)
408 {
409     if (complete == 0) {
410         complete.full("--all");
411         completeClients(complete);
412     } else {
413         complete.none();
414     }
415 }
416 
applyTmpRuleCmd(Input input,Output output)417 int ClientManager::applyTmpRuleCmd(Input input, Output output)
418 {
419     string clientStr;
420     if (!(input >> clientStr)) {
421         return HERBST_NEED_MORE_ARGS;
422     }
423     // 'applyTo' is a pointer to a map containing those to which the
424     // above rule shall be applied
425     std::unordered_map<Window, Client*> singletonMap;
426     std::unordered_map<Window, Client*>* applyTo = &singletonMap;
427     if (clientStr == "--all") {
428         // apply to all clients
429         applyTo = &clients_;
430     } else {
431         // use the 'singletonMap' if the rule shall be applied
432         // to only one client
433         Client* client = this->client(clientStr);
434         if (!client) {
435             output << "No such (managed) client: " << clientStr << "\n";
436             return HERBST_INVALID_ARGUMENT;
437         }
438         (*applyTo)[client->x11Window()] = client;
439     }
440     // parse the rule
441     bool prepend = false;
442     Rule rule;
443     int status = RuleManager::parseRule(input, output, rule, prepend);
444     if (status != 0) {
445         return status;
446     }
447     for (auto& it : *applyTo) {
448         Client* client = it.second;
449         ClientChanges changes;
450         changes.focus = client == focus();
451         rule.evaluate(client, changes, output);
452         if (applyTo->size() > 1) {
453             // if we apply the rule to more than one
454             // client, then we leave the focus where it was
455             changes.focus = false;
456         }
457         applyChanges(client, changes, output);
458     }
459     return 0;
460 }
461 
applyTmpRuleCompletion(Completion & complete)462 void ClientManager::applyTmpRuleCompletion(Completion& complete)
463 {
464     if (complete == 0) {
465         complete.full("--all");
466         completeClients(complete);
467     } else {
468         Root::get()->rules->addRuleCompletion(complete);
469     }
470 }
471 
unmap_notify(Window win)472 void ClientManager::unmap_notify(Window win) {
473     auto client = this->client(win);
474     if (!client) {
475         return;
476     }
477     if (!client->ignore_unmapnotify()) {
478         force_unmanage(client);
479     }
480 }
481 
force_unmanage(Client * client)482 void ClientManager::force_unmanage(Client* client) {
483     if (dragged() == client) {
484         dragged = nullptr;
485         Root::get()->mouse->mouse_stop_drag();
486     }
487     if (client->tag() && client->slice) {
488         client->tag()->stack->removeSlice(client->slice);
489     }
490     // remove from tag
491     client->tag()->removeClient(client);
492     // ignore events from it
493     XSelectInput(g_display, client->window_, 0);
494     //XUngrabButton(g_display, AnyButton, AnyModifier, win);
495     // permanently remove it
496     XUnmapWindow(g_display, client->decorationWindow());
497     XReparentWindow(g_display, client->window_, g_root, 0, 0);
498     client->clear_properties();
499     HSTag* tag = client->tag();
500 
501 
502     // and arrange monitor after the client has been removed from the stack
503     needsRelayout.emit(tag);
504     ewmh->removeClient(client->window_);
505     tag_set_flags_dirty();
506     // delete client
507     this->remove(client->window_);
508     if (client == focus()) {
509         // this should never happen because we forced a relayout
510         // of the client's tag, so 'focus' must have been updated
511         // in the meantime. Anyway, lets be safe:
512         focus = nullptr;
513     }
514     delete client;
515 }
516 
clientSetAttribute(string attribute,Input input,Output output)517 int ClientManager::clientSetAttribute(string attribute,
518                                       Input input,
519                                       Output output)
520 {
521     string value = input.empty() ? "toggle" : input.front();
522     Client* c = get_current_client();
523     if (c) {
524         Attribute* a = c->attribute(attribute);
525         if (!a) {
526             return HERBST_UNKNOWN_ERROR;
527         }
528         string error_message = a->change(value);
529         if (!error_message.empty()) {
530             output << input.command() << ": illegal argument \""
531                    << value << "\": "
532                    << error_message << endl;
533             return HERBST_INVALID_ARGUMENT;
534         }
535     }
536     return 0;
537 }
538 
pseudotile_cmd(Input input,Output output)539 int ClientManager::pseudotile_cmd(Input input, Output output)
540 {
541     return clientSetAttribute("pseudotile", input, output);
542 }
543 
fullscreen_cmd(Input input,Output output)544 int ClientManager::fullscreen_cmd(Input input, Output output)
545 {
546     return clientSetAttribute("fullscreen", input, output);
547 }
548 
pseudotile_complete(Completion & complete)549 void ClientManager::pseudotile_complete(Completion& complete)
550 {
551     fullscreen_complete(complete);
552 }
553 
fullscreen_complete(Completion & complete)554 void ClientManager::fullscreen_complete(Completion& complete)
555 {
556     if (complete == 0) {
557         // we want this command to have a completion, even if no client
558         // is focused at the moment.
559         bool value = true;
560         Converter<bool>::complete(complete, &value);
561     } else {
562         complete.none();
563     }
564 }
565 
566