1 #include "monitormanager.h"
2
3 #include <X11/Xlib.h>
4 #include <cassert>
5 #include <memory>
6
7 #include "command.h"
8 #include "completion.h"
9 #include "ewmh.h"
10 #include "floating.h"
11 #include "frametree.h"
12 #include "globals.h"
13 #include "ipc-protocol.h"
14 #include "monitor.h"
15 #include "monitordetection.h"
16 #include "panelmanager.h"
17 #include "rectangle.h"
18 #include "root.h"
19 #include "settings.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::make_pair;
29 using std::make_shared;
30 using std::pair;
31 using std::shared_ptr;
32 using std::string;
33 using std::to_string;
34 using std::vector;
35
36 MonitorManager* g_monitors;
37
MonitorManager()38 MonitorManager::MonitorManager()
39 : IndexingObject<Monitor>()
40 , focus(*this, "focus")
41 , by_name_(*this)
42 , panels_(nullptr)
43 , tags_(nullptr)
44 , settings_(nullptr)
45 {
46 cur_monitor = 0;
47 setDoc("Every monitor is a rectangular part of the screen "
48 "on which a tag is shown. These monitors may or may"
49 "not match the actual outputs.\n"
50 "This has an entry \'INDEX\' for each monitor with"
51 "index \'INDEX\'.");
52 focus.setDoc("the focused monitor.");
53 // TODO: add this as soon as by_name_ is of type Child_<ByName>
54 // by_name_.setDoc("contains an entry for each monitor with "
55 // "a name.");
56 }
57
~MonitorManager()58 MonitorManager::~MonitorManager() {
59 clearChildren();
60 }
61
injectDependencies(Settings * s,TagManager * t,PanelManager * p)62 void MonitorManager::injectDependencies(Settings* s, TagManager* t, PanelManager* p) {
63 settings_ = s;
64 tags_ = t;
65 panels_ = p;
66 }
67
clearChildren()68 void MonitorManager::clearChildren() {
69 IndexingObject<Monitor>::clearChildren();
70 focus = {};
71 tags_ = {};
72 }
73
ensure_monitors_are_available()74 void MonitorManager::ensure_monitors_are_available() {
75 if (size() > 0) {
76 // nothing to do
77 return;
78 }
79 // add monitor if necessary
80 Rectangle rect = { 0, 0,
81 DisplayWidth(g_display, DefaultScreen(g_display)),
82 DisplayHeight(g_display, DefaultScreen(g_display))};
83 HSTag* tag = tags_->ensure_tags_are_available();
84 // add monitor with first tag
85 Monitor* m = addMonitor(rect, tag);
86 m->tag->setVisible(true);
87 cur_monitor = 0;
88
89 autoUpdatePads();
90 monitor_update_focus_objects();
91 }
92
indexInDirection(Monitor * relativeTo,Direction dir)93 int MonitorManager::indexInDirection(Monitor* relativeTo, Direction dir) {
94 RectangleIdxVec rects;
95 if (!relativeTo) {
96 return -1;
97 }
98 for (Monitor* mon : *this) {
99 rects.push_back(make_pair(mon->index(), mon->rect));
100 }
101 int result = Floating::find_rectangle_in_direction(rects, int(relativeTo->index), dir);
102 return result;
103 }
104
string_to_monitor_index(string str)105 int MonitorManager::string_to_monitor_index(string str) {
106 if (str[0] == '\0') {
107 return cur_monitor;
108 } else if (str[0] == '-' || str[0] == '+') {
109 if (isdigit(str[1])) {
110 // relative monitor index
111 int idx = cur_monitor + atoi(str.c_str());
112 return MOD(idx, size());
113 } else if (str[0] == '-') {
114 try {
115 auto dir = Converter<Direction>::parse(str.substr(1));
116 return indexInDirection(focus(), dir);
117 } catch (...) {
118 return -1;
119 }
120 } else {
121 return -1;
122 }
123 } else if (isdigit(str[0])) {
124 // absolute monitor index
125 int idx = atoi(str.c_str());
126 if (idx < 0 || idx >= (int)size()) {
127 return -1;
128 }
129 return idx;
130 } else {
131 // monitor string
132 for (unsigned i = 0; i < size(); i++) {
133 if (byIdx(i)->name == str) {
134 return (int)i;
135 }
136 }
137 return -1;
138 }
139 }
140
completeMonitorName(Completion & complete)141 void MonitorManager::completeMonitorName(Completion& complete) {
142 complete.full(""); // the focused monitor
143 // complete against relative indices
144 complete.full("-1");
145 complete.full("+0");
146 complete.full("+1");
147 for (auto m : *this) {
148 // complete against the absolute index
149 complete.full(to_string(m->index()));
150 // complete against the name
151 if (m->name != "") {
152 complete.full(m->name);
153 }
154 }
155 }
156
157
list_monitors(Output output)158 int MonitorManager::list_monitors(Output output) {
159 string monitor_name = "";
160 int i = 0;
161 for (auto monitor : *this) {
162 if (monitor->name != "" ) {
163 monitor_name = ", named \"" + monitor->name() + "\"";
164 } else {
165 monitor_name = "";
166 }
167 output << i << ": " << monitor->rect
168 << " with tag \""
169 << (monitor->tag ? monitor->tag->name->c_str() : "???")
170 << "\""
171 << monitor_name
172 << ((cur_monitor == i) ? " [FOCUS]" : "")
173 << (monitor->lock_tag ? " [LOCKED]" : "")
174 << "\n";
175 i++;
176 }
177 return 0;
178 }
179
byString(string str)180 Monitor* MonitorManager::byString(string str) {
181 int idx = string_to_monitor_index(str);
182 return ((idx >= 0) && idx < static_cast<int>(size())) ? byIdx(idx) : nullptr;
183 }
184
byFirstArg(MonitorCommand cmd)185 function<int(Input, Output)> MonitorManager::byFirstArg(MonitorCommand cmd)
186 {
187 return [this,cmd](Input input, Output output) -> int {
188 Monitor *monitor;
189 string monitor_name;
190 if (!(input >> monitor_name)) {
191 monitor = get_current_monitor();
192 } else {
193 monitor = byString(monitor_name);
194 if (!monitor) {
195 output << input.command() <<
196 ": Monitor \"" << monitor_name << "\" not found!\n";
197 return HERBST_INVALID_ARGUMENT;
198 }
199 }
200 return cmd(*monitor, Input(input.command(), input.toVector()), output);
201 };
202 }
203
byFirstArg(MonitorCommand moncmd,MonitorCompletion moncomplete)204 CommandBinding MonitorManager::byFirstArg(MonitorCommand moncmd, MonitorCompletion moncomplete)
205 {
206 auto cmdBound = byFirstArg(moncmd);
207 auto completeBound = [this,moncomplete](Completion& complete) {
208 if (complete == 0) {
209 this->completeMonitorName(complete);
210 } else {
211 Monitor* monitor = byString(complete[0]);
212 if (!monitor) {
213 monitor = focus();
214 }
215 moncomplete(*monitor, complete);
216 }
217 };
218 return {cmdBound, completeBound};
219 }
220
tagCommand(TagCommand cmd,TagCompletion completer)221 CommandBinding MonitorManager::tagCommand(TagCommand cmd, TagCompletion completer)
222 {
223 auto cmdBound = [this,cmd](Input input, Output output) {
224 return cmd(*(this->focus()->tag), input, output);
225 };
226 auto completeBound = [this,completer](Completion& complete) {
227 completer(*(this->focus()->tag), complete);
228 };
229 return {cmdBound, completeBound};
230 }
231
tagCommand(function<int (HSTag &)> cmd)232 CommandBinding MonitorManager::tagCommand(function<int (HSTag&)> cmd)
233 {
234 return CommandBinding([this,cmd]() {
235 return cmd(*(this->focus()->tag));
236 });
237 }
238
239
byTag(HSTag * tag)240 Monitor* MonitorManager::byTag(HSTag* tag) {
241 for (Monitor* m : *this) {
242 if (m->tag == tag) {
243 return m;
244 }
245 }
246 return nullptr;
247 }
248
249 /**
250 * @brief Find the monitor having the given coordinate
251 * @param coordinate on a monitor
252 * @return The monitor with the coordinate or nullptr if the coordinate is outside of any monitor
253 */
byCoordinate(Point2D p)254 Monitor* MonitorManager::byCoordinate(Point2D p)
255 {
256 for (Monitor* m : *this) {
257 if (m->rect->x + m->pad_left <= p.x
258 && m->rect->x + m->rect->width - m->pad_right > p.x
259 && m->rect->y + m->pad_up <= p.y
260 && m->rect->y + m->rect->height - m->pad_down > p.y) {
261 return &* m;
262 }
263 }
264 return nullptr;
265 }
266
byFrame(shared_ptr<Frame> frame)267 Monitor* MonitorManager::byFrame(shared_ptr<Frame> frame)
268 {
269 for (Monitor* m : *this) {
270 if (m->tag->frame->contains(frame)) {
271 return m;
272 }
273 }
274 return nullptr;
275 }
276
relayoutTag(HSTag * tag)277 void MonitorManager::relayoutTag(HSTag* tag)
278 {
279 Monitor* m = byTag(tag);
280 if (m) {
281 m->applyLayout();
282 }
283 }
284
relayoutAll()285 void MonitorManager::relayoutAll()
286 {
287 for (Monitor* m : *this) {
288 m->applyLayout();
289 }
290 }
291
removeMonitor(Input input,Output output)292 int MonitorManager::removeMonitor(Input input, Output output)
293 {
294 string monitorIdxString;
295 if (!(input >> monitorIdxString)) {
296 return HERBST_NEED_MORE_ARGS;
297 }
298 auto monitor = byString(monitorIdxString);
299
300 if (monitor == nullptr) {
301 output << input.command() << ": Monitor \"" << monitorIdxString << "\" not found!\n";
302 return HERBST_INVALID_ARGUMENT;
303 }
304
305 if (size() <= 1) {
306 output << input.command() << ": Can't remove the last monitor\n";
307 return HERBST_FORBIDDEN;
308 }
309
310 removeMonitor(monitor);
311
312 return HERBST_EXIT_SUCCESS;
313 }
314
removeMonitor(Monitor * monitor)315 void MonitorManager::removeMonitor(Monitor* monitor)
316 {
317 auto monitorIdx = index_of(monitor);
318
319 if (cur_monitor > index_of(monitor)) {
320 // Take into account that the current monitor will have a new
321 // index after removal:
322 cur_monitor--;
323 }
324
325 // Hide all clients visible in monitor
326 assert(monitor->tag != nullptr);
327 assert(monitor->tag->frame->root_ != nullptr);
328 monitor->tag->setVisible(false);
329
330 monitorStack_.remove(monitor);
331 g_monitors->removeIndexed(monitorIdx);
332
333 if (cur_monitor >= static_cast<int>(g_monitors->size())) {
334 cur_monitor--;
335 // if selection has changed, then relayout focused monitor
336 get_current_monitor()->applyLayout();
337 monitor_update_focus_objects();
338 // also announce the new selection
339 Ewmh::get().updateCurrentDesktop();
340 emit_tag_changed(get_current_monitor()->tag, cur_monitor);
341 }
342 monitor_update_focus_objects();
343 }
344
addMonitor(Input input,Output output)345 int MonitorManager::addMonitor(Input input, Output output)
346 {
347 // usage: add_monitor RECTANGLE [TAG [NAME]]
348 string rectString, tagName, monitorName;
349 input >> rectString;
350 if (!input) {
351 return HERBST_NEED_MORE_ARGS;
352 }
353 HSTag* tag = nullptr;
354 if (input >> tagName) {
355 tag = find_tag(tagName.c_str());
356 if (!tag) {
357 output << input.command() << ": Tag \"" << tagName << "\" does not exist\n";
358 return HERBST_INVALID_ARGUMENT;
359 }
360 if (find_monitor_with_tag(tag)) {
361 output << input.command() <<
362 ": Tag \"" << tagName << "\" is already being viewed on a monitor\n";
363 return HERBST_TAG_IN_USE;
364 }
365 } else { // if no tag is supplied
366 tag = tags_->unusedTag();
367 if (!tag) {
368 output << input.command() << ": There are not enough free tags\n";
369 return HERBST_TAG_IN_USE;
370 }
371 }
372 // TODO: error message on invalid rectString
373 auto rect = Rectangle::fromStr(rectString);
374 if (input >> monitorName) {
375 string error;
376 if (monitorName.empty()) {
377 error = "An empty monitor name is not permitted";
378 } else {
379 error = isValidMonitorName(monitorName);
380 }
381 if (!error.empty()) {
382 output << input.command() << ": " << error << "\n";
383 return HERBST_INVALID_ARGUMENT;
384 }
385 }
386 auto monitor = addMonitor(rect, tag);
387 if (!monitorName.empty()) {
388 monitor->name = monitorName;
389 }
390
391 autoUpdatePads();
392 monitor->applyLayout();
393 tag->setVisible(true);
394 emit_tag_changed(tag, g_monitors->size() - 1);
395 dropEnterNotifyEvents.emit();
396
397 return HERBST_EXIT_SUCCESS;
398 }
399
isValidMonitorName(string name)400 string MonitorManager::isValidMonitorName(string name) {
401 if (isdigit(name[0])) {
402 return "Invalid name \"" + name + "\": The monitor name may not start with a number";
403 }
404 if (name.empty()) {
405 // clearing a name is always OK.
406 return "";
407 }
408 if (find_monitor_by_name(name.c_str())) {
409 return "A monitor with the name \"" + name + "\" already exists";
410 }
411 return "";
412 }
413
414 //! automatically update the pad settings for all monitors
autoUpdatePads()415 void MonitorManager::autoUpdatePads()
416 {
417 for (Monitor* m : *this) {
418 PanelManager::ReservedSpace rs = panels_->computeReservedSpace(m->rect);
419 // all the sides in the order as it matters for pad_automatically_set
420 vector<pair<Attribute_<int>&, int>> sides = {
421 { m->pad_up, rs.top_ },
422 { m->pad_right, rs.right_ },
423 { m->pad_down, rs.bottom_ },
424 { m->pad_left, rs.left_ },
425 };
426 size_t idx = 0;
427 for (auto& it : sides ) {
428 if (it.first() != it.second) {
429 if (it.second != 0) {
430 // if some panel was added or resized
431 it.first.operator=(it.second);
432 m->pad_automatically_set[idx] = true;
433 } else {
434 // if there is no panel, then only clear the pad
435 // if the pad was added by us before
436 if (m->pad_automatically_set[idx]) {
437 it.first.operator=(0);
438 m->pad_automatically_set[idx] = false;
439 }
440 }
441 }
442 idx++;
443 }
444 }
445 }
446
addMonitor(Rectangle rect,HSTag * tag)447 Monitor* MonitorManager::addMonitor(Rectangle rect, HSTag* tag) {
448 Monitor* m = new Monitor(settings_, this, rect, tag);
449 addIndexed(m);
450 monitorStack_.insert(m);
451 m->monitorMoved.connect([this]() {
452 this->autoUpdatePads();
453 });
454 return m;
455 }
456
457
lock()458 void MonitorManager::lock() {
459 settings_->monitors_locked = settings_->monitors_locked() + 1;
460 lock_number_changed();
461 }
462
unlock()463 void MonitorManager::unlock() {
464 settings_->monitors_locked = std::max(0, settings_->monitors_locked() - 1);
465 lock_number_changed();
466 }
467
lock_number_changed()468 string MonitorManager::lock_number_changed() {
469 if (settings_->monitors_locked() < 0) {
470 return "must be non-negative";
471 }
472 if (!settings_->monitors_locked()) {
473 // if not locked anymore, then repaint all the dirty monitors
474 for (auto m : *this) {
475 if (m->dirty) {
476 m->applyLayout();
477 }
478 }
479 }
480 return {};
481 }
482
483 //! return the stack of windows by successive calls to the given yield
484 //function. The stack is returned from top to bottom, i.e. the topmost element
485 //is the first element yielded
extractWindowStack(bool real_clients,function<void (Window)> yield)486 void MonitorManager::extractWindowStack(bool real_clients, function<void(Window)> yield)
487 {
488 for (Monitor* monitor : monitorStack_) {
489 if (!real_clients) {
490 yield(monitor->stacking_window);
491 }
492 monitor->tag->stack->extractWindows(real_clients, yield);
493 }
494 }
495
496 //! restack the entire stack including all monitors
restack()497 void MonitorManager::restack() {
498 vector<Window> buf;
499 extractWindowStack(false, [&buf](Window w) { buf.push_back(w); });
500 XRestackWindows(g_display, buf.data(), buf.size());
501 Ewmh::get().updateClientListStacking();
502 }
503
504 class StringTree : public TreeInterface {
505 public:
StringTree(string label,vector<shared_ptr<StringTree>> children={})506 StringTree(string label, vector<shared_ptr<StringTree>> children = {})
507 : children_(children)
508 , label_(label)
509 {};
510
childCount()511 size_t childCount() override {
512 return children_.size();
513 };
514
nthChild(size_t idx)515 shared_ptr<TreeInterface> nthChild(size_t idx) override {
516 return children_.at(idx);
517 };
518
appendCaption(Output output)519 void appendCaption(Output output) override {
520 if (!label_.empty()) {
521 output << " " << label_;
522 }
523 };
524
525 private:
526 vector<shared_ptr<StringTree>> children_;
527 string label_;
528 };
529
stackCommand(Output output)530 int MonitorManager::stackCommand(Output output) {
531 vector<shared_ptr<StringTree>> monitors;
532 for (Monitor* monitor : monitorStack_) {
533 vector<shared_ptr<StringTree>> layers;
534 for (size_t layerIdx = 0; layerIdx < LAYER_COUNT; layerIdx++) {
535 auto layer = monitor->tag->stack->layers_[layerIdx];
536
537 vector<shared_ptr<StringTree>> slices;
538 for (auto& slice : layer) {
539 slices.push_back(make_shared<StringTree>(slice->getLabel()));
540 }
541
542 auto layerLabel = g_layer_names[layerIdx];
543 layers.push_back(make_shared<StringTree>(layerLabel, slices));
544 }
545
546 monitors.push_back(make_shared<StringTree>(monitor->getDescription(), layers));
547 }
548
549 auto stackRoot = make_shared<StringTree>("", monitors);
550 tree_print_to(stackRoot, output);
551 return 0;
552 }
553
554 /** Add, Move, Remove monitors such that the monitor list matches the given
555 * vector of Rectangles
556 */
setMonitors(const RectangleVec & templates)557 int MonitorManager::setMonitors(const RectangleVec& templates) {
558 if (templates.empty()) {
559 return HERBST_INVALID_ARGUMENT;
560 }
561 HSTag* tag = nullptr;
562 unsigned i;
563 for (i = 0; i < std::min(templates.size(), size()); i++) {
564 auto m = byIdx(i);
565 if (!m) {
566 continue;
567 }
568 m->rect = templates[i];
569 }
570 // add additional monitors
571 for (; i < templates.size(); i++) {
572 tag = tags_->unusedTag();
573 if (!tag) {
574 return HERBST_TAG_IN_USE;
575 }
576 addMonitor(templates[i], tag);
577 tag->setVisible(true);
578 }
579 // remove monitors if there are too much
580 while (i < size()) {
581 removeMonitor(byIdx(i));
582 }
583 monitor_update_focus_objects();
584 autoUpdatePads();
585 all_monitors_apply_layout();
586 return 0;
587 }
588
setMonitorsCommand(Input input,Output output)589 int MonitorManager::setMonitorsCommand(Input input, Output output) {
590 RectangleVec templates;
591 string rectangleString;
592 while (input >> rectangleString) {
593 Rectangle rect = Rectangle::fromStr(rectangleString);
594 if (rect.width == 0 || rect.height == 0)
595 {
596 output << input.command()
597 << ": Rectangle invalid or too small: "
598 << rectangleString << endl;
599 return HERBST_INVALID_ARGUMENT;
600 }
601 templates.push_back(rect);
602 }
603 if (templates.empty()) {
604 return HERBST_NEED_MORE_ARGS;
605 }
606 int status = setMonitors(templates);
607
608 if (status == HERBST_TAG_IN_USE) {
609 output << input.command() << ": There are not enough free tags\n";
610 } else if (status == HERBST_INVALID_ARGUMENT) {
611 return HERBST_NEED_MORE_ARGS;
612 }
613 return status;
614 }
615
setMonitorsCompletion(Completion &)616 void MonitorManager::setMonitorsCompletion(Completion&) {
617 // every parameter can be a rectangle specification.
618 // we don't have completion for rectangles
619 }
620
detectMonitorsCompletion(Completion & complete)621 void MonitorManager::detectMonitorsCompletion(Completion& complete)
622 {
623 complete.full({"-l", "--list", "--list-all", "--no-disjoin"});
624 }
625
detectMonitorsCommand(Input input,Output output)626 int MonitorManager::detectMonitorsCommand(Input input, Output output)
627 {
628 bool list_all = false;
629 bool list_only = false;
630 bool disjoin = true;
631 string arg;
632 while (input >> arg) {
633 if (arg == "-l" || arg == "--list") {
634 list_only = true;
635 } else if (arg == "--list-all") {
636 list_all = true;
637 } else if (arg == "--no-disjoin") {
638 disjoin = false;
639 } else {
640 output << input.command() << ": unknown flag \"" << arg << "\"\n";
641 return HERBST_INVALID_ARGUMENT;
642 }
643 }
644
645 auto root = Root::get();
646 if (list_all) {
647 for (const auto& detector : MonitorDetection::detectors()) {
648 output << detector.name_ << ":";
649 if (detector.detect_) {
650 for (auto m : detector.detect_(root->X)) {
651 output << " " << m;
652 }
653 } else {
654 output << " disabled";
655 }
656 output << endl;
657 }
658 return 0;
659 }
660
661 RectangleVec monitor_rects = {};
662 for (const auto& detector : MonitorDetection::detectors()) {
663 if (detector.detect_) {
664 auto rects = detector.detect_(root->X);
665 // remove duplicates
666 std::sort(rects.begin(), rects.end());
667 rects.erase(std::unique(rects.begin(), rects.end()), rects.end());
668 // check if this has more outputs than we know already
669 if (rects.size() > monitor_rects.size()) {
670 monitor_rects = rects;
671 }
672 }
673 }
674 if (monitor_rects.empty()) {
675 monitor_rects = { root->X.windowSize(root->X.root()) };
676 }
677 if (list_only) {
678 for (auto m : monitor_rects) {
679 output << m << "\n";
680 }
681 } else {
682 // possibly disjoin them
683 if (disjoin) {
684 monitor_rects = disjoin_rects(monitor_rects);
685 }
686 // apply it
687 int ret = g_monitors->setMonitors(monitor_rects);
688 if (ret == HERBST_TAG_IN_USE) {
689 output << input.command() << ": There are not enough free tags\n";
690 }
691 return ret;
692 }
693 return 0;
694 }
695
696 /**
697 * @brief Transform a rectangle on the screen into a rectangle relative to one of the monitor.
698 * Currently, we pick the monitor that has the biggest intersection with the given rectangle.
699 *
700 * @param globalGeometry A rectangle whose coordinates are interpreted relative to (0,0) on the screen
701 * @return The given rectangle with coordinates relative to a monitor
702 */
interpretGlobalGeometry(Rectangle globalGeometry)703 Rectangle MonitorManager::interpretGlobalGeometry(Rectangle globalGeometry)
704 {
705 int bestArea = 0;
706 Monitor* best = nullptr;
707 for (Monitor* m : *this) {
708 auto intersection = m->rect->intersectionWith(globalGeometry);
709 if (!intersection) {
710 continue;
711 }
712 auto area = intersection.width * intersection.height;
713 if (area > bestArea) {
714 bestArea = area;
715 best = m;
716 }
717 }
718 if (best) {
719 globalGeometry.x -= best->rect->x + *best->pad_left;
720 globalGeometry.y -= best->rect->y + *best->pad_up;
721 }
722 return globalGeometry;
723 }
724
raiseMonitorCommand(Input input,Output output)725 int MonitorManager::raiseMonitorCommand(Input input, Output output) {
726 string monitorName = "";
727 input >> monitorName;
728 Monitor* monitor = string_to_monitor(monitorName.c_str());
729 if (!monitor) {
730 output << input.command() << ": Monitor \"" << monitorName << "\" not found!\n";
731 return HERBST_INVALID_ARGUMENT;
732 }
733 monitorStack_.raise(monitor);
734 restack();
735 return 0;
736 }
737
raiseMonitorCompletion(Completion & complete)738 void MonitorManager::raiseMonitorCompletion(Completion& complete) {
739 if (complete == 0) {
740 completeMonitorName(complete);
741 } else {
742 complete.none();
743 }
744 }
745
746