From bc250b26a06be744f98912d309504deec9ca1c8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Fri, 16 Oct 2015 11:34:19 +0200 Subject: [PATCH] Allow multiple tray_output directives. This patch introduces the possibility to specify the tray_output directive multiple times. All values will be used by i3bar, in the order they are given. This way, a single bar configuration can be used for several machines with internal output names "eDP1" and "LVDS-0" by specifying tray_output for both. Any external output (e.g., "DP-0") will still not receive the tray. The same effect can be achieved by using "primary", but forces the user to couple the tray display to the primary output which may not be desirable behavior. relates to #555 --- docs/userguide | 5 ++ i3bar/include/config.h | 8 ++- i3bar/src/config.c | 27 +++++-- i3bar/src/xcb.c | 94 +++++++++++++++++------- include/config.h | 13 +++- src/config.c | 7 +- src/config_directives.c | 135 +++++++++++++++++------------------ src/ipc.c | 14 +++- testcases/t/177-bar-config.t | 4 +- 9 files changed, 200 insertions(+), 107 deletions(-) diff --git a/docs/userguide b/docs/userguide index ca39757a..e596aeae 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1369,6 +1369,11 @@ NetworkManager, VLC, Pidgin, etc. can place little icons. You can configure on which output (monitor) the icons should be displayed or you can turn off the functionality entirely. +You can use mutliple +tray_output+ directives in your config to specify a list +of outputs on which you want the tray to appear. The first available output in +that list as defined by the order of the directives will be used for the tray +output. + *Syntax*: --------------------------------- tray_output none|primary| diff --git a/i3bar/include/config.h b/i3bar/include/config.h index 1ce5dfa6..2a059046 100644 --- a/i3bar/include/config.h +++ b/i3bar/include/config.h @@ -29,6 +29,12 @@ typedef struct binding_t { TAILQ_ENTRY(binding_t) bindings; } binding_t; +typedef struct tray_output_t { + char *output; + + TAILQ_ENTRY(tray_output_t) tray_outputs; +} tray_output_t; + typedef struct config_t { int modifier; TAILQ_HEAD(bindings_head, binding_t) bindings; @@ -42,7 +48,7 @@ typedef struct config_t { char *command; char *fontname; i3String *separator_symbol; - char *tray_output; + TAILQ_HEAD(tray_outputs_head, tray_output_t) tray_outputs; int tray_padding; int num_outputs; char **outputs; diff --git a/i3bar/src/config.c b/i3bar/src/config.c index 6476d15f..bc13f3d9 100644 --- a/i3bar/src/config.c +++ b/i3bar/src/config.c @@ -21,6 +21,7 @@ static char *cur_key; static bool parsing_bindings; +static bool parsing_tray_outputs; /* * Parse a key. @@ -32,14 +33,20 @@ static int config_map_key_cb(void *params_, const unsigned char *keyVal, size_t FREE(cur_key); sasprintf(&(cur_key), "%.*s", keyLen, keyVal); - if (strcmp(cur_key, "bindings") == 0) + if (strcmp(cur_key, "bindings") == 0) { parsing_bindings = true; + } + + if (strcmp(cur_key, "tray_outputs") == 0) { + parsing_tray_outputs = true; + } return 1; } static int config_end_array_cb(void *params_) { parsing_bindings = false; + parsing_tray_outputs = false; return 1; } @@ -90,6 +97,14 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len return 0; } + if (parsing_tray_outputs) { + DLOG("Adding tray_output = %.*s to the list.\n", len, val); + tray_output_t *tray_output = scalloc(1, sizeof(tray_output_t)); + sasprintf(&(tray_output->output), "%.*s", len, val); + TAILQ_INSERT_TAIL(&(config.tray_outputs), tray_output, tray_outputs); + return 1; + } + if (!strcmp(cur_key, "mode")) { DLOG("mode = %.*s, len = %d\n", len, val, len); config.hide_on_modifier = (len == 4 && !strncmp((const char *)val, "dock", strlen("dock")) ? M_DOCK @@ -195,10 +210,13 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len return 1; } + /* We keep the old single tray_output working for users who only restart i3bar + * after updating. */ if (!strcmp(cur_key, "tray_output")) { - DLOG("tray_output %.*s\n", len, val); - FREE(config.tray_output); - sasprintf(&config.tray_output, "%.*s", len, val); + DLOG("Found deprecated key tray_output %.*s.\n", len, val); + tray_output_t *tray_output = scalloc(1, sizeof(tray_output_t)); + sasprintf(&(tray_output->output), "%.*s", len, val); + TAILQ_INSERT_TAIL(&(config.tray_outputs), tray_output, tray_outputs); return 1; } @@ -317,6 +335,7 @@ void parse_config_json(char *json) { handle = yajl_alloc(&outputs_callbacks, NULL, NULL); TAILQ_INIT(&(config.bindings)); + TAILQ_INIT(&(config.tray_outputs)); state = yajl_parse(handle, (const unsigned char *)json, strlen(json)); diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 20206c8b..92b0d1ab 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -728,25 +728,50 @@ static void handle_client_message(xcb_client_message_event_t *event) { } DLOG("X window %08x requested docking\n", client); - i3_output *walk, *output = NULL; - SLIST_FOREACH(walk, outputs, slist) { - if (!walk->active) - continue; - if (config.tray_output) { - if ((strcasecmp(walk->name, config.tray_output) != 0) && - (!walk->primary || strcasecmp("primary", config.tray_output) != 0)) + i3_output *output = NULL; + i3_output *walk = NULL; + tray_output_t *tray_output = NULL; + /* We need to iterate through the tray_output assignments first in + * order to prioritize them. Otherwise, if this bar manages two + * outputs and both are assigned as tray_output as well, the first + * output in our list would receive the tray rather than the first + * one defined via tray_output. */ + TAILQ_FOREACH(tray_output, &(config.tray_outputs), tray_outputs) { + SLIST_FOREACH(walk, outputs, slist) { + if (!walk->active) continue; + + if (strcasecmp(walk->name, tray_output->output) == 0) { + DLOG("Found tray_output assignment for output %s.\n", walk->name); + output = walk; + break; + } + + if (walk->primary && strcasecmp("primary", tray_output->output) == 0) { + DLOG("Found tray_output assignment on primary output %s.\n", walk->name); + output = walk; + break; + } } - DLOG("using output %s\n", walk->name); - output = walk; - break; + /* If we found an output, we're done. */ + if (output != NULL) + break; } + + /* Check whether any "tray_output primary" was defined for this bar. */ + bool contains_primary = false; + TAILQ_FOREACH(tray_output, &(config.tray_outputs), tray_outputs) { + if (strcasecmp("primary", tray_output->output) == 0) { + contains_primary = true; + break; + } + } + /* In case of tray_output == primary and there is no primary output - * configured, we fall back to the first available output. */ - if (output == NULL && - config.tray_output && - strcasecmp("primary", config.tray_output) == 0) { + * configured, we fall back to the first available output. We do the + * same if no tray_output was specified. */ + if (output == NULL && (contains_primary || TAILQ_EMPTY(&(config.tray_outputs)))) { SLIST_FOREACH(walk, outputs, slist) { if (!walk->active) continue; @@ -1707,20 +1732,37 @@ void reconfig_windows(bool redraw_bars) { exit(EXIT_FAILURE); } - const char *tray_output = (config.tray_output ? config.tray_output : SLIST_FIRST(outputs)->name); - if (!tray_configured && strcasecmp(tray_output, "none") != 0) { - /* Configuration sanity check: ensure this i3bar instance handles the output on - * which the tray should appear (e.g. don’t initialize a tray if tray_output == - * VGA-1 but output == [HDMI-1]). - */ - i3_output *output; - SLIST_FOREACH(output, outputs, slist) { - if (strcasecmp(output->name, tray_output) == 0 || - (strcasecmp(tray_output, "primary") == 0 && output->primary)) { - init_tray(); - break; + /* Unless "tray_output none" was specified, we need to initialize the tray. */ + const char *first = (TAILQ_EMPTY(&(config.tray_outputs))) ? SLIST_FIRST(outputs)->name : TAILQ_FIRST(&(config.tray_outputs))->output; + if (!tray_configured && strcasecmp(first, "none") != 0) { + /* We do a sanity check here to ensure that this i3bar instance actually handles + * the output on which the tray should appear. For example, + * consider tray_output == [VGA-1], but output == [HDMI-1]. */ + + /* If no tray_output was specified, we go ahead and initialize the tray as + * we will be using the first available output. */ + if (TAILQ_EMPTY(&(config.tray_outputs))) + init_tray(); + + /* If one or more tray_output assignments were specified, we ensure that at least one of + * them is actually an output managed by this instance. */ + tray_output_t *tray_output; + TAILQ_FOREACH(tray_output, &(config.tray_outputs), tray_outputs) { + i3_output *output; + bool found = false; + SLIST_FOREACH(output, outputs, slist) { + if (strcasecmp(output->name, tray_output->output) == 0 || + (strcasecmp(tray_output->output, "primary") == 0 && output->primary)) { + found = true; + init_tray(); + break; + } } + + if (found) + break; } + tray_configured = true; } } else { diff --git a/include/config.h b/include/config.h index 1c4ccce6..5b98ce6e 100644 --- a/include/config.h +++ b/include/config.h @@ -248,9 +248,10 @@ struct Barconfig { * simplicity (since we store just strings). */ char **outputs; - /** Output on which the tray should be shown. The special value of 'no' - * disables the tray (it’s enabled by default). */ - char *tray_output; + /* List of outputs on which the tray is allowed to be shown, in order. + * The special value "none" disables it (per default, it will be shown) and + * the special value "primary" enabled it on the primary output. */ + TAILQ_HEAD(tray_outputs_head, tray_output_t) tray_outputs; /* Padding around the tray icons. */ int tray_padding; @@ -366,6 +367,12 @@ struct Barbinding { TAILQ_ENTRY(Barbinding) bindings; }; +struct tray_output_t { + char *output; + + TAILQ_ENTRY(tray_output_t) tray_outputs; +}; + /** * Finds the configuration file to use (either the one specified by * override_configpath), the user’s one or the system default) and calls diff --git a/src/config.c b/src/config.c index c1c31e30..89c1d09a 100644 --- a/src/config.c +++ b/src/config.c @@ -110,8 +110,13 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, FREE(barconfig->id); for (int c = 0; c < barconfig->num_outputs; c++) free(barconfig->outputs[c]); + while (!TAILQ_EMPTY(&(barconfig->tray_outputs))) { + struct tray_output_t *tray_output = TAILQ_FIRST(&(barconfig->tray_outputs)); + FREE(tray_output->output); + TAILQ_REMOVE(&(barconfig->tray_outputs), tray_output, tray_outputs); + FREE(tray_output); + } FREE(barconfig->outputs); - FREE(barconfig->tray_output); FREE(barconfig->socket_path); FREE(barconfig->status_command); FREE(barconfig->i3bar_command); diff --git a/src/config_directives.c b/src/config_directives.c index fb6ae94c..419d663a 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -399,57 +399,57 @@ CFGFUN(no_focus) { * Bar configuration (i3bar) ******************************************************************************/ -static Barconfig current_bar; +static Barconfig *current_bar; CFGFUN(bar_font, const char *font) { - FREE(current_bar.font); - current_bar.font = sstrdup(font); + FREE(current_bar->font); + current_bar->font = sstrdup(font); } CFGFUN(bar_separator_symbol, const char *separator) { - FREE(current_bar.separator_symbol); - current_bar.separator_symbol = sstrdup(separator); + FREE(current_bar->separator_symbol); + current_bar->separator_symbol = sstrdup(separator); } CFGFUN(bar_mode, const char *mode) { - current_bar.mode = (strcmp(mode, "dock") == 0 ? M_DOCK : (strcmp(mode, "hide") == 0 ? M_HIDE : M_INVISIBLE)); + current_bar->mode = (strcmp(mode, "dock") == 0 ? M_DOCK : (strcmp(mode, "hide") == 0 ? M_HIDE : M_INVISIBLE)); } CFGFUN(bar_hidden_state, const char *hidden_state) { - current_bar.hidden_state = (strcmp(hidden_state, "hide") == 0 ? S_HIDE : S_SHOW); + current_bar->hidden_state = (strcmp(hidden_state, "hide") == 0 ? S_HIDE : S_SHOW); } CFGFUN(bar_id, const char *bar_id) { - current_bar.id = sstrdup(bar_id); + current_bar->id = sstrdup(bar_id); } CFGFUN(bar_output, const char *output) { - int new_outputs = current_bar.num_outputs + 1; - current_bar.outputs = srealloc(current_bar.outputs, sizeof(char *) * new_outputs); - current_bar.outputs[current_bar.num_outputs] = sstrdup(output); - current_bar.num_outputs = new_outputs; + int new_outputs = current_bar->num_outputs + 1; + current_bar->outputs = srealloc(current_bar->outputs, sizeof(char *) * new_outputs); + current_bar->outputs[current_bar->num_outputs] = sstrdup(output); + current_bar->num_outputs = new_outputs; } CFGFUN(bar_verbose, const char *verbose) { - current_bar.verbose = eval_boolstr(verbose); + current_bar->verbose = eval_boolstr(verbose); } CFGFUN(bar_modifier, const char *modifier) { if (strcmp(modifier, "Mod1") == 0) - current_bar.modifier = M_MOD1; + current_bar->modifier = M_MOD1; else if (strcmp(modifier, "Mod2") == 0) - current_bar.modifier = M_MOD2; + current_bar->modifier = M_MOD2; else if (strcmp(modifier, "Mod3") == 0) - current_bar.modifier = M_MOD3; + current_bar->modifier = M_MOD3; else if (strcmp(modifier, "Mod4") == 0) - current_bar.modifier = M_MOD4; + current_bar->modifier = M_MOD4; else if (strcmp(modifier, "Mod5") == 0) - current_bar.modifier = M_MOD5; + current_bar->modifier = M_MOD5; else if (strcmp(modifier, "Control") == 0 || strcmp(modifier, "Ctrl") == 0) - current_bar.modifier = M_CONTROL; + current_bar->modifier = M_CONTROL; else if (strcmp(modifier, "Shift") == 0) - current_bar.modifier = M_SHIFT; + current_bar->modifier = M_SHIFT; } static void bar_configure_binding(const char *button, const char *command) { @@ -465,7 +465,7 @@ static void bar_configure_binding(const char *button, const char *command) { } struct Barbinding *current; - TAILQ_FOREACH(current, &(current_bar.bar_bindings), bindings) { + TAILQ_FOREACH(current, &(current_bar->bar_bindings), bindings) { if (current->input_code == input_code) { ELOG("command for button %s was already specified, ignoring.\n", button); return; @@ -475,7 +475,7 @@ static void bar_configure_binding(const char *button, const char *command) { struct Barbinding *new_binding = scalloc(1, sizeof(struct Barbinding)); new_binding->input_code = input_code; new_binding->command = sstrdup(command); - TAILQ_INSERT_TAIL(&(current_bar.bar_bindings), new_binding, bindings); + TAILQ_INSERT_TAIL(&(current_bar->bar_bindings), new_binding, bindings); } CFGFUN(bar_wheel_up_cmd, const char *command) { @@ -493,29 +493,29 @@ CFGFUN(bar_bindsym, const char *button, const char *command) { } CFGFUN(bar_position, const char *position) { - current_bar.position = (strcmp(position, "top") == 0 ? P_TOP : P_BOTTOM); + current_bar->position = (strcmp(position, "top") == 0 ? P_TOP : P_BOTTOM); } CFGFUN(bar_i3bar_command, const char *i3bar_command) { - FREE(current_bar.i3bar_command); - current_bar.i3bar_command = sstrdup(i3bar_command); + FREE(current_bar->i3bar_command); + current_bar->i3bar_command = sstrdup(i3bar_command); } CFGFUN(bar_color, const char *colorclass, const char *border, const char *background, const char *text) { -#define APPLY_COLORS(classname) \ - do { \ - if (strcmp(colorclass, #classname) == 0) { \ - if (text != NULL) { \ - /* New syntax: border, background, text */ \ - current_bar.colors.classname##_border = sstrdup(border); \ - current_bar.colors.classname##_bg = sstrdup(background); \ - current_bar.colors.classname##_text = sstrdup(text); \ - } else { \ - /* Old syntax: text, background */ \ - current_bar.colors.classname##_bg = sstrdup(background); \ - current_bar.colors.classname##_text = sstrdup(border); \ - } \ - } \ +#define APPLY_COLORS(classname) \ + do { \ + if (strcmp(colorclass, #classname) == 0) { \ + if (text != NULL) { \ + /* New syntax: border, background, text */ \ + current_bar->colors.classname##_border = sstrdup(border); \ + current_bar->colors.classname##_bg = sstrdup(background); \ + current_bar->colors.classname##_text = sstrdup(text); \ + } else { \ + /* Old syntax: text, background */ \ + current_bar->colors.classname##_bg = sstrdup(background); \ + current_bar->colors.classname##_text = sstrdup(border); \ + } \ + } \ } while (0) APPLY_COLORS(focused_workspace); @@ -528,73 +528,72 @@ CFGFUN(bar_color, const char *colorclass, const char *border, const char *backgr } CFGFUN(bar_socket_path, const char *socket_path) { - FREE(current_bar.socket_path); - current_bar.socket_path = sstrdup(socket_path); + FREE(current_bar->socket_path); + current_bar->socket_path = sstrdup(socket_path); } CFGFUN(bar_tray_output, const char *output) { - FREE(current_bar.tray_output); - current_bar.tray_output = sstrdup(output); + struct tray_output_t *tray_output = scalloc(1, sizeof(struct tray_output_t)); + tray_output->output = sstrdup(output); + TAILQ_INSERT_TAIL(&(current_bar->tray_outputs), tray_output, tray_outputs); } CFGFUN(bar_tray_padding, const long padding_px) { - current_bar.tray_padding = padding_px; + current_bar->tray_padding = padding_px; } CFGFUN(bar_color_single, const char *colorclass, const char *color) { if (strcmp(colorclass, "background") == 0) - current_bar.colors.background = sstrdup(color); + current_bar->colors.background = sstrdup(color); else if (strcmp(colorclass, "separator") == 0) - current_bar.colors.separator = sstrdup(color); + current_bar->colors.separator = sstrdup(color); else if (strcmp(colorclass, "statusline") == 0) - current_bar.colors.statusline = sstrdup(color); + current_bar->colors.statusline = sstrdup(color); else if (strcmp(colorclass, "focused_background") == 0) - current_bar.colors.focused_background = sstrdup(color); + current_bar->colors.focused_background = sstrdup(color); else if (strcmp(colorclass, "focused_separator") == 0) - current_bar.colors.focused_separator = sstrdup(color); + current_bar->colors.focused_separator = sstrdup(color); else - current_bar.colors.focused_statusline = sstrdup(color); + current_bar->colors.focused_statusline = sstrdup(color); } CFGFUN(bar_status_command, const char *command) { - FREE(current_bar.status_command); - current_bar.status_command = sstrdup(command); + FREE(current_bar->status_command); + current_bar->status_command = sstrdup(command); } CFGFUN(bar_binding_mode_indicator, const char *value) { - current_bar.hide_binding_mode_indicator = !eval_boolstr(value); + current_bar->hide_binding_mode_indicator = !eval_boolstr(value); } CFGFUN(bar_workspace_buttons, const char *value) { - current_bar.hide_workspace_buttons = !eval_boolstr(value); + current_bar->hide_workspace_buttons = !eval_boolstr(value); } CFGFUN(bar_strip_workspace_numbers, const char *value) { - current_bar.strip_workspace_numbers = eval_boolstr(value); + current_bar->strip_workspace_numbers = eval_boolstr(value); } CFGFUN(bar_start) { - TAILQ_INIT(&(current_bar.bar_bindings)); - current_bar.tray_padding = 2; + current_bar = scalloc(1, sizeof(struct Barconfig)); + TAILQ_INIT(&(current_bar->bar_bindings)); + TAILQ_INIT(&(current_bar->tray_outputs)); + current_bar->tray_padding = 2; } CFGFUN(bar_finish) { DLOG("\t new bar configuration finished, saving.\n"); /* Generate a unique ID for this bar if not already configured */ - if (!current_bar.id) - sasprintf(¤t_bar.id, "bar-%d", config.number_barconfigs); + if (current_bar->id == NULL) + sasprintf(¤t_bar->id, "bar-%d", config.number_barconfigs); config.number_barconfigs++; /* If no font was explicitly set, we use the i3 font as default */ - if (!current_bar.font && font_pattern) - current_bar.font = sstrdup(font_pattern); + if (current_bar->font == NULL && font_pattern != NULL) + current_bar->font = sstrdup(font_pattern); - /* Copy the current (static) structure into a dynamically allocated - * one, then cleanup our static one. */ - Barconfig *bar_config = scalloc(1, sizeof(Barconfig)); - memcpy(bar_config, ¤t_bar, sizeof(Barconfig)); - TAILQ_INSERT_TAIL(&barconfigs, bar_config, configs); - - memset(¤t_bar, '\0', sizeof(Barconfig)); + TAILQ_INSERT_TAIL(&barconfigs, current_bar, configs); + /* Simply reset the pointer, but don't free the resources. */ + current_bar = NULL; } diff --git a/src/ipc.c b/src/ipc.c index 8e448c7c..276a737a 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -551,6 +551,18 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) { y(array_close); } + if (!TAILQ_EMPTY(&(config->tray_outputs))) { + ystr("tray_outputs"); + y(array_open); + + struct tray_output_t *tray_output; + TAILQ_FOREACH(tray_output, &(config->tray_outputs), tray_outputs) { + ystr(tray_output->output); + } + + y(array_close); + } + #define YSTR_IF_SET(name) \ do { \ if (config->name) { \ @@ -559,8 +571,6 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) { } \ } while (0) - YSTR_IF_SET(tray_output); - ystr("tray_padding"); y(integer, config->tray_padding); diff --git a/testcases/t/177-bar-config.t b/testcases/t/177-bar-config.t index 8ef94e57..956b0caa 100644 --- a/testcases/t/177-bar-config.t +++ b/testcases/t/177-bar-config.t @@ -138,7 +138,7 @@ ok(!$bar_config->{binding_mode_indicator}, 'mode indicator disabled'); is($bar_config->{mode}, 'dock', 'dock mode'); is($bar_config->{position}, 'top', 'position top'); is_deeply($bar_config->{outputs}, [ 'HDMI1', 'HDMI2' ], 'outputs ok'); -is($bar_config->{tray_output}, 'HDMI2', 'tray_output ok'); +is_deeply($bar_config->{tray_outputs}, [ 'LVDS1', 'HDMI2' ], 'tray_output ok'); is($bar_config->{tray_padding}, 0, 'tray_padding ok'); is($bar_config->{font}, 'Terminus', 'font ok'); is($bar_config->{socket_path}, '/tmp/foobar', 'socket_path ok'); @@ -294,7 +294,7 @@ ok($bar_config->{binding_mode_indicator}, 'mode indicator enabled'); is($bar_config->{mode}, 'dock', 'dock mode'); is($bar_config->{position}, 'top', 'position top'); is_deeply($bar_config->{outputs}, [ 'HDMI1', 'HDMI2' ], 'outputs ok'); -is($bar_config->{tray_output}, 'HDMI2', 'tray_output ok'); +is_deeply($bar_config->{tray_outputs}, [ 'LVDS1', 'HDMI2' ], 'tray_output ok'); is($bar_config->{font}, 'Terminus', 'font ok'); is($bar_config->{socket_path}, '/tmp/foobar', 'socket_path ok'); is_deeply($bar_config->{colors},