1 #include "tag.h"
2 
3 #include <sstream>
4 #include <type_traits>
5 
6 #include "argparse.h"
7 #include "client.h"
8 #include "completion.h"
9 #include "ewmh.h"
10 #include "floating.h"
11 #include "frametree.h"
12 #include "hlwmcommon.h"
13 #include "hook.h"
14 #include "ipc-protocol.h"
15 #include "layout.h"
16 #include "monitormanager.h"
17 #include "root.h"
18 #include "settings.h"
19 #include "stack.h"
20 #include "tagmanager.h"
21 
22 using std::endl;
23 using std::function;
24 using std::make_shared;
25 using std::shared_ptr;
26 using std::string;
27 using std::stringstream;
28 
29 static bool    g_tag_flags_dirty = true;
30 
HSTag(string name_,TagManager * tags,Settings * settings)31 HSTag::HSTag(string name_, TagManager* tags, Settings* settings)
32     : frame(*this, "tiling")
33     , index(this, "index", 0, &HSTag::isValidTagIndex)
34     , visible(this, "visible", false)
35     , floating(this, "floating", false, [](bool){return "";})
__anon63a881580202(bool)36     , floating_focused(this, "floating_focused", false, [](bool){return "";})
37     , name(this, "name", name_,
__anon63a881580302(string newName) 38         [tags](string newName) { return tags->isValidTagName(newName); })
39     , frame_count(this, "frame_count", &HSTag::computeFrameCount)
40     , client_count(this, "client_count", &HSTag::computeClientCount)
41     , urgent_count(this, "urgent_count", &HSTag::countUrgentClients)
42     , curframe_windex(this, "curframe_windex",
__anon63a881580402() 43         [this] () { return frame->focusedFrame()->getSelection(); } )
44     , curframe_wcount(this, "curframe_wcount",
__anon63a881580502() 45         [this] () { return frame->focusedFrame()->clientCount(); } )
46     , focused_client(*this, "focused_client", &HSTag::focusedClient)
47     , flags(0)
48     , floating_clients_focus_(0)
49     , oldName_(name_)
50     , tags_(tags)
51     , settings_(settings)
52 {
53     stack = make_shared<Stack>();
54     frame.init(this, settings);
__anon63a881580602(unsigned long newIdx) 55     index.changed().connect([this, tags](unsigned long newIdx) {
56         tags->indexChangeRequested(this, newIdx);
57         foreachClient([this](Client* client) {
58             Ewmh::get().windowUpdateTag(client->window_, this);
59         });
60     });
61     floating.changed().connect(this, &HSTag::onGlobalFloatingChange);
62     // FIXME: actually this connection of the signals like this
63     // must work:
64     //   floating_focused.changedByUser().connect(needsRelayout_);
65     // however, we need to call this:
__anon63a881580802() 66     floating_focused.changedByUser().connect([this] () {
67         this->needsRelayout_.emit();
68     });
__anon63a881580902(bool v) 69     floating_focused.setValidator([this](bool v) {
70         return this->floatingLayerCanBeFocused(v);
71     });
72 
73     name.setDoc("name of the tag (must be non-empty)");
74     index.setDoc("index of this tag (the first index is 0)");
75     visible.setDoc("if this tag is shown on some monitor");
76     floating.setDoc("if the entire tag is set to floating mode");
77     floating_focused.setDoc("if the floating layer is focused"
78                             " (otherwise the tiling layer is)");
79     frame_count.setDoc("the number of frames on this tag");
80     client_count.setDoc("the number of clients on this tag");
81     urgent_count.setDoc("the number of urgent clients on this tag");
82     curframe_windex.setDoc("index of the focused client in the selected frame");
83     curframe_wcount.setDoc("number of clients in the selected frame");
84 }
85 
~HSTag()86 HSTag::~HSTag() {
87     frame.reset();
88 }
89 
setIndexAttribute(unsigned long new_index)90 void HSTag::setIndexAttribute(unsigned long new_index) {
91     index = new_index;
92 }
93 
94 //! give the focus within this tag to the specified client
focusClient(Client * client)95 bool HSTag::focusClient(Client* client)
96 {
97     if (frame->focusClient(client)) {
98         floating_focused = false;
99         return true;
100     } else {
101         auto& v = floating_clients_;
102         auto it = std::find(v.begin(), v.end(), client);
103         if (it == v.end()) {
104             return false;
105         }
106         floating_focused = true;
107         floating_clients_focus_ = it - v.begin();
108         return true;
109     }
110 }
111 
112 /**
113  * @brief To be called whenever the floating or minimization
114  * state of a client changes.
115  * @param client
116  */
applyClientState(Client * client)117 void HSTag::applyClientState(Client* client)
118 {
119     if (!client) {
120         return;
121     }
122     bool focused = client == focusedClient();
123     if (focused) {
124         // make it that client stays focused
125         floating_focused = client->floating_();
126     }
127     // only floated clients can be minimized
128     if (client->floating_() || client->minimized_()) {
129         // client wants to be floated
130         if (frame->root_->removeClient(client)) {
131             floating_clients_.push_back(client);
132             if (focused && !client->minimized_()) {
133                 floating_clients_focus_ = floating_clients_.size() - 1;
134             }
135             stack->sliceRemoveLayer(client->slice, LAYER_NORMAL);
136             stack->sliceAddLayer(client->slice, LAYER_FLOATING);
137         }
138     } else {
139         // client wants to be tiled again
140         auto it = std::find(floating_clients_.begin(), floating_clients_.end(), client);
141         if (it != floating_clients_.end()) {
142             floating_clients_.erase(it);
143             frame->focusedFrame()->insertClient(client, true);
144             if (!floating()) {
145                 stack->sliceRemoveLayer(client->slice, LAYER_FLOATING);
146             }
147             stack->sliceAddLayer(client->slice, LAYER_NORMAL);
148         }
149     }
150     if (!hasVisibleFloatingClients()) {
151         floating_focused = false;
152     }
153     bool client_becomes_visible = !client->minimized_() && this->visible();
154     if (client_becomes_visible) {
155         needsRelayout_.emit();
156         client->set_visible(client_becomes_visible);
157     } else {
158         client->set_visible(client_becomes_visible);
159         needsRelayout_.emit();
160     }
161 }
162 
setVisible(bool newVisible)163 void HSTag::setVisible(bool newVisible)
164 {
165     visible = newVisible;
166     // always pass the visibility state correctly
167     // to the clients, even though the state of
168     // `visible` may not have changed.
169     frame->root_->setVisibleRecursive(visible);
170     for (Client* c : floating_clients_) {
171         if (c->minimized_()) {
172             c->set_visible(false);
173         } else {
174             c->set_visible(visible);
175         }
176     }
177 }
178 
removeClient(Client * client)179 bool HSTag::removeClient(Client* client) {
180     if (frame->root_->removeClient(client)) {
181         return true;
182     }
183     auto it = std::find(floating_clients_.begin(), floating_clients_.end(), client);
184     if (it == floating_clients_.end()) {
185         return false;
186     }
187     floating_clients_.erase(it);
188     fixFocusIndex();
189     if (!hasVisibleFloatingClients()) {
190         // if it was the last floating client
191         // focus back the tiling
192         floating_focused = false;
193     }
194     return true;
195 }
196 
197 /**
198  * @brief returns whether there are floating clients that
199  * are visible. Equivalently, whether there are floating and non-minimized clients
200  * @return
201  */
hasVisibleFloatingClients() const202 bool HSTag::hasVisibleFloatingClients() const
203 {
204     for (Client* c : floating_clients_) {
205         if (!c->minimized_()) {
206             return true;
207         }
208     }
209     return false;
210 }
211 
foreachClient(function<void (Client *)> loopBody)212 void HSTag::foreachClient(function<void (Client *)> loopBody)
213 {
214 
215     frame->root_->foreachClient(loopBody);
216     for (Client* c: floating_clients_) {
217         loopBody(c);
218     }
219 }
220 
focusFrame(shared_ptr<FrameLeaf> frameToFocus)221 void HSTag::focusFrame(shared_ptr<FrameLeaf> frameToFocus)
222 {
223     floating_focused = false;
224     FrameTree::focusFrame(frameToFocus);
225     needsRelayout_.emit();
226 }
227 
focusedClient()228 Client *HSTag::focusedClient()
229 {
230     if (floating_focused()) {
231         fixFocusIndex();
232         if (floating_clients_focus_ < floating_clients_.size()) {
233             return floating_clients_[floating_clients_focus_];
234         } else {
235             return nullptr;
236         }
237     } else {
238         return frame->root_->focusedClient();
239     }
240 }
241 
insertClient(Client * client,string frameIndex,bool focus)242 void HSTag::insertClient(Client* client, string frameIndex, bool focus)
243 {
244     if (client->floating_() || client->minimized_()) {
245         floating_clients_.push_back(client);
246         if (focus && !client->minimized_()) {
247             floating_clients_focus_ = floating_clients_.size() - 1;
248             floating_focused = true;
249         }
250     } else {
251         auto target = FrameTree::focusedFrame(frame->lookup(frameIndex));
252         if (focus) {
253             // ensure that the target frame is focused in the entire tree
254             frame->focusFrame(target);
255             floating_focused = false;
256         }
257         target->insertClient(client, focus);
258     }
259 }
260 
insertClientSlice(Client * client)261 void HSTag::insertClientSlice(Client* client)
262 {
263     stack->insertSlice(client->slice);
264     if (floating()) {
265         stack->sliceAddLayer(client->slice, LAYER_FLOATING);
266     } else if (!client->floating_() && !client->minimized_()) {
267         stack->sliceRemoveLayer(client->slice, LAYER_FLOATING);
268     }
269 }
270 
removeClientSlice(Client * client)271 void HSTag::removeClientSlice(Client* client)
272 {
273     if (floating() && !client->floating_()) {
274         stack->sliceRemoveLayer(client->slice, LAYER_FLOATING);
275     }
276     stack->removeSlice(client->slice);
277 }
278 
279 //! directional focus command
focusInDirCommand(Input input,Output output)280 int HSTag::focusInDirCommand(Input input, Output output)
281 {
282     bool external_only = settings_->default_direction_external_only();
283     Direction direction = Direction::Left; // some default to satisfy the linter
284     ArgParse ap;
285     ap.flags({
286         {"-i", [&external_only] () { external_only = false; }},
287         {"-e", [&external_only] () { external_only = true; }},
288     });
289     ap.mandatory(direction);
290     if (ap.parsingAllFails(input, output)) {
291         return ap.exitCode();
292     }
293 
294     auto focusedFrame = frame->focusedFrame();
295     bool neighbour_found = true;
296     if (floating || floating_focused) {
297         neighbour_found = Floating::focusDirection(direction);
298     } else {
299         neighbour_found = frame->focusInDirection(direction, external_only);
300         if (neighbour_found) {
301             needsRelayout_.emit();
302         }
303     }
304     if (!neighbour_found && settings_->focus_crosses_monitor_boundaries()) {
305         // find monitor in the specified direction
306         int idx = g_monitors->indexInDirection(get_current_monitor(), direction);
307         if (idx >= 0) {
308             monitor_focus_by_index(idx);
309             return 0;
310         }
311     }
312     if (!neighbour_found) {
313         output << input.command() << ": No neighbour found\n";
314         return HERBST_FORBIDDEN;
315     }
316     return 0;
317 }
318 
focusInDirCompletion(Completion & complete)319 void HSTag::focusInDirCompletion(Completion &complete)
320 {
321     if (complete == 0) {
322         complete.full({"-i", "-e"});
323         Converter<Direction>::complete(complete, nullptr);
324     } else if (complete == 1
325                && (complete[0] == "-i" || complete[0] == "-e"))
326     {
327         Converter<Direction>::complete(complete, nullptr);
328     } else {
329         complete.none();
330     }
331 }
332 
shiftInDirCommand(Input input,Output output)333 int HSTag::shiftInDirCommand(Input input, Output output)
334 {
335     bool external_only = settings_->default_direction_external_only();
336     Direction direction = Direction::Left; // some default to satisfy the linter
337     ArgParse ap;
338     ap.flags({
339         {"-i", [&external_only] () { external_only = false; }},
340         {"-e", [&external_only] () { external_only = true; }},
341     });
342     ap.mandatory(direction);
343     if (ap.parsingAllFails(input, output)) {
344         return ap.exitCode();
345     }
346     shared_ptr<FrameLeaf> sourceFrame = this->frame->focusedFrame();
347     Client* currentClient = focusedClient();
348     if (currentClient && currentClient->is_client_floated()) {
349         // try to move the floating window
350         bool success = Floating::shiftDirection(direction);
351         return success ? 0 : HERBST_FORBIDDEN;
352     }
353     // don't look for neighbours within the frame if 'external_only' is set
354     int indexInFrame = external_only ? (-1) : sourceFrame->getInnerNeighbourIndex(direction);
355     if (indexInFrame >= 0) {
356         sourceFrame->moveClient(indexInFrame);
357         needsRelayout_.emit();
358     } else {
359         shared_ptr<Frame> neighbour = sourceFrame->neighbour(direction);
360         Client* client = sourceFrame->focusedClient();
361         if (client && neighbour) { // if neighbour was found
362             // move window to neighbour
363             sourceFrame->removeClient(client);
364             FrameTree::focusedFrame(neighbour)->insertClient(client);
365             neighbour->frameWithClient(client)->select(client);
366 
367             // change selection in parent
368             shared_ptr<FrameSplit> parent = neighbour->getParent();
369             assert(parent);
370             parent->swapSelection();
371 
372             // layout was changed, so update it
373             get_current_monitor()->applyLayout();
374         } else if (!client) {
375             output << input.command() << ": No client focused\n";
376             return HERBST_FORBIDDEN;
377         } else {
378             output << input.command() << ": No neighbour found\n";
379             return HERBST_FORBIDDEN;
380         }
381     }
382     return 0;
383 }
384 
shiftInDirCompletion(Completion & complete)385 void HSTag::shiftInDirCompletion(Completion& complete)
386 {
387     focusInDirCompletion(complete);
388 }
389 
cycleAllCommand(Input input,Output output)390 int HSTag::cycleAllCommand(Input input, Output output)
391 {
392     bool skip_invisible = false;
393     int delta = 1;
394     ArgParse ap;
395     ap.flags({{"--skip-invisible", &skip_invisible}}).optional(delta);
396     if (ap.parsingAllFails(input, output)) {
397         return HERBST_INVALID_ARGUMENT;
398     }
399     if (delta < -1 || delta > 1) {
400         output << "argument out of range." << endl;
401         return HERBST_INVALID_ARGUMENT;
402     }
403     if (delta == 0) {
404         return 0; // nothing to do
405     }
406     if (floating_focused()) {
407         int newIndex = static_cast<int>(floating_clients_focus_) + delta;
408         // skip minimized clients
409         while (newIndex >= 0
410                && static_cast<size_t>(newIndex) < floating_clients_.size()
411                && floating_clients_[newIndex]->minimized_())
412         {
413             newIndex += delta;
414         }
415         if (newIndex < 0) {
416             floating_focused = false;
417             frame->cycleAll(FrameTree::CycleDelta::End, skip_invisible);
418         } else if (static_cast<size_t>(newIndex) >= floating_clients_.size()) {
419             floating_focused = false;
420             frame->cycleAll(FrameTree::CycleDelta::Begin, skip_invisible);
421         } else {
422             floating_clients_focus_ = static_cast<size_t>(newIndex);
423         }
424     } else {
425         FrameTree::CycleDelta cdelta = (delta == 1)
426                 ? FrameTree::CycleDelta::Next
427                 : FrameTree::CycleDelta::Previous;
428         bool focusChanged = frame->cycleAll(cdelta, skip_invisible);
429         if (!focusChanged) {
430             // if frame->cycleAll() reached the end of the tiling layer
431             if (!hasVisibleFloatingClients()) {
432                 // we need to wrap. when cycling forward, we wrap to the beginning
433                 FrameTree::CycleDelta rewind = (delta == 1)
434                             ? FrameTree::CycleDelta::Begin
435                             : FrameTree::CycleDelta::End;
436                 frame->cycleAll(rewind, skip_invisible);
437             } else {
438                 // if there are floating clients, switch to the floating layer
439                 floating_focused = true;
440                 // we know that there is at least one non-minimized client
441                 // because hasVisibleFloatingClients() is true.
442                 // so first wrap to first or last client:
443                 size_t idx = (delta == 1) ? 0 : (floating_clients_.size() - 1);
444                 // and then iterate delta until we find the first/last non-minimized
445                 // floating client:
446                 while (floating_clients_[idx]->minimized_()) {
447                     idx += delta;
448                 }
449                 floating_clients_focus_ = idx;
450             }
451         }
452     }
453     Client* newFocus = focusedClient();
454     if (newFocus && newFocus->is_client_floated()) {
455         newFocus->raise();
456     }
457     // finally, redraw the layout
458     needsRelayout_.emit();
459     return 0;
460 }
461 
cycleAllCompletion(Completion & complete)462 void HSTag::cycleAllCompletion(Completion& complete)
463 {
464     if (complete == 0) {
465         complete.full({"+1", "-1", "--skip-invisible" });
466     } else if (complete == 1 && complete[0] == "--skip-invisible") {
467         complete.full({"+1", "-1"});
468     } else {
469         complete.none();
470     }
471 }
472 
resizeCommand(Input input,Output output)473 int HSTag::resizeCommand(Input input, Output output)
474 {
475     Direction direction = Direction::Left;
476     FixPrecDec delta = FixPrecDec::approxFrac(1, 50); // 0.02
477     auto ap = ArgParse().mandatory(direction).optional(delta);
478     if (ap.parsingFails(input, output)) {
479         return ap.exitCode();
480     }
481     Client* client = focusedClient();
482     if (client && client->is_client_floated()) {
483         if (!Floating::resizeDirection(this, client, direction)) {
484             // no error message because this shouldn't happen anyway
485             return HERBST_FORBIDDEN;
486         }
487     } else {
488         if (!frame->resizeFrame(delta, direction)) {
489             output << input.command() << ": No neighbour found\n";
490             return HERBST_FORBIDDEN;
491         }
492     }
493     needsRelayout_.emit();
494     return 0;
495 }
496 
resizeCompletion(Completion & complete)497 void HSTag::resizeCompletion(Completion& complete)
498 {
499     if (complete == 0) {
500         Converter<Direction>::complete(complete, nullptr);
501     } else if (complete == 1) {
502         complete.full({"-0.02", "0.02"});
503     } else {
504         complete.none();
505     }
506 }
507 
onGlobalFloatingChange(bool newState)508 void HSTag::onGlobalFloatingChange(bool newState)
509 {
510     // move tiling clients to the floating layer or remove them
511     // from the floating layer
512     //
513     // we do it first for the focused tiling client such that
514     // it is guaranteed to be above the other tiling clients.
515     Client* tilingFocus = frame->root_->focusedClient();
516     if (tilingFocus && newState) {
517         stack->sliceAddLayer(tilingFocus->slice, LAYER_FLOATING, false);
518     }
519     for (Slice* slice : stack->layers_[LAYER_NORMAL]) {
520         // we add the tiled clients from the bottom such that they do not
521         // cover single-floated clients. Also, we do this by iterating over
522         // the tiling layer such that the relative stacking order between
523         // tiled clients is preserved
524         //
525         if (newState) {
526             stack->sliceAddLayer(slice, LAYER_FLOATING, false);
527         } else {
528             stack->sliceRemoveLayer(slice, LAYER_FLOATING);
529         }
530     }
531     needsRelayout_.emit();
532 }
533 
fixFocusIndex()534 void HSTag::fixFocusIndex()
535 {
536    static_assert(std::is_same<decltype(floating_clients_focus_), size_t>::value,
537                  "we assume that index cannot be negative.");
538    if (floating_clients_focus_ >= floating_clients_.size()) {
539        if (floating_clients_.empty()) {
540            floating_clients_focus_  = 0;
541        } else {
542            floating_clients_focus_  = floating_clients_.size() - 1;
543        }
544    }
545 }
546 
computeFrameCount()547 int HSTag::computeFrameCount() {
548     int count = 0;
549     frame->root_->fmap([](FrameSplit*) {},
550                 [&count](FrameLeaf*) { count++; },
551                 0);
552     return count;
553 }
554 
countUrgentClients()555 int HSTag::countUrgentClients()
556 {
557     int count = 0;
558     foreachClient([&](Client* client) {
559         if (client->urgent_()) {
560             count++;
561         }
562     });
563     return count;
564 }
565 
computeClientCount()566 int HSTag::computeClientCount() {
567     int count = static_cast<int>(floating_clients_.size());
568     frame->root_->fmap([](FrameSplit*) {},
569                 [&count](FrameLeaf* l) { count += l->clientCount(); },
570                 0);
571     return count;
572 }
573 
tag_get_count()574 int    tag_get_count() {
575     return global_tags->size();
576 }
577 
find_tag(const char * name)578 HSTag* find_tag(const char* name) {
579     for (auto t : *global_tags) {
580         if (t->name == name) {
581             return &* t;
582         }
583     }
584     return nullptr;
585 }
586 
get_tag_by_index(int index)587 HSTag* get_tag_by_index(int index) {
588     return &* global_tags->byIdx(index);
589 }
590 
tag_force_update_flags()591 void tag_force_update_flags() {
592     g_tag_flags_dirty = false;
593     // unset all tags
594     for (auto t : *global_tags) {
595         t->flags = 0;
596     }
597     // update flags
598     for (auto c : Root::common().clients()) {
599         auto client = c.second;
600         TAG_SET_FLAG(client->tag(), TAG_FLAG_USED);
601         if (client->urgent_) {
602             TAG_SET_FLAG(client->tag(), TAG_FLAG_URGENT);
603         }
604     }
605 }
606 
tag_update_flags()607 void tag_update_flags() {
608     if (g_tag_flags_dirty) {
609         tag_force_update_flags();
610     }
611 }
612 
tag_set_flags_dirty()613 void tag_set_flags_dirty() {
614     g_tag_flags_dirty = true;
615     hook_emit({"tag_flags"});
616 }
617 
618 //! close the focused client or remove if the frame is empty
closeOrRemoveCommand()619 int HSTag::closeOrRemoveCommand() {
620     Client* client = focusedClient();
621     if (client) {
622         client->requestClose();
623         return 0;
624     } else if (!floating_focused) {
625         // since the tiling layer is focused
626         // and no client is focused, we know that the
627         // focused frame is empty.
628         return frame->removeFrameCommand();
629     }
630     return 0;
631 }
632 
isValidTagIndex(unsigned long newIndex)633 string HSTag::isValidTagIndex(unsigned long newIndex)
634 {
635     if (newIndex < tags_->size()) {
636         return "";
637     }
638     stringstream ss;
639     ss << "Index must be between 0 and " << (tags_->size() - 1);
640     return ss.str();
641 }
642 
floatingLayerCanBeFocused(bool floatingFocused)643 string HSTag::floatingLayerCanBeFocused(bool floatingFocused)
644 {
645     if (floatingFocused && !hasVisibleFloatingClients()) {
646         return "There are no (non-minimized) floating windows;"
647                " cannot focus empty floating layer.";
648     } else {
649         return "";
650     }
651 }
652 
653 //! same as close or remove but directly remove frame after last client
closeAndRemoveCommand()654 int HSTag::closeAndRemoveCommand() {
655     Client* client = focusedClient();
656     if (client) {
657         // note that this just sends the closing signal
658         client->requestClose();
659         // so the client still exists in the following
660     }
661     // remove a frame if a frame is focused, that is if
662     // the tag is in tiling mode and the tiling layer is focused
663     bool frameFocused = !floating() && !floating_focused;
664     if (frameFocused && frame->focusedFrame()->clientCount() <= 1) {
665         return frame->removeFrameCommand();
666     }
667     return 0;
668 }
669 
670