1 #include "command.h"
2
3 #include <cstdio>
4 #include <cstring>
5 #include <sstream>
6
7 #include "client.h"
8 #include "completion.h"
9 #include "hlwmcommon.h"
10 #include "ipc-protocol.h"
11 #include "monitor.h"
12 #include "monitormanager.h"
13 #include "root.h"
14 #include "tag.h"
15 #include "utils.h"
16
17 using std::endl;
18 using std::function;
19 using std::shared_ptr;
20 using std::string;
21 using std::to_string;
22 using std::unique_ptr;
23 using std::vector;
24
25 static void try_complete(const char* needle, string to_check, Output output);
26 static void try_complete(const char* needle, const char* to_check, Output output);
27
28 static void complete_against_tags(int argc, char** argv, int pos, Output output);
29 static void complete_against_monitors(int argc, char** argv, int pos, Output output);
30 static void complete_against_winids(int argc, char** argv, int pos, Output output);
31 static void complete_merge_tag(int argc, char** argv, int pos, Output output);
32 static int complete_against_commands(int argc, char** argv, int position, Output output);
33
34 // if the current completion needs shell quoting and other shell specific
35 // behaviour
36 static bool g_shell_quoting = false;
37
38 static const char* completion_directions[] = { "left", "right", "down", "up",nullptr};
39 static const char* completion_special_winids[]= { "urgent", "", nullptr };
40 static const char* completion_use_index_args[]= { "--skip-visible", nullptr };
41 static const char* completion_pm_one[]= { "+1", "-1", nullptr };
42 static const char* completion_split_modes[]= { "horizontal", "vertical", "left", "right", "top", "bottom", "explode", "auto", nullptr };
43 static const char* completion_split_ratios[]= {
44 "0.1", "0.2", "0.3", "0.4", "0.5", "0.6", "0.7", "0.8", "0.9", nullptr };
45
no_completion(int,char **,int)46 static bool no_completion(int, char**, int) {
47 return false;
48 }
49
50 static bool first_parameter_is_tag(int argc, char** argv, int pos);
51
52 /* find out, if a command still expects a parameter at a certain index.
53 * only if this returns true, than a completion will be searched.
54 *
55 * if no match is found, then it defaults to "command still expects a
56 * parameter".
57 */
58 struct {
59 const char* command; /* the first argument */
60 int min_index; /* rule will only be considered */
61 /* if current pos >= min_index */
62 bool (*function)(int argc, char** argv, int pos);
63 } g_parameter_expected[] = {
64 { "focus_nth", 2, no_completion },
65 { "close", 2, no_completion },
66 { "cycle", 2, no_completion },
67 { "split", 4, no_completion },
68 { "raise", 2, no_completion },
69 { "jumpto", 2, no_completion },
70 { "bring", 2, no_completion },
71 { "focus_edge", 2, no_completion },
72 { "shift_edge", 2, no_completion },
73 { "cycle_monitor", 2, no_completion },
74 { "focus_monitor", 2, no_completion },
75 { "shift_to_monitor",2, no_completion },
76 { "add", 2, no_completion },
77 { "use", 2, no_completion },
78 { "use_index", 3, no_completion },
79 { "merge_tag", 3, no_completion },
80 { "rename", 3, no_completion },
81 { "move", 2, no_completion },
82 { "move_index", 3, no_completion },
83 { "add_monitor", 7, no_completion },
84 { "remove_monitor", 2, no_completion },
85 { "monitor_rect", 3, no_completion },
86 { "pad", 6, no_completion },
87 { "layout", 3, no_completion },
88 { "dump", 3, no_completion },
89 { "load", 3, no_completion },
90 { "load", 2, first_parameter_is_tag },
91 { "tag_status", 2, no_completion },
92 { "object_tree", 2, no_completion },
93 };
94
95 enum IndexCompare {
96 LE, /* lower equal */
97 EQ, /* equal to */
98 GE, /* greater equal */
99 };
100
101 /* list of completions, if a line matches, then it will be used, the order
102 * does not matter */
103 struct {
104 const char* command;
105 IndexCompare relation; /* defines how the index matches */
106 int index; /* which parameter to complete */
107 /* command name is index = 0 */
108 /* GE 0 matches any position */
109 /* LE 3 matches position from 0 to 3 */
110 /* === various methods, how to complete === */
111 /* completion by function */
112 void (*function)(int argc, char** argv, int pos, Output output);
113 /* completion by a list of strings */
114 const char** list;
115 } g_completions[] = {
116 /* name , relation, index, completion method */
117 { "add_monitor", EQ, 2, complete_against_tags, 0 },
118 { "bring", EQ, 1, nullptr, completion_special_winids },
119 { "bring", EQ, 1, complete_against_winids, 0 },
120 { "close", EQ, 1, complete_against_winids, 0 },
121 { "cycle", EQ, 1, nullptr, completion_pm_one },
122 { "cycle_monitor", EQ, 1, nullptr, completion_pm_one },
123 { "dump", EQ, 1, complete_against_tags, 0 },
124 { "layout", EQ, 1, complete_against_tags, 0 },
125 { "load", EQ, 1, complete_against_tags, 0 },
126 { "merge_tag", EQ, 1, complete_against_tags, 0 },
127 { "merge_tag", EQ, 2, complete_merge_tag, 0 },
128 { "move", EQ, 1, complete_against_tags, 0 },
129 { "move_index", EQ, 2, nullptr, completion_use_index_args },
130 { "rename", EQ, 1, complete_against_tags, 0 },
131 { "raise", EQ, 1, nullptr, completion_special_winids },
132 { "raise", EQ, 1, complete_against_winids, 0 },
133 { "jumpto", EQ, 1, nullptr, completion_special_winids },
134 { "jumpto", EQ, 1, complete_against_winids, 0 },
135 { "shift_edge", EQ, 1, nullptr, completion_directions },
136 { "split", EQ, 1, nullptr, completion_split_modes },
137 { "split", EQ, 2, nullptr, completion_split_ratios },
138 { "use", EQ, 1, complete_against_tags, 0 },
139 { "use_index", EQ, 1, nullptr, completion_pm_one },
140 { "use_index", EQ, 2, nullptr, completion_use_index_args },
141 { "focus_monitor", EQ, 1, complete_against_monitors, 0 },
142 { "shift_to_monitor",EQ, 1, complete_against_monitors, 0 },
143 { "remove_monitor", EQ, 1, complete_against_monitors, 0 },
144 { "name_monitor", EQ, 1, complete_against_monitors, 0 },
145 { "monitor_rect", EQ, 1, complete_against_monitors, 0 },
146 { "pad", EQ, 1, complete_against_monitors, 0 },
147 { "tag_status", EQ, 1, complete_against_monitors, 0 },
148 };
149
150 // Implementation of CommandBinding
151
CommandBinding(function<int (Output)> cmd)152 CommandBinding::CommandBinding(function<int(Output)> cmd)
153 : command([cmd](Input, Output output) { return cmd(output); })
__anonb96659db0402(Completion& c) 154 , completion_([](Completion& c) { c.none(); })
155 {}
156
CommandBinding(function<int ()> cmd)157 CommandBinding::CommandBinding(function<int()> cmd)
158 : command([cmd](Input, Output) { return cmd(); })
__anonb96659db0602(Completion& c) 159 , completion_([](Completion& c) { c.none(); })
160 {}
161
162 // Nearly all of the following can go away, if all C-style command functions
163 // have been migrated to int(Input, Output).
164
165 /* Creates an ephemeral argv array from the given Input */
commandFromCFunc(function<int (int argc,char ** argv,Output output)> func)166 function <int(Input,Output)> CommandBinding::commandFromCFunc(
167 function <int(int argc, char**argv, Output output)> func) {
168 return [func](Input args, Output out) {
169 /* Note that instead of copying the arguments, we point to their
170 * original location here. This only works because Input stores its
171 * payload in shared pointers and other references to them are held
172 * until the command is finished.
173 */
174 shared_ptr<char*> argv(new char*[args.size() + 1],
175 std::default_delete<char*[]>());
176
177 // Most of the commands want a char**, not a const char**. Let's
178 // hope, they don't actually modify it.
179 argv.get()[0] = const_cast<char*>(args.command().c_str());
180 auto elem = args.begin();
181 for (size_t i = 0; i < args.size(); i++, elem++) {
182 argv.get()[i+1] = const_cast<char*>(elem->c_str());
183 }
184
185 return func(args.size() + 1, argv.get(), out);
186 };
187 }
188
CommandBinding(int func (int argc,const char ** argv,Output output))189 CommandBinding::CommandBinding(int func(int argc, const char** argv, Output output))
190 : command(commandFromCFunc([func](int argc, char** argv, Output out) {
191 return func(argc, const_cast<const char**>(argv), out);
192 }))
193 {}
194
CommandBinding(int func (int argc,char ** argv))195 CommandBinding::CommandBinding(int func(int argc, char** argv))
196 : command(commandFromCFunc([func](int argc, char **argv, Output) {
197 return func(argc, argv);
198 }))
199 {}
200
CommandBinding(int func (int argc,const char ** argv))201 CommandBinding::CommandBinding(int func(int argc, const char** argv))
202 : command(commandFromCFunc([func](int argc, char** argv, Output) {
203 return func(argc, const_cast<const char**>(argv));
204 }))
205 {}
206
207
208 /** Complete the given list of arguments
209 */
complete(Completion & completion) const210 void CommandBinding::complete(Completion& completion) const {
211 if ((bool) completion_) {
212 completion_(completion);
213 }
214 }
215
216 // Implementation of CommandTable
callCommand(Input args,Output out) const217 int CommandTable::callCommand(Input args, Output out) const {
218 if (args.command().empty()) {
219 // may happen if you call sprintf, but nothing afterwards
220 return HERBST_NEED_MORE_ARGS;
221 }
222
223 const auto cmd = map.find(args.command());
224
225 if (cmd == map.end()) {
226 out << "error: Command \"" << args.command() << "\" not found" << endl;
227 return HERBST_COMMAND_NOT_FOUND;
228 }
229
230 return cmd->second(args, out);
231 }
232
233 namespace Commands {
234 shared_ptr<const CommandTable> command_table;
235 }
236
initialize(unique_ptr<const CommandTable> commands)237 void Commands::initialize(unique_ptr<const CommandTable> commands) {
238 if (!command_table) {
239 command_table = move(commands);
240 }
241 // TODO What do we do in the 'already initialized' case?
242 }
243
call(Input args,Output out)244 int Commands::call(Input args, Output out) {
245 if (!command_table) {
246 return HERBST_COMMAND_NOT_FOUND;
247 }
248 return command_table->callCommand(args, out);
249 }
250
commandExists(const string & commandName)251 bool Commands::commandExists(const string& commandName)
252 {
253 if (!command_table) {
254 return false;
255 }
256 return command_table->find(commandName) != command_table->end();
257 }
258
get()259 shared_ptr<const CommandTable> Commands::get() {
260 if (!command_table) {
261 throw std::logic_error("CommandTable not initialized, but get() called.");
262 }
263 return command_table;
264 }
265
complete(Completion & completion)266 void Commands::complete(Completion& completion) {
267 // wrap around complete_against_commands
268 // Once we have migrated all completions, we can implement command
269 // completion directly with the minimal interface that Completion provides.
270 // Then we can also unfriend this function from the Completion class.
271 char** argv = new char*[completion.args_.size() + 1];
272 argv[completion.args_.size()] = nullptr;
273 for (size_t i = 0; i < completion.args_.size(); i++) {
274 argv[i] = const_cast<char*>((completion.args_.begin() + i)->c_str());
275 }
276 int res = complete_against_commands(completion.args_.size(),
277 argv,
278 completion.index_,
279 completion.output_);
280 delete[] argv;
281 if (res == HERBST_NO_PARAMETER_EXPECTED) {
282 completion.none();
283 } else if (res != 0) {
284 completion.invalidArguments();
285 }
286 }
287
list_commands(Output output)288 int list_commands(Output output)
289 {
290 for (auto cmd : *Commands::get()) {
291 output << cmd.first << endl;
292 }
293 return 0;
294 }
295
try_complete_suffix(const char * needle,const char * to_check,const char * suffix,const char * prefix,Output output)296 static void try_complete_suffix(const char* needle, const char* to_check, const char* suffix,
297 const char* prefix, Output output)
298 {
299 bool matches = !needle;
300 if (!matches) {
301 matches = true; // set it to true if the loop successfully runs
302 // find the first difference between needle and to_check
303 for (int i = 0; true ; i++) {
304 // check if needle is a prefix of to_check
305 if (!needle[i]) {
306 break;
307 }
308 // if the needle is longer than to_check, then needle isn't a
309 // correct prefix of to_check
310 if (!to_check[i]) {
311 matches = false;
312 break;
313 }
314 // only proceed if they are identical
315 if (to_check[i] != needle[i]) {
316 matches = false;
317 break;
318 }
319 }
320 }
321 if (matches) {
322 char* escaped = nullptr;
323 if (g_shell_quoting) {
324 escaped = posix_sh_escape(to_check);
325 }
326 char* prefix_escaped = nullptr;
327 if (prefix) {
328 if (g_shell_quoting) {
329 prefix_escaped = posix_sh_escape(prefix);
330 }
331 output << (prefix_escaped ? prefix_escaped : prefix);
332 }
333 output << (escaped ? escaped : to_check);
334 free(escaped);
335 output << suffix;
336 }
337 }
338
try_complete(const char * needle,string to_check,Output output)339 void try_complete(const char* needle, string to_check, Output output) {
340 try_complete(needle, to_check.c_str(), output);
341 }
342
try_complete(const char * needle,const char * to_check,Output output)343 void try_complete(const char* needle, const char* to_check, Output output) {
344 const char* suffix = g_shell_quoting ? " \n" : "\n";
345 try_complete_suffix(needle, to_check, suffix, nullptr, output);
346 }
347
complete_against_list(const char * needle,const char ** list,Output output)348 static void complete_against_list(const char* needle, const char** list, Output output) {
349 while (*list) {
350 const char* name = *list;
351 try_complete(needle, name, output);
352 list++;
353 }
354 }
355
complete_against_tags(int argc,char ** argv,int pos,Output output)356 void complete_against_tags(int argc, char** argv, int pos, Output output) {
357 const char* needle;
358 if (pos >= argc) {
359 needle = "";
360 } else {
361 needle = argv[pos];
362 }
363 for (int i = 0; i < tag_get_count(); i++) {
364 const char* name = get_tag_by_index(i)->name->c_str();
365 try_complete(needle, name, output);
366 }
367 }
368
complete_against_monitors(int argc,char ** argv,int pos,Output output)369 void complete_against_monitors(int argc, char** argv, int pos, Output output) {
370 const char* needle;
371 if (pos >= argc) {
372 needle = "";
373 } else {
374 needle = argv[pos];
375 }
376 // complete against relative indices
377 try_complete(needle, "-1", output);
378 try_complete(needle, "+1", output);
379 try_complete(needle, "+0", output);
380 for (auto m : *g_monitors) {
381 // complete against the absolute index
382 try_complete(needle, to_string(m->index()), output);
383 // complete against the name
384 if (m->name != "") {
385 try_complete(needle, m->name->c_str(), output);
386 }
387 }
388 }
389
complete_against_winids(int argc,char ** argv,int pos,Output output)390 void complete_against_winids(int argc, char** argv, int pos, Output output) {
391 const char* needle;
392 if (pos >= argc) {
393 needle = "";
394 } else {
395 needle = argv[pos];
396 }
397 for (auto c : Root::common().clients()) {
398 char buf[100];
399 snprintf(buf, LENGTH(buf), "0x%lx", c.second->window_);
400 try_complete(needle, buf, output);
401 }
402 }
403
complete_merge_tag(int argc,char ** argv,int pos,Output output)404 void complete_merge_tag(int argc, char** argv, int pos, Output output) {
405 const char* first = (argc > 1) ? argv[1] : "";
406 const char* needle;
407 if (pos >= argc) {
408 needle = "";
409 } else {
410 needle = argv[pos];
411 }
412 for (int i = 0; i < tag_get_count(); i++) {
413 const char* name = get_tag_by_index(i)->name->c_str();
414 if (!strcmp(name, first)) {
415 // merge target must not be equal to tag to remove
416 continue;
417 }
418 try_complete(needle, name, output);
419 }
420 }
421
parameter_expected(int argc,char ** argv,int pos)422 static bool parameter_expected(int argc, char** argv, int pos) {
423 if (pos <= 0 || argc < 1) {
424 /* no parameter if there is no command */
425 return false;
426 }
427 for (size_t i = 0; i < LENGTH(g_parameter_expected)
428 && g_parameter_expected[i].command; i++) {
429 if (pos < g_parameter_expected[i].min_index) {
430 continue;
431 }
432 if (!strcmp(g_parameter_expected[i].command, argv[0])) {
433 return g_parameter_expected[i].function(argc, argv, pos);
434 }
435 }
436 return true;
437 }
438
complete_command(int argc,char ** argv,Output output)439 int complete_command(int argc, char** argv, Output output) {
440 // usage: complete POSITION command to complete ...
441 if (argc < 2) {
442 return HERBST_NEED_MORE_ARGS;
443 }
444 char* cmdname = argv[0];
445 g_shell_quoting = !strcmp(cmdname, "complete_shell");
446 // index must be between first and last arg of "command to complete ..."
447 int position = CLAMP(atoi(argv[1]), 0, argc-2);
448 (void)SHIFT(argc, argv);
449 (void)SHIFT(argc, argv);
450 if (g_shell_quoting) {
451 for (int i = 0; i < argc; i++) {
452 posix_sh_compress_inplace(argv[i]);
453 }
454 }
455 return complete_against_commands(argc, argv, position, output);
456 }
457
complete_against_commands(int argc,char ** argv,int position,Output output)458 int complete_against_commands(int argc, char** argv, int position,
459 Output output) {
460 // complete command
461 if (position == 0) {
462 char* str = (argc >= 1) ? argv[0] : nullptr;
463 for (auto cmd : *Commands::get()) {
464 // only check the first len bytes
465 try_complete(str, cmd.first.c_str(), output);
466 }
467 return 0;
468 }
469 // try to get completion from the command binding
470 string commandName = argv[0];
471 auto commandTable = Commands::get();
472 auto it = commandTable->find(commandName);
473 if (it == commandTable->end()) {
474 return HERBST_INVALID_ARGUMENT;
475 }
476 if (it->second.hasCompletion()) {
477 vector<string> arguments;
478 for (int i = 1; i < argc; i++) {
479 arguments.push_back(argv[i]);
480 }
481 // the new completion context has the command name removed
482 Completion completion(arguments, position - 1, g_shell_quoting, output);
483 it->second.complete(completion);
484 return completion.noParameterExpected() ?
485 HERBST_NO_PARAMETER_EXPECTED
486 : 0;
487 }
488 if (!parameter_expected(argc, argv, position)) {
489 return HERBST_NO_PARAMETER_EXPECTED;
490 }
491 if (argc >= 1) {
492 const char* cmd_str = (argc >= 1) ? argv[0] : "";
493 // complete parameters for commands
494 for (size_t i = 0; i < LENGTH(g_completions); i++) {
495 bool matches = false;
496 switch (g_completions[i].relation) {
497 case LE: matches = position <= g_completions[i].index; break;
498 case EQ: matches = position == g_completions[i].index; break;
499 case GE: matches = position >= g_completions[i].index; break;
500 }
501 if (!matches
502 || !g_completions[i].command
503 || strcmp(cmd_str, g_completions[i].command) != 0) {
504 continue;
505 }
506 const char* needle = (position < argc) ? argv[position] : "";
507 if (!needle) {
508 needle = "";
509 }
510 // try to complete
511 if (g_completions[i].function) {
512 g_completions[i].function(argc, argv, position, output);
513 }
514 if (g_completions[i].list) {
515 complete_against_list(needle, g_completions[i].list,
516 output);
517 }
518 }
519 }
520 return 0;
521 }
522
first_parameter_is_tag(int argc,char ** argv,int pos)523 static bool first_parameter_is_tag(int argc, char** argv, int pos) {
524 // only complete if first parameter is a valid tag
525 if (argc >= 2 && find_tag(argv[1]) && pos == 2) {
526 return true;
527 } else {
528 return false;
529 }
530 }
531