1 #include "tagmanager.h"
2 
3 #include <memory>
4 
5 #include "client.h"
6 #include "command.h"
7 #include "completion.h"
8 #include "ewmh.h"
9 #include "globals.h"
10 #include "ipc-protocol.h"
11 #include "monitor.h"
12 #include "monitormanager.h"
13 #include "utils.h"
14 
15 using std::function;
16 using std::string;
17 using std::shared_ptr;
18 using std::vector;
19 
20 TagManager* global_tags;
21 
TagManager()22 TagManager::TagManager()
23     : IndexingObject()
24     , by_name_(*this)
25     , settings_(nullptr)
26     , focus_(*this, "focus")
27 {
28     indicesChanged.connect([](){
29         Ewmh::get().updateDesktopNames();
30         Ewmh::get().updateCurrentDesktop();
31         tag_set_flags_dirty();
32     });
33 
34     setDoc(
35         "The tags (or virtual desktops or workspaces). This contains "
36         " an entry \'index\' for each tag with the given \'index\'."
37     );
38     focus_.setDoc(
39         "the object of the focused tag, equivalently, "
40         "the tag on the focused monitor."
41     );
42 }
43 
injectDependencies(MonitorManager * m,Settings * s)44 void TagManager::injectDependencies(MonitorManager* m, Settings *s) {
45     monitors_ = m;
46     settings_ = s;
47 }
48 
find(const string & name)49 HSTag* TagManager::find(const string& name) {
50     for (auto t : *this) {
51         if (t->name == name) {
52             return t;
53         }
54     }
55     return {};
56 }
57 
completeTag(Completion & complete)58 void TagManager::completeTag(Completion& complete) {
59     for (auto t : *this) {
60         complete.full(t->name);
61     }
62 }
63 
64 //! if the name is a valid tag name, return "", otherwise return an error message
isValidTagName(string name)65 string TagManager::isValidTagName(string name) {
66     if (name.empty()) {
67         return "An empty tag name is not permitted";
68     }
69     if (find(name)) {
70         return "A tag with the name \"" + name + "\" already exists";
71     }
72     return "";
73 }
74 
add_tag(const string & name)75 HSTag* TagManager::add_tag(const string& name) {
76     HSTag* find_result = find(name);
77     if (find_result) {
78         // nothing to do
79         return find_result;
80     }
81     if (name.empty()) {
82         // empty name is not allowed
83         return nullptr;
84     }
85     HSTag* tag = new HSTag(name, this, settings_);
86     addIndexed(tag);
87     tag->name.changed().connect([this,tag]() {
88         this->onTagRename(tag);
89         tag->oldName_ = tag->name;
90     });
91     tag->needsRelayout_.connect([this,tag]() { this->needsRelayout_.emit(tag); });
92 
93     Ewmh::get().updateDesktops();
94     Ewmh::get().updateDesktopNames();
95     tag_set_flags_dirty();
96     return tag;
97 }
98 
tag_add_command(Input input,Output output)99 int TagManager::tag_add_command(Input input, Output output) {
100     if (input.empty()) {
101         return HERBST_NEED_MORE_ARGS;
102     }
103     if (input.front().empty()) {
104         output << input.command() << ": An empty tag name is not permitted\n";
105         return HERBST_INVALID_ARGUMENT;
106     }
107     HSTag* tag = add_tag(input.front());
108     hook_emit({"tag_added", tag->name});
109     return 0;
110 }
111 
removeTag(Input input,Output output)112 int TagManager::removeTag(Input input, Output output) {
113     string tagNameToRemove;
114     if (!(input >> tagNameToRemove)) {
115         return HERBST_NEED_MORE_ARGS;
116     }
117     auto targetTagName = input.empty() ? get_current_monitor()->tag->name : input.front();
118 
119     auto targetTag = find(targetTagName);
120     auto tagToRemove = find(tagNameToRemove);
121 
122     if (!tagToRemove) {
123         output << input.command() << ": Tag \"" << tagNameToRemove << "\" not found\n";
124         return HERBST_INVALID_ARGUMENT;
125     }
126 
127     if (!targetTag) {
128         output << input.command() << ": Tag \"" << targetTagName << "\" not found\n";
129         return HERBST_INVALID_ARGUMENT;
130     }
131 
132     if (find_monitor_with_tag(tagToRemove)) {
133         output << input.command() << ": Cannot merge the currently viewed tag\n";
134         return HERBST_TAG_IN_USE;
135     }
136 
137     // Prevent dangling tag_previous pointers
138     all_monitors_replace_previous_tag(tagToRemove, targetTag);
139 
140     // Collect all clients in tag
141     vector<Client*> clients;
142     tagToRemove->foreachClient([&clients](Client* client) {
143         clients.push_back(client);
144     });
145 
146     // Move clients to target tag
147     for (auto client : clients) {
148         client->tag()->removeClientSlice(client);
149         client->setTag(targetTag);
150         client->tag()->insertClientSlice(client);
151         targetTag->insertClient(client, {}, false);
152     }
153 
154     // Ask target tag to make transferred clients visible if necessary
155     Monitor* monitor_target = find_monitor_with_tag(targetTag);
156     if (monitor_target) {
157         monitor_target->applyLayout();
158         targetTag->setVisible(true);
159     }
160 
161     // Remove tag
162     string removedName = tagToRemove->name;
163     removeIndexed(index_of(tagToRemove));
164     Ewmh::get().updateCurrentDesktop();
165     Ewmh::get().updateDesktops();
166     Ewmh::get().updateDesktopNames();
167     tag_set_flags_dirty();
168     hook_emit({"tag_removed", removedName, targetTag->name()});
169 
170     return HERBST_EXIT_SUCCESS;
171 }
172 
tag_rename_command(Input input,Output output)173 int TagManager::tag_rename_command(Input input, Output output) {
174     string old_name, new_name;
175     if (!(input >> old_name >> new_name)) {
176         return HERBST_NEED_MORE_ARGS;
177     }
178     HSTag* tag = find(old_name);
179     if (!tag) {
180         output << input.command() << ": Tag \"" << old_name << "\" not found\n";
181         return HERBST_INVALID_ARGUMENT;
182     }
183     auto error = tag->name.change(new_name);
184     if (!error.empty()) {
185         output << input.command() << ": " << error << "\n";
186         return HERBST_INVALID_ARGUMENT;
187     }
188     return 0;
189 }
190 
onTagRename(HSTag * tag)191 void TagManager::onTagRename(HSTag* tag) {
192     Ewmh::get().updateDesktopNames();
193     hook_emit({"tag_renamed", tag->oldName_, tag->name()});
194 }
195 
ensure_tags_are_available()196 HSTag* TagManager::ensure_tags_are_available() {
197     if (size() > 0) {
198         return byIdx(0);
199     } else {
200         return add_tag("default");
201     }
202 }
203 
byIndexStr(const string & index_str,bool skip_visible_tags)204 HSTag* TagManager::byIndexStr(const string& index_str, bool skip_visible_tags) {
205     int index;
206     try {
207         index = stoi(index_str);
208     } catch (...) {
209         return nullptr;
210     }
211     // index must be treated relative, if its first char is + or -
212     bool is_relative = index_str[0] == '+' || index_str[0] == '-';
213     Monitor* monitor = get_current_monitor();
214     if (is_relative) {
215         int current = index_of(monitor->tag);
216         int delta = index;
217         index = delta + current;
218         // ensure index is valid
219         index = MOD(index, size());
220         if (skip_visible_tags) {
221             HSTag* tag = byIdx(index);
222             for (size_t i = 0; find_monitor_with_tag(tag); i++) {
223                 if (i >= size()) {
224                     // if we tried each tag then there is no invisible tag
225                     return nullptr;
226                 }
227                 index += delta;
228                 index = MOD(index, size());
229                 tag = byIdx(index);
230             }
231         }
232     } else {
233         // if it is absolute, then check index
234         if (index < 0 || (size_t)index >= size()) {
235             HSDebug("invalid tag index %d\n", index);
236             return nullptr;
237         }
238     }
239     return byIdx(index);
240 }
241 
moveFocusedClient(HSTag * target)242 void TagManager::moveFocusedClient(HSTag* target) {
243     Client* client = monitors_->focus()->tag->focusedClient();
244     if (!client) {
245         return;
246     }
247     moveClient(client, target);
248 }
249 
moveClient(Client * client,HSTag * target,string frameIndex,bool focus)250 void TagManager::moveClient(Client* client, HSTag* target, string frameIndex, bool focus) {
251     HSTag* tag_source = client->tag();
252     Monitor* monitor_source = find_monitor_with_tag(tag_source);
253     if (tag_source == target && frameIndex.empty()) {
254         // nothing to do
255         return;
256     }
257     Monitor* monitor_target = find_monitor_with_tag(target);
258     tag_source->removeClient(client);
259     // insert window into target
260     target->insertClient(client, frameIndex, focus);
261     if (tag_source != target) {
262         client->tag()->removeClientSlice(client);
263         client->setTag(target);
264         client->tag()->insertClientSlice(client);
265     }
266 
267     // refresh things, hide things, layout it, and then show it if needed
268     if (monitor_source && !monitor_target) {
269         // window is moved to invisible tag
270         // so hide it
271         client->set_visible(false);
272     }
273     if (monitor_source) {
274         monitor_source->applyLayout();
275     }
276     if (monitor_target) {
277         monitor_target->applyLayout();
278     }
279     if (!monitor_source && monitor_target) {
280         client->set_visible(!client->minimized_());
281     }
282     tag_set_flags_dirty();
283 }
284 
tag_move_window_command(Input argv,Output output)285 int TagManager::tag_move_window_command(Input argv, Output output) {
286     if (argv.empty()) {
287         return HERBST_NEED_MORE_ARGS;
288     }
289     HSTag* target = find(argv.front());
290     if (!target) {
291         output << argv.command() << ": Tag \"" << argv.front() << "\" not found\n";
292         return HERBST_INVALID_ARGUMENT;
293     }
294     moveFocusedClient(target);
295     return 0;
296 }
297 
tag_move_window_by_index_command(Input argv,Output output)298 int TagManager::tag_move_window_by_index_command(Input argv, Output output) {
299     if (argv.empty()) {
300         return HERBST_NEED_MORE_ARGS;
301     }
302     auto tagIndex = argv.front();
303     argv.shift();
304     bool skip_visible = (!argv.empty() && argv.front() == "--skip-visible");
305 
306     HSTag* tag = byIndexStr(tagIndex, skip_visible);
307     if (!tag) {
308         output << argv.command() << ": Invalid index \"" << tagIndex << "\"\n";
309         return HERBST_INVALID_ARGUMENT;
310     }
311     moveFocusedClient(tag);
312     return 0;
313 }
314 
frameCommand(FrameCommand cmd)315 function<int(Input, Output)> TagManager::frameCommand(FrameCommand cmd) {
316     return [cmd,this](Input input, Output output) -> int {
317         return cmd(*(this->focus_()->frame()), input, output);
318     };
319 }
320 
frameCommand(FrameCommand cmd,FrameCompleter completer)321 CommandBinding TagManager::frameCommand(FrameCommand cmd, FrameCompleter completer)
322 {
323     return { frameCommand(cmd), frameCompletion(completer) };
324 }
frameCommand(function<int (FrameTree &)> cmd)325 function<int()> TagManager::frameCommand(function<int(FrameTree&)> cmd) {
326     return [cmd,this]() -> int {
327         return cmd(*(this->focus_()->frame()));
328     };
329 }
330 
frameCompletion(FrameCompleter completer)331 function<void(Completion&)> TagManager::frameCompletion(FrameCompleter completer) {
332     return [completer,this](Completion& complete) {
333         FrameTree* ft = focus_()->frame();
334         (ft ->* completer)(complete);
335     };
336 }
337 
unusedTag()338 HSTag* TagManager::unusedTag() {
339     for (auto t : *this) {
340         if (!find_monitor_with_tag(&* t)) {
341             return t;
342         }
343     }
344     return nullptr;
345 }
346 
updateFocusObject(Monitor * focusedMonitor)347 void TagManager::updateFocusObject(Monitor* focusedMonitor) {
348     focus_ = focusedMonitor->tag;
349 }
350 
floatingCmd(Input input,Output output)351 int TagManager::floatingCmd(Input input, Output output) {
352     // usage: floating [[tag] on|off|toggle|status]
353     string newValue, tagName;
354     if (input.size() >= 2) {
355         input >> tagName >> newValue;
356     } else if (input.size() == 1) {
357         input >> newValue;
358     } else {
359         tagName = "";
360     }
361     if (newValue.empty()) {
362         newValue = "toggle";
363     }
364     HSTag* tag = monitors_->focus()->tag;
365     if (!tagName.empty()) {
366         tag = find(tagName);
367         if (!tag) {
368             output << input.command() << ": Tag \"" << tagName << "\" not found\n";
369             return HERBST_INVALID_ARGUMENT;
370         }
371     }
372     if (newValue == "status") {
373         output << (tag->floating ? "on" : "off");
374     } else {
375         string msg = tag->floating.change(newValue);
376         if (!msg.empty()) {
377             output << msg << "\n";
378             return HERBST_INVALID_ARGUMENT;
379         }
380     }
381     return 0;
382 }
383 
floatingComplete(Completion & complete)384 void TagManager::floatingComplete(Completion &complete)
385 {
386    if (complete == 0) {
387        completeTag(complete);
388    }
389    if (complete == 0 || complete == 1) {
390        if (complete == 1 && !find(complete[0])) {
391            // if the first parameter is not a tag, then
392            // there can't be a second parameter
393            complete.none();
394            return;
395        }
396        complete.full("status");
397        // here, we pass a bool-pointer to the completion to get 'toggle' as one of the completion options
398        // This is much simpler than passing the actual floating state of the tag
399        bool b = true;
400        Converter<bool>::complete(complete, &b);
401    } else {
402        complete.none();
403    }
404 }
405