1 #include "frametree.h"
2 
3 #include <algorithm>
4 #include <cassert>
5 #include <limits>
6 #include <regex>
7 
8 #include "argparse.h"
9 #include "client.h"
10 #include "completion.h"
11 #include "fixprecdec.h"
12 #include "framedata.h"
13 #include "frameparser.h"
14 #include "ipc-protocol.h"
15 #include "layout.h"
16 #include "monitor.h"
17 #include "stack.h"
18 #include "tag.h"
19 #include "tagmanager.h"
20 #include "utils.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::vector;
28 
FrameTree(HSTag * tag,Settings * settings)29 FrameTree::FrameTree(HSTag* tag, Settings* settings)
30     : rootLink_(*this, "root")
31     , focused_frame_(*this, "focused_frame", &FrameTree::focusedFramePlainPtr)
32     , tag_(tag)
33     , settings_(settings)
34 {
35     root_ = make_shared<FrameLeaf>(tag, settings, shared_ptr<FrameSplit>());
36     rootLink_ = root_.get();
37     (void) tag_;
38     (void) settings_;
39     focused_frame_.setDoc("The focused frame (leaf) in this frame tree");
40 }
41 
foreachClient(function<void (Client *)> action)42 void FrameTree::foreachClient(function<void(Client*)> action)
43 {
44     root_->foreachClient(action);
45 }
46 
dump(shared_ptr<Frame> frame,Output output)47 void FrameTree::dump(shared_ptr<Frame> frame, Output output)
48 {
49     auto l = frame->isLeaf();
50     if (l) {
51         output << "(clients "
52                << Converter<LayoutAlgorithm>::str(l->layout) << ":"
53                << l->selection;
54         for (auto client : l->clients) {
55             output << " " << WindowID(client->x11Window()).str();
56         }
57         output << ")";
58     }
59     auto s = frame->isSplit();
60     if (s) {
61         output
62             << "(split "
63             << Converter<SplitAlign>::str(s->align_)
64             << ":"
65             << Converter<FixPrecDec>::str(s->fraction_)
66             << ":"
67             << s->selection_
68             << " ";
69         FrameTree::dump(s->a_, output);
70         output << " ";
71         FrameTree::dump(s->b_, output);
72         output << ")";
73     }
74 }
75 
76 
77 /*! look up a specific frame in the frame tree
78  */
lookup(const string & path)79 shared_ptr<Frame> FrameTree::lookup(const string& path) {
80     shared_ptr<Frame> node = root_;
81     for (char c : path) {
82         if (c == 'e') {
83             shared_ptr<FrameLeaf> emptyFrame = findEmptyFrameNearFocus(node);
84             if (emptyFrame) {
85                 // go to the empty node if we had found some
86                 node = emptyFrame;
87             }
88             continue;
89         }
90         if (c == '@') {
91             node = focusedFrame(node);
92             continue;
93         }
94         if (c == 'p') {
95             auto parent = node->parent_;
96             // only change the 'node' if 'parent' is set.
97             // if 'parent' is not set, then 'node' is already
98             // the root node; in this case we stay at the
99             // root.
100             if (parent.lock()) {
101                 node = parent.lock();
102             }
103             continue;
104         }
105         node = node->switchcase<shared_ptr<Frame>>(
106             [](shared_ptr<FrameLeaf> l) {
107                 // nothing to do on a leaf
108                 return l;
109             },
110             [c](shared_ptr<FrameSplit> l) {
111                 switch (c) {
112                     case '0': return l->a_;
113                     case '1': return l->b_;
114                     case '/': return (l->selection_ == 0) ? l->b_ : l->a_;
115                     case '.': /* fallthru */
116                     default: return (l->selection_ == 0) ? l->a_ : l->b_;
117                 }
118             }
119         );
120     }
121     return node;
122 }
123 
124 /*! get the frame leaf that is focused within this frame tree.
125  */
focusedFrame()126 shared_ptr<FrameLeaf> FrameTree::focusedFrame() {
127     return focusedFrame(root_);
128 }
129 
130 /*! get the focused frame within the subtree of the given node
131  */
focusedFrame(shared_ptr<Frame> node)132 shared_ptr<FrameLeaf> FrameTree::focusedFrame(shared_ptr<Frame> node) {
133     while (node->isLeaf() == nullptr) {
134         // node must be a frame split
135         auto s = node->isSplit();
136         node = (s->selection_ == 0) ? s->a_ : s->b_;
137     }
138     assert(node->isLeaf() != nullptr);
139     return node->isLeaf();
140 }
141 
142 
cycleSelectionCommand(Input input,Output output)143 int FrameTree::cycleSelectionCommand(Input input, Output output) {
144     int delta = 1;
145     string deltaStr;
146     if (input >> deltaStr) {
147         delta = atoi(deltaStr.c_str());
148     }
149     // find current selection
150     auto frame = focusedFrame();
151     auto count = frame->clientCount();
152     if (count != 0) {
153         frame->setSelection(MOD(frame->getSelection() + delta, count));
154     }
155     get_current_monitor()->applyLayout();
156     return 0;
157 }
158 
159 //! focus the nth window within the focused frame
focusNthCommand(Input input,Output output)160 int FrameTree::focusNthCommand(Input input, Output output) {
161     string index;
162     if (!(input >> index)) {
163         return HERBST_NEED_MORE_ARGS;
164     }
165     focusedFrame()->setSelection(atoi(index.c_str()));
166     get_current_monitor()->applyLayout();
167     return 0;
168 }
169 
170 //! command that removes the focused frame
removeFrameCommand()171 int FrameTree::removeFrameCommand() {
172     auto frame = focusedFrame();
173     if (!frame->getParent()) {
174         // do nothing if is toplevel frame
175         return 0;
176     }
177     auto clientFocusIndex = frame->getSelection();
178     auto parent = frame->getParent();
179     bool insertAtFront = (frame == parent->firstChild());
180     auto newparent =
181             insertAtFront ? parent->secondChild() : parent->firstChild();
182     auto removedFrameClients = frame->removeAllClients();
183     // determine the 'targetFrameLeaf' where the clients of 'frame' will go to.
184     // this target frame shall be the frame leaf that is closest
185     // to the 'frame' that is removed (such that the clients visually travel as
186     // little distance as possible).
187     auto targetFrameAbstract = newparent;
188     while (targetFrameAbstract->isSplit()) {
189         auto s = targetFrameAbstract->isSplit();
190         if (s->getAlign() == parent->getAlign()) {
191             // for splits with the same alignment, go as near
192             // as possible towards 'frame', and also adjust the focus
193             // into that direction
194             s->setSelection(insertAtFront ? 0 : 1);
195         } else {
196             // for splits with the other alignment, we can
197             // follow the focus, since this is most likely the frame
198             // used last
199         }
200         targetFrameAbstract = s->selectedChild();
201     }
202     // if targetFrameAbstract is not a split, it must be a leaf:
203     auto targetFrameLeaf = targetFrameAbstract->isLeaf();
204     int oldClientCount = (int)targetFrameLeaf->clientCount();
205     targetFrameLeaf->addClients(removedFrameClients, insertAtFront);
206     // now, frame is empty
207     replaceNode(parent, newparent);
208 
209     // focus the same client again
210     if (!removedFrameClients.empty()) {
211         if (insertAtFront) {
212             targetFrameLeaf->setSelection(clientFocusIndex);
213         } else {
214             targetFrameLeaf->setSelection(clientFocusIndex + oldClientCount);
215         }
216     }
217     get_current_monitor()->applyLayout();
218     return 0;
219 }
220 
rotateCommand()221 int FrameTree::rotateCommand() {
222     void (*onSplit)(FrameSplit*) =
223         [] (FrameSplit* s) {
224             switch (s->align_) {
225                 case SplitAlign::vertical:
226                     s->align_ = SplitAlign::horizontal;
227                     break;
228                 case SplitAlign::horizontal:
229                     s->align_ = SplitAlign::vertical;
230                     s->selection_ = s->selection_ ? 0 : 1;
231                     s->swapChildren();
232                     s->fraction_ = FixPrecDec::fromInteger(1) - s->fraction_;
233                     break;
234             }
235         };
236     void (*onLeaf)(FrameLeaf*) =
237         [] (FrameLeaf*) {
238         };
239     // first hide children => order = 2
240     root_->fmap(onSplit, onLeaf, -1);
241     get_current_monitor()->applyLayout();
242     return 0;
243 }
244 
245 template<>
246 Finite<FrameTree::MirrorDirection>::ValueList Finite<FrameTree::MirrorDirection>::values = {
247     { FrameTree::MirrorDirection::Horizontal, "horizontal" },
248     { FrameTree::MirrorDirection::Vertical, "vertical" },
249     { FrameTree::MirrorDirection::Both, "both" },
250 };
251 
mirrorCommand(Input input,Output output)252 int FrameTree::mirrorCommand(Input input, Output output)
253 {
254     using MD = MirrorDirection;
255     MirrorDirection dir = MD::Horizontal;
256     ArgParse ap = ArgParse().optional(dir);
257     if (ap.parsingFails(input, output)) {
258         return ap.exitCode();
259     }
260     auto onSplit = [dir] (FrameSplit* s) {
261             bool mirror =
262                 dir == MD::Both
263                 || (dir == MD::Horizontal && s->align_ == SplitAlign::horizontal)
264                 || (dir == MD::Vertical && s->align_ == SplitAlign::vertical);
265             if (mirror) {
266                 s->selection_ = s->selection_ ? 0 : 1;
267                 s->swapChildren();
268                 s->fraction_ = FixPrecDec::fromInteger(1) - s->fraction_;
269             }
270         };
271     root_->fmap(onSplit, [] (FrameLeaf*) { }, -1);
272     get_current_monitor()->applyLayout();
273     return 0;
274 }
275 
mirrorCompletion(Completion & complete)276 void FrameTree::mirrorCompletion(Completion& complete)
277 {
278     if (complete == 0) {
279         Converter<MirrorDirection>::complete(complete);
280     }
281 }
282 
treeInterface(shared_ptr<Frame> frame,shared_ptr<FrameLeaf> focus)283 shared_ptr<TreeInterface> FrameTree::treeInterface(
284         shared_ptr<Frame> frame,
285         shared_ptr<FrameLeaf> focus)
286 {
287     class LeafTI : public TreeInterface {
288     public:
289         LeafTI(shared_ptr<FrameLeaf> l, shared_ptr<FrameLeaf> focus)
290             : l_(l), focus_(focus)
291         {}
292         shared_ptr<TreeInterface> nthChild(size_t idx) override {
293             return {};
294         }
295         size_t childCount() override { return 0; };
296         void appendCaption(Output output) override {
297             output << " " << Converter<LayoutAlgorithm>::str(l_->layout) << ":";
298             for (auto client : l_->clients) {
299                 output << " " << WindowID(client->x11Window()).str();
300             }
301             if (l_ == focus_) {
302                 output << " [FOCUS]";
303             }
304         }
305     private:
306         shared_ptr<FrameLeaf> l_;
307         shared_ptr<FrameLeaf> focus_;
308     };
309     class SplitTI : public TreeInterface {
310     public:
311         SplitTI(shared_ptr<FrameSplit> s, shared_ptr<FrameLeaf> focus)
312             : s_(s), focus_(focus) {}
313         shared_ptr<TreeInterface> nthChild(size_t idx) override {
314             return treeInterface(((idx == 0) ? s_->firstChild()
315                                             : s_->secondChild()),
316                                  focus_);
317         }
318         size_t childCount() override { return 2; };
319         void appendCaption(Output output) override {
320             output << " " << Converter<SplitAlign>::str(s_->align_)
321                    << " " << (s_->fraction_.value_ * 100 / s_->fraction_.unit_)
322                    << "%"
323                    << " selection=" << s_->selection_;
324         }
325     private:
326         shared_ptr<FrameSplit> s_;
327         shared_ptr<FrameLeaf> focus_;
328     };
329     return frame->switchcase<shared_ptr<TreeInterface>>(
330         [focus] (shared_ptr<FrameLeaf> l) {
331             return std::static_pointer_cast<TreeInterface>(
332                     make_shared<LeafTI>(l, focus));
333         },
334         [focus] (shared_ptr<FrameSplit> s) {
335             return std::static_pointer_cast<TreeInterface>(
336                     make_shared<SplitTI>(s, focus));
337         }
338     );
339 }
340 
prettyPrint(shared_ptr<Frame> frame,Output output)341 void FrameTree::prettyPrint(shared_ptr<Frame> frame, Output output) {
342     auto focus = get_current_monitor()->tag->frame->focusedFrame();
343     tree_print_to(treeInterface(frame, focus), output);
344 }
345 
findEmptyFrameNearFocusGeometrically(shared_ptr<Frame> subtree)346 shared_ptr<FrameLeaf> FrameTree::findEmptyFrameNearFocusGeometrically(shared_ptr<Frame> subtree)
347 {
348     // render frame geometries.
349     TilingResult tileres = subtree->computeLayout({0, 0, 800, 800});
350     function<Rectangle(shared_ptr<FrameLeaf>)> frame2geometry =
351             [tileres] (shared_ptr<FrameLeaf> frame) -> Rectangle {
352         for (auto& framedata : tileres.frames) {
353             if (framedata.first == frame->decoration) {
354                 return framedata.second.geometry;
355             }
356         }
357         // if not found, return an invalid rectangle;
358         return { 0, 0, -1, -1};
359     };
360     vector<shared_ptr<FrameLeaf>> emptyLeafs;
361     subtree->fmap([](FrameSplit*){}, [&emptyLeafs](FrameLeaf* leaf) {
362         if (leaf->clientCount() == 0) {
363             emptyLeafs.push_back(leaf->thisLeaf());
364         }
365     });
366     Rectangle geoFocused = frame2geometry(focusedFrame(subtree));
367     if (!geoFocused) { // this should never happen actually
368         return {};
369     }
370     int bestDistance = std::numeric_limits<int>::max();
371     shared_ptr<FrameLeaf> closestFrame = {};
372     for (auto l : emptyLeafs) {
373         Rectangle r = frame2geometry(l);
374         if (!r) {
375             continue;
376         }
377         int dist = geoFocused.manhattanDistanceTo(r);
378         if (dist < bestDistance) {
379             closestFrame = l;
380             bestDistance = dist;
381         }
382     }
383     return closestFrame;
384 }
385 
focusedFramePlainPtr()386 FrameLeaf* FrameTree::focusedFramePlainPtr()
387 {
388     auto shared = focusedFrame();
389     if (shared) {
390         return shared.get();
391     } else {
392         return nullptr;
393     }
394 }
395 
396 //! check whether there is an empty frame in the given subtree,
397 //! and if there are some, try to find one that is as close as possible to the
398 //! focused frame leaf. returns {} if there is no empty frame in the subtree
findEmptyFrameNearFocus(shared_ptr<Frame> subtree)399 shared_ptr<FrameLeaf> FrameTree::findEmptyFrameNearFocus(shared_ptr<Frame> subtree)
400 {
401     // the following algorithm is quadratic in the number of vertices in the
402     // frame, because we look for empty frames in the same subtree over and
403     // over again. However, this is much easier to implement then checking
404     // only those frames for emptyness that have not been checked before.
405 
406     // start at the focused leaf
407     shared_ptr<Frame> current = focusedFrame(subtree);
408     // and then go upward in the tree
409     while (current) {
410         auto emptyNode = findEmptyFrameNearFocusGeometrically(current);
411         if (emptyNode) {
412             return emptyNode;
413         }
414         if (current == subtree) {
415             // if we reached the root of the subtree, stop searching
416             return {};
417         }
418         current = current->getParent();
419     }
420     return {};
421 }
422 
findFrameWithClient(Client * client)423 shared_ptr<FrameLeaf> FrameTree::findFrameWithClient(Client* client) {
424     shared_ptr<FrameLeaf> frame = {};
425     root_->fmap(
426         [](FrameSplit*) {},
427         [&](FrameLeaf* l) {
428             auto& cs = l->clients;
429             if (std::find(cs.begin(), cs.end(), client) != cs.end()) {
430                 frame = l->thisLeaf();
431             }
432         });
433     return frame;
434 }
435 
contains(shared_ptr<Frame> frame) const436 bool FrameTree::contains(shared_ptr<Frame> frame) const
437 {
438     return frame->root() == this->root_;
439 }
440 
441 //! resize the borders of the focused client in the specific direction by 'delta'
442 //! returns whether the focused frame has a border in the specified direction.
resizeFrame(FixPrecDec delta,Direction direction)443 bool FrameTree::resizeFrame(FixPrecDec delta, Direction direction)
444 {
445     // if direction is left or up we have to flip delta
446     // because e.g. resize up by 0.1 actually means:
447     // reduce fraction by 0.1, i.e. delta = -0.1
448     if (direction == Direction::Left || direction == Direction::Up) {
449         delta.value_ = delta.value_ * -1;
450     }
451 
452     shared_ptr<Frame> neighbour = focusedFrame()->neighbour(direction);
453     if (!neighbour) {
454         // then try opposite direction
455         std::map<Direction, Direction> flip = {
456             {Direction::Left, Direction::Right},
457             {Direction::Right, Direction::Left},
458             {Direction::Down, Direction::Up},
459             {Direction::Up, Direction::Down},
460         };
461         direction = flip[direction];
462         neighbour = focusedFrame()->neighbour(direction);
463         if (!neighbour) {
464             return false;
465         }
466     }
467     auto parent = neighbour->getParent();
468     assert(parent); // if has neighbour, it also must have a parent
469     parent->adjustFraction(delta);
470     return true;
471 }
472 
focusClient(Client * client)473 bool FrameTree::focusClient(Client* client) {
474     auto frameLeaf = findFrameWithClient(client);
475     if (!frameLeaf) {
476         return false;
477     }
478     // 1. focus client within its frame
479     auto& cs = frameLeaf->clients;
480     int index = std::find(cs.begin(), cs.end(), client) - cs.begin();
481     frameLeaf->selection = index;
482     // 2. make the frame focused
483     focusFrame(frameLeaf);
484     return true;
485 }
486 
focusFrame(shared_ptr<Frame> frame)487 void FrameTree::focusFrame(shared_ptr<Frame> frame) {
488     while (frame) {
489         auto parent = frame->getParent();
490         if (!parent) {
491             break;
492         }
493         if (parent->firstChild() == frame) {
494             parent->selection_ = 0;
495         } else {
496             parent->selection_ = 1;
497         }
498         frame = parent;
499     }
500 }
501 
502 //! focus a client/frame in the given direction. if externalOnly=true,
503 //! focus the frame in the specified direction; otherwise, focus the frame or client
504 //! in the specified direction. Return whether the focus changed
focusInDirection(Direction direction,bool externalOnly)505 bool FrameTree::focusInDirection(Direction direction, bool externalOnly)
506 {
507     auto curframe = focusedFrame();
508     if (!externalOnly) {
509         int index = curframe->getInnerNeighbourIndex(direction);
510         if (index >= 0) {
511             curframe->setSelection(index);
512             return true;
513         }
514     }
515     // if this didn't succeed, find a frame:
516     shared_ptr<Frame> neighbour = curframe->neighbour(direction);
517     if (neighbour) { // if neighbour was found
518         shared_ptr<FrameSplit> parent = neighbour->getParent();
519         if (parent) {
520             // alter focus (from 0 to 1, from 1 to 0)
521             parent->swapSelection();
522         }
523         return true;
524     }
525     return false;
526 }
527 
528 //! go to the specified frame. Return true on success, return false if
529 //! the end is reached (this command never wraps). Skips covered windows
530 //! if skipInvisible is set.
cycleAll(FrameTree::CycleDelta cdelta,bool skip_invisible)531 bool FrameTree::cycleAll(FrameTree::CycleDelta cdelta, bool skip_invisible)
532 {
533     shared_ptr<FrameLeaf> focus = focusedFrame();
534     if (cdelta == CycleDelta::Begin || cdelta == CycleDelta::End) {
535         // go to first resp. last frame
536         cycle_frame([cdelta] (size_t idx, size_t len) {
537             if (cdelta == CycleDelta::Begin) {
538                 return size_t(0);
539             } else { // cdelta == CycleDelta::End
540                 return len - 1;
541             }
542         });
543         // go to first resp. last window in it
544         auto frame = focusedFrame();
545         if (!(frame->layout == LayoutAlgorithm::max && skip_invisible)) {
546             auto count = frame->clientCount();
547             if (cdelta == CycleDelta::Begin) {
548                 frame->setSelection(0);
549             } else if (count > 0) { // cdelta == CycleDelta::End
550                 frame->setSelection(int(count - 1));
551             }
552         }
553         return true;
554     }
555     int delta = (cdelta == CycleDelta::Next) ? 1 : -1;
556     bool frameChanges = (focus->layout == LayoutAlgorithm::max && skip_invisible)
557         || (focus->clientCount() == 0)
558         || (delta == 1 && focus->getSelection() + 1 == static_cast<int>(focus->clientCount()))
559         || (delta == -1 && focus->getSelection() == 0);
560     if (!frameChanges) {
561         // if the focused frame does not change, it's simple
562         auto count = focus->clientCount();
563         if (count != 0) {
564             focus->setSelection(MOD(focus->getSelection() + delta, count));
565         }
566     } else { // if the frame changes:
567         // otherwise we need to find the next frame in direction 'delta'
568         bool wouldWrap = false;
569         cycle_frame([delta, &wouldWrap](size_t idx, size_t len) {
570             wouldWrap = (idx == 0 && delta == -1)
571                     || (idx + 1 >= len && delta == 1);
572             if (wouldWrap) {
573                 return idx; // do nothing
574             } else {
575                 return idx + delta;
576             }
577         });
578         if (wouldWrap) {
579             // do not wrap, do not go there
580             return false;
581         }
582         // if it does not wrap
583         focus = focusedFrame();
584         // fix the selection within the freshly focused frame.
585         if (focus->layout == LayoutAlgorithm::max && skip_invisible) {
586             // nothing to do
587         } else if (delta == 1) {
588             // focus the first client
589             focus->setSelection(0);
590         } else { // delta == -1
591             // focus the last client
592             if (focus->clientCount() > 0) {
593                 focus->setSelection(focus->clientCount() - 1);
594             }
595         }
596     }
597     return true;
598 }
599 
cycle_frame(function<size_t (size_t,size_t)> indexAndLenToIndex)600 void FrameTree::cycle_frame(function<size_t(size_t,size_t)> indexAndLenToIndex) {
601     shared_ptr<FrameLeaf> focus = focusedFrame();
602     // First, enumerate all frames in traversal order
603     // and find the focused frame in there
604     vector<shared_ptr<FrameLeaf>> frames;
605     size_t index = 0;
606     root_->fmap(
607         [](FrameSplit*) {},
608         [&](FrameLeaf* l) {
609             if (l == focus.get()) {
610                 // the index of the next item we push back
611                 index = frames.size();
612             }
613             frames.push_back(l->thisLeaf());
614         });
615     index = indexAndLenToIndex(index, frames.size());
616     focusFrame(frames[index]);
617 }
618 
cycle_frame(int delta)619 void FrameTree::cycle_frame(int delta) {
620     cycle_frame([delta](size_t index, size_t len) {
621         return MOD(index + delta, len);
622     });
623 }
624 
cycleFrameCommand(Input input,Output output)625 int FrameTree::cycleFrameCommand(Input input, Output output) {
626     string s = "1";
627     int delta = 1;
628     input >> s; // try to read the optional argument
629     try {
630         delta = std::stoi(s);
631     } catch (std::invalid_argument const& e) {
632         output << "invalid argument: " << e.what() << endl;
633         return HERBST_INVALID_ARGUMENT;
634     } catch (std::out_of_range const& e) {
635         output << "out of range: " << e.what() << endl;
636         return HERBST_INVALID_ARGUMENT;
637     }
638     cycle_frame(delta);
639     get_current_monitor()->applyLayout();
640     return 0;
641 }
642 
loadCommand(Input input,Output output)643 int FrameTree::loadCommand(Input input, Output output) {
644     // usage: load TAG LAYOUT
645     HSTag* tag = nullptr;
646     string layoutString, tagName;
647     if (input.size() >= 2) {
648         input >> tagName >> layoutString;
649         tag = find_tag(tagName.c_str());
650         if (!tag) {
651             output << input.command() << ": Tag \"" << tagName << "\" not found\n";
652             return HERBST_INVALID_ARGUMENT;
653         }
654     } else if (input.size() == 1) {
655         input >> layoutString;
656         tag = get_current_monitor()->tag;
657     } else {
658         return HERBST_NEED_MORE_ARGS;
659     }
660     assert(tag != nullptr);
661     FrameParser parsingResult(layoutString);
662     if (parsingResult.error_) {
663         output << input.command() << ": Syntax error at "
664                << parsingResult.error_->first.first << ": "
665                << parsingResult.error_->second << ":"
666                << endl;
667         std::regex whitespace ("[ \n\t]");
668         // print the layout again
669         output << "\"" << std::regex_replace(layoutString, whitespace, string(" "))
670                << "\"" << endl;
671         // and underline the token
672         int token_len = std::max((size_t)1, parsingResult.error_->first.second.size());
673         output << " " // for the \" above
674                << string(parsingResult.error_->first.first, ' ')
675                << string(token_len, '~')
676                << endl;
677         return HERBST_INVALID_ARGUMENT;
678     }
679     if (!parsingResult.unknownWindowIDs_.empty()) {
680         output << "Warning: Unknown window IDs";
681         for (const auto& e : parsingResult.unknownWindowIDs_) {
682             output << " " << WindowID(e.second).str()
683                    << "(\'" << e.first.second << "\')";
684         }
685         output << endl;
686     }
687     // apply the new frame tree
688     tag->frame->applyFrameTree(tag->frame->root_, parsingResult.root_);
689     tag_set_flags_dirty(); // we probably changed some window positions
690     // arrange monitor
691     Monitor* m = find_monitor_with_tag(tag);
692     if (m) {
693         tag->frame->root_->setVisibleRecursive(true);
694         m->applyLayout();
695         monitor_update_focus_objects();
696     } else {
697         tag->frame->root_->setVisibleRecursive(false);
698     }
699     return 0;
700 }
701 
702 //! target must not be null, source may be null
applyFrameTree(shared_ptr<Frame> target,shared_ptr<RawFrameNode> source)703 void FrameTree::applyFrameTree(shared_ptr<Frame> target,
704                                shared_ptr<RawFrameNode> source)
705 {
706     if (!source) {
707         // nothing to do
708         return;
709     }
710     shared_ptr<FrameSplit> targetSplit = target->isSplit();
711     shared_ptr<FrameLeaf> targetLeaf = target->isLeaf();
712     shared_ptr<RawFrameSplit> sourceSplit = source->isSplit();
713     shared_ptr<RawFrameLeaf> sourceLeaf = source->isLeaf();
714     if (sourceLeaf) {
715         // detach the clients from their current frame
716         // this might even involve the above targetLeaf / targetSplit
717         // so we need to do this before everything else
718         for (const auto& client : sourceLeaf->clients) {
719             // first un-minimize and un-float the client
720             // such that we know that it is in the frame-tree
721             client->floating_ = false;
722             client->minimized_ = false;
723             client->tag()->frame->root_->removeClient(client);
724             if (client->tag() != tag_) {
725                 client->tag()->stack->removeSlice(client->slice);
726                 client->setTag(tag_);
727                 client->tag()->stack->insertSlice(client->slice);
728             }
729         }
730         vector<Client*> clients = sourceLeaf->clients;
731         // collect all the remaining clients in the target
732         target->foreachClient(
733             [&clients](Client* c) {
734                 clients.push_back(c);
735             }
736         );
737         // assert that "target" is a FrameLeaf
738         if (targetSplit) {
739             // if its a split, then replace the split
740             targetLeaf = make_shared<FrameLeaf>(
741                                 tag_, settings_, target->getParent());
742             replaceNode(target, targetLeaf);
743             target = targetLeaf;
744             targetSplit = {};
745         }
746         // make the targetLeaf look like the sourceLeaf
747         targetLeaf->clients = clients;
748         targetLeaf->setSelection(sourceLeaf->selection);
749         targetLeaf->layout = sourceLeaf->layout;
750     } else {
751         // assert that target is a FrameSplit
752         if (targetLeaf) {
753             targetLeaf->split(sourceSplit->align_, sourceSplit->fraction_, 0);
754             targetSplit = targetLeaf->getParent();
755             target = targetSplit;
756             targetLeaf = {}; // we don't need this anymore
757         }
758         assert(target == targetSplit);
759         targetSplit->align_ = sourceSplit->align_;
760         targetSplit->fraction_ = sourceSplit->fraction_;
761         targetSplit->selection_ = sourceSplit->selection_;
762         applyFrameTree(targetSplit->a_, sourceSplit->a_);
763         applyFrameTree(targetSplit->b_, sourceSplit->b_);
764     }
765 }
766 
replaceNode(shared_ptr<Frame> old,shared_ptr<Frame> replacement)767 void FrameTree::replaceNode(shared_ptr<Frame> old,
768                             shared_ptr<Frame> replacement) {
769     auto parent = old->getParent();
770     if (!parent) {
771         assert(old == root_);
772         root_ = replacement;
773         rootLink_ = root_.get();
774         // root frame should never have a parent:
775         root_->parent_ = {};
776     } else {
777         parent->replaceChild(old, replacement);
778     }
779 }
780 
cycleLayoutCommand(Input input,Output output)781 int FrameTree::cycleLayoutCommand(Input input, Output output) {
782     int delta = 1;
783     auto cur_frame = focusedFrame();
784     string deltaStr;
785     if (input >> deltaStr) {
786         try {
787             delta = Converter<int>::parse(deltaStr);
788         } catch (const std::exception& e) {
789             output << input.command() << ": " << e.what();
790             return HERBST_INVALID_ARGUMENT;
791         }
792     }
793     int layout_index;
794     if (!input.empty()) {
795         /* cycle through a given list of layouts */
796         string curname = Converter<LayoutAlgorithm>::str(cur_frame->getLayout());
797         size_t count = input.end() - input.begin();
798         auto curposition = std::find(input.begin(), input.end(), curname);
799         size_t idx = 0;  // take the first in the list per default
800         if (curposition != input.end()) {
801             // if the current layout name is in the list, take the next one
802             // (respective to the delta)
803             idx = MOD((curposition - input.begin()) + delta, count);
804         }
805         try {
806             layout_index = (int)Converter<LayoutAlgorithm>::parse(*(input.begin() + idx));
807         } catch (const std::exception& e) {
808             output << input.command() << ": " << e.what();
809             return HERBST_INVALID_ARGUMENT;
810         }
811     } else {
812         /* cycle through the default list of layouts */
813         layout_index = MOD((int)cur_frame->getLayout() + delta, layoutAlgorithmCount());
814     }
815     cur_frame->setLayout((LayoutAlgorithm)layout_index);
816     get_current_monitor()->applyLayout();
817     return 0;
818 }
819 
cycleLayoutCompletion(Completion & complete)820 void FrameTree::cycleLayoutCompletion(Completion& complete) {
821     if (complete == 0) {
822         complete.full({ "-1", "+1" });
823     } else if (complete <= layoutAlgorithmCount()) {
824         // it does not make sense to mention layout names multiple times
825         Converter<LayoutAlgorithm>::complete(complete, nullptr);
826     } else {
827         complete.none();
828     }
829 }
830 
setLayoutCommand(Input input,Output output)831 int FrameTree::setLayoutCommand(Input input, Output output) {
832     LayoutAlgorithm layout = LayoutAlgorithm::vertical;
833     ArgParse ap;
834     ap.mandatory(layout);
835     if (ap.parsingFails(input, output)) {
836         return ap.exitCode();
837     }
838 
839     auto curFrame = focusedFrame();
840     curFrame->setLayout(layout);
841     get_current_monitor()->applyLayout();
842 
843     return HERBST_EXIT_SUCCESS;
844 }
845 
setLayoutCompletion(Completion & complete)846 void FrameTree::setLayoutCompletion(Completion& complete) {
847     if (complete == 0) {
848         Converter<LayoutAlgorithm>::complete(complete, nullptr);
849     } else {
850         complete.none();
851     }
852 }
853 
854 //! modes for the 'split' command
855 class SplitMode {
856 public:
SplitMode(string name_,SplitAlign align_,bool frameToFirst_,int selection_)857     SplitMode(string name_, SplitAlign align_, bool frameToFirst_, int selection_)
858         : name(name_)
859           , align(align_)
860           , frameToFirst(frameToFirst_)
861           , selection(selection_)
862     {}
863 
864     string name;
865     SplitAlign align;
866 
867     //! If former frame moves to first child
868     bool frameToFirst;
869 
870     //! Which child to select after the split
871     int selection;
872 
873     static vector<SplitMode> modes(SplitAlign align_explode = SplitAlign::horizontal, SplitAlign align_auto = SplitAlign::horizontal);
874 };
875 
modes(SplitAlign align_explode,SplitAlign align_auto)876 vector<SplitMode> SplitMode::modes(SplitAlign align_explode, SplitAlign align_auto)
877 {
878     return {
879         { "top",        SplitAlign::vertical,     false,  1   },
880         { "bottom",     SplitAlign::vertical,     true,   0   },
881         { "vertical",   SplitAlign::vertical,     true,   0   },
882         { "right",      SplitAlign::horizontal,   true,   0   },
883         { "horizontal", SplitAlign::horizontal,   true,   0   },
884         { "left",       SplitAlign::horizontal,   false,  1   },
885         { "explode",    align_explode,            true,   -1  },
886         { "auto",       align_auto,               true,   0   },
887     };
888 }
889 
splitCommand(Input input,Output output)890 int FrameTree::splitCommand(Input input, Output output)
891 {
892     // usage: split t|b|l|r|h|v FRACTION [Frameindex]
893     string splitType;
894     bool userDefinedFraction = false;
895     FixPrecDec fraction = FixPrecDec::approxFrac(1, 2);
896     string frameIndex = "@"; // split the focused frame per default
897     ArgParse ap;
898     ap.mandatory(splitType).optional(fraction, &userDefinedFraction);
899     ap.optional(frameIndex);
900     if (ap.parsingFails(input, output)) {
901         return ap.exitCode();
902     }
903     fraction = FrameSplit::clampFraction(fraction);
904     shared_ptr<Frame> frame = lookup(frameIndex);
905     int lh = frame->lastRect().height;
906     int lw = frame->lastRect().width;
907     SplitAlign align_auto = (lw > lh) ? SplitAlign::horizontal : SplitAlign::vertical;
908     SplitAlign align_explode = SplitAlign::vertical;
909     auto availableModes = SplitMode::modes(align_explode, align_auto);
910     auto mode = std::find_if(
911             availableModes.begin(), availableModes.end(),
912             [=](const SplitMode &x){ return x.name[0] == splitType[0]; });
913     if (mode == availableModes.end()) {
914         output << input.command() << ": Invalid alignment \"" << splitType << "\"\n";
915         return HERBST_INVALID_ARGUMENT;
916     }
917     SplitMode m = *mode;
918     auto layout = frame->switchcase<LayoutAlgorithm>(&FrameLeaf::getLayout,
919         [] (shared_ptr<FrameSplit> f) {
920             return splitAlignToLayoutAlgorithm(f->getAlign());
921     });
922     // if 'frame' is a FrameSplit, we simply set the
923     // window count to 0, because we do not have a count of windows
924     // that stay in the 'old frame'.
925     auto windowcount = frame->switchcase<size_t>(&FrameLeaf::clientCount,
926         [](shared_ptr<FrameSplit>) { return 0; });
927     bool exploding = m.name == "explode";
928     if (exploding) {
929         if (windowcount <= 1) {
930             m.align = align_auto;
931         } else if (layout == LayoutAlgorithm::max) {
932             m.align = align_auto;
933         } else if (layout == LayoutAlgorithm::grid && windowcount == 2) {
934             m.align = SplitAlign::horizontal;
935         } else if (layout == LayoutAlgorithm::horizontal) {
936             m.align = SplitAlign::horizontal;
937         } else {
938             m.align = SplitAlign::vertical;
939         }
940         size_t count1 = windowcount;
941         size_t nc1 = (count1 + 1) / 2;      // new count for the first frame
942         if ((layout == LayoutAlgorithm::horizontal
943             || layout == LayoutAlgorithm::vertical)
944             && !userDefinedFraction && count1 > 1) {
945             fraction.value_ = (nc1 * fraction.unit_) / count1;
946         }
947     }
948     // move second half of the window buf to second frame
949     size_t childrenLeaving = 0;
950     if (exploding) {
951         childrenLeaving = windowcount / 2;
952     }
953     auto frameIsLeaf = frame->isLeaf();
954     auto frameIsSplit = frame->isSplit();
955     shared_ptr<FrameSplit> frameParent = {}; // the new parent
956     if (frameIsLeaf) {
957         if (!frameIsLeaf->split(m.align, fraction, childrenLeaving)) {
958             return 0;
959         }
960         frameParent = frameIsLeaf->getParent();
961     }
962     if (frameIsSplit) {
963         if (!frameIsSplit->split(m.align, fraction)) {
964             return 0;
965         }
966         frameParent = frameIsSplit->getParent();
967     }
968 
969     if (!m.frameToFirst) {
970         frameParent->swapChildren();
971     }
972     if (m.selection >= 0) {
973         frameParent->setSelection(m.selection);
974     }
975 
976     // redraw monitor
977     get_current_monitor()->applyLayout();
978     return 0;
979 }
980 
981 
982 //! Implementation of the commands "dump" and "layout"
dumpLayoutCommand(Input input,Output output)983 int FrameTree::dumpLayoutCommand(Input input, Output output) {
984     shared_ptr<Frame> frame = root_;
985     string tagName;
986     if (input >> tagName) {
987         FrameTree* tree = this;
988         // an empty tagName means 'current tag'
989         // (this is a special case that is not handled by find_tag()
990         // so we handle it explicitly here)
991         if (!tagName.empty()) {
992             HSTag* tag = find_tag(tagName.c_str());
993             if (!tag) {
994                 output << input.command() << ": Tag \"" << tagName << "\" not found\n";
995                 return HERBST_INVALID_ARGUMENT;
996             }
997             tree = tag->frame();
998         }
999         string frameIndex;
1000         if (input >> frameIndex) {
1001             frame = tree->lookup(frameIndex);
1002         } else {
1003             frame = tree->root_;
1004         }
1005     }
1006     if (input.command() == "dump") {
1007         FrameTree::dump(frame, output);
1008     } else { // input.command() == "layout"
1009         FrameTree::prettyPrint(frame, output);
1010     }
1011     return 0;
1012 }
1013 
dumpLayoutCompletion(Completion & complete)1014 void FrameTree::dumpLayoutCompletion(Completion& complete) {
1015     if (complete == 0) {
1016         global_tags->completeTag(complete);
1017     } else if (complete == 1) {
1018         // no completion for frame index
1019     } else {
1020         complete.none();
1021     }
1022 }
1023 
1024