From b3d7531947a25d9212f8ed8db86909f64456bb3c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 24 Dec 2012 16:53:20 +0100 Subject: [PATCH] refactor both i3-nagbar starts into src/util.c With this change, libev >= 4 is a hard dependency. It should be present in all major linux distributions (even the latest ubuntu LTS). --- DEPENDS | 2 +- debian/control | 2 +- i3-nagbar/main.c | 35 ++++++- include/config_parser.h | 2 + include/key_press.h | 2 + include/libi3.h | 6 ++ include/util.h | 26 +++-- libi3/get_process_filename.c | 58 ++++++++++ src/commands.c | 4 +- src/config_parser.c | 198 +++++------------------------------ src/key_press.c | 183 ++++---------------------------- src/util.c | 142 ++++++++++++++++++------- 12 files changed, 271 insertions(+), 389 deletions(-) create mode 100644 libi3/get_process_filename.c diff --git a/DEPENDS b/DEPENDS index fe9ba17f..c0423f89 100644 --- a/DEPENDS +++ b/DEPENDS @@ -10,7 +10,7 @@ │ pkg-config │ 0.25 │ 0.26 │ http://pkgconfig.freedesktop.org/ │ │ libxcb │ 1.1.93 │ 1.7 │ http://xcb.freedesktop.org/dist/ │ │ xcb-util │ 0.3.3 │ 0.3.8 │ http://xcb.freedesktop.org/dist/ │ -│ libev │ 4.0 │ 4.04 │ http://libev.schmorp.de/ │ +│ libev │ 4.0 │ 4.11 │ http://libev.schmorp.de/ │ │ flex │ 2.5.35 │ 2.5.35 │ http://flex.sourceforge.net/ │ │ bison │ 2.4.1 │ 2.4.1 │ http://www.gnu.org/software/bison/ │ │ yajl │ 1.0.8 │ 2.0.1 │ http://lloyd.github.com/yajl/ │ diff --git a/debian/control b/debian/control index 02f00de2..1ec0ad9c 100644 --- a/debian/control +++ b/debian/control @@ -14,7 +14,7 @@ Build-Depends: debhelper (>= 7.0.50~), xmlto, docbook-xml, pkg-config, - libev-dev, + libev-dev (>= 1:4.04), flex, bison, libyajl-dev, diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index a38d1839..54425599 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -10,6 +10,7 @@ */ #include #include +#include #include #include #include @@ -20,6 +21,7 @@ #include #include #include +#include #include #include @@ -135,7 +137,38 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve button_t *button = get_button_at(event->event_x, event->event_y); if (!button) return; - start_application(button->action); + + /* We need to create a custom script containing our actual command + * since not every terminal emulator which is contained in + * i3-sensible-terminal supports -e with multiple arguments (and not + * all of them support -e with one quoted argument either). + * + * NB: The paths need to be unique, that is, don’t assume users close + * their nagbars at any point in time (and they still need to work). + * */ + char *script_path = get_process_filename("nagbar-cmd"); + + int fd = open(script_path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IXUSR); + if (fd == -1) { + warn("Could not create temporary script to store the nagbar command"); + return; + } + FILE *script = fdopen(fd, "w"); + if (script == NULL) { + warn("Could not fdopen() temporary script to store the nagbar command"); + return; + } + fprintf(script, "#!/bin/sh\nrm %s\n%s", script_path, button->action); + /* Also closes fd */ + fclose(script); + + char *terminal_cmd; + sasprintf(&terminal_cmd, "i3-sensible-terminal -e \"%s\"", script_path); + + start_application(terminal_cmd); + + free(terminal_cmd); + free(script_path); /* TODO: unset flag, re-render */ } diff --git a/include/config_parser.h b/include/config_parser.h index 27e12a59..fb863f3b 100644 --- a/include/config_parser.h +++ b/include/config_parser.h @@ -12,6 +12,8 @@ #include +extern pid_t config_error_nagbar_pid; + /* * The result of a parse_config call. Currently unused, but the JSON output * will be useful in the future when we implement a config parsing IPC command. diff --git a/include/key_press.h b/include/key_press.h index 417843a1..b231b8f5 100644 --- a/include/key_press.h +++ b/include/key_press.h @@ -10,6 +10,8 @@ #ifndef I3_KEY_PRESS_H #define I3_KEY_PRESS_H +extern pid_t command_error_nagbar_pid; + /** * There was a key press. We compare this key code with our bindings table and pass * the bound action to parse_command(). diff --git a/include/libi3.h b/include/libi3.h index 7547845b..54fa0cc7 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -355,4 +355,10 @@ xcb_visualtype_t *get_visualtype(xcb_screen_t *screen); */ bool is_debug_build() __attribute__((const)); +/** + * Returns the name of a temporary file with the specified prefix. + * + */ +char *get_process_filename(const char *prefix); + #endif diff --git a/include/util.h b/include/util.h index e397a4e8..61a38f3e 100644 --- a/include/util.h +++ b/include/util.h @@ -105,13 +105,6 @@ char *resolve_tilde(const char *path); */ bool path_exists(const char *path); - -/** - * Returns the name of a temporary file with the specified prefix. - * - */ -char *get_process_filename(const char *prefix); - /** * Restart i3 in-place * appends -a to argument list to disable autostart @@ -130,4 +123,23 @@ void *memmem(const void *l, size_t l_len, const void *s, size_t s_len); #endif +/** + * Starts an i3-nagbar instance with the given parameters. Takes care of + * handling SIGCHLD and killing i3-nagbar when i3 exits. + * + * The resulting PID will be stored in *nagbar_pid and can be used with + * kill_nagbar() to kill the bar later on. + * + */ +void start_nagbar(pid_t *nagbar_pid, char *argv[]); + +/** + * Kills the i3-nagbar process, if *nagbar_pid != -1. + * + * If wait_for_it is set (restarting i3), this function will waitpid(), + * otherwise, ev is assumed to handle it (reloading). + * + */ +void kill_nagbar(pid_t *nagbar_pid, bool wait_for_it); + #endif diff --git a/libi3/get_process_filename.c b/libi3/get_process_filename.c new file mode 100644 index 00000000..0a653870 --- /dev/null +++ b/libi3/get_process_filename.c @@ -0,0 +1,58 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE) + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libi3.h" + +/* + * Returns the name of a temporary file with the specified prefix. + * + */ +char *get_process_filename(const char *prefix) { + /* dir stores the directory path for this and all subsequent calls so that + * we only create a temporary directory once per i3 instance. */ + static char *dir = NULL; + if (dir == NULL) { + /* Check if XDG_RUNTIME_DIR is set. If so, we use XDG_RUNTIME_DIR/i3 */ + if ((dir = getenv("XDG_RUNTIME_DIR"))) { + char *tmp; + sasprintf(&tmp, "%s/i3", dir); + dir = tmp; + struct stat buf; + if (stat(dir, &buf) != 0) { + if (mkdir(dir, 0700) == -1) { + perror("mkdir()"); + return NULL; + } + } + } else { + /* If not, we create a (secure) temp directory using the template + * /tmp/i3-.XXXXXX */ + struct passwd *pw = getpwuid(getuid()); + const char *username = pw ? pw->pw_name : "unknown"; + sasprintf(&dir, "/tmp/i3-%s.XXXXXX", username); + /* mkdtemp modifies dir */ + if (mkdtemp(dir) == NULL) { + perror("mkdtemp()"); + return NULL; + } + } + } + char *filename; + sasprintf(&filename, "%s/%s.%d", dir, prefix, getpid()); + return filename; +} diff --git a/src/commands.c b/src/commands.c index 2ca8387c..323c5ae4 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1602,8 +1602,8 @@ void cmd_exit(I3_CMD) { */ void cmd_reload(I3_CMD) { LOG("reloading\n"); - kill_configerror_nagbar(false); - kill_commanderror_nagbar(false); + kill_nagbar(&config_error_nagbar_pid, false); + kill_nagbar(&command_error_nagbar_pid, false); load_configuration(conn, NULL, true); x_set_i3_atoms(); /* Send an IPC event just in case the ws names have changed */ diff --git a/src/config_parser.c b/src/config_parser.c index b81aa3c3..7391896b 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -43,10 +43,7 @@ #define ystr(str) yajl_gen_string(command_output.json_gen, (unsigned char*)str, strlen(str)) #ifndef TEST_PARSER -static pid_t configerror_pid = -1; -/* The path to the temporary script files used by i3-nagbar. We need to keep - * them around to delete the files in the i3-nagbar SIGCHLD handler. */ -static char *edit_script_path, *pager_script_path; +pid_t config_error_nagbar_pid = -1; static struct context *context; #endif @@ -664,93 +661,6 @@ int main(int argc, char *argv[]) { #else -/* - * Writes the given command as a shell script to path. - * Returns true unless something went wrong. - * - */ -static bool write_nagbar_script(const char *path, const char *command) { - int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IXUSR); - if (fd == -1) { - warn("Could not create temporary script to store the nagbar command"); - return false; - } - write(fd, "#!/bin/sh\n", strlen("#!/bin/sh\n")); - write(fd, command, strlen(command)); - close(fd); - return true; -} - -/* - * Handler which will be called when we get a SIGCHLD for the nagbar, meaning - * it exited (or could not be started, depending on the exit code). - * - */ -static void nagbar_exited(EV_P_ ev_child *watcher, int revents) { - ev_child_stop(EV_A_ watcher); - - if (unlink(edit_script_path) != 0) - warn("Could not delete temporary i3-nagbar script %s", edit_script_path); - if (unlink(pager_script_path) != 0) - warn("Could not delete temporary i3-nagbar script %s", pager_script_path); - - if (!WIFEXITED(watcher->rstatus)) { - fprintf(stderr, "ERROR: i3-nagbar did not exit normally.\n"); - return; - } - - int exitcode = WEXITSTATUS(watcher->rstatus); - printf("i3-nagbar process exited with status %d\n", exitcode); - if (exitcode == 2) { - fprintf(stderr, "ERROR: i3-nagbar could not be found. Is it correctly installed on your system?\n"); - } - - configerror_pid = -1; -} - -/* We need ev >= 4 for the following code. Since it is not *that* important (it - * only makes sure that there are no i3-nagbar instances left behind) we still - * support old systems with libev 3. */ -#if EV_VERSION_MAJOR >= 4 -/* - * Cleanup handler. Will be called when i3 exits. Kills i3-nagbar with signal - * SIGKILL (9) to make sure there are no left-over i3-nagbar processes. - * - */ -static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) { - if (configerror_pid != -1) { - LOG("Sending SIGKILL (9) to i3-nagbar with PID %d\n", configerror_pid); - kill(configerror_pid, SIGKILL); - } -} -#endif - -/* - * Kills the configerror i3-nagbar process, if any. - * - * Called when reloading/restarting. - * - * If wait_for_it is set (restarting), this function will waitpid(), otherwise, - * ev is assumed to handle it (reloading). - * - */ -void kill_configerror_nagbar(bool wait_for_it) { - if (configerror_pid == -1) - return; - - if (kill(configerror_pid, SIGTERM) == -1) - warn("kill(configerror_nagbar) failed"); - - if (!wait_for_it) - return; - - /* When restarting, we don’t enter the ev main loop anymore and after the - * exec(), our old pid is no longer watched. So, ev won’t handle SIGCHLD - * for us and we would end up with a process. Therefore we - * waitpid() here. */ - waitpid(configerror_pid, NULL, 0); -} - /* * Goes through each line of buf (separated by \n) and checks for statements / * commands which only occur in i3 v4 configuration files. If it finds any, it @@ -972,86 +882,6 @@ static void check_for_duplicate_bindings(struct context *context) { } } -/* - * Starts an i3-nagbar process which alerts the user that his configuration - * file contains one or more errors. Also offers two buttons: One to launch an - * $EDITOR on the config file and another one to launch a $PAGER on the error - * logfile. - * - */ -static void start_configerror_nagbar(const char *config_path) { - if (only_check_config) - return; - - fprintf(stderr, "Starting i3-nagbar due to configuration errors\n"); - - /* We need to create a custom script containing our actual command - * since not every terminal emulator which is contained in - * i3-sensible-terminal supports -e with multiple arguments (and not - * all of them support -e with one quoted argument either). - * - * NB: The paths need to be unique, that is, don’t assume users close - * their nagbars at any point in time (and they still need to work). - * */ - edit_script_path = get_process_filename("nagbar-cfgerror-edit"); - pager_script_path = get_process_filename("nagbar-cfgerror-pager"); - - configerror_pid = fork(); - if (configerror_pid == -1) { - warn("Could not fork()"); - return; - } - - /* child */ - if (configerror_pid == 0) { - char *edit_command, *pager_command; - sasprintf(&edit_command, "i3-sensible-editor \"%s\" && i3-msg reload\n", config_path); - sasprintf(&pager_command, "i3-sensible-pager \"%s\"\n", errorfilename); - if (!write_nagbar_script(edit_script_path, edit_command) || - !write_nagbar_script(pager_script_path, pager_command)) - return; - - char *editaction, - *pageraction; - sasprintf(&editaction, "i3-sensible-terminal -e \"%s\"", edit_script_path); - sasprintf(&pageraction, "i3-sensible-terminal -e \"%s\"", pager_script_path); - char *argv[] = { - NULL, /* will be replaced by the executable path */ - "-t", - (context->has_errors ? "error" : "warning"), - "-m", - (context->has_errors ? - "You have an error in your i3 config file!" : - "Your config is outdated. Please fix the warnings to make sure everything works."), - "-b", - "edit config", - editaction, - (errorfilename ? "-b" : NULL), - (context->has_errors ? "show errors" : "show warnings"), - pageraction, - NULL - }; - exec_i3_utility("i3-nagbar", argv); - } - - /* parent */ - /* install a child watcher */ - ev_child *child = smalloc(sizeof(ev_child)); - ev_child_init(child, &nagbar_exited, configerror_pid, 0); - ev_child_start(main_loop, child); - -/* We need ev >= 4 for the following code. Since it is not *that* important (it - * only makes sure that there are no i3-nagbar instances left behind) we still - * support old systems with libev 3. */ -#if EV_VERSION_MAJOR >= 4 - /* install a cleanup watcher (will be called when i3 exits and i3-nagbar is - * still running) */ - ev_cleanup *cleanup = smalloc(sizeof(ev_cleanup)); - ev_cleanup_init(cleanup, nagbar_cleanup); - ev_cleanup_start(main_loop, cleanup); -#endif -} - /* * Parses the given file by first replacing the variables, then calling * parse_config and possibly launching i3-nagbar. @@ -1222,7 +1052,31 @@ void parse_file(const char *f) { ELOG("FYI: You are using i3 version " I3_VERSION "\n"); if (version == 3) ELOG("Please convert your configfile first, then fix any remaining errors (see above).\n"); - start_configerror_nagbar(f); + + char *editaction, + *pageraction; + sasprintf(&editaction, "i3-sensible-editor \"%s\" && i3-msg reload\n", f); + sasprintf(&pageraction, "i3-sensible-pager \"%s\"\n", errorfilename); + char *argv[] = { + NULL, /* will be replaced by the executable path */ + "-t", + (context->has_errors ? "error" : "warning"), + "-m", + (context->has_errors ? + "You have an error in your i3 config file!" : + "Your config is outdated. Please fix the warnings to make sure everything works."), + "-b", + "edit config", + editaction, + (errorfilename ? "-b" : NULL), + (context->has_errors ? "show errors" : "show warnings"), + pageraction, + NULL + }; + + start_nagbar(&config_error_nagbar_pid, argv); + free(editaction); + free(pageraction); } FREE(context->line_copy); diff --git a/src/key_press.c b/src/key_press.c index 5919e64c..ca5c3a0b 100644 --- a/src/key_press.c +++ b/src/key_press.c @@ -19,169 +19,7 @@ static int current_nesting_level; static bool parse_error_key; static bool command_failed; -/* XXX: I don’t want to touch too much of the nagbar code at once, but we - * should refactor this with src/cfgparse.y into a clean generic nagbar - * interface. It might come in handy in other situations within i3, too. */ -static char *pager_script_path; -static pid_t nagbar_pid = -1; - -/* - * Handler which will be called when we get a SIGCHLD for the nagbar, meaning - * it exited (or could not be started, depending on the exit code). - * - */ -static void nagbar_exited(EV_P_ ev_child *watcher, int revents) { - ev_child_stop(EV_A_ watcher); - - if (unlink(pager_script_path) != 0) - warn("Could not delete temporary i3-nagbar script %s", pager_script_path); - - if (!WIFEXITED(watcher->rstatus)) { - fprintf(stderr, "ERROR: i3-nagbar did not exit normally.\n"); - return; - } - - int exitcode = WEXITSTATUS(watcher->rstatus); - printf("i3-nagbar process exited with status %d\n", exitcode); - if (exitcode == 2) { - fprintf(stderr, "ERROR: i3-nagbar could not be found. Is it correctly installed on your system?\n"); - } - - nagbar_pid = -1; -} - -/* We need ev >= 4 for the following code. Since it is not *that* important (it - * only makes sure that there are no i3-nagbar instances left behind) we still - * support old systems with libev 3. */ -#if EV_VERSION_MAJOR >= 4 -/* - * Cleanup handler. Will be called when i3 exits. Kills i3-nagbar with signal - * SIGKILL (9) to make sure there are no left-over i3-nagbar processes. - * - */ -static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) { - if (nagbar_pid != -1) { - LOG("Sending SIGKILL (%d) to i3-nagbar with PID %d\n", SIGKILL, nagbar_pid); - kill(nagbar_pid, SIGKILL); - } -} -#endif - -/* - * Writes the given command as a shell script to path. - * Returns true unless something went wrong. - * - */ -static bool write_nagbar_script(const char *path, const char *command) { - int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IXUSR); - if (fd == -1) { - warn("Could not create temporary script to store the nagbar command"); - return false; - } - write(fd, "#!/bin/sh\n", strlen("#!/bin/sh\n")); - write(fd, command, strlen(command)); - close(fd); - return true; -} - -/* - * Starts an i3-nagbar process which alerts the user that his configuration - * file contains one or more errors. Also offers two buttons: One to launch an - * $EDITOR on the config file and another one to launch a $PAGER on the error - * logfile. - * - */ -static void start_commanderror_nagbar(void) { - if (nagbar_pid != -1) { - DLOG("i3-nagbar for command error already running, not starting again.\n"); - return; - } - - DLOG("Starting i3-nagbar due to command error\n"); - - /* We need to create a custom script containing our actual command - * since not every terminal emulator which is contained in - * i3-sensible-terminal supports -e with multiple arguments (and not - * all of them support -e with one quoted argument either). - * - * NB: The paths need to be unique, that is, don’t assume users close - * their nagbars at any point in time (and they still need to work). - * */ - pager_script_path = get_process_filename("nagbar-cfgerror-pager"); - - nagbar_pid = fork(); - if (nagbar_pid == -1) { - warn("Could not fork()"); - return; - } - - /* child */ - if (nagbar_pid == 0) { - char *pager_command; - sasprintf(&pager_command, "i3-sensible-pager \"%s\"\n", errorfilename); - if (!write_nagbar_script(pager_script_path, pager_command)) - return; - - char *pageraction; - sasprintf(&pageraction, "i3-sensible-terminal -e \"%s\"", pager_script_path); - char *argv[] = { - NULL, /* will be replaced by the executable path */ - "-t", - "error", - "-m", - "The configured command for this shortcut could not be run successfully.", - "-b", - "show errors", - pageraction, - NULL - }; - exec_i3_utility("i3-nagbar", argv); - } - - /* parent */ - /* install a child watcher */ - ev_child *child = smalloc(sizeof(ev_child)); - ev_child_init(child, &nagbar_exited, nagbar_pid, 0); - ev_child_start(main_loop, child); - -/* We need ev >= 4 for the following code. Since it is not *that* important (it - * only makes sure that there are no i3-nagbar instances left behind) we still - * support old systems with libev 3. */ -#if EV_VERSION_MAJOR >= 4 - /* install a cleanup watcher (will be called when i3 exits and i3-nagbar is - * still running) */ - ev_cleanup *cleanup = smalloc(sizeof(ev_cleanup)); - ev_cleanup_init(cleanup, nagbar_cleanup); - ev_cleanup_start(main_loop, cleanup); -#endif -} - -/* - * Kills the commanderror i3-nagbar process, if any. - * - * Called when reloading/restarting, since the user probably fixed his wrong - * keybindings. - * - * If wait_for_it is set (restarting), this function will waitpid(), otherwise, - * ev is assumed to handle it (reloading). - * - */ -void kill_commanderror_nagbar(bool wait_for_it) { - if (nagbar_pid == -1) - return; - - if (kill(nagbar_pid, SIGTERM) == -1) - warn("kill(configerror_nagbar) failed"); - - if (!wait_for_it) - return; - - /* When restarting, we don’t enter the ev main loop anymore and after the - * exec(), our old pid is no longer watched. So, ev won’t handle SIGCHLD - * for us and we would end up with a process. Therefore we - * waitpid() here. */ - waitpid(nagbar_pid, NULL, 0); -} +pid_t command_error_nagbar_pid = -1; static int json_boolean(void *ctx, int boolval) { DLOG("Got bool: %d, parse_error_key %d, nesting_level %d\n", boolval, parse_error_key, current_nesting_level); @@ -302,8 +140,23 @@ void handle_key_press(xcb_key_press_event_t *event) { if (state != yajl_status_ok) { ELOG("Could not parse my own reply. That's weird. reply is %.*s\n", (int)length, reply); } else { - if (command_failed) - start_commanderror_nagbar(); + if (command_failed) { + char *pageraction; + sasprintf(&pageraction, "i3-sensible-pager \"%s\"\n", errorfilename); + char *argv[] = { + NULL, /* will be replaced by the executable path */ + "-t", + "error", + "-m", + "The configured command for this shortcut could not be run successfully.", + "-b", + "show errors", + pageraction, + NULL + }; + start_nagbar(&command_error_nagbar_pid, argv); + free(pageraction); + } } yajl_free(handle); diff --git a/src/util.c b/src/util.c index e623ce81..e2df3ca9 100644 --- a/src/util.c +++ b/src/util.c @@ -183,44 +183,6 @@ static char **append_argument(char **original, char *argument) { return result; } -/* - * Returns the name of a temporary file with the specified prefix. - * - */ -char *get_process_filename(const char *prefix) { - /* dir stores the directory path for this and all subsequent calls so that - * we only create a temporary directory once per i3 instance. */ - static char *dir = NULL; - if (dir == NULL) { - /* Check if XDG_RUNTIME_DIR is set. If so, we use XDG_RUNTIME_DIR/i3 */ - if ((dir = getenv("XDG_RUNTIME_DIR"))) { - char *tmp; - sasprintf(&tmp, "%s/i3", dir); - dir = tmp; - if (!path_exists(dir)) { - if (mkdir(dir, 0700) == -1) { - perror("mkdir()"); - return NULL; - } - } - } else { - /* If not, we create a (secure) temp directory using the template - * /tmp/i3-.XXXXXX */ - struct passwd *pw = getpwuid(getuid()); - const char *username = pw ? pw->pw_name : "unknown"; - sasprintf(&dir, "/tmp/i3-%s.XXXXXX", username); - /* mkdtemp modifies dir */ - if (mkdtemp(dir) == NULL) { - perror("mkdtemp()"); - return NULL; - } - } - } - char *filename; - sasprintf(&filename, "%s/%s.%d", dir, prefix, getpid()); - return filename; -} - #define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__) #define ystr(str) yajl_gen_string(gen, (unsigned char*)str, strlen(str)) @@ -304,8 +266,8 @@ char *store_restart_layout(void) { void i3_restart(bool forget_layout) { char *restart_filename = forget_layout ? NULL : store_restart_layout(); - kill_configerror_nagbar(true); - kill_commanderror_nagbar(true); + kill_nagbar(&config_error_nagbar_pid, true); + kill_nagbar(&command_error_nagbar_pid, true); restore_geometry(); @@ -382,3 +344,103 @@ void *memmem(const void *l, size_t l_len, const void *s, size_t s_len) { } #endif + +/* + * Handler which will be called when we get a SIGCHLD for the nagbar, meaning + * it exited (or could not be started, depending on the exit code). + * + */ +static void nagbar_exited(EV_P_ ev_child *watcher, int revents) { + ev_child_stop(EV_A_ watcher); + + if (!WIFEXITED(watcher->rstatus)) { + ELOG("ERROR: i3-nagbar did not exit normally.\n"); + return; + } + + int exitcode = WEXITSTATUS(watcher->rstatus); + DLOG("i3-nagbar process exited with status %d\n", exitcode); + if (exitcode == 2) { + ELOG("ERROR: i3-nagbar could not be found. Is it correctly installed on your system?\n"); + } + + *((pid_t*)watcher->data) = -1; +} + +/* + * Cleanup handler. Will be called when i3 exits. Kills i3-nagbar with signal + * SIGKILL (9) to make sure there are no left-over i3-nagbar processes. + * + */ +static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) { + pid_t *nagbar_pid = (pid_t*)watcher->data; + if (*nagbar_pid != -1) { + LOG("Sending SIGKILL (%d) to i3-nagbar with PID %d\n", SIGKILL, *nagbar_pid); + kill(*nagbar_pid, SIGKILL); + } +} + +/* + * Starts an i3-nagbar instance with the given parameters. Takes care of + * handling SIGCHLD and killing i3-nagbar when i3 exits. + * + * The resulting PID will be stored in *nagbar_pid and can be used with + * kill_nagbar() to kill the bar later on. + * + */ +void start_nagbar(pid_t *nagbar_pid, char *argv[]) { + if (*nagbar_pid != -1) { + DLOG("i3-nagbar already running (PID %d), not starting again.\n", *nagbar_pid); + return; + } + + *nagbar_pid = fork(); + if (*nagbar_pid == -1) { + warn("Could not fork()"); + return; + } + + /* child */ + if (*nagbar_pid == 0) + exec_i3_utility("i3-nagbar", argv); + + DLOG("Starting i3-nagbar with PID %d\n", *nagbar_pid); + + /* parent */ + /* install a child watcher */ + ev_child *child = smalloc(sizeof(ev_child)); + ev_child_init(child, &nagbar_exited, *nagbar_pid, 0); + child->data = nagbar_pid; + ev_child_start(main_loop, child); + + /* install a cleanup watcher (will be called when i3 exits and i3-nagbar is + * still running) */ + ev_cleanup *cleanup = smalloc(sizeof(ev_cleanup)); + ev_cleanup_init(cleanup, nagbar_cleanup); + cleanup->data = nagbar_pid; + ev_cleanup_start(main_loop, cleanup); +} + +/* + * Kills the i3-nagbar process, if *nagbar_pid != -1. + * + * If wait_for_it is set (restarting i3), this function will waitpid(), + * otherwise, ev is assumed to handle it (reloading). + * + */ +void kill_nagbar(pid_t *nagbar_pid, bool wait_for_it) { + if (*nagbar_pid == -1) + return; + + if (kill(*nagbar_pid, SIGTERM) == -1) + warn("kill(configerror_nagbar) failed"); + + if (!wait_for_it) + return; + + /* When restarting, we don’t enter the ev main loop anymore and after the + * exec(), our old pid is no longer watched. So, ev won’t handle SIGCHLD + * for us and we would end up with a process. Therefore we + * waitpid() here. */ + waitpid(*nagbar_pid, NULL, 0); +}