mirror of
https://codeberg.org/dnkl/yambar.git
synced 2025-04-23 12:35:41 +02:00
commit
239ce16a79
3 changed files with 143 additions and 50 deletions
|
@ -9,6 +9,11 @@
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
* i3: `persistent` attribute, allowing persistent workspaces
|
||||||
|
(https://codeberg.org/dnkl/yambar/issues/72).
|
||||||
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
### Deprecated
|
### Deprecated
|
||||||
### Removed
|
### Removed
|
||||||
|
|
|
@ -65,6 +65,10 @@ with the _application_ and _title_ tags to replace the X11-only
|
||||||
: enum
|
: enum
|
||||||
: no
|
: no
|
||||||
: How to sort the list of workspaces; one of _none_, _ascending_ or _descending_, defaults to _none_.
|
: 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
|
| left-spacing
|
||||||
: int
|
: int
|
||||||
: no
|
: no
|
||||||
|
|
184
modules/i3.c
184
modules/i3.c
|
@ -30,7 +30,8 @@ struct ws_content {
|
||||||
|
|
||||||
struct workspace {
|
struct workspace {
|
||||||
char *name;
|
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;
|
char *output;
|
||||||
bool visible;
|
bool visible;
|
||||||
|
@ -60,8 +61,26 @@ struct private {
|
||||||
|
|
||||||
enum sort_mode sort_mode;
|
enum sort_mode sort_mode;
|
||||||
tll(struct workspace) workspaces;
|
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
|
static bool
|
||||||
workspace_from_json(const struct json_object *json, struct workspace *ws)
|
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);
|
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) {
|
*ws = (struct workspace) {
|
||||||
.name = strdup(name_as_string),
|
.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)),
|
.output = strdup(json_object_get_string(output)),
|
||||||
.visible = json_object_get_boolean(visible),
|
.visible = json_object_get_boolean(visible),
|
||||||
.focused = json_object_get_boolean(focused),
|
.focused = json_object_get_boolean(focused),
|
||||||
|
@ -109,18 +118,21 @@ workspace_from_json(const struct json_object *json, struct workspace *ws)
|
||||||
static void
|
static void
|
||||||
workspace_free(struct workspace *ws)
|
workspace_free(struct workspace *ws)
|
||||||
{
|
{
|
||||||
free(ws->name);
|
free(ws->name); ws->name = NULL;
|
||||||
free(ws->output);
|
free(ws->output); ws->output = NULL;
|
||||||
free(ws->window.title);
|
free(ws->window.title); ws->window.title = NULL;
|
||||||
free(ws->window.application);
|
free(ws->window.application); ws->window.application = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
workspaces_free(struct private *m)
|
workspaces_free(struct private *m, bool free_persistent)
|
||||||
{
|
{
|
||||||
tll_foreach(m->workspaces, it)
|
tll_foreach(m->workspaces, it) {
|
||||||
workspace_free(&it->item);
|
if (free_persistent || !it->item.persistent) {
|
||||||
tll_free(m->workspaces);
|
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;
|
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
|
static bool
|
||||||
handle_get_workspaces_reply(int type, const struct json_object *json, void *_mod)
|
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);
|
mtx_lock(&mod->lock);
|
||||||
|
|
||||||
workspaces_free(m);
|
workspaces_free(m, false);
|
||||||
m->dirty = true;
|
m->dirty = true;
|
||||||
|
|
||||||
size_t count = json_object_array_length(json);
|
size_t count = json_object_array_length(json);
|
||||||
|
|
||||||
for (size_t i = 0; i < count; i++) {
|
for (size_t i = 0; i < count; i++) {
|
||||||
struct workspace ws = {};
|
if (!workspace_update_or_add(m, json_object_array_get_idx(json, i)))
|
||||||
if (!workspace_from_json(json_object_array_get_idx(json, i), &ws)) {
|
goto err;
|
||||||
workspaces_free(m);
|
|
||||||
mtx_unlock(&mod->lock);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DBG("#%zu: %s", i, m->workspaces.v[i].name);
|
|
||||||
workspace_add(m, ws);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mtx_unlock(&mod->lock);
|
mtx_unlock(&mod->lock);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
err:
|
||||||
|
workspaces_free(m, false);
|
||||||
|
mtx_unlock(&mod->lock);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
|
@ -301,24 +340,21 @@ handle_workspace_event(int type, const struct json_object *json, void *_mod)
|
||||||
mtx_lock(&mod->lock);
|
mtx_lock(&mod->lock);
|
||||||
|
|
||||||
if (is_init) {
|
if (is_init) {
|
||||||
struct workspace *already_exists = workspace_lookup(m, current_name);
|
if (!workspace_update_or_add(m, current))
|
||||||
if (already_exists != NULL) {
|
goto err;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (is_empty) {
|
else if (is_empty) {
|
||||||
assert(workspace_lookup(m, current_name) != NULL);
|
struct workspace *ws = workspace_lookup(m, current_name);
|
||||||
workspace_del(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) {
|
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 */
|
/* Mark all workspaces on current's output invisible */
|
||||||
tll_foreach(m->workspaces, it) {
|
tll_foreach(m->workspaces, it) {
|
||||||
struct workspace *ws = &it->item;
|
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;
|
ws->visible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -558,6 +594,18 @@ run(struct module *mod)
|
||||||
return 1;
|
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_GET_VERSION, NULL);
|
||||||
i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[\"workspace\", \"window\", \"mode\"]");
|
i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[\"workspace\", \"window\", \"mode\"]");
|
||||||
i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
|
i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
|
||||||
|
@ -589,7 +637,11 @@ destroy(struct module *mod)
|
||||||
}
|
}
|
||||||
|
|
||||||
free(m->ws_content.v);
|
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->mode);
|
||||||
free(m);
|
free(m);
|
||||||
|
@ -693,7 +745,9 @@ struct i3_workspaces {
|
||||||
|
|
||||||
static struct module *
|
static struct module *
|
||||||
i3_new(struct i3_workspaces workspaces[], size_t workspace_count,
|
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));
|
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->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();
|
struct module *mod = module_common_new();
|
||||||
mod->private = m;
|
mod->private = m;
|
||||||
mod->run = &run;
|
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 *left_spacing = yml_get_value(node, "left-spacing");
|
||||||
const struct yml_node *right_spacing = yml_get_value(node, "right-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 *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) :
|
int left = spacing != NULL ? yml_value_as_int(spacing) :
|
||||||
left_spacing != NULL ? yml_value_as_int(left_spacing) : 0;
|
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, "none") == 0 ? SORT_NONE :
|
||||||
strcmp(sort_value, "ascending") == 0 ? SORT_ASCENDING : SORT_DESCENDING;
|
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)];
|
struct i3_workspaces workspaces[yml_dict_length(c)];
|
||||||
|
|
||||||
size_t idx = 0;
|
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);
|
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
|
static bool
|
||||||
|
@ -791,6 +868,12 @@ verify_sort(keychain_t *chain, const struct yml_node *node)
|
||||||
chain, node, (const char *[]){"none", "ascending", "descending"}, 3);
|
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
|
static bool
|
||||||
verify_conf(keychain_t *chain, const struct yml_node *node)
|
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},
|
{"left-spacing", false, &conf_verify_int},
|
||||||
{"right-spacing", false, &conf_verify_int},
|
{"right-spacing", false, &conf_verify_int},
|
||||||
{"sort", false, &verify_sort},
|
{"sort", false, &verify_sort},
|
||||||
|
{"persistent", false, &verify_persistent},
|
||||||
{"content", true, &verify_content},
|
{"content", true, &verify_content},
|
||||||
{"anchors", false, NULL},
|
{"anchors", false, NULL},
|
||||||
{NULL, false, NULL},
|
{NULL, false, NULL},
|
||||||
|
|
Loading…
Add table
Reference in a new issue