Merge pull request #4311 from i3/i3bar-ws-protocol
i3bar: Add protocol for workspace buttons
This commit is contained in:
commit
26990d90f2
184
docs/i3bar-workspace-protocol
Normal file
184
docs/i3bar-workspace-protocol
Normal 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" ]'
|
||||||
|
------------------------------
|
@ -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:
|
||||||
|
@ -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);
|
||||||
|
@ -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.
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
@ -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 */
|
||||||
|
@ -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 don’t care for the version. This might change
|
/* At the moment, we don’t 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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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\" ]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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 *)¶ms);
|
||||||
yajl_status state;
|
yajl_status state = yajl_parse(handle, json, size);
|
||||||
|
|
||||||
handle = yajl_alloc(&mode_callbacks, NULL, (void *)¶ms);
|
|
||||||
|
|
||||||
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) {
|
||||||
|
@ -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 *)¶ms);
|
||||||
yajl_status state;
|
yajl_status state = yajl_parse(handle, json, size);
|
||||||
handle = yajl_alloc(&outputs_callbacks, NULL, (void *)¶ms);
|
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 *)¶ms);
|
||||||
params.json = json;
|
yajl_status state = yajl_parse(handle, json, size);
|
||||||
|
|
||||||
yajl_handle handle;
|
|
||||||
yajl_status state;
|
|
||||||
handle = yajl_alloc(&workspaces_callbacks, NULL, (void *)¶ms);
|
|
||||||
|
|
||||||
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);
|
||||||
|
@ -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, that’s 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 we’re 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) {
|
||||||
|
@ -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]'.
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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',
|
||||||
],
|
],
|
||||||
|
@ -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'
|
||||||
|
@ -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
|
||||||
|
1
release-notes/changes/1-workspace_command
Normal file
1
release-notes/changes/1-workspace_command
Normal file
@ -0,0 +1 @@
|
|||||||
|
add workspace_command option in i3bar
|
1
release-notes/changes/2-focus-workspace
Normal file
1
release-notes/changes/2-focus-workspace
Normal file
@ -0,0 +1 @@
|
|||||||
|
add "focus workspace" command
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user