Merge pull request #4311 from i3/i3bar-ws-protocol

i3bar: Add protocol for workspace buttons
This commit is contained in:
Orestis Floros 2023-01-22 19:05:01 +01:00 committed by GitHub
commit 26990d90f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 923 additions and 304 deletions

View File

@ -0,0 +1,184 @@
i3bar workspace buttons protocol
================================
This document explains the protocol in which i3bar expects input for
configuring workspace buttons. This feature is available since i3 version 4.23.
The program defined by the +workspace_command+ configuration option for i3bar can
modify the workspace buttons displayed by i3bar. The command should constantly
print in its standard output a stream of messages following the protocol
defined in this page.
If you are looking for the status line protocol instead, see https://i3wm.org/docs/i3bar-protocol.html.
== The protocol
Each message should be a newline-delimited JSON array. The array is in the same
format as the +GET_WORKSPACES+ ipc event, see
https://i3wm.org/docs/ipc.html#_workspaces_reply.
As an example, this is the output of the +i3-msg -t get_workspaces+ command:
------------------------------
[
{
"id": 94131549984064,
"num": 1,
"name": "1",
"visible": false,
"focused": false,
"output": "HDMI-A-0",
"urgent": false
},
{
"id": 94131550477584,
"num": 2,
"name": "2",
"visible": true,
"focused": true,
"output": "HDMI-A-0",
"urgent": false
},
{
"id": 94131550452704,
"num": 3,
"name": "3:some workspace",
"visible": false,
"focused": false,
"output": "HDMI-A-0",
"urgent": false
}
]
------------------------------
Please note that this example was pretty printed for human consumption, with
the +"rect"+ field removed. Workspace button commands should output each array
in one line.
Each element in the array represents a workspace. i3bar creates one workspace
button for each element in the array. The order of these buttons is the same as
the order of the elements in the array.
In general, we recommend subscribing to the +workspace+ and +output+
https://i3wm.org/docs/ipc.html#_workspace_event[events],
fetching the current workspace information with +GET_WORKSPACES+, modifying the
JSON array in the response according to your needs and then printing it to the
standard output. However, you are free to build a new message from the ground
up.
=== Workspace objects in detail
The documentation of +GET_WORKSPACES+ should be sufficient to understand the
meaning of each property but here we provide extra notes for each property and
its meaning with respect to i3bar.
All properties but +name+ are optional.
id (integer)::
If it is included it will be used to switch to that workspace when you
click the corresponding button. If it's not provided, the +name+ will be
used. You can use the +id+ field to present workspaces under a modified
name.
num (integer)::
The only use of a workspace's number is if the +strip_workspace_numbers+
setting is enabled.
name (string)::
The only required property. If an +id+ is provided you can freely change
the +name+ as you wish, effectively renaming the buttons of i3bar.
visible (boolean)::
Defaults to +false+ if not included. +focused+ takes precedence over it,
however +visible+ is important for more than one monitors.
focused (boolean)::
Defaults to +false+ if not included. Generally, exactly one of the
workspaces should be +focused+. If not, no button will have the
+focused_workspace+ color.
urgent (boolean)::
Defaults to +false+ if not included.
rect (map)::
Not used by i3bar but will be ignored.
output (string)::
Defaults to the primary output if not included.
== Examples
These example scripts require the https://stedolan.github.io/jq/[jq] utility to
be installed but otherwise just use the standard +i3-msg+ utility included with
i3. However, you can write your own scripts in your preferred language, with
the help of one of the
https://i3wm.org/docs/ipc.html#_see_also_existing_libraries[pre-existing i3
libraries]
=== Base configuration
------------------------------
bar {
workspace_command /path/to/your/script.sh
}
------------------------------
=== Re-create the default behaviour of i3bar
Not very useful by itself but this will be the basic building block of all the
following scripts. This one does not require +jq+.
------------------------------
#!/bin/sh
i3-msg -t subscribe -m '["workspace", "output"]' | {
# Initially print the current workspaces before we receive any events. This
# avoids having an empty bar when starting up.
i3-msg -t get_workspaces;
# Then, while we receive events, update the workspace information.
while read; do i3-msg -t get_workspaces; done;
}
------------------------------
=== Hide workspace named +foo+ unless if it is focused.
------------------------------
#!/bin/sh
i3-msg -t subscribe -m '["workspace", "output"]' | {
i3-msg -t get_workspaces;
while read; do i3-msg -t get_workspaces; done;
} | jq --unbuffered -c '[ .[] | select(.name != "foo" or .focused) ]'
------------------------------
Important! Make sure you use the +--unbuffered+ flag with +jq+, otherwise you
might not get the changes in real-time but whenever they are flushed, which
might mean that you are getting an empty bar until enough events are written.
=== Show empty workspaces +foo+ and +bar+ on LVDS1 even if they do not exist at the moment.
------------------------------
#!/bin/sh
i3-msg -t subscribe -m '["workspace", "output"]' | {
i3-msg -t get_workspaces;
while read; do i3-msg -t get_workspaces; done;
} | jq --unbuffered -c '
def fake_ws(name): {
name: name,
output: "LVDS1",
};
. + [ fake_ws("foo"), fake_ws("bar") ] | unique_by(.name)
'
------------------------------
=== Sort workspaces in reverse alphanumeric order
------------------------------
#!/bin/sh
i3-msg -t subscribe -m '["workspace", "output"]' | {
i3-msg -t get_workspaces;
while read; do i3-msg -t get_workspaces; done;
} | jq --unbuffered -c 'sort_by(.name) | reverse'
------------------------------
=== Append "foo" to the name of each workspace
------------------------------
#!/bin/sh
i3-msg -t subscribe -m '["workspace", "output"]' | {
i3-msg -t get_workspaces;
while read; do i3-msg -t get_workspaces; done;
} | jq --unbuffered -c '[ .[] | .name |= . + " foo" ]'
------------------------------

View File

@ -1611,6 +1611,30 @@ bar {
} }
------------------------------------------------- -------------------------------------------------
[[workspace_command]]
=== Workspace buttons command
Since i3 4.23, i3bar can run a program and use its +stdout+ output to define
the workspace buttons displayed on the left hand side of the bar. With this
feature, you can, for example, rename the buttons of workspaces, hide specific
workspaces, always show a workspace button even if the workspace does not exist
or change the order of the buttons.
Also see <<status_command>> for the statusline option and
https://i3wm.org/docs/i3bar-workspace-protocol.html for the detailed protocol.
*Syntax*:
------------------------
workspace_command <command>
------------------------
*Example*:
-------------------------------------------------
bar {
workspace_command /path/to/script.sh
}
-------------------------------------------------
=== Display mode === Display mode
You can either have i3bar be visible permanently at one edge of the screen You can either have i3bar be visible permanently at one edge of the screen
@ -2397,6 +2421,9 @@ available:
<criteria>:: <criteria>::
Sets focus to the container that matches the specified criteria. Sets focus to the container that matches the specified criteria.
See <<command_criteria>>. See <<command_criteria>>.
workspace::
Sets focus to the workspace that contains the container that matches the
specified criteria.
left|right|up|down:: left|right|up|down::
Sets focus to the nearest container in the given direction. Sets focus to the nearest container in the given direction.
parent:: parent::
@ -2423,6 +2450,7 @@ output::
*Syntax*: *Syntax*:
---------------------------------------------- ----------------------------------------------
<criteria> focus <criteria> focus
<criteria> focus workspace
focus left|right|down|up focus left|right|down|up
focus parent|child|floating|tiling|mode_toggle focus parent|child|floating|tiling|mode_toggle
focus next|prev [sibling] focus next|prev [sibling]
@ -2434,6 +2462,10 @@ focus output left|right|down|up|current|primary|nonprimary|next|<output1> [outpu
# Focus firefox # Focus firefox
bindsym $mod+F1 [class="Firefox"] focus bindsym $mod+F1 [class="Firefox"] focus
# Focus the workspace where firefox is, without necessarily focusing firefox
# itself.
bindsym $mod+x [class="Firefox"] focus workspace
# Focus container on the left, bottom, top, right # Focus container on the left, bottom, top, right
bindsym $mod+j focus left bindsym $mod+j focus left
bindsym $mod+k focus down bindsym $mod+k focus down
@ -2462,7 +2494,7 @@ bindsym $mod+x focus output primary
bindsym $mod+x focus output nonprimary bindsym $mod+x focus output nonprimary
# Cycle focus between outputs VGA1 and LVDS1 but not DVI0 # Cycle focus between outputs VGA1 and LVDS1 but not DVI0
bindsym $mod+x move workspace to output VGA1 LVDS1 bindsym $mod+x focus output VGA1 LVDS1
------------------------------------------------- -------------------------------------------------
Note that you might not have a primary output configured yet. To do so, run: Note that you might not have a primary output configured yet. To do so, run:

View File

@ -11,7 +11,7 @@
#include <config.h> #include <config.h>
#include <stdbool.h> #include <ev.h>
#define STDIN_CHUNK_SIZE 1024 #define STDIN_CHUNK_SIZE 1024
@ -40,6 +40,18 @@ typedef struct {
*/ */
bool click_events; bool click_events;
bool click_events_init; bool click_events_init;
/**
* stdin- and SIGCHLD-watchers
*/
ev_io *stdin_io;
ev_child *child_sig;
int stdin_fd;
/**
* Line read from child that did not include a newline character.
*/
char *pending_line;
} i3bar_child; } i3bar_child;
/* /*
@ -50,36 +62,66 @@ void clear_statusline(struct statusline_head *head, bool free_resources);
/* /*
* Start a child process with the specified command and reroute stdin. * 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 * We actually start a shell to execute the command so we don't have to care
* about arguments and such * about arguments and such.
*
* If `command' is NULL, such as in the case when no `status_command' is given
* in the bar config, no child will be started.
* *
*/ */
void start_child(char *command); void start_child(char *command);
/*
* Same as start_child but starts the configured client that manages workspace
* buttons.
*
*/
void start_ws_child(char *command);
/*
* Returns true if the status child process is alive.
*
*/
bool status_child_is_alive(void);
/*
* Returns true if the workspace child process is alive.
*
*/
bool ws_child_is_alive(void);
/* /*
* kill()s the child process (if any). Called when exit()ing. * kill()s the child process (if any). Called when exit()ing.
* *
*/ */
void kill_child_at_exit(void); void kill_children_at_exit(void);
/* /*
* kill()s the child process (if any) and closes and * kill()s the child process (if any) and closes and free()s the stdin- and
* free()s the stdin- and SIGCHLD-watchers * SIGCHLD-watchers
* *
*/ */
void kill_child(void); void kill_child(void);
/*
* 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);
/* /*
* Sends a SIGSTOP to the child process (if existent) * Sends a SIGSTOP to the child process (if existent)
* *
*/ */
void stop_child(void); void stop_children(void);
/* /*
* Sends a SIGCONT to the child process (if existent) * Sends a SIGCONT to the child process (if existent)
* *
*/ */
void cont_child(void); void cont_children(void);
/* /*
* Whether or not the child want click events * Whether or not the child want click events
@ -92,3 +134,14 @@ bool child_want_click_events(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); 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);
/*
* 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);
/*
* Replaces the workspace buttons with an error message.
*/
void set_workspace_button_error(const char *message);

