forked from external/yambar
module/i3: add ‘persistent’ attribute
Add ‘persistent’, a list-of-strings specifying workspace names that should be persistent. That is, workspaces that should never be removed, even if empty. Note that the workspaces _are_ still destroyed (in i3/Sway), but yambar keeps abstractions for them around. This is useful to e.g. keep a strict order between your “core” workspaces. Closes #72
This commit is contained in:
parent
21adc40a52
commit
7da13a26d0
3 changed files with 143 additions and 50 deletions
|
@ -9,6 +9,11 @@
|
|||
|
||||
## Unreleased
|
||||
### Added
|
||||
|
||||
* i3: `persistent` attribute, allowing persistent workspaces
|
||||
(https://codeberg.org/dnkl/yambar/issues/72).
|
||||
|
||||
|
||||
### Changed
|
||||
### Deprecated
|
||||
### Removed
|
||||
|
|
|
@ -65,6 +65,10 @@ with the _application_ and _title_ tags to replace the X11-only
|
|||
: enum
|
||||
: no
|
||||
: How to sort the list of workspaces; one of _none_, _ascending_ or _descending_, defaults to _none_.
|
||||
| persistent
|
||||
: list of strings
|
||||
: no
|
||||
: Persistent workspaces. I.e. workspaces that are never removed, even if empty.
|
||||
| left-spacing
|
||||
: int
|
||||
: no
|
||||
|
|
184
modules/i3.c
184
modules/i3.c
|
@ -30,7 +30,8 @@ struct ws_content {
|
|||
|
||||
struct workspace {
|
||||
char *name;
|
||||
int name_as_int; /* -1 is name is not a decimal number */
|
||||
int name_as_int; /* -1 if name is not a decimal number */
|
||||
bool persistent;
|
||||
|
||||
char *output;
|
||||
bool visible;
|
||||
|
@ -60,8 +61,26 @@ struct private {
|
|||
|
||||
enum sort_mode sort_mode;
|
||||
tll(struct workspace) workspaces;
|
||||
|
||||
size_t persistent_count;
|
||||
char **persistent_workspaces;
|
||||
};
|
||||
|
||||
static int
|
||||
workspace_name_as_int(const char *name)
|
||||
{
|
||||
int name_as_int = 0;
|
||||
for (const char *p = name; *p != '\0'; p++) {
|
||||
if (!(*p >= '0' && *p <= '9'))
|
||||
return -1;
|
||||
|
||||
name_as_int *= 10;
|
||||
name_as_int += *p - '0';
|
||||
}
|
||||
|
||||
return name_as_int;
|
||||
}
|
||||
|
||||
static bool
|
||||
workspace_from_json(const struct json_object *json, struct workspace *ws)
|
||||
{
|
||||
|
@ -82,20 +101,10 @@ workspace_from_json(const struct json_object *json, struct workspace *ws)
|
|||
|
||||
const char *name_as_string = json_object_get_string(name);
|
||||
|
||||
int name_as_int = 0;
|
||||
for (const char *p = name_as_string; *p != '\0'; p++) {
|
||||
if (!(*p >= '0' && *p <= '9')) {
|
||||
name_as_int = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
name_as_int *= 10;
|
||||
name_as_int += *p - '0';
|
||||
}
|
||||
|
||||
*ws = (struct workspace) {
|
||||
.name = strdup(name_as_string),
|
||||
.name_as_int = name_as_int,
|
||||
.name_as_int = workspace_name_as_int(name_as_string),
|
||||
.persistent = false,
|
||||
.output = strdup(json_object_get_string(output)),
|
||||
.visible = json_object_get_boolean(visible),
|
||||
.focused = json_object_get_boolean(focused),
|
||||
|
@ -109,18 +118,21 @@ workspace_from_json(const struct json_object *json, struct workspace *ws)
|
|||
static void
|
||||
workspace_free(struct workspace *ws)
|
||||
{
|
||||
free(ws->name);
|
||||
free(ws->output);
|
||||
free(ws->window.title);
|
||||
free(ws->window.application);
|
||||
free(ws->name); ws->name = NULL;
|
||||
free(ws->output); ws->output = NULL;
|
||||
free(ws->window.title); ws->window.title = NULL;
|
||||
free(ws->window.application); ws->window.application = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
workspaces_free(struct private *m)
|
||||
workspaces_free(struct private *m, bool free_persistent)
|
||||
{
|
||||
tll_foreach(m->workspaces, it)
|
||||
workspace_free(&it->item);
|
||||
tll_free(m->workspaces);
|
||||
tll_foreach(m->workspaces, it) {
|
||||
if (free_persistent || !it->item.persistent) {
|
||||
workspace_free(&it->item);
|
||||
tll_remove(m->workspaces, it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -234,6 +246,35 @@ handle_subscribe_reply(int type, const struct json_object *json, void *_m)
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
workspace_update_or_add(struct private *m, const struct json_object *ws_json)
|
||||
{
|
||||
struct json_object *name;
|
||||
if (!json_object_object_get_ex(ws_json, "name", &name))
|
||||
return false;
|
||||
|
||||
const char *name_as_string = json_object_get_string(name);
|
||||
struct workspace *already_exists = workspace_lookup(m, name_as_string);
|
||||
|
||||
if (already_exists != NULL) {
|
||||
bool persistent = already_exists->persistent;
|
||||
assert(persistent);
|
||||
|
||||
workspace_free(already_exists);
|
||||
if (!workspace_from_json(ws_json, already_exists))
|
||||
return false;
|
||||
already_exists->persistent = persistent;
|
||||
} else {
|
||||
struct workspace ws;
|
||||
if (!workspace_from_json(ws_json, &ws))
|
||||
return false;
|
||||
|
||||
workspace_add(m, ws);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
handle_get_workspaces_reply(int type, const struct json_object *json, void *_mod)
|
||||
{
|
||||
|
@ -242,25 +283,23 @@ handle_get_workspaces_reply(int type, const struct json_object *json, void *_mod
|
|||
|
||||
mtx_lock(&mod->lock);
|
||||
|
||||
workspaces_free(m);
|
||||
workspaces_free(m, false);
|
||||
m->dirty = true;
|
||||
|
||||
size_t count = json_object_array_length(json);
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
struct workspace ws = {};
|
||||
if (!workspace_from_json(json_object_array_get_idx(json, i), &ws)) {
|
||||
workspaces_free(m);
|
||||
mtx_unlock(&mod->lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_DBG("#%zu: %s", i, m->workspaces.v[i].name);
|
||||
workspace_add(m, ws);
|
||||
if (!workspace_update_or_add(m, json_object_array_get_idx(json, i)))
|
||||
goto err;
|
||||
}
|
||||
|
||||
mtx_unlock(&mod->lock);
|
||||
return true;
|
||||
|
||||
err:
|
||||
workspaces_free(m, false);
|
||||
mtx_unlock(&mod->lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -301,24 +340,21 @@ handle_workspace_event(int type, const struct json_object *json, void *_mod)
|
|||
mtx_lock(&mod->lock);
|
||||
|
||||
if (is_init) {
|
||||
struct workspace *already_exists = workspace_lookup(m, current_name);
|
||||
if (already_exists != NULL) {
|
||||
LOG_WARN("workspace 'init' event for already existing workspace: %s", current_name);
|
||||
workspace_free(already_exists);
|
||||
if (!workspace_from_json(current, already_exists))
|
||||
goto err;
|
||||
} else {
|
||||
struct workspace ws;
|
||||
if (!workspace_from_json(current, &ws))
|
||||
goto err;
|
||||
|
||||
workspace_add(m, ws);
|
||||
}
|
||||
if (!workspace_update_or_add(m, current))
|
||||
goto err;
|
||||
}
|
||||
|
||||
else if (is_empty) {
|
||||
assert(workspace_lookup(m, current_name) != NULL);
|
||||
workspace_del(m, current_name);
|
||||
struct workspace *ws = workspace_lookup(m, current_name);
|
||||
assert(ws != NULL);
|
||||
|
||||
if (!ws->persistent)
|
||||
workspace_del(m, current_name);
|
||||
else {
|
||||
workspace_free(ws);
|
||||
ws->name = strdup(current_name);
|
||||
assert(ws->persistent);
|
||||
}
|
||||
}
|
||||
|
||||
else if (is_focused) {
|
||||
|
@ -340,7 +376,7 @@ handle_workspace_event(int type, const struct json_object *json, void *_mod)
|
|||
/* Mark all workspaces on current's output invisible */
|
||||
tll_foreach(m->workspaces, it) {
|
||||
struct workspace *ws = &it->item;
|
||||
if (strcmp(ws->output, w->output) == 0)
|
||||
if (ws->output != NULL && strcmp(ws->output, w->output) == 0)
|
||||
ws->visible = false;
|
||||
}
|
||||
|
||||
|
@ -558,6 +594,18 @@ run(struct module *mod)
|
|||
return 1;
|
||||
}
|
||||
|
||||
struct private *m = mod->private;
|
||||
for (size_t i = 0; i < m->persistent_count; i++) {
|
||||
const char *name_as_string = m->persistent_workspaces[i];
|
||||
|
||||
struct workspace ws = {
|
||||
.name = strdup(name_as_string),
|
||||
.name_as_int = workspace_name_as_int(name_as_string),
|
||||
.persistent = true,
|
||||
};
|
||||
workspace_add(m, ws);
|
||||
}
|
||||
|
||||
i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_VERSION, NULL);
|
||||
i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[\"workspace\", \"window\", \"mode\"]");
|
||||
i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
|
||||
|
@ -589,7 +637,11 @@ destroy(struct module *mod)
|
|||
}
|
||||
|
||||
free(m->ws_content.v);
|
||||
workspaces_free(m);
|
||||
workspaces_free(m, true);
|
||||
|
||||
for (size_t i = 0; i < m->persistent_count; i++)
|
||||
free(m->persistent_workspaces[i]);
|
||||
free(m->persistent_workspaces);
|
||||
|
||||
free(m->mode);
|
||||
free(m);
|
||||
|
@ -693,7 +745,9 @@ struct i3_workspaces {
|
|||
|
||||
static struct module *
|
||||
i3_new(struct i3_workspaces workspaces[], size_t workspace_count,
|
||||
int left_spacing, int right_spacing, enum sort_mode sort_mode)
|
||||
int left_spacing, int right_spacing, enum sort_mode sort_mode,
|
||||
size_t persistent_count,
|
||||
const char *persistent_workspaces[static persistent_count])
|
||||
{
|
||||
struct private *m = calloc(1, sizeof(*m));
|
||||
|
||||
|
@ -711,6 +765,13 @@ i3_new(struct i3_workspaces workspaces[], size_t workspace_count,
|
|||
|
||||
m->sort_mode = sort_mode;
|
||||
|
||||
m->persistent_count = persistent_count;
|
||||
m->persistent_workspaces = calloc(
|
||||
persistent_count, sizeof(m->persistent_workspaces[0]));
|
||||
|
||||
for (size_t i = 0; i < persistent_count; i++)
|
||||
m->persistent_workspaces[i] = strdup(persistent_workspaces[i]);
|
||||
|
||||
struct module *mod = module_common_new();
|
||||
mod->private = m;
|
||||
mod->run = &run;
|
||||
|
@ -728,6 +789,7 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited)
|
|||
const struct yml_node *left_spacing = yml_get_value(node, "left-spacing");
|
||||
const struct yml_node *right_spacing = yml_get_value(node, "right-spacing");
|
||||
const struct yml_node *sort = yml_get_value(node, "sort");
|
||||
const struct yml_node *persistent = yml_get_value(node, "persistent");
|
||||
|
||||
int left = spacing != NULL ? yml_value_as_int(spacing) :
|
||||
left_spacing != NULL ? yml_value_as_int(left_spacing) : 0;
|
||||
|
@ -740,6 +802,20 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited)
|
|||
strcmp(sort_value, "none") == 0 ? SORT_NONE :
|
||||
strcmp(sort_value, "ascending") == 0 ? SORT_ASCENDING : SORT_DESCENDING;
|
||||
|
||||
const size_t persistent_count =
|
||||
persistent != NULL ? yml_list_length(persistent) : 0;
|
||||
const char *persistent_workspaces[persistent_count];
|
||||
|
||||
if (persistent != NULL) {
|
||||
size_t idx = 0;
|
||||
for (struct yml_list_iter it = yml_list_iter(persistent);
|
||||
it.node != NULL;
|
||||
yml_list_next(&it), idx++)
|
||||
{
|
||||
persistent_workspaces[idx] = yml_value_as_string(it.node);
|
||||
}
|
||||
}
|
||||
|
||||
struct i3_workspaces workspaces[yml_dict_length(c)];
|
||||
|
||||
size_t idx = 0;
|
||||
|
@ -751,7 +827,8 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited)
|
|||
workspaces[idx].content = conf_to_particle(it.value, inherited);
|
||||
}
|
||||
|
||||
return i3_new(workspaces, yml_dict_length(c), left, right, sort_mode);
|
||||
return i3_new(workspaces, yml_dict_length(c), left, right, sort_mode,
|
||||
persistent_count, persistent_workspaces);
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -791,6 +868,12 @@ verify_sort(keychain_t *chain, const struct yml_node *node)
|
|||
chain, node, (const char *[]){"none", "ascending", "descending"}, 3);
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_persistent(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
return conf_verify_list(chain, node, &conf_verify_string);
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_conf(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
|
@ -799,6 +882,7 @@ verify_conf(keychain_t *chain, const struct yml_node *node)
|
|||
{"left-spacing", false, &conf_verify_int},
|
||||
{"right-spacing", false, &conf_verify_int},
|
||||
{"sort", false, &verify_sort},
|
||||
{"persistent", false, &verify_persistent},
|
||||
{"content", true, &verify_content},
|
||||
{"anchors", false, NULL},
|
||||
{NULL, false, NULL},
|
||||
|
|
Loading…
Add table
Reference in a new issue