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