1 #include "monitor.h"
2 
3 #include <X11/Xlib.h>
4 #include <algorithm>
5 #include <cassert>
6 #include <cstring>
7 #include <sstream>
8 #include <vector>
9 
10 #include "client.h"
11 #include "clientmanager.h"
12 #include "completion.h"
13 #include "ewmh.h"
14 #include "floating.h"
15 #include "frametree.h"
16 #include "globals.h"
17 #include "hook.h"
18 #include "ipc-protocol.h"
19 #include "layout.h"
20 #include "monitormanager.h"
21 #include "root.h"
22 #include "settings.h"
23 #include "stack.h"
24 #include "tag.h"
25 #include "tagmanager.h"
26 #include "utils.h"
27 
28 using std::endl;
29 using std::string;
30 using std::stringstream;
31 using std::vector;
32 
33 extern MonitorManager* g_monitors;
34 
Monitor(Settings * settings_,MonitorManager * monman_,Rectangle rect_,HSTag * tag_)35 Monitor::Monitor(Settings* settings_, MonitorManager* monman_, Rectangle rect_, HSTag* tag_)
36     : tag(tag_)
37     , tag_previous(tag_)
38     , name      (this, "name", "",
39            [monman_](string n) { return monman_->isValidMonitorName(n); })
40     , index     (this, "index", 0)
41     , tag_string(this, "tag", &Monitor::getTagString, &Monitor::setTagString)
42     , pad_up    (this, "pad_up", 0)
43     , pad_right (this, "pad_right", 0)
44     , pad_down  (this, "pad_down", 0)
45     , pad_left  (this, "pad_left", 0)
46     , lock_tag  (this, "lock_tag", false)
47     , pad_automatically_set ({false, false, false, false})
48     , dirty(true)
49     , lock_frames(false)
50     , mouse { 0, 0 }
51     , rect(this, "geometry", rect_, &Monitor::atLeastMinWindowSize)
52     , settings(settings_)
53     , monman(monman_)
54 {
55     // explicitly set members writable such that gendoc.py recognizes it
56     pad_up.setWritable();
57     pad_right.setWritable();
58     pad_down.setWritable();
59     pad_left.setWritable();
60     lock_tag.setWritable();
61     for (auto i : {&pad_up, &pad_left, &pad_right, &pad_down}) {
62         i->changed().connect(this, &Monitor::applyLayout);
63     }
64     rect.changedByUser().connect(this, &Monitor::applyLayout);
65 
66     rect.setDoc("the outer geometry of the monitor");
67 
68     stacking_window = XCreateSimpleWindow(g_display, g_root,
69                                              42, 42, 42, 42, 1, 0, 0);
70     setDoc("The monitor is a rectangular part on the screen that holds "
71            "precisely one tag at a time. The pad attributes reserve "
72            "space on the monitor\'s edge for panels, so this space "
73            "(given in number of pixels) is never occupied by tiled clients.");
74     name.setDoc("the monitor\'s name (can be empty)");
75     index.setDoc("the monitor\'s index (starts at index 0)");
76     tag_string.setDoc("the name of the tag viewed here");
77     pad_up.setDoc("space for panels at the monitor\'s upper edge");
78     pad_right.setDoc("space for panels at the monitor\'s right edge");
79     pad_down.setDoc("space for panels at the monitor\'s lower edge");
80     pad_left.setDoc("space for panels at the monitor\'s left edge");
81     lock_tag.setDoc("if activated, then it it is not possible to switch "
82                     "this monitor to a different tag.");
83 }
84 
~Monitor()85 Monitor::~Monitor() {
86     XDestroyWindow(g_display, stacking_window);
87 }
88 
getTagString()89 string Monitor::getTagString() {
90     return tag->name();
91 }
92 
setTagString(string new_tag_string)93 string Monitor::setTagString(string new_tag_string) {
94     HSTag* new_tag = find_tag(new_tag_string.c_str());
95     if (!new_tag) {
96         return "no tag named \"" + new_tag_string + "\" exists.";
97     }
98     if (new_tag == tag) {
99         return ""; // nothing to do
100     }
101     bool success = this->setTag(new_tag);
102     if (!success) {
103         return "tag \"" + new_tag_string + "\" is already on another monitor";
104         /* Note: To change this to tag-swapping between monitors, implement a method
105          * MonitorManager::stealTag() that will fetch the corresponding monitor
106          * and perform the swap */
107     }
108     return "to be implemented"; // TODO: implement in setTag()
109 }
110 
setIndexAttribute(unsigned long new_index)111 void Monitor::setIndexAttribute(unsigned long new_index) {
112     index = new_index;
113 }
114 
lock_tag_cmd(Input,Output)115 int Monitor::lock_tag_cmd(Input, Output) {
116     lock_tag = true;
117     return 0;
118 }
119 
unlock_tag_cmd(Input,Output)120 int Monitor::unlock_tag_cmd(Input, Output) {
121     lock_tag = false;
122     return 0;
123 }
124 
noComplete(Completion & complete)125 void Monitor::noComplete(Completion &complete)
126 {
127     complete.none();
128 }
129 
list_padding(Input,Output output)130 int Monitor::list_padding(Input, Output output) {
131     output     << pad_up()
132         << " " << pad_right()
133         << " " << pad_down()
134         << " " << pad_left()
135         << "\n";
136     return 0;
137 }
138 
139 /** Set the tag shown on the monitor.
140  * Return false if tag is already shown on another monitor.
141  */
142 // TODO this is the job of monitormanager
setTag(HSTag * new_tag)143 bool Monitor::setTag(HSTag* new_tag) {
144     auto owner = find_monitor_with_tag(new_tag);
145     if (!owner || owner != this) {
146         // TODO do the work!
147         return true;
148     }
149     return owner == this;
150 }
151 
applyLayout()152 void Monitor::applyLayout() {
153     if (settings->monitors_locked) {
154         dirty = true;
155         return;
156     }
157     dirty = false;
158     Rectangle cur_rect = rect;
159     // apply pad
160     // FIXME: why does the following + work for attributes pad_* ?
161     cur_rect.x += pad_left();
162     cur_rect.width -= (pad_left() + pad_right());
163     cur_rect.y += pad_up();
164     cur_rect.height -= (pad_up() + pad_down());
165     if (!g_settings->smart_frame_surroundings() || tag->frame->root_->isSplit()) {
166         // apply frame gap
167         cur_rect.x += settings->frame_gap();
168         cur_rect.y += settings->frame_gap();
169         cur_rect.height -= settings->frame_gap();
170         cur_rect.width -= settings->frame_gap();
171     }
172     bool isFocused = get_current_monitor() == this;
173     TilingResult res = tag->frame->root_->computeLayout(cur_rect);
174     if (tag->floating_focused) {
175         res.focus = tag->focusedClient();
176     }
177     if (tag->floating) {
178         for (auto& p : res.data) {
179             p.second.floated = true;
180             // deactivate smart_window_surroundings in floating mode
181             p.second.minimalDecoration = false;
182         }
183     }
184     // preprocessing
185     for (auto& p : res.data) {
186         if (p.first->fullscreen_() || p.second.floated) {
187             // do not hide fullscreen windows
188             p.second.visible = true;
189         }
190         if (settings->hide_covered_windows) {
191             // apply hiding of windows: move them to out of the screen:
192             if (!p.second.visible) {
193                 Rectangle& geo = p.second.geometry;
194                 geo.x = -100 - geo.width;
195                 geo.y = -100 - geo.height;
196             }
197         }
198     }
199     // 1. Update stack (TODO: why stack first?)
200     for (auto& p : res.data) {
201         Client* c = p.first;
202         if (c->fullscreen_()) {
203             tag->stack->sliceAddLayer(c->slice, LAYER_FULLSCREEN);
204         } else {
205             tag->stack->sliceRemoveLayer(c->slice, LAYER_FULLSCREEN);
206         }
207         // special raise rules for tiled clients:
208         if (!p.second.floated) {
209             // this client is the globally focused client if this monitor
210             // is focused and if this client is the focus on this monitor:
211             bool globallyFocusedClient = isFocused && res.focus == c;
212             // if this client is globally focused, then it has a different border color
213             // and so we raise it to make the look of overlapping shadows more pleasent if
214             // a compositor is running.
215             //
216             // Thus, raise this client if it needs to be raised according
217             // to the TilingResult (if it is the selected window in a max-frame) or
218             // if this client is focused:
219             if (p.second.needsRaise || globallyFocusedClient) {
220                 c->raise();
221             }
222         }
223     }
224     tag->stack->clearLayer(LAYER_FOCUS);
225     if (res.focus) {
226         // activate the focus layer if requested by the setting
227         // or if there is a fullscreen client potentially covering
228         // the focused client.
229         if ((isFocused && g_settings->raise_on_focus_temporarily())
230             || tag->stack->isLayerEmpty(LAYER_FULLSCREEN) == false)
231         {
232             tag->stack->sliceAddLayer(res.focus->slice, LAYER_FOCUS);
233         }
234     }
235     restack();
236     // 2. Update window geometries
237     for (auto& p : res.data) {
238         Client* c = p.first;
239         bool clientFocused = isFocused && res.focus == c;
240         if (c->fullscreen_()) {
241             c->resize_fullscreen(rect, clientFocused);
242         } else if (p.second.floated) {
243             c->resize_floating(this, clientFocused);
244         } else {
245             bool minDec = p.second.minimalDecoration;
246             c->resize_tiling(p.second.geometry, clientFocused, minDec);
247         }
248     }
249     for (auto& c : tag->floating_clients_) {
250         if (c->fullscreen_()) {
251             c->resize_fullscreen(rect, res.focus == c && isFocused);
252         } else {
253             c->resize_floating(this, res.focus == c && isFocused);
254         }
255     }
256     if (tag->floating) {
257         for (auto& p : res.frames) {
258             p.first->hide();
259         }
260     } else {
261         for (auto& p : res.frames) {
262             p.first->render(p.second, p.first == res.focused_frame && isFocused);
263             p.first->updateVisibility(p.second, p.first == res.focused_frame && isFocused);
264         }
265     }
266     if (isFocused) {
267         if (res.focus) {
268             Root::get()->clients()->focus = res.focus;
269             res.focus->window_focus();
270         } else {
271             Root::get()->clients()->focus = {};
272             Client::window_unfocus_last();
273         }
274     }
275 
276     // remove all enternotify-events from the event queue that were
277     // generated while arranging the clients on this monitor
278     monman->dropEnterNotifyEvents.emit();
279 }
280 
find_monitor_by_name(const char * name)281 Monitor* find_monitor_by_name(const char* name) {
282     for (auto m : *g_monitors) {
283         if (m->name == name) {
284             return m;
285         }
286     }
287     return nullptr;
288 }
289 
string_to_monitor(const char * str)290 Monitor* string_to_monitor(const char* str) {
291   return g_monitors->byString(str);
292 }
293 
294 
move_cmd(Input input,Output output)295 int Monitor::move_cmd(Input input, Output output) {
296     // usage: move_monitor INDEX RECT [PADUP [PADRIGHT [PADDOWN [PADLEFT]]]]
297     // moves monitor with number to RECT
298     if (input.empty()) {
299         return HERBST_NEED_MORE_ARGS;
300     }
301     auto new_rect = Rectangle::fromStr(input.front());
302     if (new_rect.width < WINDOW_MIN_WIDTH || new_rect.height < WINDOW_MIN_HEIGHT) {
303         output << input.command() << ": Rectangle is too small\n";
304         return HERBST_INVALID_ARGUMENT;
305     }
306     // else: just move it:
307     this->rect = new_rect;
308     input.shift();
309     if (!input.empty()) {
310         pad_up.change(input.front());
311     }
312     input.shift();
313     if (!input.empty()) {
314         pad_right.change(input.front());
315     }
316     input.shift();
317     if (!input.empty()) {
318         pad_down.change(input.front());
319     }
320     input.shift();
321     if (!input.empty()) {
322         pad_left.change(input.front());
323     }
324     monitorMoved.emit();
325     applyLayout();
326     return 0;
327 }
328 
move_complete(Completion & complete)329 void Monitor::move_complete(Completion& complete) {
330     if (complete == 1) {
331         complete.full(Converter<Rectangle>::str(rect));
332     } else if (complete >= 2 && complete <= 5) {
333         vector<Attribute_<int>*> pads =
334             { &pad_up, &pad_right, &pad_down, &pad_left };
335         complete.full("0");
336         size_t idx = complete.needleIndex();
337         pads[idx - 2]->complete(complete);
338     } else {
339         complete.none();
340     }
341 }
342 
renameCommand(Input input,Output output)343 int Monitor::renameCommand(Input input, Output output) {
344     string new_name;
345     if (!(input >> new_name)) {
346         return HERBST_NEED_MORE_ARGS;
347     }
348     string error = name.change(new_name);
349     if (!error.empty()) {
350         output << input.command() << ": " << error << "\n";
351         return HERBST_INVALID_ARGUMENT;
352     } else {
353         return 0;
354     }
355 }
356 
renameComplete(Completion & complete)357 void Monitor::renameComplete(Completion& complete)
358 {
359    if (complete == 1) {
360        // no completion, because the completion of the
361        // converter only suggests the current name anyway.
362    } else if (complete >= 2) {
363        complete.none();
364    }
365 }
366 
monitor_rect_command(int argc,char ** argv,Output output)367 int monitor_rect_command(int argc, char** argv, Output output) {
368     // usage: monitor_rect [[-p] INDEX]
369     char* monitor_str = nullptr;
370     Monitor* m = nullptr;
371     bool with_pad = false;
372 
373     // if monitor is supplied
374     if (argc > 1) {
375         monitor_str = argv[1];
376     }
377     // if -p is supplied
378     if (argc > 2) {
379         monitor_str = argv[2];
380         if (!strcmp("-p", argv[1])) {
381             with_pad = true;
382         } else {
383             output << argv[0] <<
384                 ": Invalid argument \"" << argv[1] << "\"\n";
385             return HERBST_INVALID_ARGUMENT;
386         }
387     }
388     // if an index is set
389     if (monitor_str) {
390         m = string_to_monitor(monitor_str);
391         if (!m) {
392             output << argv[0] <<
393                 ": Monitor \"" << monitor_str << "\" not found!\n";
394             return HERBST_INVALID_ARGUMENT;
395         }
396     } else {
397         m = get_current_monitor();
398     }
399     auto rect = m->rect();
400     if (with_pad) {
401         rect.x += m->pad_left;
402         rect.width -= m->pad_left + m->pad_right;
403         rect.y += m->pad_up;
404         rect.height -= m->pad_up + m->pad_down;
405     }
406     output << rect.x << " " << rect.y << " " << rect.width << " " << rect.height;
407     return 0;
408 }
409 
monitor_set_pad_command(int argc,char ** argv,Output output)410 int monitor_set_pad_command(int argc, char** argv, Output output) {
411     if (argc < 2) {
412         return HERBST_NEED_MORE_ARGS;
413     }
414     Monitor* monitor = string_to_monitor(argv[1]);
415     if (!monitor) {
416         output << argv[0] <<
417             ": Monitor \"" << argv[1] << "\" not found!\n";
418         return HERBST_INVALID_ARGUMENT;
419     }
420     if (argc > 2 && argv[2][0] != '\0') {
421         monitor->pad_up = atoi(argv[2]);
422     }
423     if (argc > 3 && argv[3][0] != '\0') {
424         monitor->pad_right = atoi(argv[3]);
425     }
426     if (argc > 4 && argv[4][0] != '\0') {
427         monitor->pad_down = atoi(argv[4]);
428     }
429     if (argc > 5 && argv[5][0] != '\0') {
430         monitor->pad_left = atoi(argv[5]);
431     }
432     monitor->applyLayout();
433     return 0;
434 }
435 
find_monitor_with_tag(HSTag * tag)436 Monitor* find_monitor_with_tag(HSTag* tag) {
437     for (auto m : *g_monitors) {
438         if (m->tag == tag) {
439             return m;
440         }
441     }
442     return nullptr;
443 }
444 
get_current_monitor()445 Monitor* get_current_monitor() {
446     return g_monitors->byIdx(g_monitors->cur_monitor);
447 }
448 
all_monitors_apply_layout()449 void all_monitors_apply_layout() {
450     for (auto m : *g_monitors) {
451         m->applyLayout();
452     }
453 }
454 
monitor_set_tag(Monitor * monitor,HSTag * tag)455 int monitor_set_tag(Monitor* monitor, HSTag* tag) {
456     Monitor* other = find_monitor_with_tag(tag);
457     if (monitor == other) {
458         // nothing to do
459         return 0;
460     }
461     if (monitor->lock_tag) {
462         // If the monitor tag is locked, do not change the tag
463         if (other) {
464             // but if the tag is already visible, change to the
465             // displaying monitor
466             monitor_focus_by_index(other->index());
467             return 0;
468         }
469         return 1;
470     }
471     if (other) {
472         if (g_settings->swap_monitors_to_get_tag()) {
473             if (other->lock_tag) {
474                 // the monitor we want to steal the tag from is
475                 // locked. focus that monitor instead
476                 monitor_focus_by_index(other->index());
477                 return 0;
478             }
479             monitor->tag_previous = monitor->tag;
480             other->tag_previous = other->tag;
481             // swap tags
482             other->tag = monitor->tag;
483             monitor->tag = tag;
484             /* TODO: find the best order of restacking and layouting */
485             other->restack();
486             monitor->restack();
487             other->applyLayout();
488             monitor->applyLayout();
489             monitor_update_focus_objects();
490             Ewmh::get().updateCurrentDesktop();
491             emit_tag_changed(other->tag, other->index());
492             emit_tag_changed(tag, g_monitors->cur_monitor);
493         } else {
494             // if we are not allowed to steal the tag, then just focus the
495             // other monitor
496             monitor_focus_by_index(other->index());
497         }
498         return 0;
499     }
500     HSTag* old_tag = monitor->tag;
501     // save old tag
502     monitor->tag_previous = old_tag;
503     // 1. show new tag
504     monitor->tag = tag;
505     // first reset focus and arrange windows
506     monitor->restack();
507     monitor->lock_frames = true;
508     monitor->applyLayout();
509     monitor->lock_frames = false;
510     // then show them (should reduce flicker)
511     tag->setVisible(true);
512     if (!monitor->tag->floating) {
513         // monitor->tag->frame->root_->updateVisibility();
514     }
515     // 2. hide old tag
516     old_tag->setVisible(false);
517     // focus window just has been shown
518     // discard enternotify-events
519     g_monitors->dropEnterNotifyEvents.emit();
520     monitor_update_focus_objects();
521     Ewmh::get().updateCurrentDesktop();
522     emit_tag_changed(tag, g_monitors->cur_monitor);
523     return 0;
524 }
525 
monitor_set_tag_command(int argc,char ** argv,Output output)526 int monitor_set_tag_command(int argc, char** argv, Output output) {
527     if (argc < 2) {
528         return HERBST_NEED_MORE_ARGS;
529     }
530     Monitor* monitor = get_current_monitor();
531     HSTag*  tag = find_tag(argv[1]);
532     if (monitor && tag) {
533         int ret = monitor_set_tag(monitor, tag);
534         if (ret != 0) {
535             output << argv[0] << ": Could not change tag";
536             if (monitor->lock_tag) {
537                 output << " (monitor " << monitor->index() << " is locked)";
538             }
539             output << "\n";
540         }
541         return ret;
542     } else {
543         output << argv[0] <<
544             ": Invalid tag \"" << argv[1] << "\"\n";
545         return HERBST_INVALID_ARGUMENT;
546     }
547 }
548 
monitor_set_tag_by_index_command(int argc,char ** argv,Output output)549 int monitor_set_tag_by_index_command(int argc, char** argv, Output output) {
550     if (argc < 2) {
551         return HERBST_NEED_MORE_ARGS;
552     }
553     bool skip_visible = false;
554     if (argc >= 3 && !strcmp(argv[2], "--skip-visible")) {
555         skip_visible = true;
556     }
557     HSTag* tag = global_tags->byIndexStr(argv[1], skip_visible);
558     if (!tag) {
559         output << argv[0] <<
560             ": Invalid index \"" << argv[1] << "\"\n";
561         return HERBST_INVALID_ARGUMENT;
562     }
563     int ret = monitor_set_tag(get_current_monitor(), &* tag);
564     if (ret != 0) {
565         output << argv[0] <<
566             ": Could not change tag (maybe monitor is locked?)\n";
567     }
568     return ret;
569 }
570 
monitor_set_previous_tag_command(Output output)571 int monitor_set_previous_tag_command(Output output) {
572     Monitor* monitor = get_current_monitor();
573     HSTag*  tag = monitor->tag_previous;
574     if (monitor && tag) {
575         int ret = monitor_set_tag(monitor, tag);
576         if (ret != 0) {
577             output << "use_previous: Could not change tag (maybe monitor is locked?)\n";
578         }
579         return ret;
580     } else {
581         output << "use_previous: Invalid monitor or tag\n";
582         return HERBST_INVALID_ARGUMENT;
583     }
584 }
585 
monitor_focus_command(int argc,char ** argv,Output output)586 int monitor_focus_command(int argc, char** argv, Output output) {
587     if (argc < 2) {
588         return HERBST_NEED_MORE_ARGS;
589     }
590     int new_selection = g_monitors->string_to_monitor_index(argv[1]);
591     if (new_selection < 0) {
592         output << argv[0] <<
593             ": Monitor \"" << argv[1] << "\" not found!\n";
594         return HERBST_INVALID_ARGUMENT;
595     }
596     // really change selection
597     monitor_focus_by_index((unsigned)new_selection);
598     return 0;
599 }
600 
monitor_cycle_command(int argc,char ** argv)601 int monitor_cycle_command(int argc, char** argv) {
602     int delta = 1;
603     auto count = g_monitors->size();
604     if (argc >= 2) {
605         delta = atoi(argv[1]);
606     }
607     int new_selection = g_monitors->cur_monitor + delta; // signed for delta calculations
608     // really change selection
609     monitor_focus_by_index((unsigned)MOD(new_selection, count));
610     return 0;
611 }
612 
monitor_focus_by_index(unsigned new_selection)613 void monitor_focus_by_index(unsigned new_selection) {
614     // clamp to last
615     new_selection = std::min(g_monitors->size() - 1, (size_t)new_selection);
616     Monitor* old = get_current_monitor();
617     Monitor* monitor = g_monitors->byIdx(new_selection);
618     if (old == monitor) {
619         // nothing to do
620         return;
621     }
622     // change selection globals
623     assert(monitor->tag);
624     assert(monitor->tag->frame->root_);
625     g_monitors->cur_monitor = new_selection;
626     // repaint g_monitors
627     old->applyLayout();
628     monitor->applyLayout();
629     int rx, ry;
630     {
631         // save old mouse position
632         Window win, child;
633         int wx, wy;
634         unsigned int mask;
635         if (True == XQueryPointer(g_display, g_root, &win, &child,
636             &rx, &ry, &wx, &wy, &mask)) {
637             old->mouse.x = rx - old->rect->x;
638             old->mouse.y = ry - old->rect->y;
639             old->mouse.x = CLAMP(old->mouse.x, 0, old->rect->width-1);
640             old->mouse.y = CLAMP(old->mouse.y, 0, old->rect->height-1);
641         }
642     }
643     // restore position of new monitor
644     // but only if mouse pointer is not already on new monitor
645     int new_x, new_y;
646     if ((monitor->rect->x <= rx) && (rx < monitor->rect->x + monitor->rect->width)
647         && (monitor->rect->y <= ry) && (ry < monitor->rect->y + monitor->rect->height)) {
648         // mouse already is on new monitor
649     } else {
650         // If the mouse is located in a gap indicated by
651         // mouse_recenter_gap at the outer border of the monitor,
652         // recenter the mouse.
653         if (std::min(monitor->mouse.x, abs(monitor->mouse.x - monitor->rect->width))
654                 < g_settings->mouse_recenter_gap()
655             || std::min(monitor->mouse.y, abs(monitor->mouse.y - monitor->rect->height))
656                 < g_settings->mouse_recenter_gap()) {
657             monitor->mouse.x = monitor->rect->width / 2;
658             monitor->mouse.y = monitor->rect->height / 2;
659         }
660         new_x = monitor->rect->x + monitor->mouse.x;
661         new_y = monitor->rect->y + monitor->mouse.y;
662         XWarpPointer(g_display, None, g_root, 0, 0, 0, 0, new_x, new_y);
663         // discard all mouse events caused by this pointer movage from the
664         // event queue, so the focus really stays in the last focused window on
665         // this monitor and doesn't jump to the window hovered by the mouse
666         g_monitors->dropEnterNotifyEvents.emit();
667     }
668     // update objects
669     monitor_update_focus_objects();
670     // emit hooks
671     Ewmh::get().updateCurrentDesktop();
672     emit_tag_changed(monitor->tag, new_selection);
673 }
674 
monitor_update_focus_objects()675 void monitor_update_focus_objects() {
676     g_monitors->focus = g_monitors->byIdx(g_monitors->cur_monitor);
677     global_tags->updateFocusObject(g_monitors->focus());
678 }
679 
relativeX(int x_root)680 int Monitor::relativeX(int x_root) {
681     return x_root - rect->x - pad_left;
682 }
683 
relativeY(int y_root)684 int Monitor::relativeY(int y_root) {
685     return y_root - rect->y - pad_up;
686 }
687 
restack()688 void Monitor::restack() {
689     Window fullscreenFocus = 0;
690     /* don't add a focused fullscreen client to the stack because
691      * we want a focused fullscreen window to be above the panels which are
692      * usually unmanaged. All the windows passed to the XRestackWindows
693      * will end up below all unmanaged windows, so don't add a focused
694      * fullscreen window to it. Instead raise the fullscreen window
695      * manually such that it is above the panel */
696     Client* client = tag->focusedClient();
697     if (client && client->fullscreen_) {
698         fullscreenFocus = client->decorationWindow();
699         XRaiseWindow(g_display, fullscreenFocus);
700     }
701     // collect all other windows in a vector and pass it to XRestackWindows
702     vector<Window> buf = { stacking_window };
703     auto addToVector = [&buf, fullscreenFocus](Window w) {
704         if (w != fullscreenFocus) {
705             buf.push_back(w);
706         }
707     };
708     tag->stack->extractWindows(false, addToVector);
709     XRestackWindows(g_display, buf.data(), buf.size());
710 }
711 
shift_to_monitor(int argc,char ** argv,Output output)712 int shift_to_monitor(int argc, char** argv, Output output) {
713     if (argc <= 1) {
714         return HERBST_NEED_MORE_ARGS;
715     }
716     char* monitor_str = argv[1];
717     Monitor* monitor = string_to_monitor(monitor_str);
718     if (!monitor) {
719         output << monitor_str << ": Invalid monitor\n";
720         return HERBST_INVALID_ARGUMENT;
721     }
722     global_tags->moveFocusedClient(monitor->tag);
723     return 0;
724 }
725 
all_monitors_replace_previous_tag(HSTag * old,HSTag * newmon)726 void all_monitors_replace_previous_tag(HSTag *old, HSTag *newmon) {
727     for (auto m : *g_monitors) {
728         if (m->tag_previous == old) {
729             m->tag_previous = newmon;
730         }
731     }
732 }
733 
getFloatingArea() const734 Rectangle Monitor::getFloatingArea() const {
735     auto m = this;
736     auto r = m->rect();
737     r.x += m->pad_left;
738     r.width -= m->pad_left + m->pad_right;
739     r.y += m->pad_up;
740     r.height -= m->pad_up + m->pad_down;
741     return r;
742 }
743 
744 //! Returns a textual description of the monitor
getDescription()745 string Monitor::getDescription() {
746     stringstream label;
747     label << "Monitor " << index();
748     if (!name().empty()) {
749         label << " (\"" << name() << "\")";
750     }
751     label << " with tag \"" << tag->name() << "\"";
752     return label.str();
753 }
754 
evaluateClientPlacement(Client * client,ClientPlacement placement) const755 void Monitor::evaluateClientPlacement(Client* client, ClientPlacement placement) const
756 {
757     switch (placement) {
758         case ClientPlacement::Center:
759             {
760                 Point2D new_tl =
761                     // the center of the monitor
762                     getFloatingArea().dimensions() / 2
763                     // minus half the dimensions of the client
764                     - client->float_size_.dimensions() / 2;
765                 client->float_size_.x = new_tl.x;
766                 client->float_size_.y = new_tl.y;
767             }
768             break;
769 
770         case ClientPlacement::Smart:
771             {
772                 Point2D area = getFloatingArea().dimensions();
773                 Point2D new_tl = Floating::smartPlacement(tag, client,
774                                              area, settings->snap_gap);
775                 client->float_size_.x = new_tl.x;
776                 client->float_size_.y = new_tl.y;
777             }
778             break;
779 
780         case ClientPlacement::Unchanged:
781             // do not do anything
782             break;
783     }
784 }
785 
atLeastMinWindowSize(Rectangle geom)786 string Monitor::atLeastMinWindowSize(Rectangle geom)
787 {
788     if (geom.width < WINDOW_MIN_WIDTH) {
789         return "Rectangle too small; it must be at least "
790                 + Converter<int>::str(WINDOW_MIN_WIDTH) + " wide.";
791     }
792     if (geom.height < WINDOW_MIN_HEIGHT) {
793         return "Rectangle too small; it must be at least "
794                 + Converter<int>::str(WINDOW_MIN_HEIGHT) + " high.";
795     }
796     return {};
797 }
798