Merge pull request #2001 from Airblader/feature-multiple-tray-output

Allow multiple tray_output directives.
This commit is contained in:
Michael Stapelberg 2015-11-02 09:18:26 +01:00
commit bb7a36e0c7
9 changed files with 215 additions and 113 deletions

View File

@ -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|<output>

View File

@ -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;

View File

@ -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));

View File

@ -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. dont 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 {

View File

@ -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 (its 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 users one or the system default) and calls

View File

@ -75,20 +75,20 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
ungrab_all_keys(conn);
struct Mode *mode;
Binding *bind;
while (!SLIST_EMPTY(&modes)) {
mode = SLIST_FIRST(&modes);
FREE(mode->name);
/* Clear the old binding list */
bindings = mode->bindings;
while (!TAILQ_EMPTY(bindings)) {
bind = TAILQ_FIRST(bindings);
TAILQ_REMOVE(bindings, bind, bindings);
while (!TAILQ_EMPTY(mode->bindings)) {
Binding *bind = TAILQ_FIRST(mode->bindings);
TAILQ_REMOVE(mode->bindings, bind, bindings);
binding_free(bind);
}
FREE(bindings);
FREE(mode->bindings);
SLIST_REMOVE(&modes, mode, Mode, modes);
FREE(mode);
}
struct Assignment *assign;
@ -110,8 +110,22 @@ 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->bar_bindings))) {
struct Barbinding *binding = TAILQ_FIRST(&(barconfig->bar_bindings));
FREE(binding->command);
TAILQ_REMOVE(&(barconfig->bar_bindings), binding, bindings);
FREE(binding);
}
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);

View File

@ -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(&current_bar.id, "bar-%d", config.number_barconfigs);
if (current_bar->id == NULL)
sasprintf(&current_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, &current_bar, sizeof(Barconfig));
TAILQ_INSERT_TAIL(&barconfigs, bar_config, configs);
memset(&current_bar, '\0', sizeof(Barconfig));
TAILQ_INSERT_TAIL(&barconfigs, current_bar, configs);
/* Simply reset the pointer, but don't free the resources. */
current_bar = NULL;
}

View File

@ -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);

View File

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