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},