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