aboutsummaryrefslogtreecommitdiff
path: root/i3bar/src/child.c
diff options
context:
space:
mode:
Diffstat (limited to 'i3bar/src/child.c')
-rw-r--r--i3bar/src/child.c508
1 files changed, 381 insertions, 127 deletions
diff --git a/i3bar/src/child.c b/i3bar/src/child.c
index df4c6601..20858f68 100644
--- a/i3bar/src/child.c
+++ b/i3bar/src/child.c
@@ -10,6 +10,7 @@
#include "common.h"
#include "yajl_utils.h"
+#include <ctype.h> /* isspace */
#include <err.h>
#include <errno.h>
#include <ev.h>
@@ -27,14 +28,30 @@
#include <yajl/yajl_parse.h>
/* Global variables for child_*() */
-i3bar_child child = {0};
-#define DLOG_CHILD DLOG("%s: pid=%ld stopped=%d stop_signal=%d cont_signal=%d click_events=%d click_events_init=%d\n", \
- __func__, (long)child.pid, child.stopped, child.stop_signal, child.cont_signal, child.click_events, child.click_events_init)
-
-/* stdin- and SIGCHLD-watchers */
-ev_io *stdin_io;
-int stdin_fd;
-ev_child *child_sig;
+i3bar_child status_child = {0};
+i3bar_child ws_child = {0};
+
+#define DLOG_CHILD(c) \
+ do { \
+ if ((c).pid == 0) { \
+ DLOG("%s: child pid = 0\n", __func__); \
+ } else if ((c).pid == status_child.pid) { \
+ DLOG("%s: status_command: pid=%ld stopped=%d stop_signal=%d cont_signal=%d click_events=%d click_events_init=%d\n", \
+ __func__, (long)(c).pid, (c).stopped, (c).stop_signal, (c).cont_signal, (c).click_events, (c).click_events_init); \
+ } else if ((c).pid == ws_child.pid) { \
+ DLOG("%s: workspace_command: pid=%ld stopped=%d stop_signal=%d cont_signal=%d click_events=%d click_events_init=%d\n", \
+ __func__, (long)(c).pid, (c).stopped, (c).stop_signal, (c).cont_signal, (c).click_events, (c).click_events_init); \
+ } else { \
+ ELOG("%s: unknown child, this should never happen " \
+ "pid=%ld stopped=%d stop_signal=%d cont_signal=%d click_events=%d click_events_init=%d\n", \
+ __func__, (long)(c).pid, (c).stopped, (c).stop_signal, (c).cont_signal, (c).click_events, (c).click_events_init); \
+ } \
+ } while (0)
+#define DLOG_CHILDREN \
+ do { \
+ DLOG_CHILD(status_child); \
+ DLOG_CHILD(ws_child); \
+ } while (0)
/* JSON parser for stdin */
yajl_handle parser;
@@ -127,7 +144,7 @@ __attribute__((format(printf, 1, 2))) static void set_statusline_error(const cha
TAILQ_INSERT_TAIL(&statusline_head, message_block, blocks);
finish:
- FREE(message);
+ free(message);
va_end(args);
}
@@ -135,22 +152,27 @@ finish:
* Stop and free() the stdin- and SIGCHLD-watchers
*
*/
-static void cleanup(void) {
- if (stdin_io != NULL) {
- ev_io_stop(main_loop, stdin_io);
- FREE(stdin_io);
- close(stdin_fd);
- stdin_fd = 0;
- close(child_stdin);
- child_stdin = 0;
+static void cleanup(i3bar_child *c) {
+ DLOG_CHILD(*c);
+
+ if (c->stdin_io != NULL) {
+ ev_io_stop(main_loop, c->stdin_io);
+ FREE(c->stdin_io);
+
+ if (c->pid == status_child.pid) {
+ close(child_stdin);
+ child_stdin = 0;
+ }
+ close(c->stdin_fd);
}
- if (child_sig != NULL) {
- ev_child_stop(main_loop, child_sig);
- FREE(child_sig);
+ if (c->child_sig != NULL) {
+ ev_child_stop(main_loop, c->child_sig);
+ FREE(c->child_sig);
}
- memset(&child, 0, sizeof(i3bar_child));
+ FREE(c->pending_line);
+ memset(c, 0, sizeof(i3bar_child));
}
/*
@@ -362,15 +384,13 @@ static int stdin_end_array(void *context) {
* Returns NULL on EOF.
*
*/
-static unsigned char *get_buffer(ev_io *watcher, int *ret_buffer_len) {
- int fd = watcher->fd;
- int n = 0;
+static unsigned char *get_buffer(int fd, int *ret_buffer_len) {
int rec = 0;
int buffer_len = STDIN_CHUNK_SIZE;
unsigned char *buffer = smalloc(buffer_len + 1);
buffer[0] = '\0';
while (1) {
- n = read(fd, buffer + rec, buffer_len - rec);
+ const ssize_t n = read(fd, buffer + rec, buffer_len - rec);
if (n == -1) {
if (errno == EAGAIN) {
/* finish up */
@@ -390,10 +410,11 @@ static unsigned char *get_buffer(ev_io *watcher, int *ret_buffer_len) {
if (rec == buffer_len) {
buffer_len += STDIN_CHUNK_SIZE;
- buffer = srealloc(buffer, buffer_len);
+ buffer = srealloc(buffer, buffer_len + 1);
}
}
- if (*buffer == '\0') {
+ buffer[rec] = '\0';
+ if (buffer[0] == '\0') {
FREE(buffer);
rec = -1;
}
@@ -443,13 +464,14 @@ static bool read_json_input(unsigned char *input, int length) {
* in statusline
*
*/
-static void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
+static void stdin_io_cb(int fd) {
int rec;
- unsigned char *buffer = get_buffer(watcher, &rec);
- if (buffer == NULL)
+ unsigned char *buffer = get_buffer(fd, &rec);
+ if (buffer == NULL) {
return;
+ }
bool has_urgent = false;
- if (child.version > 0) {
+ if (status_child.version > 0) {
has_urgent = read_json_input(buffer, rec);
} else {
read_flat_input((char *)buffer, rec);
@@ -463,22 +485,23 @@ static void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
* whether this is JSON or plain text
*
*/
-static void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
+static void stdin_io_first_line_cb(int fd) {
int rec;
- unsigned char *buffer = get_buffer(watcher, &rec);
- if (buffer == NULL)
+ unsigned char *buffer = get_buffer(fd, &rec);
+ if (buffer == NULL) {
return;
+ }
DLOG("Detecting input type based on buffer *%.*s*\n", rec, buffer);
/* Detect whether this is JSON or plain text. */
unsigned int consumed = 0;
/* At the moment, we don’t care for the version. This might change
* in the future, but for now, we just discard it. */
- parse_json_header(&child, buffer, rec, &consumed);
- if (child.version > 0) {
- /* If hide-on-modifier is set, we start of by sending the
- * child a SIGSTOP, because the bars aren't mapped at start */
+ parse_json_header(&status_child, buffer, rec, &consumed);
+ if (status_child.version > 0) {
+ /* If hide-on-modifier is set, we start of by sending the status_child
+ * a SIGSTOP, because the bars aren't mapped at start */
if (config.hide_on_modifier) {
- stop_child();
+ stop_children();
}
draw_bars(read_json_input(buffer + consumed, rec - consumed));
} else {
@@ -489,9 +512,133 @@ static void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int rev
read_flat_input((char *)buffer, rec);
}
free(buffer);
- ev_io_stop(main_loop, stdin_io);
- ev_io_init(stdin_io, &stdin_io_cb, stdin_fd, EV_READ);
- ev_io_start(main_loop, stdin_io);
+}
+
+static bool isempty(char *s) {
+ while (*s != '\0') {
+ if (!isspace(*s)) {
+ return false;
+ }
+ s++;
+ }
+ return true;
+}
+
+static char *append_string(const char *previous, const char *str) {
+ if (previous != NULL) {
+ char *result;
+ sasprintf(&result, "%s%s", previous, str);
+ return result;
+ }
+ return sstrdup(str);
+}
+
+static char *ws_last_json;
+
+static void ws_stdin_io_cb(int fd) {
+ int rec;
+ unsigned char *buffer = get_buffer(fd, &rec);
+ if (buffer == NULL) {
+ return;
+ }
+
+ gchar **strings = g_strsplit((const char *)buffer, "\n", 0);
+ for (int idx = 0; strings[idx] != NULL; idx++) {
+ if (ws_child.pending_line == NULL && isempty(strings[idx])) {
+ /* In the normal case where the buffer ends with '\n', the last
+ * string should be empty */
+ continue;
+ }
+
+ if (strings[idx + 1] == NULL) {
+ /* This is the last string but it is not empty, meaning that we have
+ * read data that is incomplete, save it for later. */
+ char *new = append_string(ws_child.pending_line, strings[idx]);
+ free(ws_child.pending_line);
+ ws_child.pending_line = new;
+ continue;
+ }
+
+ free(ws_last_json);
+ ws_last_json = append_string(ws_child.pending_line, strings[idx]);
+ FREE(ws_child.pending_line);
+
+ parse_workspaces_json((const unsigned char *)ws_last_json, strlen(ws_last_json));
+ }
+
+ g_strfreev(strings);
+ free(buffer);
+
+ draw_bars(false);
+}
+
+static void common_stdin_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
+ if (watcher == status_child.stdin_io) {
+ if (status_child.version == (uint32_t)-1) {
+ stdin_io_first_line_cb(watcher->fd);
+ } else {
+ stdin_io_cb(watcher->fd);
+ }
+ } else if (watcher == ws_child.stdin_io) {
+ ws_stdin_io_cb(watcher->fd);
+ } else {
+ ELOG("Got callback for unknown watcher fd=%d\n", watcher->fd);
+ }
+}
+
+/*
+ * When workspace_command is enabled this function is used to re-parse the
+ * latest received JSON from the client.
+ */
+void repeat_last_ws_json(void) {
+ if (ws_last_json) {
+ DLOG("Repeating last workspace JSON\n");
+ parse_workspaces_json((const unsigned char *)ws_last_json, strlen(ws_last_json));
+ }
+}
+
+/*
+ * Wrapper around set_workspace_button_error to mimic the call of
+ * set_statusline_error.
+ */
+__attribute__((format(printf, 1, 2))) static void set_workspace_button_error_f(const char *format, ...) {
+ char *message;
+ va_list args;
+ va_start(args, format);
+ if (vasprintf(&message, format, args) == -1) {
+ goto finish;
+ }
+
+ set_workspace_button_error(message);
+
+finish:
+ free(message);
+ va_end(args);
+}
+
+/*
+ * Replaces the workspace buttons with an error message.
+ */
+void set_workspace_button_error(const char *message) {
+ free_workspaces();
+
+ char *name = NULL;
+ sasprintf(&name, "Error: %s", message);
+
+ i3_output *output;
+ SLIST_FOREACH (output, outputs, slist) {
+ i3_ws *fake_ws = scalloc(1, sizeof(i3_ws));
+ /* Don't set the canonical_name field to make this workspace unfocusable. */
+ fake_ws->name = i3string_from_utf8(name);
+ fake_ws->name_width = predict_text_width(fake_ws->name);
+ fake_ws->num = -1;
+ fake_ws->urgent = fake_ws->visible = true;
+ fake_ws->output = output;
+
+ TAILQ_INSERT_TAIL(output->workspaces, fake_ws, tailq);
+ }
+
+ free(name);
}
/*
@@ -501,27 +648,45 @@ static void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int rev
*
*/
static void child_sig_cb(struct ev_loop *loop, ev_child *watcher, int revents) {
- int exit_status = WEXITSTATUS(watcher->rstatus);
+ const int exit_status = WEXITSTATUS(watcher->rstatus);
ELOG("Child (pid: %d) unexpectedly exited with status %d\n",
- child.pid,
+ watcher->pid,
exit_status);
+ void (*error_function_pointer)(const char *, ...) = NULL;
+ const char *command_type = "";
+ i3bar_child *c = NULL;
+ if (watcher->pid == status_child.pid) {
+ command_type = "status_command";
+ error_function_pointer = set_statusline_error;
+ c = &status_child;
+ } else if (watcher->pid == ws_child.pid) {
+ command_type = "workspace_command";
+ error_function_pointer = set_workspace_button_error_f;
+ c = &ws_child;
+ } else {
+ ELOG("Unknown child pid, this should never happen\n");
+ return;
+ }
+ DLOG_CHILD(*c);
+
/* this error is most likely caused by a user giving a nonexecutable or
* nonexistent file, so we will handle those cases separately. */
- if (exit_status == 126)
- set_statusline_error("status_command is not executable (exit %d)", exit_status);
- else if (exit_status == 127)
- set_statusline_error("status_command not found or is missing a library dependency (exit %d)", exit_status);
- else
- set_statusline_error("status_command process exited unexpectedly (exit %d)", exit_status);
+ if (exit_status == 126) {
+ error_function_pointer("%s is not executable (exit %d)", command_type, exit_status);
+ } else if (exit_status == 127) {
+ error_function_pointer("%s not found or is missing a library dependency (exit %d)", command_type, exit_status);
+ } else {
+ error_function_pointer("%s process exited unexpectedly (exit %d)", command_type, exit_status);
+ }
- cleanup();
+ cleanup(c);
draw_bars(false);
}
static void child_write_output(void) {
- if (child.click_events) {
+ if (status_child.click_events) {
const unsigned char *output;
size_t size;
ssize_t n;
@@ -535,7 +700,7 @@ static void child_write_output(void) {
yajl_gen_clear(gen);
if (n == -1) {
- child.click_events = false;
+ status_child.click_events = false;
kill_child();
set_statusline_error("child_write_output failed");
draw_bars(false);
@@ -543,6 +708,41 @@ static void child_write_output(void) {
}
}
+static pid_t sfork(void) {
+ const pid_t pid = fork();
+ if (pid == -1) {
+ ELOG("Couldn't fork(): %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ return pid;
+}
+
+static void spipe(int pipedes[2]) {
+ if (pipe(pipedes) == -1) {
+ err(EXIT_FAILURE, "pipe(pipe_in)");
+ }
+}
+
+static void exec_shell(char *command) {
+ execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (char *)NULL);
+}
+
+static void setup_child_cb(i3bar_child *child) {
+ /* We set O_NONBLOCK because blocking is evil in event-driven software */
+ fcntl(child->stdin_fd, F_SETFL, O_NONBLOCK);
+
+ child->stdin_io = smalloc(sizeof(ev_io));
+ ev_io_init(child->stdin_io, &common_stdin_cb, child->stdin_fd, EV_READ);
+ ev_io_start(main_loop, child->stdin_io);
+
+ /* We must cleanup, if the child unexpectedly terminates */
+ child->child_sig = smalloc(sizeof(ev_child));
+ ev_child_init(child->child_sig, &child_sig_cb, child->pid, 0);
+ ev_child_start(main_loop, child->child_sig);
+
+ DLOG_CHILD(*child);
+}
+
/*
* Start a child process with the specified command and reroute stdin.
* We actually start a shell to execute the command so we don't have to care
@@ -553,8 +753,9 @@ static void child_write_output(void) {
*
*/
void start_child(char *command) {
- if (command == NULL)
+ if (command == NULL) {
return;
+ }
/* Allocate a yajl parser which will be used to parse stdin. */
static yajl_callbacks callbacks = {
@@ -568,69 +769,77 @@ void start_child(char *command) {
.yajl_end_array = stdin_end_array,
};
parser = yajl_alloc(&callbacks, NULL, &parser_context);
-
gen = yajl_gen_alloc(NULL);
int pipe_in[2]; /* pipe we read from */
int pipe_out[2]; /* pipe we write to */
+ spipe(pipe_in);
+ spipe(pipe_out);
- if (pipe(pipe_in) == -1)
- err(EXIT_FAILURE, "pipe(pipe_in)");
- if (pipe(pipe_out) == -1)
- err(EXIT_FAILURE, "pipe(pipe_out)");
-
- child.pid = fork();
- switch (child.pid) {
- case -1:
- ELOG("Couldn't fork(): %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- case 0:
- /* Child-process. Reroute streams and start shell */
-
- close(pipe_in[0]);
- close(pipe_out[1]);
+ status_child.pid = sfork();
+ if (status_child.pid == 0) {
+ /* Child-process. Reroute streams and start shell */
+ close(pipe_in[0]);
+ close(pipe_out[1]);
- dup2(pipe_in[1], STDOUT_FILENO);
- dup2(pipe_out[0], STDIN_FILENO);
+ dup2(pipe_in[1], STDOUT_FILENO);
+ dup2(pipe_out[0], STDIN_FILENO);
- setpgid(child.pid, 0);
- execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (char *)NULL);
- return;
- default:
- /* Parent-process. Reroute streams */
+ setpgid(status_child.pid, 0);
+ exec_shell(command);
+ return;
+ }
+ /* Parent-process. Reroute streams */
+ close(pipe_in[1]);
+ close(pipe_out[0]);
- close(pipe_in[1]);
- close(pipe_out[0]);
+ status_child.stdin_fd = pipe_in[0];
+ child_stdin = pipe_out[1];
+ status_child.version = -1;
- stdin_fd = pipe_in[0];
- child_stdin = pipe_out[1];
+ setup_child_cb(&status_child);
+}
- break;
+/*
+ * Same as start_child but starts the configured client that manages workspace
+ * buttons.
+ *
+ */
+void start_ws_child(char *command) {
+ if (command == NULL) {
+ return;
}
- /* We set O_NONBLOCK because blocking is evil in event-driven software */
- fcntl(stdin_fd, F_SETFL, O_NONBLOCK);
+ ws_child.stop_signal = SIGSTOP;
+ ws_child.cont_signal = SIGCONT;
- stdin_io = smalloc(sizeof(ev_io));
- ev_io_init(stdin_io, &stdin_io_first_line_cb, stdin_fd, EV_READ);
- ev_io_start(main_loop, stdin_io);
+ int pipe_in[2]; /* pipe we read from */
+ spipe(pipe_in);
- /* We must cleanup, if the child unexpectedly terminates */
- child_sig = smalloc(sizeof(ev_child));
- ev_child_init(child_sig, &child_sig_cb, child.pid, 0);
- ev_child_start(main_loop, child_sig);
+ ws_child.pid = sfork();
+ if (ws_child.pid == 0) {
+ /* Child-process. Reroute streams and start shell */
+ close(pipe_in[0]);
+ dup2(pipe_in[1], STDOUT_FILENO);
- atexit(kill_child_at_exit);
- DLOG_CHILD;
+ setpgid(ws_child.pid, 0);
+ exec_shell(command);
+ return;
+ }
+ /* Parent-process. Reroute streams */
+ close(pipe_in[1]);
+ ws_child.stdin_fd = pipe_in[0];
+
+ setup_child_cb(&ws_child);
}
static void child_click_events_initialize(void) {
- DLOG_CHILD;
+ DLOG_CHILD(status_child);
- if (!child.click_events_init) {
+ if (!status_child.click_events_init) {
yajl_gen_array_open(gen);
child_write_output();
- child.click_events_init = true;
+ status_child.click_events_init = true;
}
}
@@ -639,7 +848,7 @@ static void child_click_events_initialize(void) {
*
*/
void send_block_clicked(int button, const char *name, const char *instance, int x, int y, int x_rel, int y_rel, int out_x, int out_y, int width, int height, int mods) {
- if (!child.click_events) {
+ if (!status_child.click_events) {
return;
}
@@ -706,35 +915,85 @@ void send_block_clicked(int button, const char *name, const char *instance, int
child_write_output();
}
+static bool is_alive(i3bar_child *c) {
+ return c->pid > 0;
+}
+
+/*
+ * Returns true if the status child process is alive.
+ *
+ */
+bool status_child_is_alive(void) {
+ return is_alive(&status_child);
+}
+
+/*
+ * Returns true if the workspace child process is alive.
+ *
+ */
+bool ws_child_is_alive(void) {
+ return is_alive(&ws_child);
+}
+
/*
* kill()s the child process (if any). Called when exit()ing.
*
*/
-void kill_child_at_exit(void) {
- DLOG_CHILD;
+void kill_children_at_exit(void) {
+ DLOG_CHILDREN;
+ cont_children();
- if (child.pid > 0) {
- if (child.cont_signal > 0 && child.stopped)
- killpg(child.pid, child.cont_signal);
- killpg(child.pid, SIGTERM);
+ if (is_alive(&status_child)) {
+ killpg(status_child.pid, SIGTERM);
+ }
+ if (is_alive(&ws_child)) {
+ killpg(ws_child.pid, SIGTERM);
}
}
+static void cont_child(i3bar_child *c) {
+ if (is_alive(c) && c->cont_signal > 0 && c->stopped) {
+ c->stopped = false;
+ killpg(c->pid, c->cont_signal);
+ }
+}
+
+static void kill_and_wait(i3bar_child *c) {
+ DLOG_CHILD(*c);
+ if (!is_alive(c)) {
+ return;
+ }
+
+ cont_child(c);
+ killpg(c->pid, SIGTERM);
+ int status;
+ waitpid(c->pid, &status, 0);
+ cleanup(c);
+}
+
/*
- * kill()s the child process (if existent) and closes and
- * free()s the stdin- and SIGCHLD-watchers
+ * kill()s the child process (if any) and closes and free()s the stdin- and
+ * SIGCHLD-watchers
*
*/
void kill_child(void) {
- DLOG_CHILD;
+ kill_and_wait(&status_child);
+}
- if (child.pid > 0) {
- if (child.cont_signal > 0 && child.stopped)
- killpg(child.pid, child.cont_signal);
- killpg(child.pid, SIGTERM);
- int status;
- waitpid(child.pid, &status, 0);
- cleanup();
+/*
+ * kill()s the workspace child process (if any) and closes and free()s the
+ * stdin- and SIGCHLD-watchers.
+ * Similar to kill_child.
+ *
+ */
+void kill_ws_child(void) {
+ kill_and_wait(&ws_child);
+}
+
+static void stop_child(i3bar_child *c) {
+ if (c->stop_signal > 0 && !c->stopped) {
+ c->stopped = true;
+ killpg(c->pid, c->stop_signal);
}
}
@@ -742,26 +1001,21 @@ void kill_child(void) {
* Sends a SIGSTOP to the child process (if existent)
*
*/
-void stop_child(void) {
- DLOG_CHILD;
-
- if (child.stop_signal > 0 && !child.stopped) {
- child.stopped = true;
- killpg(child.pid, child.stop_signal);
- }
+void stop_children(void) {
+ DLOG_CHILDREN;
+ stop_child(&status_child);
+ stop_child(&ws_child);
}
/*
* Sends a SIGCONT to the child process (if existent)
*
*/
-void cont_child(void) {
- DLOG_CHILD;
+void cont_children(void) {
+ DLOG_CHILDREN;
- if (child.cont_signal > 0 && child.stopped) {
- child.stopped = false;
- killpg(child.pid, child.cont_signal);
- }
+ cont_child(&status_child);
+ cont_child(&ws_child);
}
/*
@@ -769,5 +1023,5 @@ void cont_child(void) {
*
*/
bool child_want_click_events(void) {
- return child.click_events;
+ return status_child.click_events;
}