View File

@ -62,6 +62,7 @@ typedef struct config_t {
bool strip_ws_name; bool strip_ws_name;
char *bar_id; char *bar_id;
char *command; char *command;
char *workspace_command;
char *fontname; char *fontname;
i3String *separator_symbol; i3String *separator_symbol;
TAILQ_HEAD(tray_outputs_head, tray_output_t) tray_outputs; TAILQ_HEAD(tray_outputs_head, tray_output_t) tray_outputs;
@ -79,17 +80,17 @@ typedef struct config_t {
extern config_t config; extern config_t config;
/** /**
* Start parsing the received bar configuration JSON string * Parse the received bar configuration JSON string
* *
*/ */
void parse_config_json(char *json); void parse_config_json(const unsigned char *json, size_t size);
/** /**
* Start parsing the received bar configuration list. The only usecase right * Parse the received bar configuration list. The only usecase right now is to
* now is to automatically get the first bar id. * automatically get the first bar id.
* *
*/ */
void parse_get_first_i3bar_config(char *json); void parse_get_first_i3bar_config(const unsigned char *json, size_t size);
/** /**
* free()s the color strings as soon as they are not needed anymore. * free()s the color strings as soon as they are not needed anymore.

View File

@ -24,7 +24,7 @@ struct mode {
typedef struct mode mode; typedef struct mode mode;
/* /*
* Start parsing the received JSON string * Parse the received JSON string
* *
*/ */
void parse_mode_json(char *json); void parse_mode_json(const unsigned char *json, size_t size);

View File

@ -22,10 +22,10 @@ SLIST_HEAD(outputs_head, i3_output);
extern struct outputs_head* outputs; extern struct outputs_head* outputs;
/* /*
* Start parsing the received JSON string * Parse the received JSON string
* *
*/ */
void parse_outputs_json(char* json); void parse_outputs_json(const unsigned char* json, size_t size);
/* /*
* Initiate the outputs list * Initiate the outputs list

View File

@ -18,10 +18,10 @@ typedef struct i3_ws i3_ws;
TAILQ_HEAD(ws_head, i3_ws); TAILQ_HEAD(ws_head, i3_ws);
/* /*
* Start parsing the received JSON string * Parse the received JSON string
* *
*/ */
void parse_workspaces_json(char *json); void parse_workspaces_json(const unsigned char *json, size_t size);
/* /*
* free() all workspace data structures * free() all workspace data structures
@ -38,7 +38,6 @@ struct i3_ws {
bool visible; /* If the ws is currently visible on an output */ bool visible; /* If the ws is currently visible on an output */
bool focused; /* If the ws is currently focused */ bool focused; /* If the ws is currently focused */
bool urgent; /* If the urgent hint of the ws is set */ bool urgent; /* If the urgent hint of the ws is set */
rect rect; /* The rect of the ws (not used (yet)) */
struct i3_output *output; /* The current output of the ws */ struct i3_output *output; /* The current output of the ws */
TAILQ_ENTRY(i3_ws) tailq; /* Pointer for the TAILQ-Macro */ TAILQ_ENTRY(i3_ws) tailq; /* Pointer for the TAILQ-Macro */

View File

@ -10,6 +10,7 @@
#include "common.h" #include "common.h"
#include "yajl_utils.h" #include "yajl_utils.h"
#include <ctype.h> /* isspace */
#include <err.h> #include <err.h>
#include <errno.h> #include <errno.h>
#include <ev.h> #include <ev.h>
@ -27,14 +28,30 @@
#include <yajl/yajl_parse.h> #include <yajl/yajl_parse.h>
/* Global variables for child_*() */ /* Global variables for child_*() */
i3bar_child child = {0}; i3bar_child status_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", \ i3bar_child ws_child = {0};
__func__, (long)child.pid, child.stopped, child.stop_signal, child.cont_signal, child.click_events, child.click_events_init)
/* stdin- and SIGCHLD-watchers */ #define DLOG_CHILD(c) \
ev_io *stdin_io; do { \
int stdin_fd; if ((c).pid == 0) { \
ev_child *child_sig; 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 */ /* JSON parser for stdin */
yajl_handle parser; 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); TAILQ_INSERT_TAIL(&statusline_head, message_block, blocks);
finish: finish:
FREE(message); free(message);
va_end(args); va_end(args);
} }
@ -135,22 +152,27 @@ finish:
* Stop and free() the stdin- and SIGCHLD-watchers * Stop and free() the stdin- and SIGCHLD-watchers
* *
*/ */
static void cleanup(void) { static void cleanup(i3bar_child *c) {
if (stdin_io != NULL) { DLOG_CHILD(*c);
ev_io_stop(main_loop, stdin_io);
FREE(stdin_io); if (c->stdin_io != NULL) {
close(stdin_fd); ev_io_stop(main_loop, c->stdin_io);
stdin_fd = 0; FREE(c->stdin_io);
close(child_stdin);
child_stdin = 0; if (c->pid == status_child.pid) {
close(child_stdin);
child_stdin = 0;
}
close(c->stdin_fd);
} }
if (child_sig != NULL) { if (c->child_sig != NULL) {
ev_child_stop(main_loop, child_sig); ev_child_stop(main_loop, c->child_sig);
FREE(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. * Returns NULL on EOF.
* *
*/ */
static unsigned char *get_buffer(ev_io *watcher, int *ret_buffer_len) { static unsigned char *get_buffer(int fd, int *ret_buffer_len) {
int fd = watcher->fd;
int n = 0;
int rec = 0; int rec = 0;
int buffer_len = STDIN_CHUNK_SIZE; int buffer_len = STDIN_CHUNK_SIZE;
unsigned char *buffer = smalloc(buffer_len + 1); unsigned char *buffer = smalloc(buffer_len + 1);
buffer[0] = '\0'; buffer[0] = '\0';
while (1) { 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 (n == -1) {
if (errno == EAGAIN) { if (errno == EAGAIN) {
/* finish up */ /* finish up */
@ -390,10 +410,11 @@ static unsigned char *get_buffer(ev_io *watcher, int *ret_buffer_len) {
if (rec == buffer_len) { if (rec == buffer_len) {
buffer_len += STDIN_CHUNK_SIZE; 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); FREE(buffer);
rec = -1; rec = -1;
} }
@ -443,13 +464,14 @@ static bool read_json_input(unsigned char *input, int length) {
* in statusline * 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; int rec;
unsigned char *buffer = get_buffer(watcher, &rec); unsigned char *buffer = get_buffer(fd, &rec);
if (buffer == NULL) if (buffer == NULL) {
return; return;
}
bool has_urgent = false; bool has_urgent = false;
if (child.version > 0) { if (status_child.version > 0) {
has_urgent = read_json_input(buffer, rec); has_urgent = read_json_input(buffer, rec);
} else { } else {
read_flat_input((char *)buffer, rec); 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 * 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; int rec;
unsigned char *buffer = get_buffer(watcher, &rec); unsigned char *buffer = get_buffer(fd, &rec);
if (buffer == NULL) if (buffer == NULL) {
return; return;
}
DLOG("Detecting input type based on buffer *%.*s*\n", rec, buffer); DLOG("Detecting input type based on buffer *%.*s*\n", rec, buffer);
/* Detect whether this is JSON or plain text. */ /* Detect whether this is JSON or plain text. */
unsigned int consumed = 0; unsigned int consumed = 0;
/* At the moment, we dont care for the version. This might change /* At the moment, we dont care for the version. This might change
* in the future, but for now, we just discard it. */ * in the future, but for now, we just discard it. */
parse_json_header(&child, buffer, rec, &consumed); parse_json_header(&status_child, buffer, rec, &consumed);
if (child.version > 0) { if (status_child.version > 0) {
/* If hide-on-modifier is set, we start of by sending the /* If hide-on-modifier is set, we start of by sending the status_child
* child a SIGSTOP, because the bars aren't mapped at start */ * a SIGSTOP, because the bars aren't mapped at start */
if (config.hide_on_modifier) { if (config.hide_on_modifier) {
stop_child(); stop_children();
} }
draw_bars(read_json_input(buffer + consumed, rec - consumed)); draw_bars(read_json_input(buffer + consumed, rec - consumed));
} else { } 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); read_flat_input((char *)buffer, rec);
} }
free(buffer); 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) { 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", ELOG("Child (pid: %d) unexpectedly exited with status %d\n",
child.pid, watcher->pid,
exit_status); 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 /* this error is most likely caused by a user giving a nonexecutable or
* nonexistent file, so we will handle those cases separately. */ * nonexistent file, so we will handle those cases separately. */
if (exit_status == 126) if (exit_status == 126) {
set_statusline_error("status_command is not executable (exit %d)", exit_status); error_function_pointer("%s is not executable (exit %d)", command_type, exit_status);
else if (exit_status == 127) } else if (exit_status == 127) {
set_statusline_error("status_command not found or is missing a library dependency (exit %d)", exit_status); error_function_pointer("%s not found or is missing a library dependency (exit %d)", command_type, exit_status);
else } else {
set_statusline_error("status_command process exited unexpectedly (exit %d)", exit_status); error_function_pointer("%s process exited unexpectedly (exit %d)", command_type, exit_status);
}
cleanup(); cleanup(c);
draw_bars(false); draw_bars(false);
} }
static void child_write_output(void) { static void child_write_output(void) {
if (child.click_events) { if (status_child.click_events) {
const unsigned char *output; const unsigned char *output;
size_t size; size_t size;
ssize_t n; ssize_t n;
@ -535,7 +700,7 @@ static void child_write_output(void) {
yajl_gen_clear(gen); yajl_gen_clear(gen);
if (n == -1) { if (n == -1) {
child.click_events = false; status_child.click_events = false;
kill_child(); kill_child();
set_statusline_error("child_write_output failed"); set_statusline_error("child_write_output failed");
draw_bars(false); 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. * 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 * 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) { void start_child(char *command) {
if (command == NULL) if (command == NULL) {
return; return;
}
/* Allocate a yajl parser which will be used to parse stdin. */ /* Allocate a yajl parser which will be used to parse stdin. */
static yajl_callbacks callbacks = { static yajl_callbacks callbacks = {
@ -568,69 +769,77 @@ void start_child(char *command) {
.yajl_end_array = stdin_end_array, .yajl_end_array = stdin_end_array,
}; };
parser = yajl_alloc(&callbacks, NULL, &parser_context); parser = yajl_alloc(&callbacks, NULL, &parser_context);
gen = yajl_gen_alloc(NULL); gen = yajl_gen_alloc(NULL);
int pipe_in[2]; /* pipe we read from */ int pipe_in[2]; /* pipe we read from */
int pipe_out[2]; /* pipe we write to */ int pipe_out[2]; /* pipe we write to */
spipe(pipe_in);
spipe(pipe_out);
if (pipe(pipe_in) == -1) status_child.pid = sfork();
err(EXIT_FAILURE, "pipe(pipe_in)"); if (status_child.pid == 0) {
if (pipe(pipe_out) == -1) /* Child-process. Reroute streams and start shell */
err(EXIT_FAILURE, "pipe(pipe_out)"); close(pipe_in[0]);
close(pipe_out[1]);
child.pid = fork(); dup2(pipe_in[1], STDOUT_FILENO);
switch (child.pid) { dup2(pipe_out[0], STDIN_FILENO);
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]); setpgid(status_child.pid, 0);
close(pipe_out[1]); exec_shell(command);
return;
}
/* Parent-process. Reroute streams */
close(pipe_in[1]);
close(pipe_out[0]);
dup2(pipe_in[1], STDOUT_FILENO); status_child.stdin_fd = pipe_in[0];
dup2(pipe_out[0], STDIN_FILENO); child_stdin = pipe_out[1];
status_child.version = -1;
setpgid(child.pid, 0); setup_child_cb(&status_child);
execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (char *)NULL); }
return;
default:
/* Parent-process. Reroute streams */
close(pipe_in[1]); /*
close(pipe_out[0]); * Same as start_child but starts the configured client that manages workspace
* buttons.
stdin_fd = pipe_in[0]; *
child_stdin = pipe_out[1]; */
void start_ws_child(char *command) {
break; if (command == NULL) {
return;
} }
/* We set O_NONBLOCK because blocking is evil in event-driven software */ ws_child.stop_signal = SIGSTOP;
fcntl(stdin_fd, F_SETFL, O_NONBLOCK); ws_child.cont_signal = SIGCONT;
stdin_io = smalloc(sizeof(ev_io)); int pipe_in[2]; /* pipe we read from */
ev_io_init(stdin_io, &stdin_io_first_line_cb, stdin_fd, EV_READ); spipe(pipe_in);
ev_io_start(main_loop, stdin_io);
/* We must cleanup, if the child unexpectedly terminates */ ws_child.pid = sfork();
child_sig = smalloc(sizeof(ev_child)); if (ws_child.pid == 0) {
ev_child_init(child_sig, &child_sig_cb, child.pid, 0); /* Child-process. Reroute streams and start shell */
ev_child_start(main_loop, child_sig); close(pipe_in[0]);
dup2(pipe_in[1], STDOUT_FILENO);
atexit(kill_child_at_exit); setpgid(ws_child.pid, 0);
DLOG_CHILD; 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) { 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); yajl_gen_array_open(gen);
child_write_output(); 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) { 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; return;
} }
@ -706,35 +915,85 @@ void send_block_clicked(int button, const char *name, const char *instance, int
child_write_output(); 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. * kill()s the child process (if any). Called when exit()ing.
* *
*/ */
void kill_child_at_exit(void) { void kill_children_at_exit(void) {
DLOG_CHILD; DLOG_CHILDREN;
cont_children();
if (child.pid > 0) { if (is_alive(&status_child)) {
if (child.cont_signal > 0 && child.stopped) killpg(status_child.pid, SIGTERM);
killpg(child.pid, child.cont_signal); }
killpg(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 * kill()s the child process (if any) and closes and free()s the stdin- and
* free()s the stdin- and SIGCHLD-watchers * SIGCHLD-watchers
* *
*/ */
void kill_child(void) { void kill_child(void) {
DLOG_CHILD; kill_and_wait(&status_child);
}
if (child.pid > 0) { /*
if (child.cont_signal > 0 && child.stopped) * kill()s the workspace child process (if any) and closes and free()s the
killpg(child.pid, child.cont_signal); * stdin- and SIGCHLD-watchers.
killpg(child.pid, SIGTERM); * Similar to kill_child.
int status; *
waitpid(child.pid, &status, 0); */
cleanup(); 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) * Sends a SIGSTOP to the child process (if existent)
* *
*/ */
void stop_child(void) { void stop_children(void) {
DLOG_CHILD; DLOG_CHILDREN;
stop_child(&status_child);
if (child.stop_signal > 0 && !child.stopped) { stop_child(&ws_child);
child.stopped = true;
killpg(child.pid, child.stop_signal);
}
} }
/* /*
* Sends a SIGCONT to the child process (if existent) * Sends a SIGCONT to the child process (if existent)
* *
*/ */
void cont_child(void) { void cont_children(void) {
DLOG_CHILD; DLOG_CHILDREN;
if (child.cont_signal > 0 && child.stopped) { cont_child(&status_child);
child.stopped = false; cont_child(&ws_child);
killpg(child.pid, child.cont_signal);
}
} }
/* /*
@ -769,5 +1023,5 @@ void cont_child(void) {
* *
*/ */
bool child_want_click_events(void) { bool child_want_click_events(void) {
return child.click_events; return status_child.click_events;
} }

View File

@ -188,11 +188,17 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
} }
if (!strcmp(cur_key, "status_command")) { if (!strcmp(cur_key, "status_command")) {
DLOG("command = %.*s\n", len, val); DLOG("status_command = %.*s\n", len, val);
sasprintf(&config.command, "%.*s", len, val); sasprintf(&config.command, "%.*s", len, val);
return 1; return 1;
} }
if (!strcmp(cur_key, "workspace_command")) {
DLOG("workspace_command = %.*s\n", len, val);
sasprintf(&config.workspace_command, "%.*s", len, val);
return 1;
}
if (!strcmp(cur_key, "font")) { if (!strcmp(cur_key, "font")) {
DLOG("font = %.*s\n", len, val); DLOG("font = %.*s\n", len, val);
FREE(config.fontname); FREE(config.fontname);
@ -396,16 +402,15 @@ static yajl_callbacks outputs_callbacks = {
}; };
/* /*
* Start parsing the received bar configuration JSON string * Parse the received bar configuration JSON string
* *
*/ */
void parse_config_json(char *json) { void parse_config_json(const unsigned char *json, size_t size) {
yajl_handle handle = yajl_alloc(&outputs_callbacks, NULL, NULL);
TAILQ_INIT(&(config.bindings)); TAILQ_INIT(&(config.bindings));
TAILQ_INIT(&(config.tray_outputs)); TAILQ_INIT(&(config.tray_outputs));
yajl_status state = yajl_parse(handle, (const unsigned char *)json, strlen(json)); yajl_handle handle = yajl_alloc(&outputs_callbacks, NULL, NULL);
yajl_status state = yajl_parse(handle, json, size);
/* FIXME: Proper error handling for JSON parsing */ /* FIXME: Proper error handling for JSON parsing */
switch (state) { switch (state) {
@ -418,6 +423,11 @@ void parse_config_json(char *json) {
break; break;
} }
if (config.disable_ws && config.workspace_command) {
ELOG("You have specified 'workspace_buttons no'. Your 'workspace_command %s' will be ignored.\n", config.workspace_command);
FREE(config.workspace_command);
}
yajl_free(handle); yajl_free(handle);
} }
@ -427,16 +437,16 @@ static int i3bar_config_string_cb(void *params_, const unsigned char *val, size_
} }
/* /*
* Start parsing the received bar configuration list. The only usecase right * Parse the received bar configuration list. The only usecase right now is to
* now is to automatically get the first bar id. * automatically get the first bar id.
* *
*/ */
void parse_get_first_i3bar_config(char *json) { void parse_get_first_i3bar_config(const unsigned char *json, size_t size) {
yajl_callbacks configs_callbacks = { yajl_callbacks configs_callbacks = {
.yajl_string = i3bar_config_string_cb, .yajl_string = i3bar_config_string_cb,
}; };
yajl_handle handle = yajl_alloc(&configs_callbacks, NULL, NULL); yajl_handle handle = yajl_alloc(&configs_callbacks, NULL, NULL);
yajl_parse(handle, (const unsigned char *)json, strlen(json)); yajl_parse(handle, json, size);
yajl_free(handle); yajl_free(handle);
} }

View File

@ -24,14 +24,24 @@ ev_io *i3_connection;
const char *sock_path; const char *sock_path;
typedef void (*handler_t)(char *); typedef void (*handler_t)(const unsigned char *, size_t);
/*
* Returns true when i3bar is configured to read workspace information from i3
* via JSON over the i3 IPC interface, as opposed to reading workspace
* information from the workspace_command via JSON over stdout.
*
*/
static bool i3_provides_workspaces(void) {
return !config.disable_ws && config.workspace_command == NULL;
}
/* /*
* Called, when we get a reply to a command from i3. * Called, when we get a reply to a command from i3.
* Since i3 does not give us much feedback on commands, we do not much * Since i3 does not give us much feedback on commands, we do not much
* *
*/ */
static void got_command_reply(char *reply) { static void got_command_reply(const unsigned char *reply, size_t size) {
/* TODO: Error handling for command replies */ /* TODO: Error handling for command replies */
} }
@ -39,9 +49,9 @@ static void got_command_reply(char *reply) {
* Called, when we get a reply with workspaces data * Called, when we get a reply with workspaces data
* *
*/ */
static void got_workspace_reply(char *reply) { static void got_workspace_reply(const unsigned char *reply, size_t size) {
DLOG("Got workspace data!\n"); DLOG("Got workspace data!\n");
parse_workspaces_json(reply); parse_workspaces_json(reply, size);
draw_bars(false); draw_bars(false);
} }
@ -50,7 +60,7 @@ static void got_workspace_reply(char *reply) {
* Since i3 does not give us much feedback on commands, we do not much * Since i3 does not give us much feedback on commands, we do not much
* *
*/ */
static void got_subscribe_reply(char *reply) { static void got_subscribe_reply(const unsigned char *reply, size_t size) {
DLOG("Got subscribe reply: %s\n", reply); DLOG("Got subscribe reply: %s\n", reply);
/* TODO: Error handling for subscribe commands */ /* TODO: Error handling for subscribe commands */
} }
@ -59,12 +69,12 @@ static void got_subscribe_reply(char *reply) {
* Called, when we get a reply with outputs data * Called, when we get a reply with outputs data
* *
*/ */
static void got_output_reply(char *reply) { static void got_output_reply(const unsigned char *reply, size_t size) {
DLOG("Clearing old output configuration...\n"); DLOG("Clearing old output configuration...\n");
free_outputs(); free_outputs();
DLOG("Parsing outputs JSON...\n"); DLOG("Parsing outputs JSON...\n");
parse_outputs_json(reply); parse_outputs_json(reply, size);
DLOG("Reconfiguring windows...\n"); DLOG("Reconfiguring windows...\n");
reconfig_windows(false); reconfig_windows(false);
@ -73,8 +83,19 @@ static void got_output_reply(char *reply) {
kick_tray_clients(o_walk); kick_tray_clients(o_walk);
} }
if (!config.disable_ws) { if (i3_provides_workspaces()) {
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL); i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
} else if (config.workspace_command) {
/* Communication with the workspace child is one-way. Since we called
* free_outputs() and free_workspaces() we have lost our workspace
* information which will result in no workspace buttons. A
* well-behaving client should be subscribed to output events as well
* and re-send the output information to i3bar. Even in that case
* though there is a race condition where the child can send the new
* workspace information after the output change before i3bar receives
* the output event from i3. For this reason, we re-parse the latest
* received JSON. */
repeat_last_ws_json();
} }
draw_bars(false); draw_bars(false);
@ -84,10 +105,10 @@ static void got_output_reply(char *reply) {
* Called when we get the configuration for our bar instance * Called when we get the configuration for our bar instance
* *
*/ */
static void got_bar_config(char *reply) { static void got_bar_config(const unsigned char *reply, size_t size) {
if (!config.bar_id) { if (!config.bar_id) {
DLOG("Received bar list \"%s\"\n", reply); DLOG("Received bar list \"%s\"\n", reply);
parse_get_first_i3bar_config(reply); parse_get_first_i3bar_config(reply, size);
if (!config.bar_id) { if (!config.bar_id) {
ELOG("No bar configuration found, please configure a bar block in your i3 config file.\n"); ELOG("No bar configuration found, please configure a bar block in your i3 config file.\n");
@ -106,13 +127,14 @@ static void got_bar_config(char *reply) {
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL); i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
free_colors(&(config.colors)); free_colors(&(config.colors));
parse_config_json(reply); parse_config_json(reply, size);
/* Now we can actually use 'config', so let's subscribe to the appropriate /* Now we can actually use 'config', so let's subscribe to the appropriate
* events and request the workspaces if necessary. */ * events and request the workspaces if necessary. */
subscribe_events(); subscribe_events();
if (!config.disable_ws) if (i3_provides_workspaces()) {
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL); i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
}
/* Initialize the rest of XCB */ /* Initialize the rest of XCB */
init_xcb_late(config.fontname); init_xcb_late(config.fontname);
@ -121,6 +143,7 @@ static void got_bar_config(char *reply) {
init_colors(&(config.colors)); init_colors(&(config.colors));
start_child(config.command); start_child(config.command);
start_ws_child(config.workspace_command);
} }
/* Data structure to easily call the reply handlers later */ /* Data structure to easily call the reply handlers later */
@ -143,7 +166,7 @@ handler_t reply_handlers[] = {
* Called, when a workspace event arrives (i.e. the user changed the workspace) * Called, when a workspace event arrives (i.e. the user changed the workspace)
* *
*/ */
static void got_workspace_event(char *event) { static void got_workspace_event(const unsigned char *event, size_t size) {
DLOG("Got workspace event!\n"); DLOG("Got workspace event!\n");
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL); i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
} }
@ -152,7 +175,7 @@ static void got_workspace_event(char *event) {
* Called, when an output event arrives (i.e. the screen configuration changed) * Called, when an output event arrives (i.e. the screen configuration changed)
* *
*/ */
static void got_output_event(char *event) { static void got_output_event(const unsigned char *event, size_t size) {
DLOG("Got output event!\n"); DLOG("Got output event!\n");
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL); i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
} }
@ -161,9 +184,9 @@ static void got_output_event(char *event) {
* Called, when a mode event arrives (i3 changed binding mode). * Called, when a mode event arrives (i3 changed binding mode).
* *
*/ */
static void got_mode_event(char *event) { static void got_mode_event(const unsigned char *event, size_t size) {
DLOG("Got mode event!\n"); DLOG("Got mode event!\n");
parse_mode_json(event); parse_mode_json(event, size);
draw_bars(false); draw_bars(false);
} }
@ -183,11 +206,11 @@ static bool strings_differ(char *a, char *b) {
* Called, when a barconfig_update event arrives (i.e. i3 changed the bar hidden_state or mode) * Called, when a barconfig_update event arrives (i.e. i3 changed the bar hidden_state or mode)
* *
*/ */
static void got_bar_config_update(char *event) { static void got_bar_config_update(const unsigned char *event, size_t size) {
/* check whether this affect this bar instance by checking the bar_id */ /* check whether this affect this bar instance by checking the bar_id */
char *expected_id; char *expected_id;
sasprintf(&expected_id, "\"id\":\"%s\"", config.bar_id); sasprintf(&expected_id, "\"id\":\"%s\"", config.bar_id);
char *found_id = strstr(event, expected_id); char *found_id = strstr((const char *)event, expected_id);
FREE(expected_id); FREE(expected_id);
if (found_id == NULL) if (found_id == NULL)
return; return;
@ -201,10 +224,12 @@ static void got_bar_config_update(char *event) {
DLOG("Received bar config update \"%s\"\n", event); DLOG("Received bar config update \"%s\"\n", event);
char *old_command = config.command; char *old_command = config.command;
char *old_workspace_command = config.workspace_command;
config.command = NULL; config.command = NULL;
config.workspace_command = NULL;
bar_display_mode_t old_mode = config.hide_on_modifier; bar_display_mode_t old_mode = config.hide_on_modifier;
parse_config_json(event); parse_config_json(event, size);
if (old_mode != config.hide_on_modifier) { if (old_mode != config.hide_on_modifier) {
reconfig_windows(true); reconfig_windows(true);
} }
@ -214,13 +239,21 @@ static void got_bar_config_update(char *event) {
init_colors(&(config.colors)); init_colors(&(config.colors));
/* restart status command process */ /* restart status command process */
if (strings_differ(old_command, config.command)) { if (!status_child_is_alive() || strings_differ(old_command, config.command)) {
kill_child(); kill_child();
clear_statusline(&statusline_head, true); clear_statusline(&statusline_head, true);
start_child(config.command); start_child(config.command);
} }
free(old_command); free(old_command);
/* restart workspace command process */
if (!ws_child_is_alive() || strings_differ(old_workspace_command, config.workspace_command)) {
free_workspaces();
kill_ws_child();
start_ws_child(config.workspace_command);
}
free(old_workspace_command);
draw_bars(false); draw_bars(false);
} }
@ -284,7 +317,7 @@ static void got_data(struct ev_loop *loop, ev_io *watcher, int events) {
/* Now that we know, what to expect, we can start read()ing the rest /* Now that we know, what to expect, we can start read()ing the rest
* of the message */ * of the message */
char *buffer = smalloc(size + 1); unsigned char *buffer = smalloc(size + 1);
rec = 0; rec = 0;
while (rec < size) { while (rec < size) {
@ -304,10 +337,11 @@ static void got_data(struct ev_loop *loop, ev_io *watcher, int events) {
/* And call the callback (indexed by the type) */ /* And call the callback (indexed by the type) */
if (type & (1UL << 31)) { if (type & (1UL << 31)) {
type ^= 1UL << 31; type ^= 1UL << 31;
event_handlers[type](buffer); event_handlers[type](buffer, size);
} else { } else {
if (reply_handlers[type]) if (reply_handlers[type]) {
reply_handlers[type](buffer); reply_handlers[type](buffer, size);
}
} }
FREE(header); FREE(header);
@ -377,9 +411,9 @@ void destroy_connection(void) {
* *
*/ */
void subscribe_events(void) { void subscribe_events(void) {
if (config.disable_ws) { if (i3_provides_workspaces()) {
i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"output\", \"mode\", \"barconfig_update\" ]");
} else {
i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"workspace\", \"output\", \"mode\", \"barconfig_update\" ]"); i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"workspace\", \"output\", \"mode\", \"barconfig_update\" ]");
} else {
i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"output\", \"mode\", \"barconfig_update\" ]");
} }
} }

View File

@ -185,12 +185,12 @@ int main(int argc, char **argv) {
ev_signal_start(main_loop, sig_int); ev_signal_start(main_loop, sig_int);
ev_signal_start(main_loop, sig_hup); ev_signal_start(main_loop, sig_hup);
atexit(kill_children_at_exit);
/* From here on everything should run smooth for itself, just start listening for /* From here on everything should run smooth for itself, just start listening for
* events. We stop simply stop the event loop, when we are finished */ * events. We stop simply stop the event loop, when we are finished */
ev_loop(main_loop, 0); ev_loop(main_loop, 0);
kill_child();
clean_xcb(); clean_xcb();
ev_default_destroy(); ev_default_destroy();

View File

@ -16,7 +16,6 @@
/* A datatype to pass through the callbacks to save the state */ /* A datatype to pass through the callbacks to save the state */
struct mode_json_params { struct mode_json_params {
char *json;
char *cur_key; char *cur_key;
char *name; char *name;
bool pango_markup; bool pango_markup;
@ -96,26 +95,17 @@ static yajl_callbacks mode_callbacks = {
}; };
/* /*
* Start parsing the received JSON string * Parse the received JSON string
* *
*/ */
void parse_mode_json(char *json) { void parse_mode_json(const unsigned char *json, size_t size) {
/* FIXME: Fasciliate stream processing, i.e. allow starting to interpret
* JSON in chunks */
struct mode_json_params params; struct mode_json_params params;
mode binding; mode binding;
params.cur_key = NULL; params.cur_key = NULL;
params.json = json;
params.mode = &binding; params.mode = &binding;
yajl_handle handle; yajl_handle handle = yajl_alloc(&mode_callbacks, NULL, (void *)&params);
yajl_status state; yajl_status state = yajl_parse(handle, json, size);
handle = yajl_alloc(&mode_callbacks, NULL, (void *)&params);
state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
/* FIXME: Proper error handling for JSON parsing */ /* FIXME: Proper error handling for JSON parsing */
switch (state) { switch (state) {

View File

@ -18,10 +18,8 @@
/* A datatype to pass through the callbacks to save the state */ /* A datatype to pass through the callbacks to save the state */
struct outputs_json_params { struct outputs_json_params {
struct outputs_head *outputs;
i3_output *outputs_walk; i3_output *outputs_walk;
char *cur_key; char *cur_key;
char *json;
bool in_rect; bool in_rect;
}; };
@ -263,21 +261,17 @@ void init_outputs(void) {
} }
/* /*
* Start parsing the received JSON string * Parse the received JSON string
* *
*/ */
void parse_outputs_json(char *json) { void parse_outputs_json(const unsigned char *json, size_t size) {
struct outputs_json_params params; struct outputs_json_params params;
params.outputs_walk = NULL; params.outputs_walk = NULL;
params.cur_key = NULL; params.cur_key = NULL;
params.json = json;
params.in_rect = false; params.in_rect = false;
yajl_handle handle; yajl_handle handle = yajl_alloc(&outputs_callbacks, NULL, (void *)&params);
yajl_status state; yajl_status state = yajl_parse(handle, json, size);
handle = yajl_alloc(&outputs_callbacks, NULL, (void *)&params);
state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
/* FIXME: Proper errorhandling for JSON-parsing */ /* FIXME: Proper errorhandling for JSON-parsing */
switch (state) { switch (state) {
@ -291,6 +285,7 @@ void parse_outputs_json(char *json) {
} }
yajl_free(handle); yajl_free(handle);
free(params.cur_key);
} }
/* /*
@ -319,12 +314,14 @@ void free_outputs(void) {
* *
*/ */
i3_output *get_output_by_name(char *name) { i3_output *get_output_by_name(char *name) {
i3_output *walk;
if (name == NULL) { if (name == NULL) {
return NULL; return NULL;
} }
const bool is_primary = !strcasecmp(name, "primary");
i3_output *walk;
SLIST_FOREACH (walk, outputs, slist) { SLIST_FOREACH (walk, outputs, slist) {
if (!strcmp(walk->name, name)) { if ((is_primary && walk->primary) || !strcmp(walk->name, name)) {
break; break;
} }
} }

View File

@ -19,7 +19,8 @@ struct workspaces_json_params {
struct ws_head *workspaces; struct ws_head *workspaces;
i3_ws *workspaces_walk; i3_ws *workspaces_walk;
char *cur_key; char *cur_key;
char *json; bool need_output;
bool parsing_rect;
}; };
/* /*
@ -71,26 +72,23 @@ static int workspaces_integer_cb(void *params_, long long val) {
return 1; return 1;
} }
/* rect is unused, so we don't bother to save it */
if (!strcmp(params->cur_key, "x")) { if (!strcmp(params->cur_key, "x")) {
params->workspaces_walk->rect.x = (int)val;
FREE(params->cur_key); FREE(params->cur_key);
return 1; return 1;
} }
if (!strcmp(params->cur_key, "y")) { if (!strcmp(params->cur_key, "y")) {
params->workspaces_walk->rect.y = (int)val;
FREE(params->cur_key); FREE(params->cur_key);
return 1; return 1;
} }
if (!strcmp(params->cur_key, "width")) { if (!strcmp(params->cur_key, "width")) {
params->workspaces_walk->rect.w = (int)val;
FREE(params->cur_key); FREE(params->cur_key);
return 1; return 1;
} }
if (!strcmp(params->cur_key, "height")) { if (!strcmp(params->cur_key, "height")) {
params->workspaces_walk->rect.h = (int)val;
FREE(params->cur_key); FREE(params->cur_key);
return 1; return 1;
} }
@ -156,15 +154,16 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, size_t
sasprintf(&output_name, "%.*s", len, val); sasprintf(&output_name, "%.*s", len, val);
i3_output *target = get_output_by_name(output_name); i3_output *target = get_output_by_name(output_name);
i3_ws *ws = params->workspaces_walk;
if (target != NULL) { if (target != NULL) {
params->workspaces_walk->output = target; ws->output = target;
TAILQ_INSERT_TAIL(ws->output->workspaces, ws, tailq);
TAILQ_INSERT_TAIL(params->workspaces_walk->output->workspaces,
params->workspaces_walk,
tailq);
} }
params->need_output = false;
FREE(output_name); FREE(output_name);
FREE(params->cur_key);
return 1; return 1;
} }
@ -172,28 +171,42 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, size_t
} }
/* /*
* We hit the start of a JSON map (rect or a new output) * We hit the start of a JSON map (rect or a new workspace)
* *
*/ */
static int workspaces_start_map_cb(void *params_) { static int workspaces_start_map_cb(void *params_) {
struct workspaces_json_params *params = (struct workspaces_json_params *)params_; struct workspaces_json_params *params = (struct workspaces_json_params *)params_;
i3_ws *new_workspace = NULL;
if (params->cur_key == NULL) { if (params->cur_key == NULL) {
new_workspace = smalloc(sizeof(i3_ws)); i3_ws *new_workspace = scalloc(1, sizeof(i3_ws));
new_workspace->num = -1; new_workspace->num = -1;
new_workspace->name = NULL;
new_workspace->visible = 0;
new_workspace->focused = 0;
new_workspace->urgent = 0;
memset(&new_workspace->rect, 0, sizeof(rect));
new_workspace->output = NULL;
params->workspaces_walk = new_workspace; params->workspaces_walk = new_workspace;
params->need_output = true;
params->parsing_rect = false;
} else {
params->parsing_rect = true;
}
return 1;
}
static int workspaces_end_map_cb(void *params_) {
struct workspaces_json_params *params = (struct workspaces_json_params *)params_;
i3_ws *ws = params->workspaces_walk;
const bool parsing_rect = params->parsing_rect;
params->parsing_rect = false;
if (parsing_rect || !ws || !ws->name || !params->need_output) {
return 1; return 1;
} }
ws->output = get_output_by_name("primary");
if (ws->output == NULL) {
ws->output = SLIST_FIRST(outputs);
}
TAILQ_INSERT_TAIL(ws->output->workspaces, ws, tailq);
return 1; return 1;
} }
@ -216,43 +229,42 @@ static yajl_callbacks workspaces_callbacks = {
.yajl_integer = workspaces_integer_cb, .yajl_integer = workspaces_integer_cb,
.yajl_string = workspaces_string_cb, .yajl_string = workspaces_string_cb,
.yajl_start_map = workspaces_start_map_cb, .yajl_start_map = workspaces_start_map_cb,
.yajl_end_map = workspaces_end_map_cb,
.yajl_map_key = workspaces_map_key_cb, .yajl_map_key = workspaces_map_key_cb,
}; };
/* /*
* Start parsing the received JSON string * Parse the received JSON string
* *
*/ */
void parse_workspaces_json(char *json) { void parse_workspaces_json(const unsigned char *json, size_t size) {
/* FIXME: Fasciliate stream processing, i.e. allow starting to interpret
* JSON in chunks */
struct workspaces_json_params params;
free_workspaces(); free_workspaces();
params.workspaces_walk = NULL; struct workspaces_json_params params = {0};
params.cur_key = NULL; yajl_handle handle = yajl_alloc(&workspaces_callbacks, NULL, (void *)&params);
params.json = json; yajl_status state = yajl_parse(handle, json, size);
yajl_handle handle;
yajl_status state;
handle = yajl_alloc(&workspaces_callbacks, NULL, (void *)&params);
state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
/* FIXME: Proper error handling for JSON parsing */ /* FIXME: Proper error handling for JSON parsing */
switch (state) { switch (state) {
case yajl_status_ok: case yajl_status_ok:
break; break;
case yajl_status_client_canceled: case yajl_status_client_canceled:
case yajl_status_error: case yajl_status_error: {
ELOG("Could not parse workspaces reply!\n"); unsigned char *err = yajl_get_error(handle, 1, json, size);
exit(EXIT_FAILURE); ELOG("Could not parse workspaces reply, error:\n%s\njson:---%s---\n", err, json);
yajl_free_error(handle, err);
if (config.workspace_command) {
kill_ws_child();
set_workspace_button_error("Could not parse workspace_command's JSON");
} else {
exit(EXIT_FAILURE);
}
break; break;
}
} }
yajl_free(handle); yajl_free(handle);
FREE(params.cur_key); FREE(params.cur_key);
} }
@ -261,14 +273,14 @@ void parse_workspaces_json(char *json) {
* *
*/ */
void free_workspaces(void) { void free_workspaces(void) {
i3_output *outputs_walk;
if (outputs == NULL) { if (outputs == NULL) {
return; return;
} }
i3_ws *ws_walk;
i3_output *outputs_walk;
SLIST_FOREACH (outputs_walk, outputs, slist) { SLIST_FOREACH (outputs_walk, outputs, slist) {
if (outputs_walk->workspaces != NULL && !TAILQ_EMPTY(outputs_walk->workspaces)) { if (outputs_walk->workspaces != NULL && !TAILQ_EMPTY(outputs_walk->workspaces)) {
i3_ws *ws_walk;
TAILQ_FOREACH (ws_walk, outputs_walk->workspaces, tailq) { TAILQ_FOREACH (ws_walk, outputs_walk->workspaces, tailq) {
I3STRING_FREE(ws_walk->name); I3STRING_FREE(ws_walk->name);
FREE(ws_walk->canonical_name); FREE(ws_walk->canonical_name);

View File

@ -334,7 +334,7 @@ static void hide_bars(void) {
} }
xcb_unmap_window(xcb_connection, walk->bar.id); xcb_unmap_window(xcb_connection, walk->bar.id);
} }
stop_child(); stop_children();
} }
/* /*
@ -351,7 +351,7 @@ static void unhide_bars(void) {
uint32_t mask; uint32_t mask;
uint32_t values[5]; uint32_t values[5];
cont_child(); cont_children();
SLIST_FOREACH (walk, outputs, slist) { SLIST_FOREACH (walk, outputs, slist) {
if (walk->bar.id == XCB_NONE) { if (walk->bar.id == XCB_NONE) {
@ -500,6 +500,49 @@ static int predict_button_width(int name_width) {
logical_px(config.ws_min_width)); logical_px(config.ws_min_width));
} }
static char *quote_workspace_name(const char *in) {
/* To properly handle workspace names with double quotes in them, we need
* to escape the double quotes. We allocate a large enough buffer (twice
* the unescaped size is always enough), then we copy character by
* character. */
const size_t namelen = strlen(in);
const size_t len = namelen + strlen("workspace \"\"") + 1;
char *out = scalloc(2 * len, 1);
memcpy(out, "workspace \"", strlen("workspace \""));
size_t inpos, outpos;
for (inpos = 0, outpos = strlen("workspace \"");
inpos < namelen;
inpos++, outpos++) {
if (in[inpos] == '"' || in[inpos] == '\\') {
out[outpos] = '\\';
outpos++;
}
out[outpos] = in[inpos];
}
out[outpos] = '"';
return out;
}
static void focus_workspace(i3_ws *ws) {
char *buffer = NULL;
if (ws->id != 0) {
/* Workspace ID has higher precedence since the workspace_command is
* allowed to change workspace names as long as it provides a valid ID. */
sasprintf(&buffer, "[con_id=%lld] focus workspace", ws->id);
goto done;
}
if (ws->canonical_name == NULL) {
return;
}
buffer = quote_workspace_name(ws->canonical_name);
done:
i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, buffer);
free(buffer);
}
/* /*
* Handle a button press event (i.e. a mouse click on one of our bars). * Handle a button press event (i.e. a mouse click on one of our bars).
* We determine, whether the click occurred on a workspace button or if the scroll- * We determine, whether the click occurred on a workspace button or if the scroll-
@ -620,37 +663,7 @@ static void handle_button(xcb_button_press_event_t *event) {
return; return;
} }
/* To properly handle workspace names with double quotes in them, we need focus_workspace(cur_ws);
* to escape the double quotes. Unfortunately, thats rather ugly in C: We
* first count the number of double quotes, then we allocate a large enough
* buffer, then we copy character by character. */
int num_quotes = 0;
size_t namelen = 0;
const char *utf8_name = cur_ws->canonical_name;
for (const char *walk = utf8_name; *walk != '\0'; walk++) {
if (*walk == '"' || *walk == '\\')
num_quotes++;
/* While were looping through the name anyway, we can save one
* strlen(). */
namelen++;
}
const size_t len = namelen + strlen("workspace \"\"") + 1;
char *buffer = scalloc(len + num_quotes, 1);
memcpy(buffer, "workspace \"", strlen("workspace \""));
size_t inpos, outpos;
for (inpos = 0, outpos = strlen("workspace \"");
inpos < namelen;
inpos++, outpos++) {
if (utf8_name[inpos] == '"' || utf8_name[inpos] == '\\') {
buffer[outpos] = '\\';
outpos++;
}
buffer[outpos] = utf8_name[inpos];
}
buffer[outpos] = '"';
i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, buffer);
free(buffer);
} }
/* /*
@ -674,9 +687,9 @@ static void handle_visibility_notify(xcb_visibility_notify_event_t *event) {
} }
if (num_visible == 0) { if (num_visible == 0) {
stop_child(); stop_children();
} else { } else {
cont_child(); cont_children();
} }
} }
@ -1945,10 +1958,10 @@ void reconfig_windows(bool redraw_bars) {
/* Unmap the window, and draw it again when in dock mode */ /* Unmap the window, and draw it again when in dock mode */
umap_cookie = xcb_unmap_window_checked(xcb_connection, walk->bar.id); umap_cookie = xcb_unmap_window_checked(xcb_connection, walk->bar.id);
if (config.hide_on_modifier == M_DOCK) { if (config.hide_on_modifier == M_DOCK) {
cont_child(); cont_children();
map_cookie = xcb_map_window_checked(xcb_connection, walk->bar.id); map_cookie = xcb_map_window_checked(xcb_connection, walk->bar.id);
} else { } else {
stop_child(); stop_children();
} }
if (config.hide_on_modifier == M_HIDE) { if (config.hide_on_modifier == M_HIDE) {

View File

@ -198,7 +198,7 @@ void cmd_focus_level(I3_CMD, const char *level);
* Implementation of 'focus'. * Implementation of 'focus'.
* *
*/ */
void cmd_focus(I3_CMD); void cmd_focus(I3_CMD, bool focus_workspace);
/** /**
* Implementation of 'fullscreen [enable|disable|toggle] [global]'. * Implementation of 'fullscreen [enable|disable|toggle] [global]'.

View File

@ -105,6 +105,7 @@ CFGFUN(bar_tray_output, const char *output);
CFGFUN(bar_tray_padding, const long spacing_px); CFGFUN(bar_tray_padding, const long spacing_px);
CFGFUN(bar_color_single, const char *colorclass, const char *color); CFGFUN(bar_color_single, const char *colorclass, const char *color);
CFGFUN(bar_status_command, const char *command); CFGFUN(bar_status_command, const char *command);
CFGFUN(bar_workspace_command, const char *command);
CFGFUN(bar_binding_mode_indicator, const char *value); CFGFUN(bar_binding_mode_indicator, const char *value);
CFGFUN(bar_workspace_buttons, const char *value); CFGFUN(bar_workspace_buttons, const char *value);
CFGFUN(bar_workspace_min_width, const long width); CFGFUN(bar_workspace_min_width, const long width);

View File

@ -335,6 +335,10 @@ struct Barconfig {
* Will be passed to the shell. */ * Will be passed to the shell. */
char *status_command; char *status_command;
/** Command that should be run to get the workspace buttons. Will be passed
* to the shell. */
char *workspace_command;
/** Font specification for all text rendered on the bar. */ /** Font specification for all text rendered on the bar. */
char *font; char *font;

View File

@ -46,9 +46,12 @@ Be verbose.
workspace switching buttons and a statusline generated by i3status(1) or workspace switching buttons and a statusline generated by i3status(1) or
similar. It is automatically invoked (and configured through) i3. similar. It is automatically invoked (and configured through) i3.
i3bar supports colors via a JSON protocol starting from v4.2, see i3bar supports using a JSON protocol for setting the status line, see
https://i3wm.org/docs/i3bar-protocol.html https://i3wm.org/docs/i3bar-protocol.html
Since i3 4.23, i3bar supports another JSON protocol for setting workspace
buttons. See https://i3wm.org/docs/i3bar-workspace-protocol.html.
== ENVIRONMENT == ENVIRONMENT
=== I3SOCK === I3SOCK

View File

@ -86,6 +86,7 @@ if get_option('docs')
'docs/wsbar', 'docs/wsbar',
'docs/testsuite', 'docs/testsuite',
'docs/i3bar-protocol', 'docs/i3bar-protocol',
'docs/i3bar-workspace-protocol',
'docs/layout-saving', 'docs/layout-saving',
] ]
foreach m : doc_toc_inputs foreach m : doc_toc_inputs
@ -135,6 +136,7 @@ else
'docs/wsbar.html', 'docs/wsbar.html',
'docs/testsuite.html', 'docs/testsuite.html',
'docs/i3bar-protocol.html', 'docs/i3bar-protocol.html',
'docs/i3bar-workspace-protocol.html',
'docs/layout-saving.html', 'docs/layout-saving.html',
'docs/debugging.html', 'docs/debugging.html',
], ],

View File

@ -186,6 +186,7 @@ state WORKSPACE_NUMBER:
# focus output <output> # focus output <output>
# focus tiling|floating|mode_toggle # focus tiling|floating|mode_toggle
# focus parent|child # focus parent|child
# focus workspace
# focus # focus
state FOCUS: state FOCUS:
direction = 'left', 'right', 'up', 'down' direction = 'left', 'right', 'up', 'down'
@ -198,8 +199,10 @@ state FOCUS:
-> call cmd_focus_window_mode($window_mode) -> call cmd_focus_window_mode($window_mode)
level = 'parent', 'child' level = 'parent', 'child'
-> call cmd_focus_level($level) -> call cmd_focus_level($level)
workspace = 'workspace'
-> call cmd_focus(1)
end end
-> call cmd_focus() -> call cmd_focus(0)
state FOCUS_AUTO: state FOCUS_AUTO:
'sibling' 'sibling'

View File

@ -528,6 +528,7 @@ state BAR:
'set' -> BAR_IGNORE_LINE 'set' -> BAR_IGNORE_LINE
'i3bar_command' -> BAR_BAR_COMMAND 'i3bar_command' -> BAR_BAR_COMMAND
'status_command' -> BAR_STATUS_COMMAND 'status_command' -> BAR_STATUS_COMMAND
'workspace_command' -> BAR_WORKSPACE_COMMAND
'socket_path' -> BAR_SOCKET_PATH 'socket_path' -> BAR_SOCKET_PATH
'mode' -> BAR_MODE 'mode' -> BAR_MODE
'hidden_state' -> BAR_HIDDEN_STATE 'hidden_state' -> BAR_HIDDEN_STATE
@ -567,6 +568,10 @@ state BAR_STATUS_COMMAND:
command = string command = string
-> call cfg_bar_status_command($command); BAR -> call cfg_bar_status_command($command); BAR
state BAR_WORKSPACE_COMMAND:
command = string
-> call cfg_bar_workspace_command($command); BAR
state BAR_SOCKET_PATH: state BAR_SOCKET_PATH:
path = string path = string
-> call cfg_bar_socket_path($path); BAR -> call cfg_bar_socket_path($path); BAR

View File

@ -0,0 +1 @@
add workspace_command option in i3bar

View File

@ -0,0 +1 @@
add "focus workspace" command

View File

@ -1459,7 +1459,7 @@ void cmd_focus_level(I3_CMD, const char *level) {
* Implementation of 'focus'. * Implementation of 'focus'.
* *
*/ */
void cmd_focus(I3_CMD) { void cmd_focus(I3_CMD, bool focus_workspace) {
DLOG("current_match = %p\n", current_match); DLOG("current_match = %p\n", current_match);
if (match_is_empty(current_match)) { if (match_is_empty(current_match)) {
@ -1481,11 +1481,12 @@ void cmd_focus(I3_CMD) {
Con *ws = con_get_workspace(current->con); Con *ws = con_get_workspace(current->con);
/* If no workspace could be found, this was a dock window. /* If no workspace could be found, this was a dock window.
* Just skip it, you cannot focus dock windows. */ * Just skip it, you cannot focus dock windows. */
if (!ws) if (!ws) {
continue; continue;
}
/* In case this is a scratchpad window, call scratchpad_show(). */ /* In case this is a scratchpad window, call scratchpad_show(). */
if (ws == __i3_scratch) { if (ws == __i3_scratch && !focus_workspace) {
scratchpad_show(current->con); scratchpad_show(current->con);
/* While for the normal focus case we can change focus multiple /* While for the normal focus case we can change focus multiple
* times and only a single window ends up focused, we could show * times and only a single window ends up focused, we could show
@ -1493,8 +1494,15 @@ void cmd_focus(I3_CMD) {
break; break;
} }
LOG("focusing %p / %s\n", current->con, current->con->name); if (focus_workspace) {
con_activate_unblock(current->con); /* Show the workspace of the matched container, without necessarily
* focusing it. */
LOG("focusing workspace %p / %s - %p / %s\n", current->con, current->con->name, ws, ws->name);
workspace_show(ws);
} else {
LOG("focusing %p / %s\n", current->con, current->con->name);
con_activate_unblock(current->con);
}
} }
cmd_output->needs_tree_render = true; cmd_output->needs_tree_render = true;

View File

@ -105,6 +105,7 @@ static void free_configuration(void) {
FREE(barconfig->outputs); FREE(barconfig->outputs);
FREE(barconfig->socket_path); FREE(barconfig->socket_path);
FREE(barconfig->status_command); FREE(barconfig->status_command);
FREE(barconfig->workspace_command);
FREE(barconfig->i3bar_command); FREE(barconfig->i3bar_command);
FREE(barconfig->font); FREE(barconfig->font);
FREE(barconfig->colors.background); FREE(barconfig->colors.background);

View File

@ -873,6 +873,11 @@ CFGFUN(bar_status_command, const char *command) {
current_bar->status_command = sstrdup(command); current_bar->status_command = sstrdup(command);
} }
CFGFUN(bar_workspace_command, const char *command) {
FREE(current_bar->workspace_command);
current_bar->workspace_command = sstrdup(command);
}
CFGFUN(bar_binding_mode_indicator, const char *value) { CFGFUN(bar_binding_mode_indicator, const char *value) {
current_bar->hide_binding_mode_indicator = !boolstr(value); current_bar->hide_binding_mode_indicator = !boolstr(value);
} }

View File

@ -827,6 +827,7 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) {
ystr("top"); ystr("top");
YSTR_IF_SET(status_command); YSTR_IF_SET(status_command);
YSTR_IF_SET(workspace_command);
YSTR_IF_SET(font); YSTR_IF_SET(font);
if (config->bar_height) { if (config->bar_height) {

View File

@ -106,30 +106,35 @@ is(parser_calls('resize shrink left 25 px or 33 ppt;'),
is(parser_calls('[con_mark=yay] focus'), is(parser_calls('[con_mark=yay] focus'),
"cmd_criteria_add(con_mark, yay)\n" . "cmd_criteria_add(con_mark, yay)\n" .
"cmd_focus()", "cmd_focus(0)",
'criteria focus ok'); 'criteria focus ok');
is(parser_calls('[con_mark=yay] focus workspace'),
"cmd_criteria_add(con_mark, yay)\n" .
"cmd_focus(1)",
'criteria focus workspace ok');
is(parser_calls("[con_mark=yay con_mark=bar] focus"), is(parser_calls("[con_mark=yay con_mark=bar] focus"),
"cmd_criteria_add(con_mark, yay)\n" . "cmd_criteria_add(con_mark, yay)\n" .
"cmd_criteria_add(con_mark, bar)\n" . "cmd_criteria_add(con_mark, bar)\n" .
"cmd_focus()", "cmd_focus(0)",
'criteria focus ok'); 'criteria focus ok');
is(parser_calls("[con_mark=yay\tcon_mark=bar] focus"), is(parser_calls("[con_mark=yay\tcon_mark=bar] focus"),
"cmd_criteria_add(con_mark, yay)\n" . "cmd_criteria_add(con_mark, yay)\n" .
"cmd_criteria_add(con_mark, bar)\n" . "cmd_criteria_add(con_mark, bar)\n" .
"cmd_focus()", "cmd_focus(0)",
'criteria focus ok'); 'criteria focus ok');
is(parser_calls("[con_mark=yay\tcon_mark=bar]\tfocus"), is(parser_calls("[con_mark=yay\tcon_mark=bar]\tfocus"),
"cmd_criteria_add(con_mark, yay)\n" . "cmd_criteria_add(con_mark, yay)\n" .
"cmd_criteria_add(con_mark, bar)\n" . "cmd_criteria_add(con_mark, bar)\n" .
"cmd_focus()", "cmd_focus(0)",
'criteria focus ok'); 'criteria focus ok');
is(parser_calls('[con_mark="yay"] focus'), is(parser_calls('[con_mark="yay"] focus'),
"cmd_criteria_add(con_mark, yay)\n" . "cmd_criteria_add(con_mark, yay)\n" .
"cmd_focus()", "cmd_focus(0)",
'quoted criteria focus ok'); 'quoted criteria focus ok');
# Make sure trailing whitespace is stripped off: While this is not an issue for # Make sure trailing whitespace is stripped off: While this is not an issue for

View File

@ -776,7 +776,7 @@ EOT
$expected = <<'EOT'; $expected = <<'EOT';
cfg_bar_start() cfg_bar_start()
cfg_bar_output(LVDS-1) cfg_bar_output(LVDS-1)
ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'wheel_up_cmd', 'wheel_down_cmd', 'bindsym', 'position', 'output', 'tray_output', 'tray_padding', 'font', 'separator_symbol', 'binding_mode_indicator', 'workspace_buttons', 'workspace_min_width', 'strip_workspace_numbers', 'strip_workspace_name', 'verbose', 'height', 'padding', 'colors', '}' ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'workspace_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'wheel_up_cmd', 'wheel_down_cmd', 'bindsym', 'position', 'output', 'tray_output', 'tray_padding', 'font', 'separator_symbol', 'binding_mode_indicator', 'workspace_buttons', 'workspace_min_width', 'strip_workspace_numbers', 'strip_workspace_name', 'verbose', 'height', 'padding', 'colors', '}'
ERROR: CONFIG: (in file <stdin>) ERROR: CONFIG: (in file <stdin>)
ERROR: CONFIG: Line 1: bar { ERROR: CONFIG: Line 1: bar {
ERROR: CONFIG: Line 2: output LVDS-1 ERROR: CONFIG: Line 2: output LVDS-1