1 #include <X11/X.h>
2 #include <X11/Xlib.h>
3 #include <getopt.h>
4 #include <locale.h>
5 #include <sys/wait.h>
6 #include <unistd.h>
7 #include <csignal>
8 #include <cstdio>
9 #include <cstdlib>
10 #include <iostream>
11 #include <vector>
12
13 #include "client.h"
14 #include "clientmanager.h"
15 #include "command.h"
16 #include "commandio.h"
17 #include "ewmh.h"
18 #include "fontdata.h"
19 #include "frametree.h"
20 #include "globals.h"
21 #include "hook.h"
22 #include "ipc-protocol.h"
23 #include "ipc-server.h"
24 #include "keymanager.h"
25 #include "layout.h"
26 #include "metacommands.h"
27 #include "monitordetection.h"
28 #include "monitormanager.h"
29 #include "mousemanager.h"
30 #include "rectangle.h"
31 #include "root.h"
32 #include "rulemanager.h"
33 #include "settings.h"
34 #include "tagmanager.h"
35 #include "tmp.h"
36 #include "utils.h"
37 #include "watchers.h"
38 #include "xconnection.h"
39 #include "xmainloop.h"
40
41 using std::endl;
42 using std::make_shared;
43 using std::pair;
44 using std::shared_ptr;
45 using std::string;
46 using std::unique_ptr;
47
48 // globals:
49 int g_verbose = 0;
50 Display* g_display;
51 int g_screen;
52 Window g_root;
53
54 // module internals:
55 static char* g_autostart_path = nullptr; // if not set, then find it in $HOME or $XDG_CONFIG_HOME
56 static bool g_exec_before_quit = false;
57 static char** g_exec_args = nullptr;
58 static XMainLoop* g_main_loop = nullptr;
59
60 int quit();
61 int version(Output output);
62 int print_tag_status_command(int argc, char** argv, Output output);
63 void execute_autostart_file();
64 int raise_command(int argc, char** argv, Output output);
65 int spawn(int argc, char** argv);
66 int wmexec(int argc, char** argv);
67 static void remove_zombies(int signal);
68 int custom_hook_emit(Input input);
69 int jumpto_command(int argc, char** argv, Output output);
70
commands(shared_ptr<Root> root)71 unique_ptr<CommandTable> commands(shared_ptr<Root> root) {
72 MetaCommands* meta_commands = root->meta_commands.get();
73
74 ClientManager* clients = root->clients();
75 KeyManager *keys = root->keys();
76 MonitorManager* monitors = root->monitors();
77 MouseManager* mouse = root->mouse();
78 RuleManager* rules = root->rules();
79 Settings* settings = root->settings();
80 TagManager* tags = root->tags();
81 Tmp* tmp = root->tmp();
82 Watchers* watchers = root->watchers();
83
84 std::initializer_list<pair<const string,CommandBinding>> init =
85 {
86 {"quit", { quit } },
87 {"echo", {meta_commands, &MetaCommands::echoCommand,
88 &MetaCommands::echoCompletion }},
89 {"true", {[] { return 0; }}},
90 {"false", {[] { return 1; }}},
91 {"try", {meta_commands, &MetaCommands::tryCommand,
92 &MetaCommands::completeCommandShifted1}},
93 {"silent", {meta_commands, &MetaCommands::silentCommand,
94 &MetaCommands::completeCommandShifted1}},
95 {"reload", {[] { execute_autostart_file(); return 0; }}},
96 {"version", { version }},
97 {"list_commands", { list_commands }},
98 {"list_monitors", {monitors, &MonitorManager::list_monitors }},
99 {"set_monitors", {monitors, &MonitorManager::setMonitorsCommand,
100 &MonitorManager::setMonitorsCompletion} },
101 {"disjoin_rects", disjoin_rects_command},
102 {"list_keybinds", {keys, &KeyManager::listKeybindsCommand }},
103 {"list_padding", monitors->byFirstArg(&Monitor::list_padding, &Monitor::noComplete) },
104 {"keybind", {keys, &KeyManager::addKeybindCommand,
105 &KeyManager::addKeybindCompletion}},
106 {"keyunbind", {keys, &KeyManager::removeKeybindCommand,
107 &KeyManager::removeKeybindCompletion}},
108 {"mousebind", {mouse, &MouseManager::addMouseBindCommand,
109 &MouseManager::addMouseBindCompletion}},
110 {"mouseunbind", {mouse, &MouseManager::mouse_unbind_all }},
111 {"drag", {mouse, &MouseManager::dragCommand,
112 &MouseManager::dragCompletion}},
113 {"spawn", spawn},
114 {"wmexec", wmexec},
115 {"emit_hook", { custom_hook_emit }},
116 {"bring", frame_current_bring},
117 {"focus_nth", { tags->frameCommand(&FrameTree::focusNthCommand) }},
118 {"cycle", { tags->frameCommand(&FrameTree::cycleSelectionCommand) }},
119 {"cycle_all", monitors->tagCommand(&HSTag::cycleAllCommand,
120 &HSTag::cycleAllCompletion)},
121 {"cycle_layout", tags->frameCommand(&FrameTree::cycleLayoutCommand, &FrameTree::cycleLayoutCompletion) },
122 {"cycle_frame", { tags->frameCommand(&FrameTree::cycleFrameCommand) }},
123 {"close", { close_command }},
124 {"close_or_remove",{ monitors->tagCommand(&HSTag::closeOrRemoveCommand) }},
125 {"close_and_remove",{ monitors->tagCommand(&HSTag::closeAndRemoveCommand) }},
126 {"split", { tags->frameCommand(&FrameTree::splitCommand) }},
127 {"resize", monitors->tagCommand(&HSTag::resizeCommand,
128 &HSTag::resizeCompletion)},
129 {"focus_edge", frame_focus_edge},
130 {"focus", monitors->tagCommand(&HSTag::focusInDirCommand,
131 &HSTag::focusInDirCompletion)},
132 {"shift_edge", frame_move_window_edge},
133 {"shift", monitors->tagCommand(&HSTag::shiftInDirCommand,
134 &HSTag::shiftInDirCompletion)},
135 {"shift_to_monitor",shift_to_monitor},
136 {"remove", { tags->frameCommand(&FrameTree::removeFrameCommand) }},
137 {"set", { settings, &Settings::set_cmd,
138 &Settings::set_complete }},
139 {"get", { settings, &Settings::get_cmd,
140 &Settings::get_complete }},
141 {"toggle", { settings, &Settings::toggle_cmd,
142 &Settings::toggle_complete}},
143 {"cycle_value", { settings, &Settings::cycle_value_cmd,
144 &Settings::cycle_value_complete}},
145 {"cycle_monitor", monitor_cycle_command},
146 {"focus_monitor", monitor_focus_command},
147 {"add", BIND_OBJECT(tags, tag_add_command) },
148 {"use", monitor_set_tag_command},
149 {"use_index", monitor_set_tag_by_index_command},
150 {"use_previous", { monitor_set_previous_tag_command }},
151 {"jumpto", jumpto_command},
152 {"floating", { tags, &TagManager::floatingCmd,
153 &TagManager::floatingComplete }},
154 {"fullscreen", {clients, &ClientManager::fullscreen_cmd,
155 &ClientManager::fullscreen_complete}},
156 {"pseudotile", {clients, &ClientManager::pseudotile_cmd,
157 &ClientManager::pseudotile_complete}},
158 {"tag_status", print_tag_status_command},
159 {"merge_tag", BIND_OBJECT(tags, removeTag)},
160 {"rename", BIND_OBJECT(tags, tag_rename_command) },
161 {"move", BIND_OBJECT(tags, tag_move_window_command) },
162 {"rotate", { tags->frameCommand(&FrameTree::rotateCommand) }},
163 {"mirror", { tags->frameCommand(&FrameTree::mirrorCommand, &FrameTree::mirrorCompletion) }},
164 {"move_index", BIND_OBJECT(tags, tag_move_window_by_index_command) },
165 {"add_monitor", BIND_OBJECT(monitors, addMonitor)},
166 {"raise_monitor", { monitors, &MonitorManager::raiseMonitorCommand,
167 &MonitorManager::raiseMonitorCompletion }},
168 {"remove_monitor", BIND_OBJECT(monitors, removeMonitor)},
169 {"move_monitor", monitors->byFirstArg(&Monitor::move_cmd, &Monitor::move_complete) } ,
170 {"rename_monitor", monitors->byFirstArg(&Monitor::renameCommand, &Monitor::renameComplete) },
171 {"monitor_rect", monitor_rect_command},
172 {"pad", monitor_set_pad_command},
173 {"raise", raise_command},
174 {"rule", {rules, &RuleManager::addRuleCommand,
175 &RuleManager::addRuleCompletion}},
176 {"unrule", {rules, &RuleManager::unruleCommand,
177 &RuleManager::unruleCompletion}},
178 {"apply_rules", {clients, &ClientManager::applyRulesCmd,
179 &ClientManager::applyRulesCompletion}},
180 {"apply_tmp_rule", {clients, &ClientManager::applyTmpRuleCmd,
181 &ClientManager::applyTmpRuleCompletion}},
182 {"list_rules", {rules, &RuleManager::listRulesCommand }},
183 {"layout", tags->frameCommand(&FrameTree::dumpLayoutCommand, &FrameTree::dumpLayoutCompletion)},
184 {"stack", { monitors, &MonitorManager::stackCommand }},
185 {"dump", tags->frameCommand(&FrameTree::dumpLayoutCommand, &FrameTree::dumpLayoutCompletion)},
186 {"load", { tags->frameCommand(&FrameTree::loadCommand) }},
187 {"complete", complete_command},
188 {"complete_shell", complete_command},
189 {"lock", { [monitors] { monitors->lock(); return 0; } }},
190 {"unlock", { [monitors] { monitors->unlock(); return 0; } }},
191 {"lock_tag", monitors->byFirstArg(&Monitor::lock_tag_cmd, &Monitor::noComplete) },
192 {"unlock_tag", monitors->byFirstArg(&Monitor::unlock_tag_cmd, &Monitor::noComplete) },
193 {"set_layout", { tags->frameCommand(&FrameTree::setLayoutCommand, &FrameTree::setLayoutCompletion) }},
194 {"detect_monitors",{ monitors, &MonitorManager::detectMonitorsCommand,
195 &MonitorManager::detectMonitorsCompletion }},
196 {"!", { meta_commands, &MetaCommands::negateCommand,
197 &MetaCommands::completeCommandShifted1 }},
198 {"chain", { meta_commands, &MetaCommands::chainCommand,
199 &MetaCommands::chainCompletion}},
200 {"and", { meta_commands, &MetaCommands::chainCommand,
201 &MetaCommands::chainCompletion}},
202 {"or", { meta_commands, &MetaCommands::chainCommand,
203 &MetaCommands::chainCompletion}},
204 {"object_tree", { meta_commands, &MetaCommands::print_object_tree_command,
205 &MetaCommands::print_object_tree_complete} },
206 {"substitute", { meta_commands, &MetaCommands::substitute_cmd,
207 &MetaCommands::substitute_complete} },
208 {"foreach", { meta_commands, &MetaCommands::foreachCmd,
209 &MetaCommands::foreachComplete} },
210 {"sprintf", { meta_commands, &MetaCommands::sprintf_cmd,
211 &MetaCommands::sprintf_complete} },
212 {"new_attr", { meta_commands, &MetaCommands::new_attr_cmd,
213 &MetaCommands::new_attr_complete} },
214 {"remove_attr", { meta_commands, &MetaCommands::remove_attr_cmd,
215 &MetaCommands::remove_attr_complete }},
216 {"compare", { meta_commands, &MetaCommands::compare_cmd,
217 &MetaCommands::compare_complete} },
218 {"getenv", { meta_commands, &MetaCommands::getenvCommand,
219 &MetaCommands::getenvUnsetenvCompletion}},
220 {"setenv", { meta_commands, &MetaCommands::setenvCommand,
221 &MetaCommands::setenvCompletion}},
222 {"export", { meta_commands, &MetaCommands::exportEnvCommand,
223 &MetaCommands::exportEnvCompletion}},
224 {"unsetenv", { meta_commands, &MetaCommands::unsetenvCommand,
225 &MetaCommands::getenvUnsetenvCompletion}},
226 {"get_attr", { meta_commands, &MetaCommands::get_attr_cmd,
227 &MetaCommands::get_attr_complete }},
228 {"set_attr", { meta_commands, &MetaCommands::set_attr_cmd,
229 &MetaCommands::set_attr_complete }},
230 {"help", { meta_commands, &MetaCommands::helpCommand,
231 &MetaCommands::helpCompletion }},
232 {"attr", { meta_commands, &MetaCommands::attr_cmd,
233 &MetaCommands::attr_complete }},
234 {"watch", { watchers, &Watchers::watchCommand,
235 &Watchers::watchCompletion }},
236 {"mktemp", { tmp, &Tmp::mktemp,
237 &Tmp::mktempComplete }},
238 };
239 return unique_ptr<CommandTable>(new CommandTable(init));
240 }
241
242 // core functions
quit()243 int quit() {
244 if (g_main_loop) {
245 g_main_loop->quit();
246 }
247 return 0;
248 }
249
version(Output output)250 int version(Output output) {
251 output << WINDOW_MANAGER_NAME << " " << HERBSTLUFT_VERSION << endl;
252 output << "Copyright (c) 2011-2021 Thorsten Wißmann" << endl;
253 output << "Released under the Simplified BSD License" << endl;
254 for (const auto& d : MonitorDetection::detectors()) {
255 output << d.name_ << " support: " << (d.detect_ ? "on" : "off") << endl;
256 }
257 return 0;
258 }
259
print_tag_status_command(int argc,char ** argv,Output output)260 int print_tag_status_command(int argc, char** argv, Output output) {
261 Monitor* monitor;
262 if (argc >= 2) {
263 monitor = string_to_monitor(argv[1]);
264 } else {
265 monitor = get_current_monitor();
266 }
267 if (!monitor) {
268 output << argv[0] << ": Monitor \"" << argv[1] << "\" not found!\n";
269 return HERBST_INVALID_ARGUMENT;
270 }
271 tag_update_flags();
272 output << '\t';
273 for (int i = 0; i < tag_get_count(); i++) {
274 HSTag* tag = get_tag_by_index(i);
275 // print flags
276 char c = '.';
277 if (tag->flags & TAG_FLAG_USED) {
278 c = ':';
279 }
280 Monitor *tag_monitor = find_monitor_with_tag(tag);
281 if (tag_monitor == monitor) {
282 c = '+';
283 if (monitor == get_current_monitor()) {
284 c = '#';
285 }
286 } else if (tag_monitor) {
287 c = '-';
288 if (get_current_monitor() == tag_monitor) {
289 c = '%';
290 }
291 }
292 if (tag->flags & TAG_FLAG_URGENT) {
293 c = '!';
294 }
295 output << c;
296 output << *tag->name;
297 output << '\t';
298 }
299 return 0;
300 }
301
custom_hook_emit(Input input)302 int custom_hook_emit(Input input) {
303 hook_emit(input.toVector());
304 return 0;
305 }
306
execvp_helper(char * const command[])307 static void execvp_helper(char *const command[]) {
308 execvp(command[0], command);
309 std::cerr << "herbstluftwm: execvp \"" << command << "\"";
310 perror(" failed");
311 }
312
313 // spawn() heavily inspired by dwm.c
spawn(int argc,char ** argv)314 int spawn(int argc, char** argv) {
315 if (argc < 2) {
316 return HERBST_NEED_MORE_ARGS;
317 }
318 if (fork() == 0) {
319 // only look in child
320 if (g_display) {
321 close(ConnectionNumber(g_display));
322 }
323 // shift all args in argv by 1 to the front
324 // so that we have space for a NULL entry at the end for execvp
325 char** execargs = argv_duplicate(argc, argv);
326 free(execargs[0]);
327 int i;
328 for (i = 0; i < argc-1; i++) {
329 execargs[i] = execargs[i+1];
330 }
331 execargs[i] = nullptr;
332 // do actual exec
333 setsid();
334 execvp_helper(execargs);
335 exit(0);
336 }
337 return 0;
338 }
339
wmexec(int argc,char ** argv)340 int wmexec(int argc, char** argv) {
341 if (argc >= 2) {
342 // shift all args in argv by 1 to the front
343 // so that we have space for a NULL entry at the end for execvp
344 char** execargs = argv_duplicate(argc, argv);
345 free(execargs[0]);
346 int i;
347 for (i = 0; i < argc-1; i++) {
348 execargs[i] = execargs[i+1];
349 }
350 execargs[i] = nullptr;
351 // quit and exec to new window manger
352 g_exec_args = execargs;
353 } else {
354 // exec into same command
355 g_exec_args = nullptr;
356 }
357 g_exec_before_quit = true;
358 g_main_loop->quit();
359 return EXIT_SUCCESS;
360 }
361
raise_command(int argc,char ** argv,Output output)362 int raise_command(int argc, char** argv, Output output) {
363 if (argc < 2) {
364 return HERBST_NEED_MORE_ARGS;
365 }
366 auto client = get_client(argv[1]);
367 if (client) {
368 client->raise();
369 client->needsRelayout.emit(client->tag());
370 } else {
371 auto window = get_window(argv[1]);
372 if (window) {
373 XRaiseWindow(g_display, window);
374 } else {
375 output << argv[0] << ": Could not find client \"" << argv[1] << "\".\n";
376 return HERBST_INVALID_ARGUMENT;
377 }
378 }
379 return 0;
380 }
381
jumpto_command(int argc,char ** argv,Output output)382 int jumpto_command(int argc, char** argv, Output output) {
383 if (argc < 2) {
384 return HERBST_NEED_MORE_ARGS;
385 }
386 auto client = get_client(argv[1]);
387 if (client) {
388 focus_client(client, true, true, true);
389 return 0;
390 } else {
391 output << argv[0] << ": Could not find client \"" << argv[1] << "\".\n";
392 return HERBST_INVALID_ARGUMENT;
393 }
394 }
395
execute_autostart_file()396 void execute_autostart_file() {
397 string path;
398 if (g_autostart_path) {
399 path = g_autostart_path;
400 } else {
401 // find right directory
402 char* xdg_config_home = getenv("XDG_CONFIG_HOME");
403 if (xdg_config_home) {
404 path = xdg_config_home;
405 } else {
406 char* home = getenv("HOME");
407 if (!home) {
408 HSWarning("Will not run autostart file. "
409 "Neither $HOME or $XDG_CONFIG_HOME is set.\n");
410 return;
411 }
412 path = string(home) + "/.config";
413 }
414 path += "/" HERBSTLUFT_AUTOSTART;
415 }
416 if (0 == fork()) {
417 if (g_display) {
418 close(ConnectionNumber(g_display));
419 }
420 setsid();
421 execl(path.c_str(), path.c_str(), nullptr);
422
423 const char* global_autostart = HERBSTLUFT_GLOBAL_AUTOSTART;
424 HSDebug("Cannot execute %s, falling back to %s\n", path.c_str(), global_autostart);
425 execl(global_autostart, global_autostart, nullptr);
426
427 fprintf(stderr, "herbstluftwm: execvp \"%s\"", global_autostart);
428 perror(" failed");
429 exit(EXIT_FAILURE);
430 }
431 }
432
parse_arguments(int argc,char ** argv,Globals & g)433 static void parse_arguments(int argc, char** argv, Globals& g) {
434 int exit_on_xerror = g.exitOnXlibError;
435 int no_tag_import = 0;
436 struct option long_options[] = {
437 {"version", 0, nullptr, 'v'},
438 {"help", 0, nullptr, 'h'},
439 {"autostart", 1, nullptr, 'c'},
440 {"locked", 0, nullptr, 'l'},
441 {"exit-on-xerror", 0, &exit_on_xerror, 1},
442 {"no-tag-import", 0, &no_tag_import, 1},
443 {"verbose", 0, &g_verbose, 1},
444 {}
445 };
446 // parse options
447 while (true) {
448 int option_index = 0;
449 int c = getopt_long(argc, argv, "+c:vlh", long_options, &option_index);
450 if (c == -1) {
451 break;
452 }
453 switch (c) {
454 case 0:
455 /* ignore recognized long option */
456 break;
457 case 'v':
458 version(std::cout);
459 exit(0);
460 case 'c':
461 g_autostart_path = optarg;
462 break;
463 case 'l':
464 g.initial_monitors_locked = 1;
465 break;
466 case 'h':
467 std::cout << "This starts the herbstluftwm window manager. In order to" << endl;
468 std::cout << "interact with a running herbstluftwm instance, use the" << endl;
469 std::cout << "\'herbstclient\' command." << endl;
470 std::cout << endl;
471 std::cout << "The herbstluftwm command accepts the following options:" << endl;
472 std::cout << endl;
473 for (size_t i = 0; long_options[i].name; i++) {
474 std::cout << " ";
475 if (long_options[i].val != 1) {
476 std::cout << "-" << static_cast<char>(long_options[i].val);
477 if (long_options[i].has_arg) {
478 std::cout << " ARG";
479 }
480 std::cout << ", ";
481 }
482 std::cout << "--" << long_options[i].name;
483 if (long_options[i].has_arg) {
484 std::cout << " ARG";
485 }
486 std::cout << endl;
487 }
488 std::cout << endl;
489 std::cout << "See the herbstluftwm(1) man page for their meaning." << endl;
490 exit(EXIT_SUCCESS);
491 default:
492 exit(EXIT_FAILURE);
493 }
494 }
495 g.exitOnXlibError = exit_on_xerror != 0;
496 g.importTagsFromEwmh = (no_tag_import == 0);
497 }
498
remove_zombies(int)499 static void remove_zombies(int) {
500 int bgstatus;
501 while (waitpid(-1, &bgstatus, WNOHANG) > 0) {
502 ;
503 }
504 }
505
handle_signal(int signal)506 static void handle_signal(int signal) {
507 HSDebug("Interrupted by signal %d\n", signal);
508 if (g_main_loop) {
509 g_main_loop->quit();
510 }
511 return;
512 }
513
sigaction_signal(int signum,void (* handler)(int))514 static void sigaction_signal(int signum, void (*handler)(int)) {
515 struct sigaction act = {};
516 act.sa_handler = handler;
517 sigemptyset(&act.sa_mask);
518 act.sa_flags = SA_NOCLDSTOP | SA_RESTART;
519 sigaction(signum, &act, nullptr);
520 }
521 /* ---- */
522 /* main */
523 /* ---- */
524
main(int argc,char * argv[])525 int main(int argc, char* argv[]) {
526 Globals g;
527 parse_arguments(argc, argv, g);
528
529 if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) {
530 std::cerr << "warning: no locale support" << endl;
531 }
532 XConnection::setExitOnError(g.exitOnXlibError);
533 XConnection* X = XConnection::connect();
534 g_display = X->display();
535 if (!g_display) {
536 std::cerr << "herbstluftwm: cannot open display" << endl;
537 delete X;
538 exit(EXIT_FAILURE);
539 }
540 if (X->checkotherwm()) {
541 std::cerr << "herbstluftwm: another window manager is already running" << endl;
542 delete X;
543 exit(EXIT_FAILURE);
544 }
545 // remove zombies on SIGCHLD
546 sigaction_signal(SIGCHLD, remove_zombies);
547 sigaction_signal(SIGINT, handle_signal);
548 sigaction_signal(SIGQUIT, handle_signal);
549 sigaction_signal(SIGTERM, handle_signal);
550 // set some globals
551 g_screen = X->screen();
552 g_root = X->root();
553 XSelectInput(X->display(), X->root(), SubstructureRedirectMask|SubstructureNotifyMask|ButtonPressMask|EnterWindowMask|LeaveWindowMask|StructureNotifyMask);
554
555 // setup ipc server
556 IpcServer* ipcServer = new IpcServer(*X);
557 FontData::s_xconnection = X;
558 auto root = make_shared<Root>(g, *X, *ipcServer);
559 Root::setRoot(root);
560 //test_object_system();
561
562 Commands::initialize(commands(root));
563
564 XMainLoop mainloop(*X, root.get());
565 g_main_loop = &mainloop;
566
567 // setup
568 if (g.importTagsFromEwmh) {
569 const auto& initialState = root->ewmh->initialState();
570 for (auto n : initialState.desktopNames) {
571 root->tags->add_tag(n.c_str());
572 }
573 }
574 root->monitors()->ensure_monitors_are_available();
575 mainloop.scanExistingClients();
576 tag_force_update_flags();
577 all_monitors_apply_layout();
578 root->ewmh->updateAll();
579 execute_autostart_file();
580
581 // main loop
582 mainloop.run();
583
584 // enforce to clear the root
585 root.reset();
586 Root::setRoot(root);
587 // and then close the x connection
588 FontData::s_xconnection = nullptr;
589 delete ipcServer;
590 delete X;
591 // check if we shall restart an other window manager
592 if (g_exec_before_quit) {
593 if (g_exec_args) {
594 // do actual exec
595 HSDebug("==> Doing wmexec to %s\n", g_exec_args[0]);
596 execvp_helper(g_exec_args);
597 }
598 // on failure or if no other wm given, then fall back
599 HSDebug("==> Doing wmexec to %s\n", argv[0]);
600 execvp_helper(argv);
601 return EXIT_FAILURE;
602 }
603 return EXIT_SUCCESS;
604 }
605
606