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