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