From 6f3952819f23698fd4b7fe11bb3c62a5d84b49d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 17 Apr 2024 10:42:56 +0200 Subject: [PATCH 01/64] changelog: add new 'unreleased' section --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e07a35..5221a37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +* [Unreleased](#unreleased) * [1.11.0](#1-11-0) * [1.10.0](#1-10-0) * [1.9.0](#1-9-0) @@ -11,6 +12,16 @@ * [1.5.0](#1-5-0) +## Unreleased +### Added +### Changed +### Deprecated +### Removed +### Fixed +### Security +### Contributors + + ## 1.11.0 ### Added From 547cef5afbfbcbf9fe78705c7b5661059b706346 Mon Sep 17 00:00:00 2001 From: Sertonix Date: Mon, 22 Apr 2024 15:47:10 +0200 Subject: [PATCH 02/64] network: fix missing break in switch statement This can cause the first character of the string to be read as an iface state. Fixes https://codeberg.org/dnkl/yambar/issues/377 --- CHANGELOG.md | 6 ++++++ modules/network.c | 1 + 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5221a37..3152882 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,12 @@ ### Deprecated ### Removed ### Fixed + +* network: fix missing break in switch statement([#377][377]) + +[377]: https://codeberg.org/dnkl/yambar/issues/377 + + ### Security ### Contributors diff --git a/modules/network.c b/modules/network.c index 4bb0fb2..adb8f68 100644 --- a/modules/network.c +++ b/modules/network.c @@ -595,6 +595,7 @@ handle_link(struct module *mod, uint16_t type, const struct ifinfomsg *msg, size iface->name = strdup((const char *)RTA_DATA(attr)); LOG_DBG("%s: index=%d", iface->name, iface->index); mtx_unlock(&mod->lock); + break; case IFLA_OPERSTATE: { uint8_t operstate = *(const uint8_t *)RTA_DATA(attr); if (iface->state == operstate) From 3a7455913f85f4f8887a4fb3aecc4f8306a2010a Mon Sep 17 00:00:00 2001 From: Birger Schacht Date: Sat, 20 Apr 2024 10:13:05 +0000 Subject: [PATCH 03/64] fix: typo Probaly -> Probably --- modules/network.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/network.c b/modules/network.c index adb8f68..9ed9964 100644 --- a/modules/network.c +++ b/modules/network.c @@ -670,7 +670,7 @@ handle_address(struct module *mod, uint16_t type, const struct ifaddrmsg *msg, s } if (iface == NULL) { - LOG_ERR("failed to find network interface with index %d. Probaly a yambar bug", msg->ifa_index); + LOG_ERR("failed to find network interface with index %d. Probably a yambar bug", msg->ifa_index); return; } From 00234696fe5b09a1088254d2ee62955b3273e5a2 Mon Sep 17 00:00:00 2001 From: betazoid Date: Thu, 25 Apr 2024 21:02:03 +0000 Subject: [PATCH 04/64] Add examples/river-minimal.yml --- examples/river-minimal.yml | 69 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 examples/river-minimal.yml diff --git a/examples/river-minimal.yml b/examples/river-minimal.yml new file mode 100644 index 0000000..2ca225f --- /dev/null +++ b/examples/river-minimal.yml @@ -0,0 +1,69 @@ +bg_default: &bg_default {stack: [{background: {color: 81A1C1ff}}, {underline: {size: 4, color: D8DEE9ff}}]} +bar: + height: 32 + location: top + background: 000000ff + font: NotoSans:pixelsize=16 + + right: + - clock: + content: + - string: {text: , font: "Font Awesome 6 Free:style=solid:size=12"} + - string: {text: "{date}", right-margin: 5} + - string: {text: , font: "Font Awesome 6 Free:style=solid:size=12"} + - string: {text: "{time} "} + left: + - river: + anchors: + - base: &river_base + left-margin: 10 + right-margin: 13 + default: {string: {text: }} + conditions: + id == 1: {string: {text: 1}} + id == 2: {string: {text: 2}} + id == 3: {string: {text: 3}} + id == 4: {string: {text: 4}} + id == 5: {string: {text: 5}} + + content: + map: + on-click: + left: sh -c "riverctl set-focused-tags $((1 << ({id} - 1)))" + right: sh -c "riverctl toggle-focused-tags $((1 << ({id} -1)))" + middle: sh -c "riverctl toggle-view-tags $((1 << ({id} -1)))" + conditions: + state == urgent: + map: + <<: *river_base + deco: {background: {color: D08770ff}} + state == focused: + map: + <<: *river_base + deco: *bg_default + state == visible && ~occupied: + map: + <<: *river_base + state == visible && occupied: + map: + <<: *river_base + deco: *bg_default + state == unfocused: + map: + <<: *river_base + state == invisible && ~occupied: {empty: {}} + state == invisible && occupied: + map: + <<: *river_base + deco: {underline: {size: 3, color: ea6962ff}} + + + center: + - foreign-toplevel: + content: + map: + conditions: + ~activated: {empty: {}} + activated: + - string: {text: " {app-id}", foreground: ffa0a0ff} + - string: {text: ": {title}"} From b3313cefc67dfe6da61667b6b2ed9d8ebb0a829c Mon Sep 17 00:00:00 2001 From: Delgan Date: Thu, 2 May 2024 16:28:51 +0000 Subject: [PATCH 05/64] Fix remaining typos in the codebase (and update CI checks) --- .woodpecker.yaml | 2 +- bar/bar.c | 2 +- examples/scripts/dwl-tags.sh | 2 +- examples/scripts/pacman.sh | 2 +- modules/disk-io.c | 2 +- modules/dwl.c | 2 +- modules/pipewire.c | 12 ++++++------ 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.woodpecker.yaml b/.woodpecker.yaml index 8aee7ee..e7c9151 100644 --- a/.woodpecker.yaml +++ b/.woodpecker.yaml @@ -12,7 +12,7 @@ steps: - python3 -m venv codespell-venv - source codespell-venv/bin/activate - pip install codespell - - codespell README.md CHANGELOG.md *.c *.h doc/*.scd + - codespell README.md CHANGELOG.md *.c *.h doc/*.scd bar decorations modules particles examples - deactivate - name: subprojects diff --git a/bar/bar.c b/bar/bar.c index 3890bc0..109f210 100644 --- a/bar/bar.c +++ b/bar/bar.c @@ -28,7 +28,7 @@ #define max(x, y) ((x) > (y) ? (x) : (y)) /* - * Calculate total width of left/center/rigth groups. + * Calculate total width of left/center/right groups. * Note: begin_expose() must have been called */ static void diff --git a/examples/scripts/dwl-tags.sh b/examples/scripts/dwl-tags.sh index b1dbe4c..4999548 100755 --- a/examples/scripts/dwl-tags.sh +++ b/examples/scripts/dwl-tags.sh @@ -19,7 +19,7 @@ # # Now the fun part # -# Exemple configuration: +# Example configuration: # # - script: # path: /absolute/path/to/dwl-tags.sh diff --git a/examples/scripts/pacman.sh b/examples/scripts/pacman.sh index 83e2a3f..5026b5a 100755 --- a/examples/scripts/pacman.sh +++ b/examples/scripts/pacman.sh @@ -12,7 +12,7 @@ # {aur} int number of aur packages # {pkg} int sum of both # -# Exemples configuration: +# Examples configuration: # - script: # path: /absolute/path/to/pacman.sh # args: [] diff --git a/modules/disk-io.c b/modules/disk-io.c index 90abdba..015715f 100644 --- a/modules/disk-io.c +++ b/modules/disk-io.c @@ -129,7 +129,7 @@ refresh_device_stats(struct private *m) while ((read = getline(&line, &len, fp)) != -1) { /* - * For an explanation of the fields bellow, see + * For an explanation of the fields below, see * https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats */ uint8_t major_number = 0; diff --git a/modules/dwl.c b/modules/dwl.c index 4d9e8fa..a0d5797 100644 --- a/modules/dwl.c +++ b/modules/dwl.c @@ -231,7 +231,7 @@ process_line(char *line, struct module *module) /* No need to check error IMHO */ *target = strtoul(string, NULL, 10); - /* Populate informations */ + /* Populate information */ if (index == 6) { for (size_t id = 1; id <= private->number_of_tags; ++id) { uint32_t mask = 1 << (id - 1); diff --git a/modules/pipewire.c b/modules/pipewire.c index 1a6ab1f..e614a0a 100644 --- a/modules/pipewire.c +++ b/modules/pipewire.c @@ -33,7 +33,7 @@ struct output_informations { uint32_t device_id; uint32_t card_profile_device_id; - /* informations */ + /* information */ bool muted; uint16_t linear_volume; /* classic volume */ uint16_t cubic_volume; /* volume a la pulseaudio */ @@ -333,7 +333,7 @@ device_events_param(void *userdata, int seq, uint32_t id, uint32_t index, uint32 X_FREE_SET(route->icon_name, X_STRDUP(data.icon_name)); route->direction = data.direction; - /* set missing informations if possible */ + /* set missing information if possible */ struct private *private = device->data->module->private; struct node *binded_node = NULL; struct output_informations *output_informations = NULL; @@ -358,7 +358,7 @@ device_events_param(void *userdata, int seq, uint32_t id, uint32_t index, uint32 if (output_informations->card_profile_device_id != route->profile_device_id) return; - /* Update missing informations */ + /* Update missing information */ X_FREE_SET(output_informations->form_factor, X_STRDUP(route->form_factor)); X_FREE_SET(output_informations->icon, X_STRDUP(route->icon_name)); @@ -384,7 +384,7 @@ node_events_info(void *userdata, struct pw_node_info const *info) for (size_t i = 0; i < info->n_params; ++i) { if (info->params[i].id == SPA_PARAM_Props) { void *target_node = (node_data->is_sink ? data->node_sink : data->node_source); - /* Found it, will emit a param event, the parem will then be handled + /* Found it, will emit a param event, the param will then be handled * in node_events_param */ pw_node_enum_params(target_node, 0, info->params[i].id, 0, -1, NULL); break; @@ -419,7 +419,7 @@ node_events_info(void *userdata, struct pw_node_info const *info) output_informations->card_profile_device_id = value; } - /* Device's informations has an more important priority than node's informations */ + /* Device's information has an more important priority than node's information */ /* icon_name */ struct route *route = node_find_route(data, node_data->is_sink); if (route != NULL && route->icon_name != NULL) @@ -659,7 +659,7 @@ static void try_to_bind_node(struct node_data *node_data, char const *target_name, struct node **target_node, void **target_proxy, struct spa_hook *target_listener) { - /* profile deactived */ + /* profile deactivated */ if (target_name == NULL) return; From a467f5667769a323dea2f64391969f8d979087df Mon Sep 17 00:00:00 2001 From: QuincePie Date: Sat, 18 May 2024 18:17:10 -0500 Subject: [PATCH 06/64] i3: Handle FALLBACK output for workspaces. sway moves the workspace to fallback_output when there is no output. For example: when all the screens are off. This commit adds an ignore for the fallback output. --- modules/i3.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/modules/i3.c b/modules/i3.c index 73bd9d6..5cb6e01 100644 --- a/modules/i3.c +++ b/modules/i3.c @@ -514,7 +514,6 @@ handle_workspace_event(int sock, int type, const struct json_object *json, void else if (is_move) { struct workspace *w = workspace_lookup(m, current_id); - assert(w != NULL); struct json_object *_current_output; if (!json_object_object_get_ex(current, "output", &_current_output)) { @@ -522,16 +521,22 @@ handle_workspace_event(int sock, int type, const struct json_object *json, void mtx_unlock(&mod->lock); return false; } + const char *current_output_string = json_object_get_string(_current_output); - free(w->output); - w->output = strdup(json_object_get_string(_current_output)); + /* Ignore fallback_output ("For when there's no connected outputs") */ + if (strcmp(current_output_string, "FALLBACK") != 0) { - /* - * If the moved workspace was focused, schedule a full update because - * visibility for other workspaces may have changed. - */ - if (w->focused) { - i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL); + assert(w != NULL); + free(w->output); + w->output = strdup(current_output_string); + + /* + * If the moved workspace was focused, schedule a full update because + * visibility for other workspaces may have changed. + */ + if (w->focused) { + i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL); + } } } From b8a93a26736c076d071bfc402fd1de6b136fe5ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 20 May 2024 07:45:49 +0200 Subject: [PATCH 07/64] changelog: i3/sway crash fix for output being turned on/off --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3152882..de5c52a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,9 +19,11 @@ ### Removed ### Fixed -* network: fix missing break in switch statement([#377][377]) +* network: fix missing break in switch statement ([#377][377]). +* i3/sway: crash when output is turned off an on ([#300][300]). [377]: https://codeberg.org/dnkl/yambar/issues/377 +[300]: https://codeberg.org/dnkl/yambar/issues/300 ### Security From 70efd7d15c7752754bab0c518321cdef5289126e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 20 May 2024 09:21:29 +0200 Subject: [PATCH 08/64] doc: yambar-particles: document the hard-coded spacing of short-form lists Closes #385 --- doc/yambar-particles.5.scd | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/yambar-particles.5.scd b/doc/yambar-particles.5.scd index 5dc858b..d9b0e56 100644 --- a/doc/yambar-particles.5.scd +++ b/doc/yambar-particles.5.scd @@ -214,6 +214,11 @@ content: - string: ... ``` +Note that the short form has a hard-coded *right-spacing* of 2. This +cannot be changed. If you want a different spacing, you must use an +explicit list particle (i.e. the long form). + + # MAP This particle maps the values of a specific tag to different From 0bea49b75e2cf3fe347bce3447e9dfbaaaaf2c8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 20 May 2024 09:33:45 +0200 Subject: [PATCH 09/64] module/river: return empty particle list when river is not running Closes #384 --- CHANGELOG.md | 7 +++++++ modules/river.c | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index de5c52a..eedc5f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,13 @@ ## Unreleased ### Added ### Changed + +* `river`: expand to an empty list of particles when river is not + running ([#384][384]). + +[384]: https://codeberg.org/dnkl/yambar/issues/384 + + ### Deprecated ### Removed ### Fixed diff --git a/modules/river.c b/modules/river.c index 2619c62..ec25f9f 100644 --- a/modules/river.c +++ b/modules/river.c @@ -52,6 +52,7 @@ struct seat { struct private { struct module *mod; + bool is_running; struct zxdg_output_manager_v1 *xdg_output_manager; struct zriver_status_manager_v1 *status_manager; struct particle *template; @@ -88,6 +89,11 @@ content(struct module *mod) mtx_lock(&m->mod->lock); + if (!m->is_running) { + mtx_unlock(&m->mod->lock); + return dynlist_exposable_new(NULL, 0, 0, 0); + } + uint32_t urgent = 0; uint32_t occupied = 0; uint32_t output_focused = 0; @@ -685,6 +691,8 @@ run(struct module *mod) goto out; } + m->is_running = true; + wl_display_roundtrip(display); while (true) { From 20659d3350ab0044c41b781fa4df64e1164d2051 Mon Sep 17 00:00:00 2001 From: Tomas Slusny Date: Thu, 9 May 2024 03:55:25 +0200 Subject: [PATCH 10/64] Add support for environment variable references The format is key: ${env_variable} Closes #96 Signed-off-by: Tomas Slusny --- CHANGELOG.md | 5 +++++ doc/yambar.5.scd | 5 +++++ yml.c | 40 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eedc5f6..3c13106 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,11 @@ ## Unreleased ### Added + +* environment variable substitution in config files ([#388][388]). + +[388]: https://codeberg.org/dnkl/yambar/issues/388 + ### Changed * `river`: expand to an empty list of particles when river is not diff --git a/doc/yambar.5.scd b/doc/yambar.5.scd index 1b06939..cc03f8c 100644 --- a/doc/yambar.5.scd +++ b/doc/yambar.5.scd @@ -23,6 +23,11 @@ types that are frequently used: - 000000ff: black, no transparency - 00ff00ff: green, no transparency - ff000099: red, semi-transparent +- *environment reference*: a string that contains format ${VAR}. This will be + replaced by the value of the environment variable VAR. Example: + - ${HOME} + - ${HOME}/.config/yambar + - ENV is ${ENV}, ENV2 is ${ENV2} # FORMAT [[ *Name* diff --git a/yml.c b/yml.c index ec08101..73dba66 100644 --- a/yml.c +++ b/yml.c @@ -366,6 +366,44 @@ format_error(enum yml_error err, const struct yml_node *parent, const struct yml return err_str; } +static char * +replace_env_variables(const char *str, size_t len) +{ + char *result = strndup(str, len); + char *start, *end, *key, *env_value; + char* prefix = "${"; + char* suffix = "}"; + size_t pref_len = 2; + size_t suff_len = 1; + size_t key_len; + + while ((start = strstr(result, prefix)) && (end = strstr(start, suffix))) { + key_len = end - start - pref_len; + key = strndup(start + pref_len, key_len); + env_value = getenv(key); + + if (env_value) { + size_t result_len = strlen(result); + size_t new_len = result_len - key_len - pref_len - suff_len + strlen(env_value); + char *new_result = malloc(new_len + 1); + + strncpy(new_result, result, start - result); + new_result[start - result] = '\0'; + strcat(new_result, env_value); + strcat(new_result, end + 1); + + free(result); + result = new_result; + } else { + memmove(start, end + 1, strlen(end + 1) + 1); + } + + free(key); + } + + return result; +} + struct yml_node * yml_load(FILE *yml, char **error) { @@ -456,7 +494,7 @@ yml_load(FILE *yml, char **error) case YAML_SCALAR_EVENT: { struct yml_node *new_scalar = calloc(1, sizeof(*new_scalar)); new_scalar->type = SCALAR; - new_scalar->scalar.value = strndup((const char *)event.data.scalar.value, event.data.scalar.length); + new_scalar->scalar.value = replace_env_variables((const char *)event.data.scalar.value, event.data.scalar.length); enum yml_error err = add_node(n, new_scalar, event.start_mark); if (err != YML_ERR_NONE) { From 3431d5fc7526a7c160bb12a158773b39927845f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 9 Jun 2024 10:05:21 +0200 Subject: [PATCH 11/64] yml: replace_env_variables(): const:ify function variables --- yml.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/yml.c b/yml.c index 73dba66..4769d38 100644 --- a/yml.c +++ b/yml.c @@ -370,14 +370,17 @@ static char * replace_env_variables(const char *str, size_t len) { char *result = strndup(str, len); - char *start, *end, *key, *env_value; - char* prefix = "${"; - char* suffix = "}"; - size_t pref_len = 2; - size_t suff_len = 1; + char *start, *key; + const char *end, *env_value; + const char* prefix = "${"; + const char* suffix = "}"; + const size_t pref_len = 2; + const size_t suff_len = 1; size_t key_len; - while ((start = strstr(result, prefix)) && (end = strstr(start, suffix))) { + while ((start = strstr(result, prefix)) != NULL && + (end = strstr(start, suffix)) != NULL) + { key_len = end - start - pref_len; key = strndup(start + pref_len, key_len); env_value = getenv(key); From 9cc5e0f7a7398f88f80a3520d44feecb645a0e3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 9 Jun 2024 10:08:38 +0200 Subject: [PATCH 12/64] module/network: plug memory leak Free the 'ifaces' list, not just its contents. --- modules/network.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/network.c b/modules/network.c index 9ed9964..c2f6ec3 100644 --- a/modules/network.c +++ b/modules/network.c @@ -119,7 +119,10 @@ destroy(struct module *mod) if (m->urandom_fd >= 0) close(m->urandom_fd); - tll_foreach(m->ifaces, it) free_iface(it->item); + tll_foreach(m->ifaces, it) { + free_iface(it->item); + tll_remove(m->ifaces, it); + } free(m); module_default_destroy(mod); From 8422e7e0b1baebb9addf9d18244bdbacb3e539f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 9 Jun 2024 10:12:23 +0200 Subject: [PATCH 13/64] doc: yambar(5): remove trailing whitespace --- doc/yambar.5.scd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/yambar.5.scd b/doc/yambar.5.scd index cc03f8c..38c521d 100644 --- a/doc/yambar.5.scd +++ b/doc/yambar.5.scd @@ -24,7 +24,7 @@ types that are frequently used: - 00ff00ff: green, no transparency - ff000099: red, semi-transparent - *environment reference*: a string that contains format ${VAR}. This will be - replaced by the value of the environment variable VAR. Example: + replaced by the value of the environment variable VAR. Example: - ${HOME} - ${HOME}/.config/yambar - ENV is ${ENV}, ENV2 is ${ENV2} From 739dc30323adbb93cfad1918eca4bb0f18b41359 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 9 Jun 2024 10:12:32 +0200 Subject: [PATCH 14/64] changelog: env var substitution: fix issue reference Reference the issue, not the PR. --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c13106..9c0a0f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,9 +15,10 @@ ## Unreleased ### Added -* environment variable substitution in config files ([#388][388]). +* environment variable substitution in config files ([#96][96]). + +[96]: https://codeberg.org/dnkl/yambar/issues/96 -[388]: https://codeberg.org/dnkl/yambar/issues/388 ### Changed From 1a323c6d21d337e710534559682e2459a5db591b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 18 Jul 2024 08:31:46 +0200 Subject: [PATCH 15/64] log: respect the NO_COLOR environment variable http://no-color.org/ --- CHANGELOG.md | 2 ++ log.c | 12 +++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c0a0f4..c6bb179 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ ### Added * environment variable substitution in config files ([#96][96]). +* Log output now respects the [`NO_COLOR`](http://no-color.org/) + environment variable. [96]: https://codeberg.org/dnkl/yambar/issues/96 diff --git a/log.c b/log.c index 7ba4193..ba4ebd9 100644 --- a/log.c +++ b/log.c @@ -39,9 +39,15 @@ log_init(enum log_colorize _colorize, bool _do_syslog, enum log_facility syslog_ [LOG_FACILITY_DAEMON] = LOG_DAEMON, }; - colorize = _colorize == LOG_COLORIZE_NEVER ? false - : _colorize == LOG_COLORIZE_ALWAYS ? true - : isatty(STDERR_FILENO); + /* Don't use colors if NO_COLOR is defined and not empty */ + const char *no_color_str = getenv("NO_COLOR"); + const bool no_color = no_color_str != NULL && no_color_str[0] != '\0'; + + colorize = _colorize == LOG_COLORIZE_NEVER + ? false + : _colorize == LOG_COLORIZE_ALWAYS + ? true + : !no_color && isatty(STDERR_FILENO); do_syslog = _do_syslog; log_level = _log_level; From 3e0a65f1852eb6a17ed2e8e5e381eb54b96bb8f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 7 Aug 2024 17:27:26 +0200 Subject: [PATCH 16/64] meson: fix misdetection of memfd_create() --- meson.build | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index d760e94..d9b1364 100644 --- a/meson.build +++ b/meson.build @@ -12,7 +12,9 @@ plugs_as_libs = get_option('core-plugins-as-shared-libraries') cc = meson.get_compiler('c') -if cc.has_function('memfd_create') +if cc.has_function('memfd_create', + args: ['-D_GNU_SOURCE=200809L'], + prefix: '#include ') add_project_arguments('-DMEMFD_CREATE', language: 'c') endif From 568eb1140fe2296bbb45368902acf98868a15fe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 20 Aug 2024 07:32:51 +0200 Subject: [PATCH 17/64] modules/mpd: fix reconnect when we're not using inotify When we're not able to use inotify, we rely on polling. However, we never detected poll() timeouts, which meant we never re-attempted to reconnect to MPD. Maybe #394 --- CHANGELOG.md | 2 ++ modules/mpd.c | 10 ++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6bb179..f7c2ec8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,8 @@ * network: fix missing break in switch statement ([#377][377]). * i3/sway: crash when output is turned off an on ([#300][300]). +* mpd: yambar never attempting to reconnect after MPD closed the + connection (for example, when MPD is restarted). [377]: https://codeberg.org/dnkl/yambar/issues/377 [300]: https://codeberg.org/dnkl/yambar/issues/300 diff --git a/modules/mpd.c b/modules/mpd.c index 22c0e6b..63da818 100644 --- a/modules/mpd.c +++ b/modules/mpd.c @@ -437,7 +437,7 @@ run(struct module *mod) */ while (!aborted) { struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}}; - int res = poll(fds, sizeof(fds) / sizeof(fds[0]), 10 * 1000); + int res = poll(fds, sizeof(fds) / sizeof(fds[0]), 2 * 1000); if (res < 0) { if (errno == EINTR) @@ -448,10 +448,16 @@ run(struct module *mod) break; } - if (res == 1) { + if (res == 0) { + ret = 0; + break; + } + + else if (res == 1) { assert(fds[0].revents & POLLIN); aborted = true; } + } } From a5ae61b5df3b3e04b79741927f3e70a809257a49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 30 Apr 2024 10:00:46 +0200 Subject: [PATCH 18/64] module: network: add 'type` tag This tag maps to the ifinfomsg->ifi_type member, which is set to one of the ARPHRD_xyz values, defined in linux/if_arp.h. There's a *ton* of them, and we can't possibly add a string mapping for _all_ of them, so for now, set to one of: * loopback * ether * wlan * ARPHRD_NNN, where N is a number --- CHANGELOG.md | 2 ++ doc/yambar-modules-network.5.scd | 4 ++++ modules/network.c | 34 +++++++++++++++++++++++++++++--- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7c2ec8..5dff40c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,8 +18,10 @@ * environment variable substitution in config files ([#96][96]). * Log output now respects the [`NO_COLOR`](http://no-color.org/) environment variable. +* network: `type` tag ([#380][380]). [96]: https://codeberg.org/dnkl/yambar/issues/96 +[380]: https://codeberg.org/dnkl/yambar/issues/380 ### Changed diff --git a/doc/yambar-modules-network.5.scd b/doc/yambar-modules-network.5.scd index cdc5530..eda6fd5 100644 --- a/doc/yambar-modules-network.5.scd +++ b/doc/yambar-modules-network.5.scd @@ -21,6 +21,10 @@ address per network interface. | name : string : Network interface name +| type +: string +: Interface type (*ether*, *wlan*, *loopback*, or *ARPHRD_NNN*, where + *N* is a number). | index : int : Network interface index diff --git a/modules/network.c b/modules/network.c index c2f6ec3..8a05dd7 100644 --- a/modules/network.c +++ b/modules/network.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -24,7 +25,7 @@ #include #define LOG_MODULE "network" -#define LOG_ENABLE_DBG 0 +#define LOG_ENABLE_DBG 1 #include "../bar/bar.h" #include "../config-verify.h" #include "../config.h" @@ -52,6 +53,7 @@ struct af_addr { struct iface { char *name; + char *type; uint32_t get_stats_seq_nr; @@ -104,6 +106,7 @@ free_iface(struct iface iface) { tll_free(iface.addrs); free(iface.ssid); + free(iface.type); free(iface.name); } @@ -207,6 +210,7 @@ content(struct module *mod) struct tag_set tags = { .tags = (struct tag *[]){ tag_new_string(mod, "name", iface->name), + tag_new_string(mod, "type", iface->type), tag_new_int(mod, "index", iface->index), tag_new_bool(mod, "carrier", iface->carrier), tag_new_string(mod, "state", state), @@ -221,7 +225,7 @@ content(struct module *mod) tag_new_float(mod, "dl-speed", iface->dl_speed), tag_new_float(mod, "ul-speed", iface->ul_speed), }, - .count = 14, + .count = 15, }; exposables[idx++] = m->label->instantiate(m->label, &tags); tag_set_destroy(&tags); @@ -549,6 +553,7 @@ send_nl80211_get_scan(struct private *m) return true; } + static void handle_link(struct module *mod, uint16_t type, const struct ifinfomsg *msg, size_t len) { @@ -581,9 +586,31 @@ handle_link(struct module *mod, uint16_t type, const struct ifinfomsg *msg, size } if (iface == NULL) { + char *type = NULL; + + switch (msg->ifi_type) { + case ARPHRD_ETHER: + type = strdup("ether"); + break; + + case ARPHRD_LOOPBACK: + type = strdup("loopback"); + break; + + case ARPHRD_IEEE80211: + type = strdup("wlan"); + break; + + default: + if (asprintf(&type, "ARPHRD_%hu", msg->ifi_type) < 0) + type = strdup("unknown"); + break; + } + mtx_lock(&mod->lock); tll_push_back(m->ifaces, ((struct iface){ .index = msg->ifi_index, + .type = type, .state = IF_OPER_DOWN, .addrs = tll_init(), })); @@ -596,9 +623,10 @@ handle_link(struct module *mod, uint16_t type, const struct ifinfomsg *msg, size case IFLA_IFNAME: mtx_lock(&mod->lock); iface->name = strdup((const char *)RTA_DATA(attr)); - LOG_DBG("%s: index=%d", iface->name, iface->index); + LOG_DBG("%s: index=%d, type=%s", iface->name, iface->index, iface->type); mtx_unlock(&mod->lock); break; + case IFLA_OPERSTATE: { uint8_t operstate = *(const uint8_t *)RTA_DATA(attr); if (iface->state == operstate) From 699c5630511641c931e1d287c606da14d09ea2dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 30 Apr 2024 10:22:04 +0200 Subject: [PATCH 19/64] module: network: add 'kind' tag The tag maps to the IFLA_INFO_KIND (part of the IFLA_LINKINFO) netlink attribute. This attribute is only available on virtual interfaces. Examples of valid values are: * bond * bridge * gre * tun * veth --- CHANGELOG.md | 1 + doc/yambar-modules-network.5.scd | 6 ++ modules/network.c | 134 ++++++++++++++++++++----------- 3 files changed, 93 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dff40c..461e6db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ * Log output now respects the [`NO_COLOR`](http://no-color.org/) environment variable. * network: `type` tag ([#380][380]). +* network: `type` and `kind` tags ([#380][380]). [96]: https://codeberg.org/dnkl/yambar/issues/96 [380]: https://codeberg.org/dnkl/yambar/issues/380 diff --git a/doc/yambar-modules-network.5.scd b/doc/yambar-modules-network.5.scd index eda6fd5..7e500f0 100644 --- a/doc/yambar-modules-network.5.scd +++ b/doc/yambar-modules-network.5.scd @@ -25,6 +25,12 @@ address per network interface. : string : Interface type (*ether*, *wlan*, *loopback*, or *ARPHRD_NNN*, where *N* is a number). +| kind +: string +: Interface kind. Empty for non-virtual interfaces. For virtual + interfaces, this value is taken from the _IFLA\_INFO\_KIND_ netlink + attribute. Examples of valid values are *bond*, *bridge*, *gre*, *tun* + and *veth*. | index : int : Network interface index diff --git a/modules/network.c b/modules/network.c index 8a05dd7..dfbd5da 100644 --- a/modules/network.c +++ b/modules/network.c @@ -53,7 +53,8 @@ struct af_addr { struct iface { char *name; - char *type; + char *type; /* ARPHRD_NNN */ + char *kind; /* IFLA_LINKINFO::IFLA_INFO_KIND */ uint32_t get_stats_seq_nr; @@ -106,6 +107,7 @@ free_iface(struct iface iface) { tll_free(iface.addrs); free(iface.ssid); + free(iface.kind); free(iface.type); free(iface.name); } @@ -211,6 +213,7 @@ content(struct module *mod) .tags = (struct tag *[]){ tag_new_string(mod, "name", iface->name), tag_new_string(mod, "type", iface->type), + tag_new_string(mod, "kind", iface->kind), tag_new_int(mod, "index", iface->index), tag_new_bool(mod, "carrier", iface->carrier), tag_new_string(mod, "state", state), @@ -225,7 +228,7 @@ content(struct module *mod) tag_new_float(mod, "dl-speed", iface->dl_speed), tag_new_float(mod, "ul-speed", iface->ul_speed), }, - .count = 15, + .count = 16, }; exposables[idx++] = m->label->instantiate(m->label, &tags); tag_set_destroy(&tags); @@ -553,6 +556,78 @@ send_nl80211_get_scan(struct private *m) return true; } +static bool +foreach_nlattr(struct module *mod, struct iface *iface, const struct genlmsghdr *genl, size_t len, + bool (*cb)(struct module *mod, struct iface *iface, uint16_t type, bool nested, const void *payload, + size_t len, void *ctx), + void *ctx) +{ + const uint8_t *raw = (const uint8_t *)genl + GENL_HDRLEN; + const uint8_t *end = (const uint8_t *)genl + len; + + for (const struct nlattr *attr = (const struct nlattr *)raw; raw < end; + raw += NLA_ALIGN(attr->nla_len), attr = (const struct nlattr *)raw) { + uint16_t type = attr->nla_type & NLA_TYPE_MASK; + bool nested = (attr->nla_type & NLA_F_NESTED) != 0; + ; + const void *payload = raw + NLA_HDRLEN; + + if (!cb(mod, iface, type, nested, payload, attr->nla_len - NLA_HDRLEN, ctx)) + return false; + } + + return true; +} + +static bool +foreach_nlattr_nested(struct module *mod, struct iface *iface, const void *parent_payload, size_t len, + bool (*cb)(struct module *mod, struct iface *iface, uint16_t type, bool nested, + const void *payload, size_t len, void *ctx), + void *ctx) +{ + const uint8_t *raw = parent_payload; + const uint8_t *end = parent_payload + len; + + for (const struct nlattr *attr = (const struct nlattr *)raw; raw < end; + raw += NLA_ALIGN(attr->nla_len), attr = (const struct nlattr *)raw) { + uint16_t type = attr->nla_type & NLA_TYPE_MASK; + bool nested = (attr->nla_type & NLA_F_NESTED) != 0; + const void *payload = raw + NLA_HDRLEN; + + if (!cb(mod, iface, type, nested, payload, attr->nla_len - NLA_HDRLEN, ctx)) + return false; + } + + return true; +} + +static bool +parse_linkinfo(struct module *mod, struct iface *iface, uint16_t type, + bool nested, const void *payload, size_t len, void *_void) +{ + switch (type) { + case IFLA_INFO_KIND: { + const char *kind = payload; + free(iface->kind); + iface->kind = strndup(kind, len); + + LOG_DBG("%s: IFLA_INFO_KIND: %s", iface->name, iface->kind); + break; + } + + case IFLA_INFO_DATA: + //LOG_DBG("%s: IFLA_INFO_DATA", iface->name); + break; + + default: + LOG_WARN("unrecognized IFLA_LINKINFO attribute: " + "type=%hu, nested=%d, len=%zu", + type, nested, len); + break; + } + + return true; +} static void handle_link(struct module *mod, uint16_t type, const struct ifinfomsg *msg, size_t len) @@ -661,7 +736,8 @@ handle_link(struct module *mod, uint16_t type, const struct ifinfomsg *msg, size if (memcmp(iface->mac, mac, sizeof(iface->mac)) == 0) break; - LOG_DBG("%s: IFLA_ADDRESS: %02x:%02x:%02x:%02x:%02x:%02x", iface->name, mac[0], mac[1], mac[2], mac[3], + LOG_DBG("%s: IFLA_ADDRESS: %02x:%02x:%02x:%02x:%02x:%02x", + iface->name, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); mtx_lock(&mod->lock); @@ -669,6 +745,13 @@ handle_link(struct module *mod, uint16_t type, const struct ifinfomsg *msg, size mtx_unlock(&mod->lock); break; } + + case IFLA_LINKINFO: { + foreach_nlattr_nested( + mod, iface, RTA_DATA(attr), RTA_PAYLOAD(attr), + &parse_linkinfo, NULL); + break; + } } } @@ -751,51 +834,6 @@ handle_address(struct module *mod, uint16_t type, const struct ifaddrmsg *msg, s mod->bar->refresh(mod->bar); } -static bool -foreach_nlattr(struct module *mod, struct iface *iface, const struct genlmsghdr *genl, size_t len, - bool (*cb)(struct module *mod, struct iface *iface, uint16_t type, bool nested, const void *payload, - size_t len, void *ctx), - void *ctx) -{ - const uint8_t *raw = (const uint8_t *)genl + GENL_HDRLEN; - const uint8_t *end = (const uint8_t *)genl + len; - - for (const struct nlattr *attr = (const struct nlattr *)raw; raw < end; - raw += NLA_ALIGN(attr->nla_len), attr = (const struct nlattr *)raw) { - uint16_t type = attr->nla_type & NLA_TYPE_MASK; - bool nested = (attr->nla_type & NLA_F_NESTED) != 0; - ; - const void *payload = raw + NLA_HDRLEN; - - if (!cb(mod, iface, type, nested, payload, attr->nla_len - NLA_HDRLEN, ctx)) - return false; - } - - return true; -} - -static bool -foreach_nlattr_nested(struct module *mod, struct iface *iface, const void *parent_payload, size_t len, - bool (*cb)(struct module *mod, struct iface *iface, uint16_t type, bool nested, - const void *payload, size_t len, void *ctx), - void *ctx) -{ - const uint8_t *raw = parent_payload; - const uint8_t *end = parent_payload + len; - - for (const struct nlattr *attr = (const struct nlattr *)raw; raw < end; - raw += NLA_ALIGN(attr->nla_len), attr = (const struct nlattr *)raw) { - uint16_t type = attr->nla_type & NLA_TYPE_MASK; - bool nested = (attr->nla_type & NLA_F_NESTED) != 0; - const void *payload = raw + NLA_HDRLEN; - - if (!cb(mod, iface, type, nested, payload, attr->nla_len - NLA_HDRLEN, ctx)) - return false; - } - - return true; -} - struct mcast_group { uint32_t id; const char *name; From 54902f46ab3cfa5c72dbf3bd8e08886fd8fa0021 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 30 Apr 2024 10:40:14 +0200 Subject: [PATCH 20/64] module: network: hardcode type to "wlan" when we see NL80211_CMD_NEW_INTERFACE Wlan interfaces apparently report themselves as ARPHRD_ETHER in their ifinfomsg struct (despite there being a ARPHRD_IEEE80211 type...). "Fix" by hardcoding the type to "wlan" when we receive a NL80211_CMD_NEW_INTERFACE message. --- modules/network.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/network.c b/modules/network.c index dfbd5da..1b2ceba 100644 --- a/modules/network.c +++ b/modules/network.c @@ -1370,6 +1370,8 @@ parse_genl_reply(struct module *mod, const struct nlmsghdr *hdr, size_t len) continue; LOG_DBG("%s: got interface information", iface->name); + free(iface->type); + iface->type = strdup("wlan"); foreach_nlattr(mod, iface, genl, msg_size, &handle_nl80211_new_interface, NULL); break; From 887e770202e2839027bfa138bfc5d5b4bbec688a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 30 Apr 2024 12:03:33 +0200 Subject: [PATCH 21/64] doc: network: update example Only display Ethernet and WLAN devices (not loopback, bridges etc). --- doc/yambar-modules-network.5.scd | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/doc/yambar-modules-network.5.scd b/doc/yambar-modules-network.5.scd index 7e500f0..afbbae3 100644 --- a/doc/yambar-modules-network.5.scd +++ b/doc/yambar-modules-network.5.scd @@ -101,17 +101,23 @@ address per network interface. # EXAMPLES +Display all Ethernet (including WLAN) devices. This excludes loopback, +bridges etc. + ``` bar: left: - network: content: map: - default: - string: {text: "{name}: {state} ({ipv4})"} conditions: - ipv4 == "": - string: {text: "{name}: {state}"} + type == ether || type == wlan: + map: + default: + string: {text: "{name}: {state} ({ipv4})"} + conditions: + ipv4 == "": + string: {text: "{name}: {state}"} ``` # SEE ALSO From f8ba887dcd5d063c9a069e6bd61b00d7c9f55439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 20 Aug 2024 09:11:17 +0200 Subject: [PATCH 22/64] readme: repology: use four columns --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2887f53..48566dc 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # Yambar -[![Packaging status](https://repology.org/badge/vertical-allrepos/yambar.svg)](https://repology.org/project/yambar/versions) +[![Packaging status](https://repology.org/badge/vertical-allrepos/yambar.svg?columns=4)](https://repology.org/project/yambar/versions) ## Index From 700bf5b28c3f060ed629ac9bf782e0ff2ec76636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 20 Aug 2024 14:34:45 +0200 Subject: [PATCH 23/64] tag: add 'b' formatter Divides the tag's decimal value by 8. Closes #392 --- CHANGELOG.md | 3 +++ doc/yambar-tags.5.scd | 7 +++++- tag.c | 51 +++++++++++++++++++++++++++++++------------ 3 files changed, 46 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 461e6db..33e12b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,9 +20,12 @@ environment variable. * network: `type` tag ([#380][380]). * network: `type` and `kind` tags ([#380][380]). +* tags: `b` tag formatter; divides the tag's decimal value with `8` + ([#392][392]). [96]: https://codeberg.org/dnkl/yambar/issues/96 [380]: https://codeberg.org/dnkl/yambar/issues/380 +[392]: https://codeberg.org/dnkl/yambar/issues/392 ### Changed diff --git a/doc/yambar-tags.5.scd b/doc/yambar-tags.5.scd index b778154..adda208 100644 --- a/doc/yambar-tags.5.scd +++ b/doc/yambar-tags.5.scd @@ -86,11 +86,16 @@ be used. : format : Range tags : Renders a range tag's value as a percentage value +| b +: format +: All tag types +: Renders a tag's value (in decimal) divided by 8. Note: no unit + suffix is appended | kb, mb, gb : format : All tag types : Renders a tag's value (in decimal) divided by 1000, 1000^2 or - 1000^3. Note: no unit suffix is appended) + 1000^3. Note: no unit suffix is appended | kib, mib, gib : format : All tag types diff --git a/tag.c b/tag.c index e95b1c7..438af64 100644 --- a/tag.c +++ b/tag.c @@ -510,6 +510,7 @@ tags_expand_template(const char *template, const struct tag_set *tags) FMT_HEX, FMT_OCT, FMT_PERCENT, + FMT_BYTE, FMT_KBYTE, FMT_MBYTE, FMT_GBYTE, @@ -541,6 +542,8 @@ tags_expand_template(const char *template, const struct tag_set *tags) format = FMT_OCT; else if (strcmp(tag_args[i], "%") == 0) format = FMT_PERCENT; + else if (strcmp(tag_args[i], "b") == 0) + format = FMT_BYTE; else if (strcmp(tag_args[i], "kb") == 0) format = FMT_KBYTE; else if (strcmp(tag_args[i], "mb") == 0) @@ -634,19 +637,29 @@ tags_expand_template(const char *template, const struct tag_set *tags) break; } + case FMT_BYTE: case FMT_KBYTE: case FMT_MBYTE: case FMT_GBYTE: case FMT_KIBYTE: case FMT_MIBYTE: case FMT_GIBYTE: { - const long divider = format == FMT_KBYTE ? 1000 - : format == FMT_MBYTE ? 1000 * 1000 - : format == FMT_GBYTE ? 1000 * 1000 * 1000 - : format == FMT_KIBYTE ? 1024 - : format == FMT_MIBYTE ? 1024 * 1024 - : format == FMT_GIBYTE ? 1024 * 1024 * 1024 - : 1; + const long divider = + format == FMT_BYTE + ? 8 + : format == FMT_KBYTE + ? 1000 + : format == FMT_MBYTE + ? 1000 * 1000 + : format == FMT_GBYTE + ? 1000 * 1000 * 1000 + : format == FMT_KIBYTE + ? 1024 + : format == FMT_MIBYTE + ? 1024 * 1024 + : format == FMT_GIBYTE + ? 1024 * 1024 * 1024 + : 1; char str[24]; if (tag->type(tag) == TAG_TYPE_FLOAT) { @@ -684,19 +697,29 @@ tags_expand_template(const char *template, const struct tag_set *tags) fmt = zero_pad ? "%0*lu" : "%*lu"; break; + case FMT_BYTE: case FMT_KBYTE: case FMT_MBYTE: case FMT_GBYTE: case FMT_KIBYTE: case FMT_MIBYTE: case FMT_GIBYTE: { - const long divider = format == FMT_KBYTE ? 1024 - : format == FMT_MBYTE ? 1024 * 1024 - : format == FMT_GBYTE ? 1024 * 1024 * 1024 - : format == FMT_KIBYTE ? 1000 - : format == FMT_MIBYTE ? 1000 * 1000 - : format == FMT_GIBYTE ? 1000 * 1000 * 1000 - : 1; + const long divider = + format == FMT_BYTE + ? 8 + : format == FMT_KBYTE + ? 1024 + : format == FMT_MBYTE + ? 1024 * 1024 + : format == FMT_GBYTE + ? 1024 * 1024 * 1024 + : format == FMT_KIBYTE + ? 1000 + : format == FMT_MIBYTE + ? 1000 * 1000 + : format == FMT_GIBYTE + ? 1000 * 1000 * 1000 + : 1; value /= divider; fmt = zero_pad ? "%0*lu" : "%*lu"; break; From 1b2dee55efa246d1a020fff4345988ded02123f0 Mon Sep 17 00:00:00 2001 From: fraktal Date: Wed, 4 Sep 2024 15:33:25 +0200 Subject: [PATCH 24/64] fix bar Y position in case of multi-monitor setups with mixed resolutions --- bar/xcb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bar/xcb.c b/bar/xcb.c index 2552fe6..f3167a5 100644 --- a/bar/xcb.c +++ b/bar/xcb.c @@ -101,7 +101,7 @@ setup(struct bar *_bar) backend->x = mon->x; backend->y = mon->y; bar->width = mon->width; - backend->y += bar->location == BAR_TOP ? 0 : screen->height_in_pixels - bar->height_with_border; + backend->y += bar->location == BAR_TOP ? 0 : mon->height - bar->height_with_border; found_monitor = true; From 2d651d1c0e5b74ac3db643fc0aabffe561387a1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 5 Sep 2024 08:16:25 +0200 Subject: [PATCH 25/64] changelog: bar position in multi-monitor setups, with location=bottom --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33e12b1..962c268 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ * i3/sway: crash when output is turned off an on ([#300][300]). * mpd: yambar never attempting to reconnect after MPD closed the connection (for example, when MPD is restarted). +* Bar positioning on multi-monitor setups, when `location=bottom`. [377]: https://codeberg.org/dnkl/yambar/issues/377 [300]: https://codeberg.org/dnkl/yambar/issues/300 From 9498d7e445cd444d2840bf89a3e20dd71fc45045 Mon Sep 17 00:00:00 2001 From: Zhong Jianxin Date: Sun, 1 Sep 2024 21:16:49 +0800 Subject: [PATCH 26/64] tag: combine FMT_*BYTE into one FMT_DIVIDE --- tag.c | 100 +++++++++++++++++++--------------------------------------- 1 file changed, 32 insertions(+), 68 deletions(-) diff --git a/tag.c b/tag.c index 438af64..ce4e0e8 100644 --- a/tag.c +++ b/tag.c @@ -510,13 +510,7 @@ tags_expand_template(const char *template, const struct tag_set *tags) FMT_HEX, FMT_OCT, FMT_PERCENT, - FMT_BYTE, - FMT_KBYTE, - FMT_MBYTE, - FMT_GBYTE, - FMT_KIBYTE, - FMT_MIBYTE, - FMT_GIBYTE, + FMT_DIVIDE, } format = FMT_DEFAULT; @@ -530,6 +524,7 @@ tags_expand_template(const char *template, const struct tag_set *tags) int digits = 0; int decimals = 2; + long divider = 1; bool zero_pad = false; char *point = NULL; @@ -542,20 +537,34 @@ tags_expand_template(const char *template, const struct tag_set *tags) format = FMT_OCT; else if (strcmp(tag_args[i], "%") == 0) format = FMT_PERCENT; - else if (strcmp(tag_args[i], "b") == 0) - format = FMT_BYTE; - else if (strcmp(tag_args[i], "kb") == 0) - format = FMT_KBYTE; - else if (strcmp(tag_args[i], "mb") == 0) - format = FMT_MBYTE; - else if (strcmp(tag_args[i], "gb") == 0) - format = FMT_GBYTE; - else if (strcmp(tag_args[i], "kib") == 0) - format = FMT_KIBYTE; - else if (strcmp(tag_args[i], "mib") == 0) - format = FMT_MIBYTE; - else if (strcmp(tag_args[i], "gib") == 0) - format = FMT_GIBYTE; + else if (strcmp(tag_args[i], "b") == 0) { + format = FMT_DIVIDE; + divider = 8; + } + else if (strcmp(tag_args[i], "kb") == 0) { + format = FMT_DIVIDE; + divider = 1000; + } + else if (strcmp(tag_args[i], "mb") == 0) { + format = FMT_DIVIDE; + divider = 1000 * 1000; + } + else if (strcmp(tag_args[i], "gb") == 0) { + format = FMT_DIVIDE; + divider = 1000 * 1000 * 1000; + } + else if (strcmp(tag_args[i], "kib") == 0) { + format = FMT_DIVIDE; + divider = 1024; + } + else if (strcmp(tag_args[i], "mib") == 0) { + format = FMT_DIVIDE; + divider = 1024 * 1024; + } + else if (strcmp(tag_args[i], "gib") == 0) { + format = FMT_DIVIDE; + divider = 1024 * 1024 * 1024; + } else if (strcmp(tag_args[i], "min") == 0) kind = VALUE_MIN; else if (strcmp(tag_args[i], "max") == 0) @@ -637,30 +646,7 @@ tags_expand_template(const char *template, const struct tag_set *tags) break; } - case FMT_BYTE: - case FMT_KBYTE: - case FMT_MBYTE: - case FMT_GBYTE: - case FMT_KIBYTE: - case FMT_MIBYTE: - case FMT_GIBYTE: { - const long divider = - format == FMT_BYTE - ? 8 - : format == FMT_KBYTE - ? 1000 - : format == FMT_MBYTE - ? 1000 * 1000 - : format == FMT_GBYTE - ? 1000 * 1000 * 1000 - : format == FMT_KIBYTE - ? 1024 - : format == FMT_MIBYTE - ? 1024 * 1024 - : format == FMT_GIBYTE - ? 1024 * 1024 * 1024 - : 1; - + case FMT_DIVIDE: { char str[24]; if (tag->type(tag) == TAG_TYPE_FLOAT) { const char *fmt = zero_pad ? "%0*.*f" : "%*.*f"; @@ -697,29 +683,7 @@ tags_expand_template(const char *template, const struct tag_set *tags) fmt = zero_pad ? "%0*lu" : "%*lu"; break; - case FMT_BYTE: - case FMT_KBYTE: - case FMT_MBYTE: - case FMT_GBYTE: - case FMT_KIBYTE: - case FMT_MIBYTE: - case FMT_GIBYTE: { - const long divider = - format == FMT_BYTE - ? 8 - : format == FMT_KBYTE - ? 1024 - : format == FMT_MBYTE - ? 1024 * 1024 - : format == FMT_GBYTE - ? 1024 * 1024 * 1024 - : format == FMT_KIBYTE - ? 1000 - : format == FMT_MIBYTE - ? 1000 * 1000 - : format == FMT_GIBYTE - ? 1000 * 1000 * 1000 - : 1; + case FMT_DIVIDE: { value /= divider; fmt = zero_pad ? "%0*lu" : "%*lu"; break; From 311c481bfe0b22516859584bf79fb95716927e99 Mon Sep 17 00:00:00 2001 From: Zhong Jianxin Date: Sun, 1 Sep 2024 21:21:26 +0800 Subject: [PATCH 27/64] tag: add '/N' formatter --- doc/yambar-tags.5.scd | 4 ++++ tag.c | 16 ++++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/doc/yambar-tags.5.scd b/doc/yambar-tags.5.scd index adda208..f58678c 100644 --- a/doc/yambar-tags.5.scd +++ b/doc/yambar-tags.5.scd @@ -86,6 +86,10 @@ be used. : format : Range tags : Renders a range tag's value as a percentage value +| /N +: format +: All tag types +: Renders a tag's value (in decimal) divided by N | b : format : All tag types diff --git a/tag.c b/tag.c index ce4e0e8..48155b5 100644 --- a/tag.c +++ b/tag.c @@ -430,12 +430,12 @@ sbuf_append(struct sbuf *s1, const char *s2) // stores the number in "*value" on success static bool -is_number(const char *str, int *value) +is_number(const char *str, long *value) { errno = 0; char *end; - int v = strtol(str, &end, 10); + long v = strtol(str, &end, 10); if (errno != 0 || *end != '\0') return false; @@ -522,8 +522,8 @@ tags_expand_template(const char *template, const struct tag_set *tags) } kind = VALUE_VALUE; - int digits = 0; - int decimals = 2; + long digits = 0; + long decimals = 2; long divider = 1; bool zero_pad = false; char *point = NULL; @@ -537,6 +537,14 @@ tags_expand_template(const char *template, const struct tag_set *tags) format = FMT_OCT; else if (strcmp(tag_args[i], "%") == 0) format = FMT_PERCENT; + else if (*tag_args[i] == '/') { + format = FMT_DIVIDE; + const char *divider_str = tag_args[i] + 1; + if (!is_number(divider_str, ÷r) || divider == 0) { + divider = 1; + LOG_WARN("tag `%s`: invalid divider %s, reset to 1", tag_name, divider_str); + } + } else if (strcmp(tag_args[i], "b") == 0) { format = FMT_DIVIDE; divider = 8; From c80bae7604b18f54ee25e4073e9a95b081da1541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 5 Sep 2024 08:20:59 +0200 Subject: [PATCH 28/64] changelog: /N tag formatter --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 962c268..1bbcae1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ * network: `type` and `kind` tags ([#380][380]). * tags: `b` tag formatter; divides the tag's decimal value with `8` ([#392][392]). +* tags: `/` tag formatter: divides the tag's decimal value with `N` + ([#392][392]). [96]: https://codeberg.org/dnkl/yambar/issues/96 [380]: https://codeberg.org/dnkl/yambar/issues/380 From 060586dbbeb9134ee0a8810d611fc08310da490a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 5 Sep 2024 08:23:47 +0200 Subject: [PATCH 29/64] tag: remove the :b formatter Superseded by /N. Removing since a) it's no longer needed, and b) its name is not consistent with the other kb/mb/gb formatters. --- CHANGELOG.md | 2 -- doc/yambar-tags.5.scd | 5 ----- tag.c | 4 ---- 3 files changed, 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bbcae1..0e2e456 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,8 +20,6 @@ environment variable. * network: `type` tag ([#380][380]). * network: `type` and `kind` tags ([#380][380]). -* tags: `b` tag formatter; divides the tag's decimal value with `8` - ([#392][392]). * tags: `/` tag formatter: divides the tag's decimal value with `N` ([#392][392]). diff --git a/doc/yambar-tags.5.scd b/doc/yambar-tags.5.scd index f58678c..b6b8b56 100644 --- a/doc/yambar-tags.5.scd +++ b/doc/yambar-tags.5.scd @@ -90,11 +90,6 @@ be used. : format : All tag types : Renders a tag's value (in decimal) divided by N -| b -: format -: All tag types -: Renders a tag's value (in decimal) divided by 8. Note: no unit - suffix is appended | kb, mb, gb : format : All tag types diff --git a/tag.c b/tag.c index 48155b5..d6609af 100644 --- a/tag.c +++ b/tag.c @@ -545,10 +545,6 @@ tags_expand_template(const char *template, const struct tag_set *tags) LOG_WARN("tag `%s`: invalid divider %s, reset to 1", tag_name, divider_str); } } - else if (strcmp(tag_args[i], "b") == 0) { - format = FMT_DIVIDE; - divider = 8; - } else if (strcmp(tag_args[i], "kb") == 0) { format = FMT_DIVIDE; divider = 1000; From b81e41c3c4567c78fe45c8b380c271ab111fcd89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 5 Sep 2024 11:56:10 +0200 Subject: [PATCH 30/64] module/i3: add 'output' tag This allows bars to render workspaces differently, depending on which output the workspace is on: - map: default: ... conditions: output == DP-1: ... --- modules/i3.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/i3.c b/modules/i3.c index 5cb6e01..b1d1ca8 100644 --- a/modules/i3.c +++ b/modules/i3.c @@ -876,6 +876,7 @@ content(struct module *mod) struct tag_set tags = { .tags = (struct tag *[]){ tag_new_string(mod, "name", name), + tag_new_string(mod, "output", ws->output), tag_new_bool(mod, "visible", ws->visible), tag_new_bool(mod, "focused", ws->focused), tag_new_bool(mod, "urgent", ws->urgent), @@ -887,7 +888,7 @@ content(struct module *mod) tag_new_string(mod, "mode", m->mode), }, - .count = 9, + .count = 10, }; if (ws->focused) { From c3f7fe013daba11ba32880687e4a7102e402b096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 7 Sep 2024 08:35:39 +0200 Subject: [PATCH 31/64] doc: i3/sway: add 'output' to tag list --- doc/yambar-modules-i3.5.scd | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/yambar-modules-i3.5.scd b/doc/yambar-modules-i3.5.scd index 296a2da..2014a3c 100644 --- a/doc/yambar-modules-i3.5.scd +++ b/doc/yambar-modules-i3.5.scd @@ -26,6 +26,9 @@ with the _application_ and _title_ tags to replace the X11-only | name : string : The workspace name +| output +: string +: The output (monitor) the workspace is on | visible : bool : True if the workspace is currently visible (on any output) From 0f47cbb889716dcd0824b5501cab6f5f1cc85eac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 7 Sep 2024 08:35:58 +0200 Subject: [PATCH 32/64] changelog: i3/sway: output tag --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e2e456..763873f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ * network: `type` and `kind` tags ([#380][380]). * tags: `/` tag formatter: divides the tag's decimal value with `N` ([#392][392]). +* i3/sway: `output` tag, reflecting the output (monitor) a workspace + is on. [96]: https://codeberg.org/dnkl/yambar/issues/96 [380]: https://codeberg.org/dnkl/yambar/issues/380 From 4826a52306ffc9881269d6ecf12c0f5ede123a11 Mon Sep 17 00:00:00 2001 From: bagnaram Date: Fri, 26 Jul 2024 14:40:10 -0600 Subject: [PATCH 33/64] string like operation --- CHANGELOG.md | 3 ++ doc/yambar-particles.5.scd | 20 ++++++++++++++ particles/map.c | 56 ++++++++++++++++++++++++++++++++++++++ particles/map.h | 1 + particles/map.l | 1 + particles/map.y | 14 +++++----- 6 files changed, 88 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 763873f..7b65b3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,10 +24,13 @@ ([#392][392]). * i3/sway: `output` tag, reflecting the output (monitor) a workspace is on. +* Added "string like" `~~` operator to Map particle. Allows + glob-style matching on strings using `*` and `?` characters. ([#400][400]) [96]: https://codeberg.org/dnkl/yambar/issues/96 [380]: https://codeberg.org/dnkl/yambar/issues/380 [392]: https://codeberg.org/dnkl/yambar/issues/392 +[400]: https://codeberg.org/dnkl/yambar/pulls/400 ### Changed diff --git a/doc/yambar-particles.5.scd b/doc/yambar-particles.5.scd index d9b0e56..c86a93b 100644 --- a/doc/yambar-particles.5.scd +++ b/doc/yambar-particles.5.scd @@ -265,6 +265,26 @@ To match for empty strings, use ' "" ': == "" ``` +String glob matching + +To perform string matching using globbing with "\*" & "?" characters: +\* Match any zero or more characters. +? Match exactly any one character. + +``` + ~~ "hello*" +``` + +Will match any string starting with "hello", including "hello", +"hello1", "hello123", etc. + +``` + ~~ "hello?" +``` + +Will match any string starting with "hello" followed by any single +character, including "hello1", "hello-", but not "hello". + Furthermore, you may use the boolean operators: [- && diff --git a/particles/map.c b/particles/map.c index 51fc744..348e966 100644 --- a/particles/map.c +++ b/particles/map.c @@ -13,6 +13,59 @@ #include "map.h" +// String globbing match. +// Note: Uses "non-greedy" implementation for "*" wildcard matching +static bool +string_like(const char* name, const char* pattern) +{ + LOG_DBG("pattern:%s name:%s", pattern, name); + int px = 0, nx = 0; + int nextpx = 0, nextnx = 0; + while(px < strlen(pattern) || nx < strlen(name)) + { + if(px < strlen(pattern)) + { + char c = pattern[px]; + switch (c) { + case '?': { + // single character + px++; + nx++; + continue; + } + case '*': { + // zero or more glob + nextpx=px; + nextnx=nx+1; + px++; + continue; + } + default: { + // normal character + if (nx < strlen(name) && name[nx] == c) + { + px++; + nx++; + continue; + } + } + } + + } + // mismatch + if (0 < nextnx && nextnx <= strlen(name)) { + px = nextpx; + nx = nextnx; + continue; + } + return false; + + } + LOG_DBG("map: name %s matched all the pattern %s", name, pattern); + // Matched all of pattern to all of name. Success. + return true; +} + static bool int_condition(const long tag_value, const long cond_value, enum map_op op) { @@ -75,6 +128,8 @@ str_condition(const char *tag_value, const char *cond_value, enum map_op op) return strcmp(tag_value, cond_value) >= 0; case MAP_OP_GT: return strcmp(tag_value, cond_value) > 0; + case MAP_OP_LIKE: + return string_like(tag_value, cond_value) != 0; case MAP_OP_SELF: LOG_WARN("using String tag as bool"); default: @@ -166,6 +221,7 @@ free_map_condition(struct map_condition *c) case MAP_OP_LE: case MAP_OP_LT: case MAP_OP_GE: + case MAP_OP_LIKE: case MAP_OP_GT: free(c->value); /* FALLTHROUGH */ diff --git a/particles/map.h b/particles/map.h index 23670a5..1256744 100644 --- a/particles/map.h +++ b/particles/map.h @@ -9,6 +9,7 @@ enum map_op { MAP_OP_GT, MAP_OP_SELF, MAP_OP_NOT, + MAP_OP_LIKE, MAP_OP_AND, MAP_OP_OR, diff --git a/particles/map.l b/particles/map.l index d34f086..034353c 100644 --- a/particles/map.l +++ b/particles/map.l @@ -69,6 +69,7 @@ void yyerror(const char *s); \< yylval.op = MAP_OP_LT; return CMP_OP; >= yylval.op = MAP_OP_GE; return CMP_OP; > yylval.op = MAP_OP_GT; return CMP_OP; +~~ yylval.op = MAP_OP_LIKE; return CMP_OP; && yylval.op = MAP_OP_AND; return BOOL_OP; \|\| yylval.op = MAP_OP_OR; return BOOL_OP; ~ return NOT; diff --git a/particles/map.y b/particles/map.y index ee426da..8f3f46b 100644 --- a/particles/map.y +++ b/particles/map.y @@ -35,27 +35,27 @@ result: condition { MAP_CONDITION_PARSE_RESULT = $1; }; condition: WORD { $$ = malloc(sizeof(struct map_condition)); - $$->tag = $1; + $$->tag = $1; $$->op = MAP_OP_SELF; } | WORD CMP_OP WORD { $$ = malloc(sizeof(struct map_condition)); - $$->tag = $1; + $$->tag = $1; $$->op = $2; - $$->value = $3; + $$->value = $3; } | WORD CMP_OP STRING { $$ = malloc(sizeof(struct map_condition)); - $$->tag = $1; + $$->tag = $1; $$->op = $2; - $$->value = $3; + $$->value = $3; } | L_PAR condition R_PAR { $$ = $2; } | - NOT condition { + NOT condition { $$ = malloc(sizeof(struct map_condition)); $$->cond1 = $2; $$->op = MAP_OP_NOT; @@ -79,7 +79,7 @@ static char const* token_to_str(yysymbol_kind_t tkn) { switch (tkn) { - case YYSYMBOL_CMP_OP: return "==, !=, <=, <, >=, >"; + case YYSYMBOL_CMP_OP: return "==, !=, <=, <, >=, >, ~~"; case YYSYMBOL_BOOL_OP: return "||, &&"; case YYSYMBOL_L_PAR: return "("; case YYSYMBOL_R_PAR: return ")"; From 37ecc251a43ad14b49948c5eb7d119980ad82eac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 2 Oct 2024 08:10:30 +0200 Subject: [PATCH 34/64] changelog: line-wrap --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b65b3b..09db85b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,8 +24,8 @@ ([#392][392]). * i3/sway: `output` tag, reflecting the output (monitor) a workspace is on. -* Added "string like" `~~` operator to Map particle. Allows - glob-style matching on strings using `*` and `?` characters. ([#400][400]) +* Added "string like" `~~` operator to Map particle. Allows glob-style + matching on strings using `*` and `?` characters ([#400][400]). [96]: https://codeberg.org/dnkl/yambar/issues/96 [380]: https://codeberg.org/dnkl/yambar/issues/380 From 20d48a753b4faf1c9cad92359caad234296f34a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 2 Oct 2024 08:10:53 +0200 Subject: [PATCH 35/64] particle/map: code style --- particles/map.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/particles/map.c b/particles/map.c index 348e966..c5510ff 100644 --- a/particles/map.c +++ b/particles/map.c @@ -21,10 +21,9 @@ string_like(const char* name, const char* pattern) LOG_DBG("pattern:%s name:%s", pattern, name); int px = 0, nx = 0; int nextpx = 0, nextnx = 0; - while(px < strlen(pattern) || nx < strlen(name)) - { - if(px < strlen(pattern)) - { + + while (px < strlen(pattern) || nx < strlen(name)) { + if (px < strlen(pattern)) { char c = pattern[px]; switch (c) { case '?': { @@ -52,18 +51,21 @@ string_like(const char* name, const char* pattern) } } + // mismatch - if (0 < nextnx && nextnx <= strlen(name)) { - px = nextpx; - nx = nextnx; - continue; - } + if (0 < nextnx && nextnx <= strlen(name)) { + px = nextpx; + nx = nextnx; + continue; + } + return false; } + LOG_DBG("map: name %s matched all the pattern %s", name, pattern); // Matched all of pattern to all of name. Success. - return true; + return true; } static bool From e1b6a78f227eec7abf5b6d5dc59d5d913cb8e68a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 2 Oct 2024 08:11:02 +0200 Subject: [PATCH 36/64] doc: particles: remove trailing spaces --- doc/yambar-particles.5.scd | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/yambar-particles.5.scd b/doc/yambar-particles.5.scd index c86a93b..70a5375 100644 --- a/doc/yambar-particles.5.scd +++ b/doc/yambar-particles.5.scd @@ -268,21 +268,21 @@ To match for empty strings, use ' "" ': String glob matching To perform string matching using globbing with "\*" & "?" characters: -\* Match any zero or more characters. -? Match exactly any one character. +\* Match any zero or more characters. ? Match exactly any one +character. ``` ~~ "hello*" ``` -Will match any string starting with "hello", including "hello", +Will match any string starting with "hello", including "hello", "hello1", "hello123", etc. ``` ~~ "hello?" ``` -Will match any string starting with "hello" followed by any single +Will match any string starting with "hello" followed by any single character, including "hello1", "hello-", but not "hello". Furthermore, you may use the boolean operators: From 650d1f13f9f718dbbec33f4ebe6494aefe276ed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathias=20St=C3=A6rk?= Date: Tue, 8 Oct 2024 15:42:03 +0200 Subject: [PATCH 37/64] docs: fix typo in example --- doc/yambar-particles.5.scd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/yambar-particles.5.scd b/doc/yambar-particles.5.scd index 70a5375..231b419 100644 --- a/doc/yambar-particles.5.scd +++ b/doc/yambar-particles.5.scd @@ -476,7 +476,7 @@ itself when needed. ``` content: - progres-bar: + progress-bar: tag: tag_name length: 20 start: {string: {text: ├}} From a367895dc63f822ab4063449009bc6755adbeec3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 23 Oct 2024 09:36:59 +0200 Subject: [PATCH 38/64] Open sockets, files etc with FD_CLOEXEC --- main.c | 2 +- modules/backlight.c | 8 ++++---- modules/battery.c | 36 ++++++++++++++++++------------------ modules/cpu.c | 2 +- modules/disk-io.c | 2 +- modules/dwl.c | 2 +- modules/i3.c | 2 +- modules/mem.c | 2 +- modules/network.c | 2 +- modules/pulse.c | 2 +- modules/removables.c | 2 +- modules/xwindow.c | 2 +- 12 files changed, 32 insertions(+), 32 deletions(-) diff --git a/main.c b/main.c index a9c6932..c355843 100644 --- a/main.c +++ b/main.c @@ -87,7 +87,7 @@ get_config_path(void) static struct bar * load_bar(const char *config_path, enum bar_backend backend) { - FILE *conf_file = fopen(config_path, "r"); + FILE *conf_file = fopen(config_path, "re"); if (conf_file == NULL) { LOG_ERRNO("%s: failed to open", config_path); return NULL; diff --git a/modules/backlight.c b/modules/backlight.c index 0fa1787..1495c5c 100644 --- a/modules/backlight.c +++ b/modules/backlight.c @@ -112,13 +112,13 @@ readint_from_fd(int fd) static int initialize(struct private *m) { - int backlight_fd = open("/sys/class/backlight", O_RDONLY); + int backlight_fd = open("/sys/class/backlight", O_RDONLY | O_CLOEXEC); if (backlight_fd == -1) { LOG_ERRNO("/sys/class/backlight"); return -1; } - int base_dir_fd = openat(backlight_fd, m->device, O_RDONLY); + int base_dir_fd = openat(backlight_fd, m->device, O_RDONLY | O_CLOEXEC); close(backlight_fd); if (base_dir_fd == -1) { @@ -126,7 +126,7 @@ initialize(struct private *m) return -1; } - int max_fd = openat(base_dir_fd, "max_brightness", O_RDONLY); + int max_fd = openat(base_dir_fd, "max_brightness", O_RDONLY | O_CLOEXEC); if (max_fd == -1) { LOG_ERRNO("/sys/class/backlight/%s/max_brightness", m->device); close(base_dir_fd); @@ -136,7 +136,7 @@ initialize(struct private *m) m->max_brightness = readint_from_fd(max_fd); close(max_fd); - int current_fd = openat(base_dir_fd, "brightness", O_RDONLY); + int current_fd = openat(base_dir_fd, "brightness", O_RDONLY | O_CLOEXEC); close(base_dir_fd); if (current_fd == -1) { diff --git a/modules/battery.c b/modules/battery.c index c3507d7..34b98c8 100644 --- a/modules/battery.c +++ b/modules/battery.c @@ -259,13 +259,13 @@ initialize(struct private *m) { char line_buf[512]; - int pw_fd = open("/sys/class/power_supply", O_RDONLY); + int pw_fd = open("/sys/class/power_supply", O_RDONLY | O_CLOEXEC); if (pw_fd < 0) { LOG_ERRNO("/sys/class/power_supply"); return false; } - int base_dir_fd = openat(pw_fd, m->battery, O_RDONLY); + int base_dir_fd = openat(pw_fd, m->battery, O_RDONLY | O_CLOEXEC); close(pw_fd); if (base_dir_fd < 0) { @@ -274,7 +274,7 @@ initialize(struct private *m) } { - int fd = openat(base_dir_fd, "manufacturer", O_RDONLY); + int fd = openat(base_dir_fd, "manufacturer", O_RDONLY | O_CLOEXEC); if (fd == -1) { LOG_WARN("/sys/class/power_supply/%s/manufacturer: %s", m->battery, strerror(errno)); m->manufacturer = NULL; @@ -285,7 +285,7 @@ initialize(struct private *m) } { - int fd = openat(base_dir_fd, "model_name", O_RDONLY); + int fd = openat(base_dir_fd, "model_name", O_RDONLY | O_CLOEXEC); if (fd == -1) { LOG_WARN("/sys/class/power_supply/%s/model_name: %s", m->battery, strerror(errno)); m->model = NULL; @@ -298,7 +298,7 @@ initialize(struct private *m) if (faccessat(base_dir_fd, "energy_full_design", O_RDONLY, 0) == 0 && faccessat(base_dir_fd, "energy_full", O_RDONLY, 0) == 0) { { - int fd = openat(base_dir_fd, "energy_full_design", O_RDONLY); + int fd = openat(base_dir_fd, "energy_full_design", O_RDONLY | O_CLOEXEC); if (fd == -1) { LOG_ERRNO("/sys/class/power_supply/%s/energy_full_design", m->battery); goto err; @@ -309,7 +309,7 @@ initialize(struct private *m) } { - int fd = openat(base_dir_fd, "energy_full", O_RDONLY); + int fd = openat(base_dir_fd, "energy_full", O_RDONLY | O_CLOEXEC); if (fd == -1) { LOG_ERRNO("/sys/class/power_supply/%s/energy_full", m->battery); goto err; @@ -325,7 +325,7 @@ initialize(struct private *m) if (faccessat(base_dir_fd, "charge_full_design", O_RDONLY, 0) == 0 && faccessat(base_dir_fd, "charge_full", O_RDONLY, 0) == 0) { { - int fd = openat(base_dir_fd, "charge_full_design", O_RDONLY); + int fd = openat(base_dir_fd, "charge_full_design", O_RDONLY | O_CLOEXEC); if (fd == -1) { LOG_ERRNO("/sys/class/power_supply/%s/charge_full_design", m->battery); goto err; @@ -336,7 +336,7 @@ initialize(struct private *m) } { - int fd = openat(base_dir_fd, "charge_full", O_RDONLY); + int fd = openat(base_dir_fd, "charge_full", O_RDONLY | O_CLOEXEC); if (fd == -1) { LOG_ERRNO("/sys/class/power_supply/%s/charge_full", m->battery); goto err; @@ -362,13 +362,13 @@ update_status(struct module *mod) { struct private *m = mod->private; - int pw_fd = open("/sys/class/power_supply", O_RDONLY); + int pw_fd = open("/sys/class/power_supply", O_RDONLY | O_CLOEXEC); if (pw_fd < 0) { LOG_ERRNO("/sys/class/power_supply"); return false; } - int base_dir_fd = openat(pw_fd, m->battery, O_RDONLY); + int base_dir_fd = openat(pw_fd, m->battery, O_RDONLY | O_CLOEXEC); close(pw_fd); if (base_dir_fd < 0) { @@ -376,14 +376,14 @@ update_status(struct module *mod) return false; } - int status_fd = openat(base_dir_fd, "status", O_RDONLY); + int status_fd = openat(base_dir_fd, "status", O_RDONLY | O_CLOEXEC); if (status_fd < 0) { LOG_ERRNO("/sys/class/power_supply/%s/status", m->battery); close(base_dir_fd); return false; } - int capacity_fd = openat(base_dir_fd, "capacity", O_RDONLY); + int capacity_fd = openat(base_dir_fd, "capacity", O_RDONLY | O_CLOEXEC); if (capacity_fd < 0) { LOG_ERRNO("/sys/class/power_supply/%s/capacity", m->battery); close(status_fd); @@ -391,12 +391,12 @@ update_status(struct module *mod) return false; } - int energy_fd = openat(base_dir_fd, "energy_now", O_RDONLY); - int power_fd = openat(base_dir_fd, "power_now", O_RDONLY); - int charge_fd = openat(base_dir_fd, "charge_now", O_RDONLY); - int current_fd = openat(base_dir_fd, "current_now", O_RDONLY); - int time_to_empty_fd = openat(base_dir_fd, "time_to_empty_now", O_RDONLY); - int time_to_full_fd = openat(base_dir_fd, "time_to_full_now", O_RDONLY); + int energy_fd = openat(base_dir_fd, "energy_now", O_RDONLY | O_CLOEXEC); + int power_fd = openat(base_dir_fd, "power_now", O_RDONLY | O_CLOEXEC); + int charge_fd = openat(base_dir_fd, "charge_now", O_RDONLY | O_CLOEXEC); + int current_fd = openat(base_dir_fd, "current_now", O_RDONLY | O_CLOEXEC); + int time_to_empty_fd = openat(base_dir_fd, "time_to_empty_now", O_RDONLY | O_CLOEXEC); + int time_to_full_fd = openat(base_dir_fd, "time_to_full_now", O_RDONLY | O_CLOEXEC); long capacity = readint_from_fd(capacity_fd); long energy = energy_fd >= 0 ? readint_from_fd(energy_fd) : -1; diff --git a/modules/cpu.c b/modules/cpu.c index 833c188..118361e 100644 --- a/modules/cpu.c +++ b/modules/cpu.c @@ -124,7 +124,7 @@ refresh_cpu_stats(struct cpu_stats *cpu_stats, size_t core_count) size_t len = 0; ssize_t read; - fp = fopen("/proc/stat", "r"); + fp = fopen("/proc/stat", "re"); if (NULL == fp) { LOG_ERRNO("unable to open /proc/stat"); return; diff --git a/modules/disk-io.c b/modules/disk-io.c index 015715f..c33cbef 100644 --- a/modules/disk-io.c +++ b/modules/disk-io.c @@ -105,7 +105,7 @@ refresh_device_stats(struct private *m) size_t len = 0; ssize_t read; - fp = fopen("/proc/diskstats", "r"); + fp = fopen("/proc/diskstats", "re"); if (NULL == fp) { LOG_ERRNO("unable to open /proc/diskstats"); return; diff --git a/modules/dwl.c b/modules/dwl.c index a0d5797..3b1bdcc 100644 --- a/modules/dwl.c +++ b/modules/dwl.c @@ -330,7 +330,7 @@ run_init(int *inotify_fd, int *inotify_wd, FILE **file, char *dwl_info_filename) return 1; } - *file = fopen(dwl_info_filename, "r"); + *file = fopen(dwl_info_filename, "re"); if (*file == NULL) { inotify_rm_watch(*inotify_fd, *inotify_wd); close(*inotify_fd); diff --git a/modules/i3.c b/modules/i3.c index b1d1ca8..47f6d99 100644 --- a/modules/i3.c +++ b/modules/i3.c @@ -664,7 +664,7 @@ handle_window_event(int sock, int type, const struct json_object *json, void *_m char path[64]; snprintf(path, sizeof(path), "/proc/%u/comm", ws->window.pid); - int fd = open(path, O_RDONLY); + int fd = open(path, O_RDONLY | O_CLOEXEC); if (fd == -1) { /* Application may simply have terminated */ free(ws->window.application); diff --git a/modules/mem.c b/modules/mem.c index dc9bcf8..de4e133 100644 --- a/modules/mem.c +++ b/modules/mem.c @@ -54,7 +54,7 @@ get_mem_stats(uint64_t *mem_free, uint64_t *mem_total) size_t len = 0; ssize_t read = 0; - fp = fopen("/proc/meminfo", "r"); + fp = fopen("/proc/meminfo", "re"); if (NULL == fp) { LOG_ERRNO("unable to open /proc/meminfo"); return false; diff --git a/modules/network.c b/modules/network.c index 1b2ceba..46a3148 100644 --- a/modules/network.c +++ b/modules/network.c @@ -1576,7 +1576,7 @@ out: static struct module * network_new(struct particle *label, int poll_interval, int left_spacing, int right_spacing) { - int urandom_fd = open("/dev/urandom", O_RDONLY); + int urandom_fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC); if (urandom_fd < 0) { LOG_ERRNO("failed to open /dev/urandom"); return NULL; diff --git a/modules/pulse.c b/modules/pulse.c index e605dea..f6c7f69 100644 --- a/modules/pulse.c +++ b/modules/pulse.c @@ -438,7 +438,7 @@ run(struct module *mod) } // Create refresh timer. - priv->refresh_timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); + priv->refresh_timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); if (priv->refresh_timer_fd < 0) { LOG_ERRNO("failed to create timerfd"); pa_mainloop_free(priv->mainloop); diff --git a/modules/removables.c b/modules/removables.c index e4ef98e..a4fb4ad 100644 --- a/modules/removables.c +++ b/modules/removables.c @@ -162,7 +162,7 @@ static void find_mount_points(const char *dev_path, mount_point_list_t *mount_points) { int fd = open("/proc/self/mountinfo", O_RDONLY | O_CLOEXEC); - FILE *f = fd >= 0 ? fdopen(fd, "r") : NULL; + FILE *f = fd >= 0 ? fdopen(fd, "re") : NULL; if (fd < 0 || f == NULL) { LOG_ERRNO("failed to open /proc/self/mountinfo"); diff --git a/modules/xwindow.c b/modules/xwindow.c index ffae527..c730128 100644 --- a/modules/xwindow.c +++ b/modules/xwindow.c @@ -130,7 +130,7 @@ update_application(struct module *mod) char path[1024]; snprintf(path, sizeof(path), "/proc/%d/cmdline", pid); - int fd = open(path, O_RDONLY); + int fd = open(path, O_RDONLY | O_CLOEXEC); if (fd == -1) return; From 3e0083c9f21a276840e3487a0a6a85c71e185b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 23 Oct 2024 09:40:06 +0200 Subject: [PATCH 39/64] module/removables: no need to open+fdopen, just do fopen() --- modules/removables.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/modules/removables.c b/modules/removables.c index a4fb4ad..df4ade4 100644 --- a/modules/removables.c +++ b/modules/removables.c @@ -161,13 +161,10 @@ content(struct module *mod) static void find_mount_points(const char *dev_path, mount_point_list_t *mount_points) { - int fd = open("/proc/self/mountinfo", O_RDONLY | O_CLOEXEC); - FILE *f = fd >= 0 ? fdopen(fd, "re") : NULL; + FILE *f = fopen("/proc/self/mountinfo", "re"); - if (fd < 0 || f == NULL) { + if (f == NULL) { LOG_ERRNO("failed to open /proc/self/mountinfo"); - if (fd >= 0) - close(fd); return; } From b15714b38a1ed58196046d4365c45e85f552a8ce Mon Sep 17 00:00:00 2001 From: Alexey Yerin Date: Sat, 23 Nov 2024 20:10:14 +0300 Subject: [PATCH 40/64] pipewire: Improve handling of node switching When switching to a node that has a missing property, yambar didn't reset its internal state to the default value, causing outdated information to be displayed. --- CHANGELOG.md | 2 ++ modules/pipewire.c | 55 ++++++++++++++++++++++++++-------------------- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09db85b..3192cec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,9 +50,11 @@ * mpd: yambar never attempting to reconnect after MPD closed the connection (for example, when MPD is restarted). * Bar positioning on multi-monitor setups, when `location=bottom`. +* pipewire: Improve handling of node switching ([#424][424]). [377]: https://codeberg.org/dnkl/yambar/issues/377 [300]: https://codeberg.org/dnkl/yambar/issues/300 +[424]: https://codeberg.org/dnkl/yambar/pulls/424 ### Security diff --git a/modules/pipewire.c b/modules/pipewire.c index e614a0a..98b96d8 100644 --- a/modules/pipewire.c +++ b/modules/pipewire.c @@ -45,6 +45,16 @@ struct output_informations { }; static struct output_informations const output_informations_null; +static void +output_informations_destroy(struct output_informations *output_informations) +{ + free(output_informations->name); + free(output_informations->description); + free(output_informations->icon); + free(output_informations->form_factor); + free(output_informations->bus); +} + struct data; struct private { @@ -213,18 +223,23 @@ node_find_route(struct data *data, bool is_sink) static void node_unhook_binded_node(struct data *data, bool is_sink) { + struct private *private = data->module->private; + struct node **target_node = NULL; struct spa_hook *target_listener = NULL; void **target_proxy = NULL; + struct output_informations *output_informations = NULL; if (is_sink) { target_node = &data->binded_sink; target_listener = &data->node_sink_listener; target_proxy = &data->node_sink; + output_informations = &private->sink_informations; } else { target_node = &data->binded_source; target_listener = &data->node_source_listener; target_proxy = &data->node_source; + output_informations = &private->source_informations; } if (*target_node == NULL) @@ -235,6 +250,9 @@ node_unhook_binded_node(struct data *data, bool is_sink) *target_node = NULL; *target_proxy = NULL; + + output_informations_destroy(output_informations); + *output_informations = output_informations_null; } static void @@ -398,18 +416,18 @@ node_events_info(void *userdata, struct pw_node_info const *info) struct spa_dict_item const *item = NULL; item = spa_dict_lookup_item(info->props, "node.name"); - if (item != NULL) - X_FREE_SET(output_informations->name, X_STRDUP(item->value)); + X_FREE_SET(output_informations->name, item != NULL ? X_STRDUP(item->value) : NULL); item = spa_dict_lookup_item(info->props, "node.description"); - if (item != NULL) - X_FREE_SET(output_informations->description, X_STRDUP(item->value)); + X_FREE_SET(output_informations->description, item != NULL ? X_STRDUP(item->value) : NULL); item = spa_dict_lookup_item(info->props, "device.id"); if (item != NULL) { uint32_t value = 0; spa_atou32(item->value, &value, 10); output_informations->device_id = value; + } else { + output_informations->device_id = 0; } item = spa_dict_lookup_item(info->props, "card.profile.device"); @@ -417,30 +435,29 @@ node_events_info(void *userdata, struct pw_node_info const *info) uint32_t value = 0; spa_atou32(item->value, &value, 10); output_informations->card_profile_device_id = value; + } else { + output_informations->card_profile_device_id = 0; } /* Device's information has an more important priority than node's information */ /* icon_name */ struct route *route = node_find_route(data, node_data->is_sink); if (route != NULL && route->icon_name != NULL) - output_informations->icon = X_STRDUP(route->icon_name); + X_FREE_SET(output_informations->icon, X_STRDUP(route->icon_name)); else { item = spa_dict_lookup_item(info->props, "device.icon-name"); - if (item != NULL) - X_FREE_SET(output_informations->icon, X_STRDUP(item->value)); + X_FREE_SET(output_informations->icon, item != NULL ? X_STRDUP(item->value) : NULL); } /* form_factor */ if (route != NULL && route->form_factor != NULL) - output_informations->form_factor = X_STRDUP(route->form_factor); + X_FREE_SET(output_informations->form_factor, X_STRDUP(route->form_factor)); else { item = spa_dict_lookup_item(info->props, "device.form-factor"); - if (item != NULL) - X_FREE_SET(output_informations->form_factor, X_STRDUP(item->value)); + X_FREE_SET(output_informations->form_factor, item != NULL ? X_STRDUP(item->value) : NULL); } item = spa_dict_lookup_item(info->props, "device.bus"); - if (item != NULL) - X_FREE_SET(output_informations->bus, X_STRDUP(item->value)); + X_FREE_SET(output_informations->bus, item != NULL ? X_STRDUP(item->value) : NULL); data->module->bar->refresh(data->module->bar); } @@ -827,18 +844,8 @@ destroy(struct module *module) pipewire_deinit(private->data); private->label->destroy(private->label); - /* sink */ - free(private->sink_informations.name); - free(private->sink_informations.description); - free(private->sink_informations.icon); - free(private->sink_informations.form_factor); - free(private->sink_informations.bus); - /* source */ - free(private->source_informations.name); - free(private->source_informations.description); - free(private->source_informations.icon); - free(private->source_informations.form_factor); - free(private->source_informations.bus); + output_informations_destroy(&private->sink_informations); + output_informations_destroy(&private->source_informations); free(private); module_default_destroy(module); From 57711f0dbe84f9c9b7f5e019813d0a9f52698d8d Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Tue, 24 Dec 2024 23:52:13 +0100 Subject: [PATCH 41/64] mpd: support the `single` flag This flag indicates that `mpd` will automatically stop after the current song is played. --- CHANGELOG.md | 2 ++ doc/yambar-modules-mpd.5.scd | 3 +++ modules/mpd.c | 5 ++++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3192cec..d16b477 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,11 +26,13 @@ is on. * Added "string like" `~~` operator to Map particle. Allows glob-style matching on strings using `*` and `?` characters ([#400][400]). +* Added "single" mode flag to the `mpd` module ([#428][428]). [96]: https://codeberg.org/dnkl/yambar/issues/96 [380]: https://codeberg.org/dnkl/yambar/issues/380 [392]: https://codeberg.org/dnkl/yambar/issues/392 [400]: https://codeberg.org/dnkl/yambar/pulls/400 +[428]: https://codeberg.org/dnkl/yambar/pulls/428 ### Changed diff --git a/doc/yambar-modules-mpd.5.scd b/doc/yambar-modules-mpd.5.scd index aff6227..d89407a 100644 --- a/doc/yambar-modules-mpd.5.scd +++ b/doc/yambar-modules-mpd.5.scd @@ -20,6 +20,9 @@ mpd - This module provides MPD status such as currently playing artist/album/son | consume : bool : True if the *consume* flag is set +| single +: bool +: True if the *single* flag is set | volume : range : Volume of MPD in percentage diff --git a/modules/mpd.c b/modules/mpd.c index 63da818..e70e41f 100644 --- a/modules/mpd.c +++ b/modules/mpd.c @@ -39,6 +39,7 @@ struct private bool repeat; bool random; bool consume; + bool single; int volume; char *album; char *artist; @@ -176,6 +177,7 @@ content(struct module *mod) tag_new_bool(mod, "repeat", m->repeat), tag_new_bool(mod, "random", m->random), tag_new_bool(mod, "consume", m->consume), + tag_new_bool(mod, "single", m->single), tag_new_int_range(mod, "volume", m->volume, 0, 100), tag_new_string(mod, "album", m->album), tag_new_string(mod, "artist", m->artist), @@ -187,7 +189,7 @@ content(struct module *mod) tag_new_int_realtime( mod, "elapsed", elapsed, 0, m->duration, realtime), }, - .count = 13, + .count = 14, }; mtx_unlock(&mod->lock); @@ -336,6 +338,7 @@ update_status(struct module *mod) m->repeat = mpd_status_get_repeat(status); m->random = mpd_status_get_random(status); m->consume = mpd_status_get_consume(status); + m->single = mpd_status_get_single_state(status) == MPD_SINGLE_ONESHOT; m->volume = mpd_status_get_volume(status); m->duration = mpd_status_get_total_time(status) * 1000; m->elapsed.value = mpd_status_get_elapsed_ms(status); From 61d082c802c46438be0c1b165367536290328c1c Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Tue, 24 Dec 2024 23:48:34 +0100 Subject: [PATCH 42/64] typos: fix some typos --- bar/xcb.c | 2 +- doc/yambar-decorations.5.scd | 2 +- doc/yambar-modules-disk-io.5.scd | 4 ++-- doc/yambar-modules-pipewire.5.scd | 4 ++-- doc/yambar-modules-script.5.scd | 2 +- doc/yambar-particles.5.scd | 2 +- doc/yambar.1.scd | 2 +- modules/pipewire.c | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/bar/xcb.c b/bar/xcb.c index f3167a5..ae52bf3 100644 --- a/bar/xcb.c +++ b/bar/xcb.c @@ -369,7 +369,7 @@ refresh(const struct bar *_bar) /* Send an event to handle refresh from main thread */ - /* Note: docs say that all X11 events are 32 bytes, reglardless of + /* Note: docs say that all X11 events are 32 bytes, regardless of * the size of the event structure */ xcb_expose_event_t *evt = calloc(32, 1); diff --git a/doc/yambar-decorations.5.scd b/doc/yambar-decorations.5.scd index 9dd21b8..3d7c379 100644 --- a/doc/yambar-decorations.5.scd +++ b/doc/yambar-decorations.5.scd @@ -137,7 +137,7 @@ content: # STACK -This particles combines multiple decorations. +This particle combines multiple decorations. ## CONFIGURATION diff --git a/doc/yambar-modules-disk-io.5.scd b/doc/yambar-modules-disk-io.5.scd index 5203316..3f51e79 100644 --- a/doc/yambar-modules-disk-io.5.scd +++ b/doc/yambar-modules-disk-io.5.scd @@ -17,8 +17,8 @@ currently present in the machine. for the machine | is_disk : boolean -: whether or not the device is a disk (e.g. sda, sdb) or a partition - (e.g. sda1, sda2, ...). "Total" is advertised as a disk. +: whether or not the device is a disk (e.g., sda, sdb) or a partition + (e.g., sda1, sda2, ...). "Total" is advertised as a disk. | read_speed : int : bytes read, in bytes/s diff --git a/doc/yambar-modules-pipewire.5.scd b/doc/yambar-modules-pipewire.5.scd index be94489..ba79aaf 100644 --- a/doc/yambar-modules-pipewire.5.scd +++ b/doc/yambar-modules-pipewire.5.scd @@ -19,10 +19,10 @@ pipewire - Monitors pipewire for volume, mute/unmute, device change : Current device description | form_factor : string -: Current device form factor (headset, speaker, mic, etc) +: Current device form factor (headset, speaker, mic, etc.) | bus : string -: Current device bus (bluetooth, alsa, etc) +: Current device bus (bluetooth, alsa, etc.) | icon : string : Current device icon name diff --git a/doc/yambar-modules-script.5.scd b/doc/yambar-modules-script.5.scd index d27a006..48722cf 100644 --- a/doc/yambar-modules-script.5.scd +++ b/doc/yambar-modules-script.5.scd @@ -16,7 +16,7 @@ configurable amount of time. In continuous mode, the script is executed once. It will typically run in a loop, sending an updated tag set whenever it needs, or wants to. The last tag set is used (displayed) by yambar until a new tag set -is received. This mode is intended to be used by scripts that depends +is received. This mode is intended to be used by scripts that depend on non-polling methods to update their state. Tag sets, or _transactions_, are separated by an empty line diff --git a/doc/yambar-particles.5.scd b/doc/yambar-particles.5.scd index 231b419..325ef89 100644 --- a/doc/yambar-particles.5.scd +++ b/doc/yambar-particles.5.scd @@ -155,7 +155,7 @@ content: This particle is a list (or sequence, if you like) of other particles. It can be used to render e.g. _string_ particles with -different font and/or color formatting. Or ay other particle +different font and/or color formatting. Or any other particle combinations. But note that this means you *cannot* set any attributes on the _list_ diff --git a/doc/yambar.1.scd b/doc/yambar.1.scd index 549b980..2aaa46f 100644 --- a/doc/yambar.1.scd +++ b/doc/yambar.1.scd @@ -25,7 +25,7 @@ yambar - modular status panel for X11 and Wayland *-p*,*--print-pid*=_FILE_|_FD_ Print PID to this file, or FD, when successfully started. The file (or FD) is closed immediately after writing the PID. When a _FILE_ - as been specified, the file is unlinked exit. + as been specified, the file is unlinked upon exiting. *-d*,*--log-level*={*info*,*warning*,*error*,*none*} Log level, used both for log output on stderr as well as diff --git a/modules/pipewire.c b/modules/pipewire.c index 98b96d8..a2fdcae 100644 --- a/modules/pipewire.c +++ b/modules/pipewire.c @@ -368,7 +368,7 @@ device_events_param(void *userdata, int seq, uint32_t id, uint32_t index, uint32 if (binded_node == NULL) return; - /* Node's device is the the same as route's device */ + /* Node's device is the same as route's device */ if (output_informations->device_id != route->device->id) return; From d746d12f6a36a93f8a2205ea03ccb4e4e06876af Mon Sep 17 00:00:00 2001 From: vova Date: Tue, 3 Sep 2024 21:18:05 +0200 Subject: [PATCH 43/64] add niri-workspaces and niri-language modules --- CHANGELOG.md | 2 + doc/meson.build | 6 + doc/yambar-modules-niri-language.5.scd | 34 ++ doc/yambar-modules-niri-workspaces.5.scd | 60 ++++ doc/yambar-modules.5.scd | 4 + meson.build | 2 + meson_options.txt | 4 + modules/meson.build | 14 + modules/niri-common.c | 377 +++++++++++++++++++++++ modules/niri-common.h | 45 +++ modules/niri-language.c | 160 ++++++++++ modules/niri-workspaces.c | 163 ++++++++++ plugin.c | 12 + 13 files changed, 883 insertions(+) create mode 100644 doc/yambar-modules-niri-language.5.scd create mode 100644 doc/yambar-modules-niri-workspaces.5.scd create mode 100644 modules/niri-common.c create mode 100644 modules/niri-common.h create mode 100644 modules/niri-language.c create mode 100644 modules/niri-workspaces.c diff --git a/CHANGELOG.md b/CHANGELOG.md index d16b477..aec47c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,12 +27,14 @@ * Added "string like" `~~` operator to Map particle. Allows glob-style matching on strings using `*` and `?` characters ([#400][400]). * Added "single" mode flag to the `mpd` module ([#428][428]). +* niri: add a new module for niri-workspaces and niri-language ([#405][405]). [96]: https://codeberg.org/dnkl/yambar/issues/96 [380]: https://codeberg.org/dnkl/yambar/issues/380 [392]: https://codeberg.org/dnkl/yambar/issues/392 [400]: https://codeberg.org/dnkl/yambar/pulls/400 [428]: https://codeberg.org/dnkl/yambar/pulls/428 +[405]: https://codeberg.org/dnkl/yambar/issues/405 ### Changed diff --git a/doc/meson.build b/doc/meson.build index e5728ab..90a83ec 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -44,6 +44,12 @@ endif if plugin_network_enabled plugin_pages += ['yambar-modules-network.5.scd'] endif +if plugin_niri_language_enabled + plugin_pages += ['yambar-modules-niri-language.5.scd'] +endif +if plugin_niri_workspaces_enabled + plugin_pages += ['yambar-modules-niri-workspaces.5.scd'] +endif if plugin_pipewire_enabled plugin_pages += ['yambar-modules-pipewire.5.scd'] endif diff --git a/doc/yambar-modules-niri-language.5.scd b/doc/yambar-modules-niri-language.5.scd new file mode 100644 index 0000000..befa41e --- /dev/null +++ b/doc/yambar-modules-niri-language.5.scd @@ -0,0 +1,34 @@ +yambar-modules-niri-language(5) + +# NAME +niri-language - This module provides information about niri's currently +selected language. + +# TAGS + +[[ *Name* +:[ *Type* +:< *Description* +| language +: string +: The currently selected language. + +# CONFIGURATION + +No additional attributes supported, only the generic ones (see +*GENERIC CONFIGURATION* in *yambar-modules*(5)) + +# EXAMPLES + +``` +bar: + left: + - niri-language: + content: + string: {text: "{language}"} +``` + +# SEE ALSO + +*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) + diff --git a/doc/yambar-modules-niri-workspaces.5.scd b/doc/yambar-modules-niri-workspaces.5.scd new file mode 100644 index 0000000..812bade --- /dev/null +++ b/doc/yambar-modules-niri-workspaces.5.scd @@ -0,0 +1,60 @@ +yambar-modules-niri-workspaces(5) + +# NAME +niri-workspaces - This module provides information about niri workspaces. + +# DESCRIPTION + +This module provides a map of each workspace present in niri. + +Each workspace has its _id_, _name_, and its status (_focused_, +_active_, _empty_). The workspaces are sorted by their ids. + +This module will *only* track the monitor where yambar was launched. +If you have a multi monitor setup, please launch yambar on each +individual monitor to track its workspaces. + +# TAGS + +[[ *Name* +:[ *Type* +:< *Description* +| id +: int +: The workspace id. +| name +: string +: The name of the workspace. +| active +: bool +: True if the workspace is currently visible on the current output. +| focused +: bool +: True if the workspace is currently focused. +| empty +: bool +: True if the workspace contains no window. + +# CONFIGURATION + +No additional attributes supported, only the generic ones (see +*GENERIC CONFIGURATION* in *yambar-modules*(5)) + +# EXAMPLES + +``` +bar: + left: + - niri-workspaces: + content: + map: + default: {string: {text: "| {id}"}} + conditions: + active: {string: {text: "-> {id}"}} + ~empty: {string: {text: "@ {id}"}} +``` + +# SEE ALSO + +*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) + diff --git a/doc/yambar-modules.5.scd b/doc/yambar-modules.5.scd index 765d06f..1ec4871 100644 --- a/doc/yambar-modules.5.scd +++ b/doc/yambar-modules.5.scd @@ -174,6 +174,10 @@ Available modules have their own pages: *yambar-modules-sway*(5) +*yambar-modules-niri-language*(5) + +*yambar-modules-niri-workspaces*(5) + *yambar-modules-xkb*(5) *yambar-modules-xwindow*(5) diff --git a/meson.build b/meson.build index d9b1364..81af577 100644 --- a/meson.build +++ b/meson.build @@ -189,6 +189,8 @@ summary( 'River': plugin_river_enabled, 'Script': plugin_script_enabled, 'Sway XKB keyboard': plugin_sway_xkb_enabled, + 'Niri language': plugin_niri_language_enabled, + 'Niri workspaces': plugin_niri_workspaces_enabled, 'XKB keyboard (for X11)': plugin_xkb_enabled, 'XWindow (window tracking for X11)': plugin_xwindow_enabled, }, diff --git a/meson_options.txt b/meson_options.txt index a9aac05..9fd0dd5 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -44,6 +44,10 @@ option('plugin-script', type: 'feature', value: 'auto', description: 'Script support') option('plugin-sway-xkb', type: 'feature', value: 'auto', description: 'keyboard support for Sway') +option('plugin-niri-language', type: 'feature', value: 'auto', + description: 'language support for Niri') +option('plugin-niri-workspaces', type: 'feature', value: 'auto', + description: 'workspaces support for Niri') option('plugin-xkb', type: 'feature', value: 'auto', description: 'keyboard support for X11') option('plugin-xwindow', type: 'feature', value: 'auto', diff --git a/modules/meson.build b/modules/meson.build index e2ed56e..b54e9d7 100644 --- a/modules/meson.build +++ b/modules/meson.build @@ -45,6 +45,12 @@ plugin_script_enabled = get_option('plugin-script').allowed() json_sway_xkb = dependency('json-c', required: get_option('plugin-sway-xkb')) plugin_sway_xkb_enabled = json_sway_xkb.found() +json_niri_language = dependency('json-c', required: get_option('plugin-niri-language')) +plugin_niri_language_enabled = json_niri_language.found() + +json_niri_workspaces = dependency('json-c', required: get_option('plugin-niri-workspaces')) +plugin_niri_workspaces_enabled = json_niri_workspaces.found() + xcb_xkb = dependency('xcb-xkb', required: get_option('plugin-xkb')) plugin_xkb_enabled = backend_x11 and xcb_xkb.found() @@ -121,6 +127,14 @@ if plugin_sway_xkb_enabled mod_data += {'sway-xkb': [['i3-common.c', 'i3-common.h'], [dynlist, json_sway_xkb]]} endif +if plugin_niri_language_enabled + mod_data += {'niri-language': [['niri-common.c', 'niri-common.h'], [dynlist, json_niri_language]]} +endif + +if plugin_niri_workspaces_enabled + mod_data += {'niri-workspaces': [['niri-common.c', 'niri-common.h'], [dynlist, json_niri_workspaces]]} +endif + if plugin_xkb_enabled mod_data += {'xkb': [[], [xcb_stuff, xcb_xkb]]} endif diff --git a/modules/niri-common.c b/modules/niri-common.c new file mode 100644 index 0000000..ac53921 --- /dev/null +++ b/modules/niri-common.c @@ -0,0 +1,377 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../log.h" +#include "niri-common.h" + +#define LOG_MODULE "niri:common" +#define LOG_ENABLE_DBG 0 + +static struct niri_socket instance = { + .fd = -1, + .abort_fd = -1, +}; + +static void +workspace_free(struct niri_workspace *workspace) +{ + free(workspace->name); + free(workspace); +} + +static void +parser(char *response) +{ + enum json_tokener_error error = json_tokener_success; + struct json_object *json = json_tokener_parse_verbose(response, &error); + if (error != json_tokener_success) { + LOG_WARN("failed to parse niri socket's response"); + return; + } + + enum niri_event events = 0; + struct json_object_iterator it = json_object_iter_begin(json); + struct json_object_iterator end = json_object_iter_end(json); + while (!json_object_iter_equal(&it, &end)) { + char const *key = json_object_iter_peek_name(&it); + + // "WorkspacesChanged": { + // "workspaces": [ + // { + // "id": 3, + // "idx": 1, + // "name": null, + // "output": "DP-4", + // "is_active": true, + // "is_focused": true, + // "active_window_id": 24 + // }, + // ... + // ] + // } + if (strcmp(key, "WorkspacesChanged") == 0) { + mtx_lock(&instance.mtx); + tll_foreach(instance.workspaces, it) { tll_remove_and_free(instance.workspaces, it, workspace_free); } + mtx_unlock(&instance.mtx); + + json_object *obj = json_object_iter_peek_value(&it); + json_object *workspaces = json_object_object_get(obj, "workspaces"); + + size_t length = json_object_array_length(workspaces); + for (size_t i = 0; i < length; ++i) { + json_object *ws_obj = json_object_array_get_idx(workspaces, i); + + // only add workspaces on the current yambar's monitor + struct json_object *output = json_object_object_get(ws_obj, "output"); + if (strcmp(instance.monitor, json_object_get_string(output)) != 0) + continue; + + struct niri_workspace *ws = calloc(1, sizeof(*ws)); + ws->idx = json_object_get_int(json_object_object_get(ws_obj, "idx")); + ws->id = json_object_get_int(json_object_object_get(ws_obj, "id")); + ws->active = json_object_get_boolean(json_object_object_get(ws_obj, "is_active")); + ws->focused = json_object_get_boolean(json_object_object_get(ws_obj, "is_focused")); + ws->empty = json_object_get_int(json_object_object_get(ws_obj, "active_window_id")) == 0; + + char const *name = json_object_get_string(json_object_object_get(ws_obj, "name")); + if (name) + ws->name = strdup(name); + + mtx_lock(&instance.mtx); + bool inserted = false; + tll_foreach(instance.workspaces, it) + { + if (it->item->idx > ws->idx) { + tll_insert_before(instance.workspaces, it, ws); + inserted = true; + break; + } + } + if (!inserted) + tll_push_back(instance.workspaces, ws); + mtx_unlock(&instance.mtx); + + events |= workspaces_changed; + } + } + + // "WorkspaceActivated": { + // "id": 7, + // "focused":true + // } + else if (strcmp(key, "WorkspaceActivated") == 0) { + json_object *obj = json_object_iter_peek_value(&it); + int id = json_object_get_int(json_object_object_get(obj, "id")); + + mtx_lock(&instance.mtx); + tll_foreach(instance.workspaces, it) + { + bool b = it->item->id == id; + it->item->focused = b; + it->item->active = b; + } + mtx_unlock(&instance.mtx); + + events |= workspace_activated; + } + + // "WorkspaceActiveWindowChanged": { + // "workspace_id": 3, + // "active_window_id": 8 + // } + else if (strcmp(key, "WorkspaceActiveWindowChanged") == 0) { + json_object *obj = json_object_iter_peek_value(&it); + int id = json_object_get_int(json_object_object_get(obj, "id")); + bool empty = json_object_get_int(json_object_object_get(obj, "active_window_id")) == 0; + + mtx_lock(&instance.mtx); + tll_foreach(instance.workspaces, it) + { + if (it->item->id == id) { + it->item->empty = empty; + break; + } + } + mtx_unlock(&instance.mtx); + + events |= workspace_active_window_changed; + } + + // + // "KeyboardLayoutsChanged": { + // "keyboard_layouts": { + // "names": [ + // "English (US)", + // "Russian" + // ], + // "current_idx": 0 + // } + // } + else if (strcmp(key, "KeyboardLayoutsChanged") == 0) { + tll_foreach(instance.keyboard_layouts, it) { tll_remove_and_free(instance.keyboard_layouts, it, free); } + + json_object *obj = json_object_iter_peek_value(&it); + json_object *kb_layouts = json_object_object_get(obj, "keyboard_layouts"); + + instance.keyboard_layout_index = json_object_get_int(json_object_object_get(kb_layouts, "current_idx")); + + json_object *names = json_object_object_get(kb_layouts, "names"); + size_t names_length = json_object_array_length(names); + for (size_t i = 0; i < names_length; ++i) { + char const *name = json_object_get_string(json_object_array_get_idx(names, i)); + tll_push_back(instance.keyboard_layouts, strdup(name)); + } + + events |= keyboard_layouts_changed; + } + + // "KeyboardLayoutSwitched": { + // "idx": 1 + // } + else if (strcmp(key, "KeyboardLayoutSwitched") == 0) { + json_object *obj = json_object_iter_peek_value(&it); + instance.keyboard_layout_index = json_object_get_int(json_object_object_get(obj, "idx")); + + events |= keyboard_layouts_switched; + } + + json_object_iter_next(&it); + } + + json_object_put(json); + + mtx_lock(&instance.mtx); + tll_foreach(instance.subscribers, it) + { + if (it->item->events & events) + if (write(it->item->fd, &(uint64_t){1}, sizeof(uint64_t)) == -1) + LOG_ERRNO("failed to write"); + } + mtx_unlock(&instance.mtx); +} + +static int +run(void *userdata) +{ + static char msg[] = "\"EventStream\"\n"; + static char expected[] = "{\"Ok\":\"Handled\"}"; + + if (write(instance.fd, msg, sizeof(msg) / sizeof(msg[0])) == -1) { + LOG_ERRNO("failed to sent message to niri socket"); + return thrd_error; + } + + static char buffer[8192]; + if (read(instance.fd, buffer, sizeof(buffer) / sizeof(buffer[0]) - 1) == -1) { + LOG_ERRNO("failed to read response of niri socket"); + return thrd_error; + } + + char *saveptr; + char *response = strtok_r(buffer, "\n", &saveptr); + if (response == NULL || strcmp(expected, response) != 0) { + // unexpected first response, something went wrong + LOG_ERR("unexpected response of niri socket"); + return thrd_error; + } + + while ((response = strtok_r(NULL, "\n", &saveptr)) != NULL) + parser(response); + + while (true) { + struct pollfd fds[] = { + (struct pollfd){.fd = instance.abort_fd, .events = POLLIN}, + (struct pollfd){.fd = instance.fd, .events = POLLIN}, + }; + + if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) == -1) { + if (errno == EINTR) + continue; + + LOG_ERRNO("failed to poll"); + break; + } + + if (fds[0].revents & POLLIN) + break; + + static char buffer[8192]; + ssize_t length = read(fds[1].fd, buffer, sizeof(buffer) / sizeof(buffer[0])); + + if (length == 0) + break; + + if (length == -1) { + if (errno == EAGAIN || errno == EINTR) + continue; + + LOG_ERRNO("unable to read niri socket"); + break; + } + + buffer[length] = '\0'; + saveptr = NULL; + response = strtok_r(buffer, "\n", &saveptr); + do { + parser(response); + } while ((response = strtok_r(NULL, "\n", &saveptr)) != NULL); + } + + return thrd_success; +} + +struct niri_socket * +niri_socket_open(char const *monitor) +{ + if (instance.fd >= 0) + return &instance; + + char const *path = getenv("NIRI_SOCKET"); + if (path == NULL) { + LOG_ERR("NIRI_SOCKET is empty. Is niri running?"); + return NULL; + } + + if ((instance.fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)) == -1) { + LOG_ERRNO("failed to create socket"); + goto error; + } + + struct sockaddr_un addr; + addr.sun_family = AF_UNIX; + addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; + strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); + + if (connect(instance.fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) { + LOG_ERRNO("failed to connect to niri socket"); + goto error; + } + + if ((instance.abort_fd = eventfd(0, EFD_CLOEXEC)) == -1) { + LOG_ERRNO("failed to create abort_fd"); + goto error; + } + + if (mtx_init(&instance.mtx, mtx_plain) != thrd_success) { + LOG_ERR("failed to initialize mutex"); + goto error; + } + + if (thrd_create(&instance.thrd, run, NULL) != thrd_success) { + LOG_ERR("failed to create thread"); + mtx_destroy(&instance.mtx); + goto error; + } + + instance.monitor = monitor; + + return &instance; + +error: + if (instance.fd >= 0) + close(instance.fd); + if (instance.abort_fd >= 0) + close(instance.abort_fd); + instance.fd = -1; + instance.abort_fd = -1; + instance.monitor = NULL; + + return NULL; +} + +static void +socket_close(void) +{ + if (write(instance.abort_fd, &(uint64_t){1}, sizeof(uint64_t)) != sizeof(uint64_t)) + LOG_ERRNO("failed to write to abort_fd"); + + thrd_join(instance.thrd, NULL); + + close(instance.abort_fd); + close(instance.fd); + instance.abort_fd = -1; + instance.fd = -1; + + mtx_destroy(&instance.mtx); + + tll_free_and_free(instance.subscribers, free); + tll_free_and_free(instance.workspaces, workspace_free); + tll_free_and_free(instance.keyboard_layouts, free); +} + +void +niri_socket_close(void) +{ + static once_flag flag = ONCE_FLAG_INIT; + call_once(&flag, socket_close); +} + +int +niri_socket_subscribe(enum niri_event events) +{ + int fd = eventfd(0, EFD_CLOEXEC); + if (fd == -1) { + LOG_ERRNO("failed to create eventfd"); + return -1; + } + + struct niri_subscriber *subscriber = calloc(1, sizeof(*subscriber)); + subscriber->events = events; + subscriber->fd = fd; + + mtx_lock(&instance.mtx); + tll_push_back(instance.subscribers, subscriber); + mtx_unlock(&instance.mtx); + + return subscriber->fd; +} diff --git a/modules/niri-common.h b/modules/niri-common.h new file mode 100644 index 0000000..18afe38 --- /dev/null +++ b/modules/niri-common.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include + +enum niri_event { + workspaces_changed = (1 << 0), + workspace_activated = (1 << 1), + workspace_active_window_changed = (1 << 2), + keyboard_layouts_changed = (1 << 3), + keyboard_layouts_switched = (1 << 4), +}; + +struct niri_subscriber { + int events; + int fd; +}; + +struct niri_workspace { + int id; + int idx; + char *name; + bool active; + bool focused; + bool empty; +}; + +struct niri_socket { + char const *monitor; + int abort_fd; + int fd; + + tll(struct niri_subscriber *) subscribers; + tll(struct niri_workspace *) workspaces; + tll(char *) keyboard_layouts; + size_t keyboard_layout_index; + + thrd_t thrd; + mtx_t mtx; +}; + +struct niri_socket *niri_socket_open(char const *monitor); +void niri_socket_close(void); +int niri_socket_subscribe(enum niri_event events); diff --git a/modules/niri-language.c b/modules/niri-language.c new file mode 100644 index 0000000..f8138ee --- /dev/null +++ b/modules/niri-language.c @@ -0,0 +1,160 @@ +#include +#include +#include +#include +#include + +#define LOG_MODULE "niri-language" +#define LOG_ENABLE_DBG 0 +#include "niri-common.h" + +#include "../log.h" +#include "../particles/dynlist.h" +#include "../plugin.h" + +struct private +{ + struct particle *label; + struct niri_socket *niri; +}; + +static void +destroy(struct module *module) +{ + struct private *private = module->private; + private->label->destroy(private->label); + + free(private); + + module_default_destroy(module); +} + +static const char * +description(const struct module *module) +{ + return "niri-lang"; +} + +static struct exposable * +content(struct module *module) +{ + const struct private *private = module->private; + + if (private->niri == NULL) + return dynlist_exposable_new(&((struct exposable *){0}), 0, 0, 0); + + mtx_lock(&module->lock); + mtx_lock(&private->niri->mtx); + + char *name = "???"; + size_t i = 0; + tll_foreach(private->niri->keyboard_layouts, it) + { + if (i++ == private->niri->keyboard_layout_index) + name = it->item; + } + + struct tag_set tags = { + .tags = (struct tag *[]){tag_new_string(module, "language", name)}, + .count = 1, + }; + + struct exposable *exposable = private->label->instantiate(private->label, &tags); + tag_set_destroy(&tags); + mtx_unlock(&private->niri->mtx); + mtx_unlock(&module->lock); + return exposable; +} + +static int +run(struct module *module) +{ + struct private *private = module->private; + + /* Ugly, but I didn't find better way for waiting + * the monitor's name to be set */ + char const *monitor; + do { + monitor = module->bar->output_name(module->bar); + usleep(50); + } while (monitor == NULL); + + private->niri = niri_socket_open(monitor); + if (private->niri == NULL) + return 1; + + int fd = niri_socket_subscribe(keyboard_layouts_changed | keyboard_layouts_switched); + if (fd == -1) { + niri_socket_close(); + return 1; + } + + module->bar->refresh(module->bar); + + while (true) { + struct pollfd fds[] = { + (struct pollfd){.fd = module->abort_fd, .events = POLLIN}, + (struct pollfd){.fd = fd, .events = POLLIN}, + }; + + if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) == -1) { + if (errno == EINTR) + continue; + + LOG_ERRNO("failed to poll"); + break; + } + + if (fds[0].revents & POLLIN) + break; + + if (read(fds[1].fd, &(uint64_t){0}, sizeof(uint64_t)) == -1) + LOG_ERRNO("failed to read from eventfd"); + + module->bar->refresh(module->bar); + } + + niri_socket_close(); + return 0; +} + +static struct module * +niri_language_new(struct particle *label) +{ + struct private *private = calloc(1, sizeof(struct private)); + private->label = label; + + struct module *module = module_common_new(); + module->private = private; + module->run = &run; + module->destroy = &destroy; + module->content = &content; + module->description = &description; + + return module; +} + +static struct module * +from_conf(struct yml_node const *node, struct conf_inherit inherited) +{ + struct yml_node const *content = yml_get_value(node, "content"); + return niri_language_new(conf_to_particle(content, inherited)); +} + +static bool +verify_conf(keychain_t *chain, const struct yml_node *node) +{ + static struct attr_info const attrs[] = { + MODULE_COMMON_ATTRS, + }; + return conf_verify_dict(chain, node, attrs); +} + +const struct module_iface module_niri_language_iface = { + .verify_conf = &verify_conf, + .from_conf = &from_conf, +}; + +#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) +extern const struct module_iface iface __attribute__((weak, alias("module_niri_language_iface"))); +#endif diff --git a/modules/niri-workspaces.c b/modules/niri-workspaces.c new file mode 100644 index 0000000..bca0150 --- /dev/null +++ b/modules/niri-workspaces.c @@ -0,0 +1,163 @@ +#include +#include +#include +#include + +#define LOG_MODULE "niri-workspaces" +#define LOG_ENABLE_DBG 0 +#include "niri-common.h" + +#include "../log.h" +#include "../particles/dynlist.h" +#include "../plugin.h" + +struct private +{ + struct particle *label; + struct niri_socket *niri; +}; + +static void +destroy(struct module *module) +{ + struct private *private = module->private; + private->label->destroy(private->label); + + free(private); + + module_default_destroy(module); +} + +static const char * +description(const struct module *module) +{ + return "niri-ws"; +} + +static struct exposable * +content(struct module *module) +{ + struct private const *private = module->private; + + if (private->niri == NULL) + return dynlist_exposable_new(&((struct exposable *){0}), 0, 0, 0); + + mtx_lock(&module->lock); + mtx_lock(&private->niri->mtx); + + size_t i = 0; + struct exposable *exposable[tll_length(private->niri->workspaces)]; + tll_foreach(private->niri->workspaces, it) + { + struct tag_set tags = { + .tags = (struct tag*[]){ + tag_new_int(module, "id", it->item->idx), + tag_new_string(module, "name", it->item->name), + tag_new_bool(module, "active", it->item->active), + tag_new_bool(module, "focused", it->item->focused), + tag_new_bool(module, "empty", it->item->empty), + }, + .count = 5, + }; + + exposable[i++] = private->label->instantiate(private->label, &tags); + tag_set_destroy(&tags); + } + + mtx_unlock(&private->niri->mtx); + mtx_unlock(&module->lock); + return dynlist_exposable_new(exposable, i, 0, 0); +} + +static int +run(struct module *module) +{ + struct private *private = module->private; + + /* Ugly, but I didn't find better way for waiting + * the monitor's name to be set */ + char const *monitor; + do { + monitor = module->bar->output_name(module->bar); + usleep(50); + } while (monitor == NULL); + + private->niri = niri_socket_open(monitor); + if (private->niri == NULL) + return 1; + + int fd = niri_socket_subscribe(workspaces_changed | workspace_activated | workspace_active_window_changed); + if (fd == -1) { + niri_socket_close(); + return 1; + } + + module->bar->refresh(module->bar); + + while (true) { + struct pollfd fds[] = { + (struct pollfd){.fd = module->abort_fd, .events = POLLIN}, + (struct pollfd){.fd = fd, .events = POLLIN}, + }; + + if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) == -1) { + if (errno == EINTR) + continue; + + LOG_ERRNO("failed to poll"); + break; + } + + if (fds[0].revents & POLLIN) + break; + + if (read(fds[1].fd, &(uint64_t){0}, sizeof(uint64_t)) == -1) + LOG_ERRNO("failed to read from eventfd"); + + module->bar->refresh(module->bar); + } + + niri_socket_close(); + return 0; +} + +static struct module * +niri_workspaces_new(struct particle *label) +{ + struct private *private = calloc(1, sizeof(struct private)); + private->label = label; + + struct module *module = module_common_new(); + module->private = private; + module->run = &run; + module->destroy = &destroy; + module->content = &content; + module->description = &description; + + return module; +} + +static struct module * +from_conf(struct yml_node const *node, struct conf_inherit inherited) +{ + struct yml_node const *content = yml_get_value(node, "content"); + return niri_workspaces_new(conf_to_particle(content, inherited)); +} + +static bool +verify_conf(keychain_t *chain, const struct yml_node *node) +{ + static struct attr_info const attrs[] = { + MODULE_COMMON_ATTRS, + }; + return conf_verify_dict(chain, node, attrs); +} + +const struct module_iface module_niri_workspaces_iface = { + .verify_conf = &verify_conf, + .from_conf = &from_conf, +}; + +#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) +extern const struct module_iface iface __attribute__((weak, alias("module_niri_workspaces_iface"))); +#endif diff --git a/plugin.c b/plugin.c index 8e75389..b1e268b 100644 --- a/plugin.c +++ b/plugin.c @@ -84,6 +84,12 @@ EXTERN_MODULE(script); #if defined(HAVE_PLUGIN_sway_xkb) EXTERN_MODULE(sway_xkb); #endif +#if defined(HAVE_PLUGIN_niri_language) +EXTERN_MODULE(niri_language); +#endif +#if defined(HAVE_PLUGIN_niri_workspaces) +EXTERN_MODULE(niri_workspaces); +#endif #if defined(HAVE_PLUGIN_xkb) EXTERN_MODULE(xkb); #endif @@ -214,6 +220,12 @@ static void __attribute__((constructor)) init(void) #if defined(HAVE_PLUGIN_sway_xkb) REGISTER_CORE_MODULE(sway-xkb, sway_xkb); #endif +#if defined(HAVE_PLUGIN_niri_language) + REGISTER_CORE_MODULE(niri-language, niri_language); +#endif +#if defined(HAVE_PLUGIN_niri_workspaces) + REGISTER_CORE_MODULE(niri-workspaces, niri_workspaces); +#endif #if defined(HAVE_PLUGIN_xkb) REGISTER_CORE_MODULE(xkb, xkb); #endif From e1f7c0292fadfcd369cf26da82f4c3504cc7f628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 1 Jan 2025 13:52:52 +0100 Subject: [PATCH 44/64] changelog: fix ref for #405 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aec47c4..74181de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ [392]: https://codeberg.org/dnkl/yambar/issues/392 [400]: https://codeberg.org/dnkl/yambar/pulls/400 [428]: https://codeberg.org/dnkl/yambar/pulls/428 -[405]: https://codeberg.org/dnkl/yambar/issues/405 +[405]: https://codeberg.org/dnkl/yambar/pulls/405 ### Changed From fc24ea225d0cb66c1e6949cecd584c21ad1af9d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 1 Jan 2025 13:57:32 +0100 Subject: [PATCH 45/64] changelog: fix ref (again) for #405 - the issue number is #404 --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74181de..802c82b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,14 +27,15 @@ * Added "string like" `~~` operator to Map particle. Allows glob-style matching on strings using `*` and `?` characters ([#400][400]). * Added "single" mode flag to the `mpd` module ([#428][428]). -* niri: add a new module for niri-workspaces and niri-language ([#405][405]). +* niri: add a new module for niri-workspaces and niri-language + ([#404][404]). [96]: https://codeberg.org/dnkl/yambar/issues/96 [380]: https://codeberg.org/dnkl/yambar/issues/380 [392]: https://codeberg.org/dnkl/yambar/issues/392 [400]: https://codeberg.org/dnkl/yambar/pulls/400 [428]: https://codeberg.org/dnkl/yambar/pulls/428 -[405]: https://codeberg.org/dnkl/yambar/pulls/405 +[404]: https://codeberg.org/dnkl/yambar/issues/404 ### Changed From 21f374d2eb6d7db915bda4ca3551169454b19531 Mon Sep 17 00:00:00 2001 From: Ralph Torres Date: Mon, 24 Feb 2025 04:59:37 +0000 Subject: [PATCH 46/64] module/pipewire: add spacing config --- doc/yambar-modules-pipewire.5.scd | 12 ++++++++++++ modules/pipewire.c | 22 +++++++++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/doc/yambar-modules-pipewire.5.scd b/doc/yambar-modules-pipewire.5.scd index ba79aaf..8010449 100644 --- a/doc/yambar-modules-pipewire.5.scd +++ b/doc/yambar-modules-pipewire.5.scd @@ -43,6 +43,18 @@ pipewire - Monitors pipewire for volume, mute/unmute, device change :[ *Type* :[ *Req* :< *Description* +| left-spacing +: int +: no +: Space, in pixels, in the left side of each rendered volume +| right-spacing +: int +: no +: Space, in pixels, on the right side of each rendered volume +| spacing +: int +: no +: Short-hand for setting both _left-spacing_ and _right-spacing_ | content : particle : yes diff --git a/modules/pipewire.c b/modules/pipewire.c index a2fdcae..1ff3642 100644 --- a/modules/pipewire.c +++ b/modules/pipewire.c @@ -60,6 +60,8 @@ struct private { struct particle *label; struct data *data; + int left_spacing; + int right_spacing; /* pipewire related */ struct output_informations sink_informations; @@ -918,7 +920,7 @@ content(struct module *module) mtx_unlock(&module->lock); - return dynlist_exposable_new(exposables, exposables_length, 0, 0); + return dynlist_exposable_new(exposables, exposables_length, private->left_spacing, private->right_spacing); } static int @@ -965,11 +967,13 @@ run(struct module *module) } static struct module * -pipewire_new(struct particle *label) +pipewire_new(struct particle *label, int left_spacing, int right_spacing) { struct private *private = calloc(1, sizeof(struct private)); assert(private != NULL); private->label = label; + private->left_spacing = left_spacing; + private->right_spacing = right_spacing; struct module *module = module_common_new(); module->private = private; @@ -987,13 +991,25 @@ static struct module * from_conf(struct yml_node const *node, struct conf_inherit inherited) { struct yml_node const *content = yml_get_value(node, "content"); - return pipewire_new(conf_to_particle(content, inherited)); + struct yml_node const *spacing = yml_get_value(node, "spacing"); + struct yml_node const *left_spacing = yml_get_value(node, "left-spacing"); + struct yml_node const *right_spacing = yml_get_value(node, "right-spacing"); + + int left = spacing != NULL ? yml_value_as_int(spacing) : left_spacing != NULL ? yml_value_as_int(left_spacing) : 0; + int right = spacing != NULL ? yml_value_as_int(spacing) + : right_spacing != NULL ? yml_value_as_int(right_spacing) + : 0; + + return pipewire_new(conf_to_particle(content, inherited), left, right); } static bool verify_conf(keychain_t *keychain, struct yml_node const *node) { static struct attr_info const attrs[] = { + {"spacing", false, &conf_verify_unsigned}, + {"left-spacing", false, &conf_verify_unsigned}, + {"right-spacing", false, &conf_verify_unsigned}, MODULE_COMMON_ATTRS, }; return conf_verify_dict(keychain, node, attrs); From 2eb1cda73337544bb0ff5782e3d43edf39371b9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 5 Mar 2025 08:19:29 +0100 Subject: [PATCH 47/64] changelog: pipewire: spacing attributes --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 802c82b..20f637a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ * Added "single" mode flag to the `mpd` module ([#428][428]). * niri: add a new module for niri-workspaces and niri-language ([#404][404]). +* pipewire: added `spacing`, `left-spacing` and `right-spacing` + attributes. [96]: https://codeberg.org/dnkl/yambar/issues/96 [380]: https://codeberg.org/dnkl/yambar/issues/380 From a242d3d56975189136a98a6feec8fde6728430e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 5 Mar 2025 08:21:03 +0100 Subject: [PATCH 48/64] ci: sr.ht: skip codespell (this is done in woodpecker) --- .builds/alpine-x64.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.builds/alpine-x64.yml b/.builds/alpine-x64.yml index e4ad80c..1703a3d 100644 --- a/.builds/alpine-x64.yml +++ b/.builds/alpine-x64.yml @@ -37,10 +37,6 @@ sources: # to: tasks: - - codespell: | - pip install codespell - cd yambar - ~/.local/bin/codespell README.md CHANGELOG.md *.c *.h doc/*.scd - fcft: | cd yambar/subprojects git clone https://codeberg.org/dnkl/fcft.git From b486088f77b6f4813a89e38afde77573d1a804e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 5 Mar 2025 08:21:54 +0100 Subject: [PATCH 49/64] examples: codespell: re-using -> reusing --- examples/configurations/laptop.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/configurations/laptop.conf b/examples/configurations/laptop.conf index e57ebb3..1bdd16c 100644 --- a/examples/configurations/laptop.conf +++ b/examples/configurations/laptop.conf @@ -4,7 +4,7 @@ # For X11/i3, you'll want to replace calls to swaymsg with i3-msg, and # the sway-xkb module with the xkb module. -# fonts we'll be re-using here and there +# fonts we'll be reusing here and there awesome: &awesome Font Awesome 6 Free:style=solid:pixelsize=14 awesome_brands: &awesome_brands Font Awesome 6 Brands:pixelsize=16 From 5a515eae99116be895634f9f0a5e9498ae7bce35 Mon Sep 17 00:00:00 2001 From: Nicholas Sudsgaard Date: Tue, 4 Mar 2025 13:42:11 +0900 Subject: [PATCH 50/64] bar/wayland: Add unused attribute to count This variable is only used in LOG_DBG which expands to nothing by default. Adding the unused attribute prevents the compiler from throwing an error (-Wunused-but-set-variable) when building. --- bar/wayland.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bar/wayland.c b/bar/wayland.c index 3d8e4e0..86ab252 100644 --- a/bar/wayland.c +++ b/bar/wayland.c @@ -1223,7 +1223,7 @@ loop(struct bar *_bar, void (*expose)(const struct bar *bar), bool do_expose = false; /* Coalesce “refresh” commands */ - size_t count = 0; + __attribute__((unused)) size_t count = 0; while (true) { uint8_t command; ssize_t r = read(backend->pipe_fds[0], &command, sizeof(command)); From b5450c3918d8a0c2c9ab6384de32933e7b5a4534 Mon Sep 17 00:00:00 2001 From: Nicholas Sudsgaard Date: Tue, 4 Mar 2025 14:46:08 +0900 Subject: [PATCH 51/64] modules/i3: Add unused attribute to focused This variable is only used in assert() which expands to nothing in release builds. Adding the unused attribute prevents the compiler from throwing an error (-Wunused-but-set-variable) when building. --- modules/i3.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/i3.c b/modules/i3.c index 47f6d99..cbdafaf 100644 --- a/modules/i3.c +++ b/modules/i3.c @@ -594,7 +594,7 @@ handle_window_event(int sock, int type, const struct json_object *json, void *_m mtx_lock(&mod->lock); struct workspace *ws = NULL; - size_t focused = 0; + __attribute__((unused)) size_t focused = 0; tll_foreach(m->workspaces, it) { if (it->item.focused) { From c27de56beab0a4e6239894599138c931e4a8d4a6 Mon Sep 17 00:00:00 2001 From: haruInDisguise Date: Fri, 5 Jul 2024 23:22:20 +0200 Subject: [PATCH 52/64] Added 'MPRIS' module This commit adds the ability to display status information for MPRIS compatible music players. Closes #53 --- CHANGELOG.md | 2 + doc/meson.build | 3 + doc/yambar-modules-mpris.5.scd | 95 +++ meson.build | 10 +- meson_options.txt | 2 + modules/dbus.h | 13 + modules/meson.build | 7 + modules/mpris.c | 1129 ++++++++++++++++++++++++++++++++ plugin.c | 6 + 9 files changed, 1266 insertions(+), 1 deletion(-) create mode 100644 doc/yambar-modules-mpris.5.scd create mode 100644 modules/dbus.h create mode 100644 modules/mpris.c diff --git a/CHANGELOG.md b/CHANGELOG.md index 20f637a..0e0e772 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ ([#404][404]). * pipewire: added `spacing`, `left-spacing` and `right-spacing` attributes. +* mpris: new module ([#53][53]). [96]: https://codeberg.org/dnkl/yambar/issues/96 [380]: https://codeberg.org/dnkl/yambar/issues/380 @@ -38,6 +39,7 @@ [400]: https://codeberg.org/dnkl/yambar/pulls/400 [428]: https://codeberg.org/dnkl/yambar/pulls/428 [404]: https://codeberg.org/dnkl/yambar/issues/404 +[53]: https://codeberg.org/dnkl/yambar/issues/53 ### Changed diff --git a/doc/meson.build b/doc/meson.build index 90a83ec..e801bf1 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -34,6 +34,9 @@ endif if plugin_mpd_enabled plugin_pages += ['yambar-modules-mpd.5.scd'] endif +if plugin_mpris_enabled + plugin_pages += ['yambar-modules-mpris.5.scd'] +endif if plugin_i3_enabled plugin_pages += ['yambar-modules-i3.5.scd'] plugin_pages += ['yambar-modules-sway.5.scd'] diff --git a/doc/yambar-modules-mpris.5.scd b/doc/yambar-modules-mpris.5.scd new file mode 100644 index 0000000..a52caba --- /dev/null +++ b/doc/yambar-modules-mpris.5.scd @@ -0,0 +1,95 @@ +yambar-modules-mpris(5) + +# NAME +mpris - This module provides MPRIS status such as currently playing artist/album/song + +# TAGS + +[[ *Name* +:[ *Type* +:< *Description* +| state +: string +: One of *offline*, *stopped*, *paused* or *playing* +| shuffle +: bool +: True if the *shuffle* flag is set +| repeat +: string +: One of *none*, *track* or *paylist* +| volume +: range +: Volume in percentage +| album +: string +: Currently playing album +| artist +: string +: Artist of currently playing song +| title +: string +: Title of currently playing song +| file +: string +: Filename or URL of currently playing song +| pos +: string +: *%M:%S*-formatted string describing the song's current position + (also see _elapsed_) +| end +: string +: *%M:%S*-formatted string describing the song's total length (also + see _duration_) +| elapsed +: realtime +: Position in currently playing song, in milliseconds. Can be used + with a _progress-bar_ particle. + +# CONFIGURATION + +[[ *Name* +:[ *Type* +:[ *Req* +:< *Description* +| identities +: list of string +: yes +: A list of MPRIS client identities + +# EXAMPLES + +``` +bar: + center: + - mpris: + identities: + - "spotify" + - "firefox" + content: + map: + conditions: + state != offline && state != stopped: + - string: {text: "{artist}", max: 30 } + - string: {text: "-" } + - string: {text: "{title}", max: 30 } +``` + +# NOTE + +The 'identity' refers a part of your clients DBus bus name. +You can obtain a list of available bus names using: + +``` +Systemd: > busctl --user --list +Playerctl: > playerctl --list-all +Libdbus: > dbus-send --session --print-reply --type=method_call --dest='org.freedesktop.DBus' /org org.freedesktop.DBus.ListNames ... | grep 'org.mpris.MediaPlayer2' +``` + +The identity refers to the part after 'org.mpris.MediaPlayer2'. +For example, firefox may use the bus name +'org.mpris.MediaPlayer2.firefox.instance_1_7' and its identity would be +'firefox' + +# SEE ALSO + +*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) diff --git a/meson.build b/meson.build index 81af577..1a6d211 100644 --- a/meson.build +++ b/meson.build @@ -4,13 +4,15 @@ project('yambar', 'c', meson_version: '>=0.59.0', default_options: ['c_std=c18', 'warning_level=1', - 'werror=true', 'b_ndebug=if-release']) is_debug_build = get_option('buildtype').startswith('debug') plugs_as_libs = get_option('core-plugins-as-shared-libraries') cc = meson.get_compiler('c') +cc_flags = [ + '-Werror=all' + ] if cc.has_function('memfd_create', args: ['-D_GNU_SOURCE=200809L'], @@ -75,7 +77,12 @@ backend_wayland = wayland_client.found() and wayland_cursor.found() tllist = dependency('tllist', version: '>=1.0.1', fallback: 'tllist') fcft = dependency('fcft', version: ['>=3.0.0', '<4.0.0'], fallback: 'fcft') +# DBus dependency. Used by 'modules/mpris' +sdbus_library = dependency('libsystemd', 'libelogind', 'basu', required: get_option('plugin-mpris')) +sdbus = declare_dependency(compile_args: ['-DHAVE_' + sdbus_library.name().to_upper()], dependencies:[sdbus_library]) + add_project_arguments( + cc_flags + ['-D_GNU_SOURCE'] + (is_debug_build ? ['-D_DEBUG'] : []) + (backend_x11 ? ['-DENABLE_X11'] : []) + @@ -180,6 +187,7 @@ summary( 'Foreign toplevel (window tracking for Wayland)': plugin_foreign_toplevel_enabled, 'Memory monitoring': plugin_mem_enabled, 'Music Player Daemon (MPD)': plugin_mpd_enabled, + 'Media Player Remote Interface Specificaion (MPRIS)': plugin_mpris_enabled, 'i3+Sway': plugin_i3_enabled, 'Label': plugin_label_enabled, 'Network monitoring': plugin_network_enabled, diff --git a/meson_options.txt b/meson_options.txt index 9fd0dd5..23a8e11 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -26,6 +26,8 @@ option('plugin-mem', type: 'feature', value: 'auto', description: 'Memory monitoring support') option('plugin-mpd', type: 'feature', value: 'auto', description: 'Music Player Daemon (MPD) support') +option('plugin-mpris', type: 'feature', value: 'enabled', + description: 'Media Player Remote Interface Specificaion (MPRIS) support') option('plugin-i3', type: 'feature', value: 'auto', description: 'i3+Sway support') option('plugin-label', type: 'feature', value: 'auto', diff --git a/modules/dbus.h b/modules/dbus.h new file mode 100644 index 0000000..6517cef --- /dev/null +++ b/modules/dbus.h @@ -0,0 +1,13 @@ +#pragma once + +// This header provides an generic interface for different versions of +// systemd-sdbus. + +#if defined(HAVE_LIBSYSTEMD) +#include +#elif defined(HAVE_LIBELOGIND) +#include +#elif defined(HAVE_BASU) +#include +#endif + diff --git a/modules/meson.build b/modules/meson.build index b54e9d7..0e65812 100644 --- a/modules/meson.build +++ b/modules/meson.build @@ -22,6 +22,9 @@ plugin_mem_enabled = get_option('plugin-mem').allowed() mpd = dependency('libmpdclient', required: get_option('plugin-mpd')) plugin_mpd_enabled = mpd.found() +mpris = sdbus +plugin_mpris_enabled = sdbus.found() + json_i3 = dependency('json-c', required: get_option('plugin-i3')) plugin_i3_enabled = json_i3.found() @@ -95,6 +98,10 @@ if plugin_mpd_enabled mod_data += {'mpd': [[], [mpd]]} endif +if plugin_mpris_enabled + mod_data += {'mpris': [[], [mpris]]} +endif + if plugin_i3_enabled mod_data += {'i3': [['i3-common.c', 'i3-common.h'], [dynlist, json_i3]]} endif diff --git a/modules/mpris.c b/modules/mpris.c new file mode 100644 index 0000000..4cf99ef --- /dev/null +++ b/modules/mpris.c @@ -0,0 +1,1129 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "dbus.h" + +#define LOG_MODULE "mpris" +#define LOG_ENABLE_DBG 1 +#include "../bar/bar.h" +#include "../config-verify.h" +#include "../config.h" +#include "../log.h" +#include "../plugin.h" + +#define QUERY_TIMEOUT 100 + +#define PATH "/org/mpris/MediaPlayer2" +#define BUS_NAME "org.mpris.MediaPlayer2" +#define SERVICE "org.mpris.MediaPlayer2" +#define INTERFACE_ROOT "org.mpris.MediaPlayer2" +#define INTERFACE_PLAYER INTERFACE_ROOT ".Player" + +#define DBUS_PATH "/org/freedesktop/DBus" +#define DBUS_BUS_NAME "org.freedesktop.DBus" +#define DBUS_SERVICE "org.freedesktop.DBus" +#define DBUS_INTERFACE_MONITORING "org.freedesktop.DBus.Monitoring" +#define DBUS_INTERFACE_PROPERTIES "org.freedesktop.DBus.Properties" + +enum status { + STATUS_OFFLINE, + STATUS_PLAYING, + STATUS_PAUSED, + STATUS_STOPPED, + STATUS_ERROR, +}; + +typedef tll(char *) string_array; + +struct metadata { + uint64_t length_us; + char *trackid; + string_array artists; + char *album; + char *title; +}; + +struct property { + struct metadata metadata; + char *playback_status; + char *loop_status; + uint64_t position_us; + double rate; + double volume; + bool shuffle; +}; + +struct client { + bool has_seeked_support; + enum status status; + const char *bus_name; + const char *bus_unique_name; + + struct property property; + + /* The unix timestamp of the last position change (ie. + * seeking, pausing) */ + struct timespec seeked_when; +}; + +struct context { + sd_bus *monitor_connection; + sd_bus_message *update_message; + + /* FIXME: There is no nice way to pass the desired identities to + * the event handler for validation. */ + char **identities_ref; + size_t identities_count; + + tll(struct client *) clients; + struct client *current_client; + + bool has_update; +}; + +struct private +{ + thrd_t refresh_thread_id; + int refresh_abort_fd; + + size_t identities_count; + const char **identities; + struct particle *label; + + struct context context; +}; + +#if 0 +static void +debug_print_argument_type(sd_bus_message *message) +{ + char type; + const char *content; + sd_bus_message_peek_type(message, &type, &content); + LOG_DBG("peek_message_type: %c -> %s", type, content); +} +#endif + +#if defined(LOG_ENABLE_DBG) +#define dump_type(message) \ + { \ + char type; \ + const char *content; \ + sd_bus_message_peek_type(message, &type, &content); \ + LOG_DBG("argument layout: %c -> %s", type, content); \ + } +#endif + +static void +metadata_clear(struct metadata *metadata) +{ + tll_free_and_free(metadata->artists, free); + + if (metadata->album != NULL) { + free(metadata->album); + } + + if (metadata->title != NULL) { + free(metadata->title); + } + + if (metadata->trackid != NULL) { + free(metadata->trackid); + } +} + +static void +property_clear(struct property *property) +{ + metadata_clear(&property->metadata); + memset(property, 0, sizeof(*property)); +} + +static void +client_free(struct client *client) +{ + property_clear(&client->property); + + free((void *)client->bus_name); + free((void *)client->bus_unique_name); + free(client); +} + +static void +clients_free_by_unique_name(struct context *context, const char *unique_name) +{ + tll_foreach(context->clients, it) + { + struct client *client = it->item; + if (strcmp(client->bus_unique_name, unique_name) == 0) { + LOG_DBG("client_remove: Removing client %s", client->bus_name); + client_free(client); + tll_remove(context->clients, it); + } + } +} + +static void +client_free_all(struct context *context) +{ + tll_free_and_free(context->clients, client_free); +} + +static void +client_add(struct context *context, const char *name, const char *unique_name) +{ + struct client *client = malloc(sizeof(*client)); + (*client) = (struct client){ + .bus_name = strdup(name), + .bus_unique_name = strdup(unique_name), + }; + + tll_push_back(context->clients, client); + LOG_DBG("client_add: name='%s' unique_name='%s'", name, unique_name); +} + +static struct client * +client_lookup_by_unique_name(struct context *context, const char *unique_name) +{ + tll_foreach(context->clients, it) + { + struct client *client = it->item; + if (strcmp(client->bus_unique_name, unique_name) == 0) { + LOG_DBG("client_lookup: name: %s", client->bus_name); + return client; + } + } + + return NULL; +} + +static void +client_change_unique_name(struct client *client, const char *new_name) +{ + if (client->bus_unique_name != NULL) { + free((void *)client->bus_unique_name); + } + + client->bus_unique_name = strdup(new_name); +} + +static bool +verify_bus_name(char **idents, const size_t ident_count, const char *name) +{ + for (size_t i = 0; i < ident_count; i++) { + const char *ident = idents[i]; + + if (strlen(name) < strlen(BUS_NAME ".") + strlen(ident)) { + continue; + } + + const char *cmp = name + strlen(BUS_NAME "."); + if (strncmp(cmp, ident, strlen(ident)) != 0) { + continue; + } + + return true; + } + + return false; +} + +static bool +read_string_array(sd_bus_message *message, string_array *list) +{ + int status = 0; + + /* message argument layout: 'vas' */ + /* enter variant */ + status = sd_bus_message_enter_container(message, SD_BUS_TYPE_VARIANT, "as"); + if (status <= 0) { + LOG_DBG("unexpected layout: errno=%d (%s)", status, strerror(-status)); + return false; + } + + /* enter array */ + status = sd_bus_message_enter_container(message, SD_BUS_TYPE_ARRAY, "s"); + assert(status >= 0); + + const char *string; + while ((status = sd_bus_message_read_basic(message, SD_BUS_TYPE_STRING, &string)) > 0) { + if (strlen(string) > 0) { + tll_push_back(*list, strdup(string)); + } + } + + if (status < 0) { + LOG_ERR("metadata: unexpected layout: errno=%d (%s)", status, strerror(-status)); + return false; + } + + /* close array */ + sd_bus_message_exit_container(message); + /* close variant */ + sd_bus_message_exit_container(message); + + return true; +} + +static bool +metadata_parse_property(const char *property_name, sd_bus_message *message, struct metadata *buffer) +{ + int status = 0; + const char *string = NULL; + + char argument_type = 0; + const char *argument_layout = NULL; + sd_bus_message_peek_type(message, &argument_type, &argument_layout); + assert(argument_type == SD_BUS_TYPE_VARIANT); + assert(argument_layout != NULL && strlen(argument_layout) > 0); + + if (strcmp(property_name, "mpris:trackid") == 0) { + if (argument_layout[0] != SD_BUS_TYPE_STRING && argument_layout[0] != SD_BUS_TYPE_OBJECT_PATH) + goto unexpected_type; + + status = sd_bus_message_read(message, "v", argument_layout, &string); + if (status > 0) + buffer->trackid = strdup(string); + + /* FIXME: "strcmp matches both 'album' as well as 'albumArtist'" */ + } else if (strcmp(property_name, "xesam:album") == 0) { + status = sd_bus_message_read(message, "v", argument_layout, &string); + if (status > 0 && strlen(string) > 0) + buffer->album = strdup(string); + + } else if (strcmp(property_name, "xesam:artist") == 0) { + status = read_string_array(message, &buffer->artists); + + } else if (strcmp(property_name, "xesam:title") == 0) { + status = sd_bus_message_read(message, "v", "s", &string); + if(status > 0) + buffer->title = strdup(string); + + } else if (strcmp(property_name, "mpris:length") == 0) { + /* MPRIS requires 'mpris:length' to be an i64 (the wording is a bit ambiguous), however some client + * use a u64 instead. */ + if (argument_layout[0] != SD_BUS_TYPE_INT64 && argument_layout[0] != SD_BUS_TYPE_UINT64) + goto unexpected_type; + + status = sd_bus_message_read(message, "v", argument_layout, &buffer->length_us); + + } else { + LOG_DBG("metadata: ignoring property: %s", property_name); + sd_bus_message_skip(message, NULL); + return true; + } + + if (status < 0) { + LOG_ERR("metadata: failed to read property: arg_type='%c' arg_layout='%s' errno=%d (%s)", argument_type, + argument_layout, status, strerror(-status)); + return false; + } + + return true; +unexpected_type: + LOG_ERR("metadata: unexpected type for '%s'", property_name); + return false; +} + +static bool +metadata_parse_array(struct metadata *metadata, sd_bus_message *message) +{ + int status = sd_bus_message_enter_container(message, SD_BUS_TYPE_VARIANT, "a{sv}"); + if (status <= 0) { + LOG_DBG("unexpected layout: errno=%d (%s)", status, strerror(-status)); + return false; + } + status = sd_bus_message_enter_container(message, SD_BUS_TYPE_ARRAY, "{sv}"); + assert(status >= 0); + + while ((status = sd_bus_message_enter_container(message, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) { + const char *property_name = NULL; + status = sd_bus_message_read_basic(message, SD_BUS_TYPE_STRING, &property_name); + if (status <= 0) { + LOG_DBG("unexpected layout: errno=%d (%s)", status, strerror(-status)); + return false; + } + + status = metadata_parse_property(property_name, message, metadata); + if (status == 0) { + return false; + } + + status = sd_bus_message_exit_container(message); + assert(status >= 0); + } + + /* close array */ + sd_bus_message_exit_container(message); + /* close variant */ + sd_bus_message_exit_container(message); + + return status >= 0; +} + +static bool +property_parse(struct property *prop, const char *property_name, sd_bus_message *message) +{ + /* This function is called in two different ways: + * 1. update_status(): The property is passed directly + * 2. update_status_from_message(): The property is passed wrapped + * inside a variant and has to be unpacked */ + const char *argument_layout = NULL; + char argument_type = 0; + int status = sd_bus_message_peek_type(message, &argument_type, &argument_layout); + + assert(status > 0); + assert(argument_type == SD_BUS_TYPE_VARIANT); + assert(argument_layout != NULL && strlen(argument_layout) > 0); + + const char *string; + if (strcmp(property_name, "PlaybackStatus") == 0) { + status = sd_bus_message_read(message, "v", "s", &string); + if (status) + prop->playback_status = strdup(string); + + } else if (strcmp(property_name, "LoopStatus") == 0) { + status = sd_bus_message_read(message, "v", "s", &string); + if (status) + prop->loop_status = strdup(string); + + } else if (strcmp(property_name, "Position") == 0) { + /* MPRIS requires 'Position' to be a i64, however some client + * use a u64 instead. */ + if (argument_layout[0] != SD_BUS_TYPE_INT64 && argument_layout[0] != SD_BUS_TYPE_UINT64) { + LOG_ERR("property: unexpected type for '%s'", property_name); + return false; + } + status = sd_bus_message_read(message, "v", argument_layout[0], &prop->position_us); + + } else if (strcmp(property_name, "Shuffle") == 0) { + status = sd_bus_message_read(message, "v", "b", &prop->shuffle); + + } else if (strcmp(property_name, "Metadata") == 0) { + metadata_clear(&prop->metadata); + status = metadata_parse_array(&prop->metadata, message); + + } else { + LOG_DBG("property: ignoring property: %s", property_name); + sd_bus_message_skip(message, NULL); + return true; + } + + return status > 0; +} + +/* ------------- */ + +static void +format_usec_timestamp(unsigned usec, char *s, size_t sz) +{ + uint32_t secs = usec / 1000 / 1000; + uint32_t hours = secs / (60 * 60); + uint32_t minutes = secs % (60 * 60) / 60; + secs %= 60; + + if (hours > 0) + snprintf(s, sz, "%02u:%02u:%02u", hours, minutes, secs); + else + snprintf(s, sz, "%02u:%02u", minutes, secs); +} + +static void +destroy(struct module *mod) +{ + struct private *m = mod->private; + struct context *context = &m->context; + + client_free_all(context); + + sd_bus_close(context->monitor_connection); + + module_default_destroy(mod); + m->label->destroy(m->label); + free(m); +} + +static void +context_event_handle_name_owner_changed(sd_bus_message *message, struct context *context) +{ + /* NameOwnerChanged (STRING name, STRING old_owner, STRING new_owner) */ + /* This signal indicates that the owner of a name has changed, ie. + * it was acquired, lost or changed */ + + const char *bus_name = NULL, *old_owner = NULL, *new_owner = NULL; + int status = sd_bus_message_read(message, "sss", &bus_name, &old_owner, &new_owner); + assert(status > 0); + +#if 1 + LOG_DBG("event_handler: 'NameOwnerChanged': bus_name: '%s' old_owner: '%s' new_ower: '%s'", bus_name, old_owner, + new_owner); +#endif + + if (strlen(new_owner) == 0 && strlen(old_owner) > 0) { + /* Target bus has been lost */ + struct client *client = client_lookup_by_unique_name(context, old_owner); + + if (client == NULL) + return; + + LOG_DBG("event_handler: 'NameOwnerChanged': Target bus disappeared: %s", client->bus_name); + clients_free_by_unique_name(context, client->bus_unique_name); + + if (context->current_client == client) + context->current_client = NULL; + + return; + } else if (strlen(old_owner) == 0 && strlen(new_owner) > 0) { + /* New unique name registered. Not used */ + return; + } + + /* Name changed */ + assert(new_owner != NULL && strlen(new_owner) > 0); + assert(old_owner != NULL && strlen(old_owner) > 0); + + struct client *client = client_lookup_by_unique_name(context, old_owner); + LOG_DBG("'NameOwnerChanged': Name changed from '%s' to '%s' for client '%s'", old_owner, new_owner, + client->bus_name); + client_change_unique_name(client, new_owner); +} + +static void +context_event_handle_name_acquired(sd_bus_message *message, struct context *context) +{ + /* Spy on applications that requested an "MPRIS style" bus name */ + + /* NameAcquired (STRING name) */ + /* " This signal is sent to a specific application when it gains ownership of a name. " */ + const char *name = NULL; + int status = sd_bus_message_read_basic(message, SD_BUS_TYPE_STRING, &name); + assert(status > 0); + + /*LOG_DBG("event_handler: 'NameAcquired': name: '%s'", name);*/ + + if (strncmp(name, BUS_NAME, strlen(BUS_NAME)) != 0) { + return; + } + + if (verify_bus_name(context->identities_ref, context->identities_count, name)) { + const char *unique_name = sd_bus_message_get_destination(message); + LOG_DBG("'NameAcquired': Acquired new client: %s unique: %s", name, unique_name); + client_add(context, name, unique_name); + } +} + +static int +context_event_handler(sd_bus_message *message, void *userdata, sd_bus_error *ret_error) +{ + struct context *context = userdata; + + const char *member = sd_bus_message_get_member(message); + const char *sender = sd_bus_message_get_sender(message); + const char *path_name = sd_bus_message_get_path(message); + +#if 0 + const char *destination = sd_bus_message_get_destination(message); + const char *self = sd_bus_message_get_sender(message); + LOG_DBG("member: '%s' self: '%s' dest: '%s' sender: '%s'", member, self, + destination, sender); +#endif + + if (tll_length(context->clients) == 0 && strcmp(member, "NameAcquired") != 0) { + return 1; + } + + /* TODO: Allow multiple clients to connect */ + if (strcmp(path_name, DBUS_PATH) == 0 && strcmp(member, "NameAcquired") == 0) { + context_event_handle_name_acquired(message, context); + } + + if (strcmp(path_name, DBUS_PATH) == 0 && strcmp(member, "NameOwnerChanged") == 0) { + context_event_handle_name_owner_changed(message, context); + return 1; + } + + /* Copy the 'PropertiesChanged/Seeked' message, so it can be parsed + * later on */ + if (strcmp(path_name, PATH) == 0 && (strcmp(member, "PropertiesChanged") == 0 || strcmp(member, "Seeked") == 0)) { + struct client *client = client_lookup_by_unique_name(context, sender); + if (client == NULL) + return 1; + + LOG_DBG("event_handler: '%s': name: '%s' unique_name: '%s'", member, client->bus_name, client->bus_unique_name); + + context->has_update = true; + context->current_client = client; + context->update_message = sd_bus_message_ref(message); + + assert(context->update_message != NULL); + } + + return 1; +} + +static bool +context_process_events(struct context *context, uint32_t timeout_ms) +{ + int status = -1; + + status = sd_bus_wait(context->monitor_connection, timeout_ms); + if (status < 0) { + if (status == -ENOTCONN) + LOG_DBG("Disconnect signal has been processed"); + else + LOG_ERR("Failed to query monitor connection: errno=%d", status); + + return false; + } + + /* 'sd_bus_process' processes one 'action' per call. + * This includes: connection, authentication, message processing */ + status = sd_bus_process(context->monitor_connection, NULL); + + if (status < 0) { + if (status == -ENOTCONN) + LOG_DBG("Disconnect signal has been processed"); + else + LOG_ERR("Failed to query monitor connection: errno=%d", status); + + return false; + } + + return true; +} + +static bool +context_new(struct private *m, struct context *context) +{ + int status = true; + sd_bus *connection; + if ((status = sd_bus_default_user(&connection)) < 0) { + LOG_ERR("Failed to connect to the desktop bus. errno: %d", status); + return -1; + } + + /* Turn this connection into a monitor */ + sd_bus_message *message; + status = sd_bus_message_new_method_call(connection, &message, DBUS_SERVICE, DBUS_PATH, DBUS_INTERFACE_MONITORING, + "BecomeMonitor"); + + const char *matching_rules[] = { + /* Listen for... */ + /* ... new MPRIS clients */ + "type='signal',interface='org.freedesktop.DBus',member='NameAcquired',path='/org/freedesktop/" + "DBus',arg0namespace='org.mpris.MediaPlayer2'", + /* ... name changes */ + "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged'," + "path='/org/freedesktop/DBus'", + /* ... property changes */ + "type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged', " + "path='/org/mpris/MediaPlayer2'", + /* ... changes in playback position */ + "type='signal',interface='org.mpris.MediaPlayer2.Player',member='Seeked', " + "path='/org/mpris/MediaPlayer2'", + }; + + /* TODO: Error handling */ + /* "BecomeMonitor" ('asu'): (Rules: String[], Flags: UINT32) */ + /* https://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-become-monitor */ + status = sd_bus_message_open_container(message, SD_BUS_TYPE_ARRAY, "s"); + for (uint32_t i = 0; i < sizeof(matching_rules) / sizeof(matching_rules[0]); i++) { + status = sd_bus_message_append(message, "s", matching_rules[i]); + } + status = sd_bus_message_close_container(message); + status = sd_bus_message_append_basic(message, SD_BUS_TYPE_UINT32, &(uint32_t){0}); + + sd_bus_message *reply = NULL; + sd_bus_error error = {}; + status = sd_bus_call(NULL, message, QUERY_TIMEOUT, &error, &reply); + + if (status < 0 && sd_bus_error_is_set(&error)) { + LOG_ERR("context_new: got error response with error: %s: %s (%d)", error.name, error.message, + sd_bus_error_get_errno(&error)); + return false; + } + + sd_bus_message_unref(message); + sd_bus_message_unref(reply); + + (*context) = (struct context){ + .monitor_connection = connection, + .identities_ref = (char **)m->identities, + .identities_count = m->identities_count, + .clients = tll_init(), + }; + + sd_bus_add_filter(connection, NULL, context_event_handler, context); + + return status >= 0; +} + +static uint64_t +timespec_diff_us(const struct timespec *a, const struct timespec *b) +{ + uint64_t nsecs_a = a->tv_sec * 1000000000 + a->tv_nsec; + uint64_t nsecs_b = b->tv_sec * 1000000000 + b->tv_nsec; + + assert(nsecs_a >= nsecs_b); + uint64_t nsec_diff = nsecs_a - nsecs_b; + return nsec_diff / 1000; +} + +static bool +update_status_from_message(struct module *mod, sd_bus_message *message) +{ + struct private *m = mod->private; + mtx_lock(&mod->lock); + + struct client *client = m->context.current_client; + int status = 1; + + /* Player.Seeked (UINT64 position)*/ + if (strcmp(sd_bus_message_get_member(message), "Seeked") == 0) { + client->has_seeked_support = true; + + status = sd_bus_message_read_basic(message, SD_BUS_TYPE_INT64, &client->property.position_us); + if (status <= 0) + return status; + + clock_gettime(CLOCK_MONOTONIC, &client->seeked_when); + return true; + } + + /* Properties.PropertiesChanged (STRING interface_name, + * ARRAY of DICT_ENTRY changed_properties, + * ARRAY invalidated_properties); */ + assert(strcmp(sd_bus_message_get_member(message), "PropertiesChanged") == 0); + assert(strcmp(sd_bus_message_get_signature(message, 1), "sa{sv}as") == 0); + + /* argument: 'interface_name' layout: 's' */ + const char *interface_name = NULL; + sd_bus_message_read_basic(message, SD_BUS_TYPE_STRING, &interface_name); + + if (strcmp(interface_name, INTERFACE_PLAYER) != 0) { + LOG_DBG("Ignoring interface: %s", interface_name); + mtx_unlock(&mod->lock); + return true; + } + + /* argument: 'changed_properties' layout: 'a{sv}' */ + + /* Make sure we reset the position on metadata change unless the + * update contains its own position value */ + bool should_reset_position = true; + bool has_entries = sd_bus_message_enter_container(message, SD_BUS_TYPE_ARRAY, "{sv}"); + + while ((has_entries = sd_bus_message_enter_container(message, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) { + const char *property_name = NULL; + int status = sd_bus_message_read_basic(message, SD_BUS_TYPE_STRING, &property_name); + assert(status > 0); + + if (!property_parse(&client->property, property_name, message)) { + return false; + } + + status = sd_bus_message_exit_container(message); + assert(status >= 0); + + if (strcmp(property_name, "PlaybackStatus") == 0) { + if (strcmp(client->property.playback_status, "Stopped") == 0) { + client->status = STATUS_STOPPED; + + } else if (strcmp(client->property.playback_status, "Playing") == 0) { + clock_gettime(CLOCK_MONOTONIC, &client->seeked_when); + client->status = STATUS_PLAYING; + + } else if (strcmp(client->property.playback_status, "Paused") == 0) { + /* Update our position to include the elapsed time */ + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + client->status = STATUS_PAUSED; + client->property.position_us += timespec_diff_us(&now, &client->seeked_when); + } + } + + /* Make sure to reset the position upon metadata/song changes */ + if (should_reset_position && strcmp(property_name, "Metadata") == 0) { + client->property.position_us = 0; + + if (client->property.playback_status == NULL) { + client->property.playback_status = "Paused"; + client->status = STATUS_PAUSED; + } + } + + if (strcmp(property_name, "Position") == 0) { + should_reset_position = false; + } + } + + status = sd_bus_message_exit_container(message); + assert(status > 0); + + mtx_unlock(&mod->lock); + return true; +} + +static struct exposable * +content_empty(struct module *mod) +{ + struct private *m = mod->private; + + struct tag_set tags = { + .tags = (struct tag *[]){ + tag_new_bool(mod, "has-seeked-support", "false"), + tag_new_string(mod, "state", "offline"), + tag_new_bool(mod, "shuffle", "false"), + tag_new_string(mod, "loop", "None"), + tag_new_int_range(mod, "volume", 0, 0, 100), + tag_new_string(mod, "album", ""), + tag_new_string(mod, "artist", ""), + tag_new_string(mod, "title", ""), + tag_new_string(mod, "pos", ""), + tag_new_string(mod, "end", ""), + tag_new_int_realtime( + mod, "elapsed", 0, 0, 0, TAG_REALTIME_NONE), + }, + .count = 10, + }; + + mtx_unlock(&mod->lock); + + struct exposable *exposable = m->label->instantiate(m->label, &tags); + + tag_set_destroy(&tags); + return exposable; +} + +static struct exposable * +content(struct module *mod) +{ + const struct private *m = mod->private; + const struct client *client = m->context.current_client; + + if (client == NULL) { + return content_empty(mod); + } + + const struct metadata *metadata = &client->property.metadata; + const struct property *property = &client->property; + + /* Calculate the current playback position */ + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + uint64_t elapsed_us = client->property.position_us; + uint64_t length_us = metadata->length_us; + + if (client->has_seeked_support && client->status == STATUS_PLAYING) { + elapsed_us += timespec_diff_us(&now, &client->seeked_when); + if (elapsed_us > length_us) { + LOG_DBG("dynamic update of elapsed overflowed: " + "elapsed=%" PRIu64 ", duration=%" PRIu64, + elapsed_us, length_us); + elapsed_us = length_us; + } + } + + /* Some clients can report misleading or incomplete updates to the + * playback position, potentially causing the position to exceed + * the length */ + if (elapsed_us > length_us) + elapsed_us = length_us = 0; + + char tag_pos_value[16] = {0}, tag_end_value[16] = {0}; + if (length_us > 0) { + format_usec_timestamp(elapsed_us, tag_pos_value, sizeof(tag_pos_value)); + format_usec_timestamp(length_us, tag_end_value, sizeof(tag_end_value)); + } + + char *tag_state_value = NULL; + switch (client->status) { + case STATUS_ERROR: + tag_state_value = "error"; + break; + case STATUS_OFFLINE: + tag_state_value = "offline"; + break; + case STATUS_PLAYING: + tag_state_value = "playing"; + break; + case STATUS_PAUSED: + tag_state_value = "paused"; + break; + case STATUS_STOPPED: + tag_state_value = "stopped"; + break; + } + + const char *tag_loop_value = (property->loop_status == NULL) ? "" : property->loop_status; + const char *tag_album_value = (metadata->album == NULL) ? "" : metadata->album; + const char *tag_artists_value = (tll_length(metadata->artists) <= 0) ? "" : tll_front(metadata->artists); + const char *tag_title_value = (metadata->title == NULL) ? "" : metadata->title; + const uint32_t tag_volume_value = (property->volume >= 0.995) ? 100 : 100 * property->volume; + const bool tag_shuffle_value = property->shuffle; + const enum tag_realtime_unit realtime_unit + = (client->has_seeked_support && client->status == STATUS_PLAYING) ? TAG_REALTIME_MSECS : TAG_REALTIME_NONE; + + struct tag_set tags = { + .tags = (struct tag *[]){ + tag_new_bool(mod, "has_seeked_support", client->has_seeked_support), + tag_new_bool(mod, "shuffle", tag_shuffle_value), + tag_new_int_range(mod, "volume", tag_volume_value, 0, 100), + tag_new_string(mod, "album", tag_album_value), + tag_new_string(mod, "artist", tag_artists_value), + tag_new_string(mod, "end", tag_end_value), + tag_new_string(mod, "loop", tag_loop_value), + tag_new_string(mod, "pos", tag_pos_value), + tag_new_string(mod, "state", tag_state_value), + tag_new_string(mod, "title", tag_title_value), + tag_new_int_realtime( + mod, "elapsed", elapsed_us, 0, length_us, realtime_unit), + }, + .count = 11, + }; + + mtx_unlock(&mod->lock); + + struct exposable *exposable = m->label->instantiate(m->label, &tags); + + tag_set_destroy(&tags); + return exposable; +} + +struct refresh_context { + struct module *mod; + int abort_fd; + long milli_seconds; +}; + +static int +refresh_in_thread(void *arg) +{ + struct refresh_context *ctx = arg; + struct module *mod = ctx->mod; + + /* Extract data from context so that we can free it */ + int abort_fd = ctx->abort_fd; + long milli_seconds = ctx->milli_seconds; + free(ctx); + + /*LOG_DBG("going to sleep for %ldms", milli_seconds);*/ + + /* Wait for timeout, or abort signal */ + struct pollfd fds[] = {{.fd = abort_fd, .events = POLLIN}}; + int r = poll(fds, 1, milli_seconds); + + if (r < 0) { + LOG_ERRNO("failed to poll() in refresh thread"); + return 1; + } + + /* Aborted? */ + if (r == 1) { + assert(fds[0].revents & POLLIN); + /*LOG_DBG("refresh thread aborted");*/ + return 0; + } + + LOG_DBG("timed refresh"); + mod->bar->refresh(mod->bar); + + return 0; +} + +static bool +refresh_in(struct module *mod, long milli_seconds) +{ + struct private *m = mod->private; + + /* Abort currently running refresh thread */ + if (m->refresh_thread_id != 0) { + /*LOG_DBG("aborting current refresh thread");*/ + + /* Signal abort to thread */ + assert(m->refresh_abort_fd != -1); + if (write(m->refresh_abort_fd, &(uint64_t){1}, sizeof(uint64_t)) != sizeof(uint64_t)) { + LOG_ERRNO("failed to signal abort to refresher thread"); + return false; + } + + /* Wait for it to finish */ + int res; + thrd_join(m->refresh_thread_id, &res); + + /* Close and cleanup */ + close(m->refresh_abort_fd); + m->refresh_abort_fd = -1; + m->refresh_thread_id = 0; + } + + /* Create a new eventfd, to be able to signal abort to the thread */ + int abort_fd = eventfd(0, EFD_CLOEXEC); + if (abort_fd == -1) { + LOG_ERRNO("failed to create eventfd"); + return false; + } + + /* Thread context */ + struct refresh_context *ctx = malloc(sizeof(*ctx)); + ctx->mod = mod; + ctx->abort_fd = m->refresh_abort_fd = abort_fd; + ctx->milli_seconds = milli_seconds; + + /* Create thread */ + int r = thrd_create(&m->refresh_thread_id, &refresh_in_thread, ctx); + + if (r != thrd_success) { + LOG_ERR("failed to create refresh thread"); + close(m->refresh_abort_fd); + m->refresh_abort_fd = -1; + m->refresh_thread_id = 0; + free(ctx); + } + + /* Detach - we don't want to have to thrd_join() it */ + // thrd_detach(tid); + return r == 0; +} + +static int +run(struct module *mod) +{ + const struct bar *bar = mod->bar; + struct private *m = mod->private; + + if (!context_new(m, &m->context)) { + LOG_ERR("Failed to setup context"); + return -1; + } + + struct context *context = &m->context; + + int ret = 0; + bool aborted = false; + while (ret == 0 && !aborted) { + const uint32_t timeout_ms = 50; + struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}}; + + /* Check for abort event */ + if (poll(fds, 1, timeout_ms) < 0) { + if (errno == EINTR) + continue; + + LOG_ERRNO("failed to poll"); + break; + } + + if (fds[0].revents & POLLIN) { + aborted = true; + break; + } + + if (!context_process_events(context, QUERY_TIMEOUT)) { + aborted = true; + break; + } + + /* Process dynamic updates, received through the contexts + * monitor connection. The 'upate_message' attribute is set + * inside the contexts event callback, if there are any + * updates to be processed. */ + if (context->has_update) { + assert(context->current_client != NULL); + assert(context->update_message != NULL); + + context->has_update = false; + aborted = !update_status_from_message(mod, context->update_message); + context->update_message = sd_bus_message_unref(context->update_message); + } + + bar->refresh(bar); + } + + LOG_DBG("exiting"); + + return ret; +} + +static const char * +description(const struct module *mod) +{ + return "mpris"; +} + +static struct module * +mpris_new(const char **ident, size_t ident_count, struct particle *label) +{ + struct private *priv = calloc(1, sizeof(*priv)); + priv->label = label; + priv->identities = malloc(sizeof(*ident) * ident_count); + priv->identities_count = ident_count; + + for (size_t i = 0; i < ident_count; i++) { + priv->identities[i] = strdup(ident[i]); + } + + struct module *mod = module_common_new(); + mod->private = priv; + mod->run = &run; + mod->destroy = &destroy; + mod->content = &content; + mod->description = &description; + mod->refresh_in = &refresh_in; + return mod; +} + +static struct module * +from_conf(const struct yml_node *node, struct conf_inherit inherited) +{ + const struct yml_node *ident_list = yml_get_value(node, "identities"); + const struct yml_node *c = yml_get_value(node, "content"); + + const size_t ident_count = yml_list_length(ident_list); + const char *ident[ident_count]; + size_t i = 0; + for (struct yml_list_iter iter = yml_list_iter(ident_list); iter.node != NULL; yml_list_next(&iter), i++) { + ident[i] = yml_value_as_string(iter.node); + } + + return mpris_new(ident, ident_count, conf_to_particle(c, inherited)); +} + +static bool +conf_verify_indentities(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) +{ + static const struct attr_info attrs[] = { + {"identities", true, &conf_verify_indentities}, + MODULE_COMMON_ATTRS, + }; + + return conf_verify_dict(chain, node, attrs); +} + +const struct module_iface module_mpris_iface = { + .verify_conf = &verify_conf, + .from_conf = &from_conf, +}; + +#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) +extern const struct module_iface iface __attribute__((weak, alias("module_mpris_iface"))); +#endif diff --git a/plugin.c b/plugin.c index b1e268b..2ed0a4f 100644 --- a/plugin.c +++ b/plugin.c @@ -57,6 +57,9 @@ EXTERN_MODULE(mem); #if defined(HAVE_PLUGIN_mpd) EXTERN_MODULE(mpd); #endif +#if defined(HAVE_PLUGIN_mpris) +EXTERN_MODULE(mpris); +#endif #if defined(HAVE_PLUGIN_i3) EXTERN_MODULE(i3); #endif @@ -193,6 +196,9 @@ static void __attribute__((constructor)) init(void) #if defined(HAVE_PLUGIN_mpd) REGISTER_CORE_MODULE(mpd, mpd); #endif +#if defined(HAVE_PLUGIN_mpris) + REGISTER_CORE_MODULE(mpris, mpris); +#endif #if defined(HAVE_PLUGIN_i3) REGISTER_CORE_MODULE(i3, i3); #endif From e68ed8d8434e016041be944416def5c8d6e2d0ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 5 Mar 2025 08:41:04 +0100 Subject: [PATCH 53/64] module: mpris: mark debug-only variables with attribute unused Fixes release builds --- modules/mpris.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/mpris.c b/modules/mpris.c index 4cf99ef..538256d 100644 --- a/modules/mpris.c +++ b/modules/mpris.c @@ -463,7 +463,8 @@ context_event_handle_name_owner_changed(sd_bus_message *message, struct context * it was acquired, lost or changed */ const char *bus_name = NULL, *old_owner = NULL, *new_owner = NULL; - int status = sd_bus_message_read(message, "sss", &bus_name, &old_owner, &new_owner); + int status __attribute__((unused)) + = sd_bus_message_read(message, "sss", &bus_name, &old_owner, &new_owner); assert(status > 0); #if 1 @@ -508,7 +509,8 @@ context_event_handle_name_acquired(sd_bus_message *message, struct context *cont /* NameAcquired (STRING name) */ /* " This signal is sent to a specific application when it gains ownership of a name. " */ const char *name = NULL; - int status = sd_bus_message_read_basic(message, SD_BUS_TYPE_STRING, &name); + int status __attribute__((unused)) + = sd_bus_message_read_basic(message, SD_BUS_TYPE_STRING, &name); assert(status > 0); /*LOG_DBG("event_handler: 'NameAcquired': name: '%s'", name);*/ @@ -727,7 +729,8 @@ update_status_from_message(struct module *mod, sd_bus_message *message) while ((has_entries = sd_bus_message_enter_container(message, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) { const char *property_name = NULL; - int status = sd_bus_message_read_basic(message, SD_BUS_TYPE_STRING, &property_name); + int status __attribute__((unused)) + = sd_bus_message_read_basic(message, SD_BUS_TYPE_STRING, &property_name); assert(status > 0); if (!property_parse(&client->property, property_name, message)) { From e423776000982ad5646dfae6f753226f743c36e3 Mon Sep 17 00:00:00 2001 From: haruInDisguise Date: Sun, 9 Mar 2025 22:28:59 +0100 Subject: [PATCH 54/64] module_mpris: Added 'query-timeout' option This enables us to configure the communication timeout with the dbus daemon. --- doc/yambar-modules-mpris.5.scd | 18 ++++++++++++------ modules/mpris.c | 23 ++++++++++++++++------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/doc/yambar-modules-mpris.5.scd b/doc/yambar-modules-mpris.5.scd index a52caba..510dc8f 100644 --- a/doc/yambar-modules-mpris.5.scd +++ b/doc/yambar-modules-mpris.5.scd @@ -55,6 +55,11 @@ mpris - This module provides MPRIS status such as currently playing artist/album : list of string : yes : A list of MPRIS client identities +| query_timeout +: int +: no +: Dbus/MPRIS client connection timeout in ms. Try setting/incrementing + this value if the module reports a timeout error. Defaults to 500. # EXAMPLES @@ -77,18 +82,19 @@ bar: # NOTE The 'identity' refers a part of your clients DBus bus name. -You can obtain a list of available bus names using: +You can obtain a list of active client names using: ``` Systemd: > busctl --user --list Playerctl: > playerctl --list-all -Libdbus: > dbus-send --session --print-reply --type=method_call --dest='org.freedesktop.DBus' /org org.freedesktop.DBus.ListNames ... | grep 'org.mpris.MediaPlayer2' +Libdbus: > dbus-send --session --print-reply --type=method_call \ + --dest='org.freedesktop.DBus' /org org.freedesktop.DBus.ListNames ``` -The identity refers to the part after 'org.mpris.MediaPlayer2'. -For example, firefox may use the bus name -'org.mpris.MediaPlayer2.firefox.instance_1_7' and its identity would be -'firefox' +MPRIS client bus names start with 'org.mpris.MediaPlayer2.'. +For example, firefox may use the bus name: +'org.mpris.MediaPlayer2.firefox.instance_1_7' which +gives us the identity 'firefox' # SEE ALSO diff --git a/modules/mpris.c b/modules/mpris.c index 538256d..c610176 100644 --- a/modules/mpris.c +++ b/modules/mpris.c @@ -14,16 +14,17 @@ #include #include "dbus.h" +#include "yml.h" #define LOG_MODULE "mpris" -#define LOG_ENABLE_DBG 1 +#define LOG_ENABLE_DBG 0 #include "../bar/bar.h" #include "../config-verify.h" #include "../config.h" #include "../log.h" #include "../plugin.h" -#define QUERY_TIMEOUT 100 +#define DEFAULT_QUERY_TIMEOUT 500 #define PATH "/org/mpris/MediaPlayer2" #define BUS_NAME "org.mpris.MediaPlayer2" @@ -99,6 +100,7 @@ struct private int refresh_abort_fd; size_t identities_count; + size_t timeout_ms; const char **identities; struct particle *label; @@ -649,10 +651,10 @@ context_new(struct private *m, struct context *context) sd_bus_message *reply = NULL; sd_bus_error error = {}; - status = sd_bus_call(NULL, message, QUERY_TIMEOUT, &error, &reply); + status = sd_bus_call(NULL, message, m->timeout_ms, &error, &reply); if (status < 0 && sd_bus_error_is_set(&error)) { - LOG_ERR("context_new: got error response with error: %s: %s (%d)", error.name, error.message, + LOG_ERR("context_new: got error response: %s: %s (%d)", error.name, error.message, sd_bus_error_get_errno(&error)); return false; } @@ -1035,7 +1037,7 @@ run(struct module *mod) break; } - if (!context_process_events(context, QUERY_TIMEOUT)) { + if (!context_process_events(context, m->timeout_ms)) { aborted = true; break; } @@ -1068,10 +1070,11 @@ description(const struct module *mod) } static struct module * -mpris_new(const char **ident, size_t ident_count, struct particle *label) +mpris_new(const char **ident, size_t ident_count, size_t timeout, struct particle *label) { struct private *priv = calloc(1, sizeof(*priv)); priv->label = label; + priv->timeout_ms = timeout; priv->identities = malloc(sizeof(*ident) * ident_count); priv->identities_count = ident_count; @@ -1093,8 +1096,13 @@ static struct module * from_conf(const struct yml_node *node, struct conf_inherit inherited) { const struct yml_node *ident_list = yml_get_value(node, "identities"); + const struct yml_node *query_timeout = yml_get_value(node, "query_timeout"); const struct yml_node *c = yml_get_value(node, "content"); + size_t timeout_ms = DEFAULT_QUERY_TIMEOUT * 1000; + if(query_timeout != NULL) + timeout_ms = yml_value_as_int(query_timeout) * 1000; + const size_t ident_count = yml_list_length(ident_list); const char *ident[ident_count]; size_t i = 0; @@ -1102,7 +1110,7 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited) ident[i] = yml_value_as_string(iter.node); } - return mpris_new(ident, ident_count, conf_to_particle(c, inherited)); + return mpris_new(ident, ident_count, timeout_ms, conf_to_particle(c, inherited)); } static bool @@ -1116,6 +1124,7 @@ verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"identities", true, &conf_verify_indentities}, + {"query_timeout", false, &conf_verify_unsigned}, MODULE_COMMON_ATTRS, }; From dcf936fd9b04cb77c9125dd5be5856872ec3b405 Mon Sep 17 00:00:00 2001 From: haruInDisguise Date: Mon, 10 Mar 2025 00:32:14 +0100 Subject: [PATCH 55/64] module_mpris: Fixed 'use after free' --- modules/mpris.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/mpris.c b/modules/mpris.c index c610176..b29e883 100644 --- a/modules/mpris.c +++ b/modules/mpris.c @@ -135,14 +135,17 @@ metadata_clear(struct metadata *metadata) if (metadata->album != NULL) { free(metadata->album); + metadata->album = NULL; } if (metadata->title != NULL) { free(metadata->title); + metadata->title = NULL; } if (metadata->trackid != NULL) { free(metadata->trackid); + metadata->trackid = NULL; } } @@ -296,7 +299,7 @@ metadata_parse_property(const char *property_name, sd_bus_message *message, stru goto unexpected_type; status = sd_bus_message_read(message, "v", argument_layout, &string); - if (status > 0) + if (status > 0 && strlen(string) > 0) buffer->trackid = strdup(string); /* FIXME: "strcmp matches both 'album' as well as 'albumArtist'" */ @@ -310,7 +313,7 @@ metadata_parse_property(const char *property_name, sd_bus_message *message, stru } else if (strcmp(property_name, "xesam:title") == 0) { status = sd_bus_message_read(message, "v", "s", &string); - if(status > 0) + if(status > 0 && strlen(string) > 0) buffer->title = strdup(string); } else if (strcmp(property_name, "mpris:length") == 0) { @@ -393,12 +396,12 @@ property_parse(struct property *prop, const char *property_name, sd_bus_message const char *string; if (strcmp(property_name, "PlaybackStatus") == 0) { status = sd_bus_message_read(message, "v", "s", &string); - if (status) + if (status && strlen(string) > 0) prop->playback_status = strdup(string); } else if (strcmp(property_name, "LoopStatus") == 0) { status = sd_bus_message_read(message, "v", "s", &string); - if (status) + if (status && strlen(string) > 0) prop->loop_status = strdup(string); } else if (strcmp(property_name, "Position") == 0) { From 7e76d53c0a4ffe51c873a3aaf3f2faea845c0045 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sun, 9 Mar 2025 21:43:47 +0100 Subject: [PATCH 56/64] modules/mpris: fix dependency search Move dependency discovery to be with other module dependency searches. Also only define the `sdbus` dependency if the module is enabled. The `mpris` module was always being compiled because the `sdbus` dependency always existed. Instead, check for the external dependency's status and create the `sdbus` bridge dependency only when necessary. --- meson.build | 4 ---- modules/meson.build | 8 +++++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/meson.build b/meson.build index 1a6d211..e1f695a 100644 --- a/meson.build +++ b/meson.build @@ -77,10 +77,6 @@ backend_wayland = wayland_client.found() and wayland_cursor.found() tllist = dependency('tllist', version: '>=1.0.1', fallback: 'tllist') fcft = dependency('fcft', version: ['>=3.0.0', '<4.0.0'], fallback: 'fcft') -# DBus dependency. Used by 'modules/mpris' -sdbus_library = dependency('libsystemd', 'libelogind', 'basu', required: get_option('plugin-mpris')) -sdbus = declare_dependency(compile_args: ['-DHAVE_' + sdbus_library.name().to_upper()], dependencies:[sdbus_library]) - add_project_arguments( cc_flags + ['-D_GNU_SOURCE'] + diff --git a/modules/meson.build b/modules/meson.build index 0e65812..f6d53d8 100644 --- a/modules/meson.build +++ b/modules/meson.build @@ -22,8 +22,9 @@ plugin_mem_enabled = get_option('plugin-mem').allowed() mpd = dependency('libmpdclient', required: get_option('plugin-mpd')) plugin_mpd_enabled = mpd.found() -mpris = sdbus -plugin_mpris_enabled = sdbus.found() +# DBus dependency. Used by 'mpris' +sdbus_library = dependency('libsystemd', 'libelogind', 'basu', required: get_option('plugin-mpris')) +plugin_mpris_enabled = sdbus_library.found() json_i3 = dependency('json-c', required: get_option('plugin-i3')) plugin_i3_enabled = json_i3.found() @@ -99,7 +100,8 @@ if plugin_mpd_enabled endif if plugin_mpris_enabled - mod_data += {'mpris': [[], [mpris]]} + sdbus = declare_dependency(compile_args: ['-DHAVE_' + sdbus_library.name().to_upper()], dependencies:[sdbus_library]) + mod_data += {'mpris': [[], [sdbus]]} endif if plugin_i3_enabled From 56467d0ba3eeddb3f57f817d720e70c5ac8b4afc Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sun, 9 Mar 2025 21:45:20 +0100 Subject: [PATCH 57/64] meson: require 0.60.0 This introduces dependencies with multiple names which is used for the `sdbus_library` dependency of the `mpris` module. --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index e1f695a..67d3096 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project('yambar', 'c', version: '1.11.0', license: 'MIT', - meson_version: '>=0.59.0', + meson_version: '>=0.60.0', default_options: ['c_std=c18', 'warning_level=1', 'b_ndebug=if-release']) From 0bcde5c453e7765deec020321e08484b0781b980 Mon Sep 17 00:00:00 2001 From: haruInDisguise Date: Mon, 10 Mar 2025 01:36:26 +0100 Subject: [PATCH 58/64] module_mpris: Fixed memory leak The identity list now uses tlllist and is deallocated properly --- modules/mpris.c | 43 +++++++++++++++++-------------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/modules/mpris.c b/modules/mpris.c index b29e883..74d2e95 100644 --- a/modules/mpris.c +++ b/modules/mpris.c @@ -83,11 +83,6 @@ struct context { sd_bus *monitor_connection; sd_bus_message *update_message; - /* FIXME: There is no nice way to pass the desired identities to - * the event handler for validation. */ - char **identities_ref; - size_t identities_count; - tll(struct client *) clients; struct client *current_client; @@ -99,14 +94,14 @@ struct private thrd_t refresh_thread_id; int refresh_abort_fd; - size_t identities_count; size_t timeout_ms; - const char **identities; struct particle *label; struct context context; }; +static tll(const char*) identity_list; + #if 0 static void debug_print_argument_type(sd_bus_message *message) @@ -225,10 +220,10 @@ client_change_unique_name(struct client *client, const char *new_name) } static bool -verify_bus_name(char **idents, const size_t ident_count, const char *name) +verify_bus_name(const char *name) { - for (size_t i = 0; i < ident_count; i++) { - const char *ident = idents[i]; + tll_foreach(identity_list, it) { + const char *ident = it->item; if (strlen(name) < strlen(BUS_NAME ".") + strlen(ident)) { continue; @@ -455,9 +450,13 @@ destroy(struct module *mod) sd_bus_close(context->monitor_connection); - module_default_destroy(mod); + tll_foreach(identity_list, it) { + free((char*)it->item); + } m->label->destroy(m->label); free(m); + + module_default_destroy(mod); } static void @@ -524,7 +523,7 @@ context_event_handle_name_acquired(sd_bus_message *message, struct context *cont return; } - if (verify_bus_name(context->identities_ref, context->identities_count, name)) { + if (verify_bus_name(name)) { const char *unique_name = sd_bus_message_get_destination(message); LOG_DBG("'NameAcquired': Acquired new client: %s unique: %s", name, unique_name); client_add(context, name, unique_name); @@ -667,8 +666,6 @@ context_new(struct private *m, struct context *context) (*context) = (struct context){ .monitor_connection = connection, - .identities_ref = (char **)m->identities, - .identities_count = m->identities_count, .clients = tll_init(), }; @@ -1061,8 +1058,8 @@ run(struct module *mod) bar->refresh(bar); } - LOG_DBG("exiting"); + LOG_DBG("exiting"); return ret; } @@ -1073,17 +1070,11 @@ description(const struct module *mod) } static struct module * -mpris_new(const char **ident, size_t ident_count, size_t timeout, struct particle *label) +mpris_new(size_t timeout, struct particle *label) { struct private *priv = calloc(1, sizeof(*priv)); priv->label = label; priv->timeout_ms = timeout; - priv->identities = malloc(sizeof(*ident) * ident_count); - priv->identities_count = ident_count; - - for (size_t i = 0; i < ident_count; i++) { - priv->identities[i] = strdup(ident[i]); - } struct module *mod = module_common_new(); mod->private = priv; @@ -1106,14 +1097,14 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited) if(query_timeout != NULL) timeout_ms = yml_value_as_int(query_timeout) * 1000; - const size_t ident_count = yml_list_length(ident_list); - const char *ident[ident_count]; + // FIXME: This is a redundant copy size_t i = 0; for (struct yml_list_iter iter = yml_list_iter(ident_list); iter.node != NULL; yml_list_next(&iter), i++) { - ident[i] = yml_value_as_string(iter.node); + const char *string = strdup(yml_value_as_string(iter.node)); + tll_push_back(identity_list, string); } - return mpris_new(ident, ident_count, timeout_ms, conf_to_particle(c, inherited)); + return mpris_new(timeout_ms, conf_to_particle(c, inherited)); } static bool From 87c74d54b792468ffe6b0348f214548a48204fd0 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 8 Feb 2025 20:17:13 +0100 Subject: [PATCH 59/64] meson: guard the `full-conf-good` test by the plugins it expects --- test/meson.build | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/meson.build b/test/meson.build index e8c7138..a3635a2 100644 --- a/test/meson.build +++ b/test/meson.build @@ -7,4 +7,9 @@ test('no-config', yambar, args: ['-C', '-c', 'xyz'], should_fail: true) test('config-isnt-file', yambar, args: ['-C', '-c', '.'], should_fail: true) test('config-no-bar', yambar, args: ['-C', '-c', join_paths(pwd, 'no-bar.yml')], should_fail: true) -test('full-conf-good', yambar, args: ['-C', '-c', join_paths(pwd, 'full-conf-good.yml')]) +if plugin_alsa_enabled and plugin_backlight_enabled and \ + plugin_battery_enabled and plugin_clock_enabled and \ + plugin_i3_enabled and plugin_mpd_enabled and plugin_network_enabled \ + and plugin_removables_enabled + test('full-conf-good', yambar, args: ['-C', '-c', join_paths(pwd, 'full-conf-good.yml')]) +endif From 6a97b364a01b0bd4af54fa0c0e434acfc2ef704f Mon Sep 17 00:00:00 2001 From: haruInDisguise Date: Mon, 10 Mar 2025 11:13:17 +0100 Subject: [PATCH 60/64] module_mpris: Fixed inconsistent string validation checks This addresses changes requested by @mathstuf --- modules/mpris.c | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/modules/mpris.c b/modules/mpris.c index 74d2e95..31b8fd2 100644 --- a/modules/mpris.c +++ b/modules/mpris.c @@ -24,8 +24,9 @@ #include "../log.h" #include "../plugin.h" -#define DEFAULT_QUERY_TIMEOUT 500 +#define is_empty_string(str) ((str) == NULL || (str)[0] == '\0') +#define DEFAULT_QUERY_TIMEOUT 500 #define PATH "/org/mpris/MediaPlayer2" #define BUS_NAME "org.mpris.MediaPlayer2" #define SERVICE "org.mpris.MediaPlayer2" @@ -259,7 +260,7 @@ read_string_array(sd_bus_message *message, string_array *list) const char *string; while ((status = sd_bus_message_read_basic(message, SD_BUS_TYPE_STRING, &string)) > 0) { - if (strlen(string) > 0) { + if (!is_empty_string(string)) { tll_push_back(*list, strdup(string)); } } @@ -287,20 +288,20 @@ metadata_parse_property(const char *property_name, sd_bus_message *message, stru const char *argument_layout = NULL; sd_bus_message_peek_type(message, &argument_type, &argument_layout); assert(argument_type == SD_BUS_TYPE_VARIANT); - assert(argument_layout != NULL && strlen(argument_layout) > 0); + assert(!is_empty_string(argument_layout)); if (strcmp(property_name, "mpris:trackid") == 0) { if (argument_layout[0] != SD_BUS_TYPE_STRING && argument_layout[0] != SD_BUS_TYPE_OBJECT_PATH) goto unexpected_type; status = sd_bus_message_read(message, "v", argument_layout, &string); - if (status > 0 && strlen(string) > 0) + if (status > 0 && !is_empty_string(string)) buffer->trackid = strdup(string); /* FIXME: "strcmp matches both 'album' as well as 'albumArtist'" */ } else if (strcmp(property_name, "xesam:album") == 0) { status = sd_bus_message_read(message, "v", argument_layout, &string); - if (status > 0 && strlen(string) > 0) + if (status > 0 && !is_empty_string(string)) buffer->album = strdup(string); } else if (strcmp(property_name, "xesam:artist") == 0) { @@ -308,7 +309,7 @@ metadata_parse_property(const char *property_name, sd_bus_message *message, stru } else if (strcmp(property_name, "xesam:title") == 0) { status = sd_bus_message_read(message, "v", "s", &string); - if(status > 0 && strlen(string) > 0) + if(status > 0 && !is_empty_string(string)) buffer->title = strdup(string); } else if (strcmp(property_name, "mpris:length") == 0) { @@ -386,17 +387,17 @@ property_parse(struct property *prop, const char *property_name, sd_bus_message assert(status > 0); assert(argument_type == SD_BUS_TYPE_VARIANT); - assert(argument_layout != NULL && strlen(argument_layout) > 0); + assert(!is_empty_string(argument_layout)); const char *string; if (strcmp(property_name, "PlaybackStatus") == 0) { status = sd_bus_message_read(message, "v", "s", &string); - if (status && strlen(string) > 0) + if (status && !is_empty_string(string)) prop->playback_status = strdup(string); } else if (strcmp(property_name, "LoopStatus") == 0) { status = sd_bus_message_read(message, "v", "s", &string); - if (status && strlen(string) > 0) + if (status && !is_empty_string(string)) prop->loop_status = strdup(string); } else if (strcmp(property_name, "Position") == 0) { @@ -476,7 +477,7 @@ context_event_handle_name_owner_changed(sd_bus_message *message, struct context new_owner); #endif - if (strlen(new_owner) == 0 && strlen(old_owner) > 0) { + if (is_empty_string(new_owner) && !is_empty_string(old_owner)) { /* Target bus has been lost */ struct client *client = client_lookup_by_unique_name(context, old_owner); @@ -490,14 +491,14 @@ context_event_handle_name_owner_changed(sd_bus_message *message, struct context context->current_client = NULL; return; - } else if (strlen(old_owner) == 0 && strlen(new_owner) > 0) { + } else if (is_empty_string(old_owner) && !is_empty_string(new_owner)) { /* New unique name registered. Not used */ return; } /* Name changed */ - assert(new_owner != NULL && strlen(new_owner) > 0); - assert(old_owner != NULL && strlen(old_owner) > 0); + assert(!is_empty_string(new_owner)); + assert(!is_empty_string(old_owner)); struct client *client = client_lookup_by_unique_name(context, old_owner); LOG_DBG("'NameOwnerChanged': Name changed from '%s' to '%s' for client '%s'", old_owner, new_owner, From dcbb0f88ae9c3f4a9b429c0c69e183551c365559 Mon Sep 17 00:00:00 2001 From: haruInDisguise Date: Mon, 10 Mar 2025 11:34:29 +0100 Subject: [PATCH 61/64] module_mpris: Cleanup Fixed inconsistent variable naming/debug logging --- modules/mpris.c | 78 ++++++++++++++++++++----------------------------- 1 file changed, 31 insertions(+), 47 deletions(-) diff --git a/modules/mpris.c b/modules/mpris.c index 31b8fd2..79f1980 100644 --- a/modules/mpris.c +++ b/modules/mpris.c @@ -13,9 +13,6 @@ #include -#include "dbus.h" -#include "yml.h" - #define LOG_MODULE "mpris" #define LOG_ENABLE_DBG 0 #include "../bar/bar.h" @@ -24,14 +21,17 @@ #include "../log.h" #include "../plugin.h" +#include "dbus.h" +#include "yml.h" + #define is_empty_string(str) ((str) == NULL || (str)[0] == '\0') -#define DEFAULT_QUERY_TIMEOUT 500 -#define PATH "/org/mpris/MediaPlayer2" -#define BUS_NAME "org.mpris.MediaPlayer2" -#define SERVICE "org.mpris.MediaPlayer2" -#define INTERFACE_ROOT "org.mpris.MediaPlayer2" -#define INTERFACE_PLAYER INTERFACE_ROOT ".Player" +#define DEFAULT_QUERY_TIMEOUT_MS (500 * 1000) + +#define MPRIS_PATH "/org/mpris/MediaPlayer2" +#define MPRIS_BUS_NAME "org.mpris.MediaPlayer2" +#define MPRIS_SERVICE "org.mpris.MediaPlayer2" +#define MPRIS_INTERFACE_PLAYER "org.mpris.MediaPlayer2.Player" #define DBUS_PATH "/org/freedesktop/DBus" #define DBUS_BUS_NAME "org.freedesktop.DBus" @@ -101,29 +101,19 @@ struct private struct context context; }; -static tll(const char*) identity_list; +static tll(const char *) identity_list; -#if 0 -static void +#if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG +static void __attribute__((unused)) debug_print_argument_type(sd_bus_message *message) { char type; const char *content; sd_bus_message_peek_type(message, &type, &content); - LOG_DBG("peek_message_type: %c -> %s", type, content); + LOG_DBG("argument type: %c -> %s", type, content); } #endif -#if defined(LOG_ENABLE_DBG) -#define dump_type(message) \ - { \ - char type; \ - const char *content; \ - sd_bus_message_peek_type(message, &type, &content); \ - LOG_DBG("argument layout: %c -> %s", type, content); \ - } -#endif - static void metadata_clear(struct metadata *metadata) { @@ -223,14 +213,15 @@ client_change_unique_name(struct client *client, const char *new_name) static bool verify_bus_name(const char *name) { - tll_foreach(identity_list, it) { + tll_foreach(identity_list, it) + { const char *ident = it->item; - if (strlen(name) < strlen(BUS_NAME ".") + strlen(ident)) { + if (strlen(name) < strlen(MPRIS_BUS_NAME ".") + strlen(ident)) { continue; } - const char *cmp = name + strlen(BUS_NAME "."); + const char *cmp = name + strlen(MPRIS_BUS_NAME "."); if (strncmp(cmp, ident, strlen(ident)) != 0) { continue; } @@ -309,7 +300,7 @@ metadata_parse_property(const char *property_name, sd_bus_message *message, stru } else if (strcmp(property_name, "xesam:title") == 0) { status = sd_bus_message_read(message, "v", "s", &string); - if(status > 0 && !is_empty_string(string)) + if (status > 0 && !is_empty_string(string)) buffer->title = strdup(string); } else if (strcmp(property_name, "mpris:length") == 0) { @@ -451,9 +442,7 @@ destroy(struct module *mod) sd_bus_close(context->monitor_connection); - tll_foreach(identity_list, it) { - free((char*)it->item); - } + tll_foreach(identity_list, it) { free((char *)it->item); } m->label->destroy(m->label); free(m); @@ -468,14 +457,11 @@ context_event_handle_name_owner_changed(sd_bus_message *message, struct context * it was acquired, lost or changed */ const char *bus_name = NULL, *old_owner = NULL, *new_owner = NULL; - int status __attribute__((unused)) - = sd_bus_message_read(message, "sss", &bus_name, &old_owner, &new_owner); + int status __attribute__((unused)) = sd_bus_message_read(message, "sss", &bus_name, &old_owner, &new_owner); assert(status > 0); -#if 1 LOG_DBG("event_handler: 'NameOwnerChanged': bus_name: '%s' old_owner: '%s' new_ower: '%s'", bus_name, old_owner, new_owner); -#endif if (is_empty_string(new_owner) && !is_empty_string(old_owner)) { /* Target bus has been lost */ @@ -514,13 +500,12 @@ context_event_handle_name_acquired(sd_bus_message *message, struct context *cont /* NameAcquired (STRING name) */ /* " This signal is sent to a specific application when it gains ownership of a name. " */ const char *name = NULL; - int status __attribute__((unused)) - = sd_bus_message_read_basic(message, SD_BUS_TYPE_STRING, &name); + int status __attribute__((unused)) = sd_bus_message_read_basic(message, SD_BUS_TYPE_STRING, &name); assert(status > 0); - /*LOG_DBG("event_handler: 'NameAcquired': name: '%s'", name);*/ + LOG_DBG("event_handler: 'NameAcquired': name: '%s'", name); - if (strncmp(name, BUS_NAME, strlen(BUS_NAME)) != 0) { + if (strncmp(name, MPRIS_BUS_NAME, strlen(MPRIS_BUS_NAME)) != 0) { return; } @@ -563,7 +548,8 @@ context_event_handler(sd_bus_message *message, void *userdata, sd_bus_error *ret /* Copy the 'PropertiesChanged/Seeked' message, so it can be parsed * later on */ - if (strcmp(path_name, PATH) == 0 && (strcmp(member, "PropertiesChanged") == 0 || strcmp(member, "Seeked") == 0)) { + if (strcmp(path_name, MPRIS_PATH) == 0 + && (strcmp(member, "PropertiesChanged") == 0 || strcmp(member, "Seeked") == 0)) { struct client *client = client_lookup_by_unique_name(context, sender); if (client == NULL) return 1; @@ -717,7 +703,7 @@ update_status_from_message(struct module *mod, sd_bus_message *message) const char *interface_name = NULL; sd_bus_message_read_basic(message, SD_BUS_TYPE_STRING, &interface_name); - if (strcmp(interface_name, INTERFACE_PLAYER) != 0) { + if (strcmp(interface_name, MPRIS_INTERFACE_PLAYER) != 0) { LOG_DBG("Ignoring interface: %s", interface_name); mtx_unlock(&mod->lock); return true; @@ -732,8 +718,7 @@ update_status_from_message(struct module *mod, sd_bus_message *message) while ((has_entries = sd_bus_message_enter_container(message, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) { const char *property_name = NULL; - int status __attribute__((unused)) - = sd_bus_message_read_basic(message, SD_BUS_TYPE_STRING, &property_name); + int status __attribute__((unused)) = sd_bus_message_read_basic(message, SD_BUS_TYPE_STRING, &property_name); assert(status > 0); if (!property_parse(&client->property, property_name, message)) { @@ -1059,7 +1044,6 @@ run(struct module *mod) bar->refresh(bar); } - LOG_DBG("exiting"); return ret; } @@ -1071,11 +1055,11 @@ description(const struct module *mod) } static struct module * -mpris_new(size_t timeout, struct particle *label) +mpris_new(size_t timeout_ms, struct particle *label) { struct private *priv = calloc(1, sizeof(*priv)); priv->label = label; - priv->timeout_ms = timeout; + priv->timeout_ms = timeout_ms; struct module *mod = module_common_new(); mod->private = priv; @@ -1094,8 +1078,8 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited) const struct yml_node *query_timeout = yml_get_value(node, "query_timeout"); const struct yml_node *c = yml_get_value(node, "content"); - size_t timeout_ms = DEFAULT_QUERY_TIMEOUT * 1000; - if(query_timeout != NULL) + size_t timeout_ms = DEFAULT_QUERY_TIMEOUT_MS; + if (query_timeout != NULL) timeout_ms = yml_value_as_int(query_timeout) * 1000; // FIXME: This is a redundant copy From dfa0970b75297cf2047d6cf45af98cf093c07e6a Mon Sep 17 00:00:00 2001 From: haruInDisguise Date: Mon, 10 Mar 2025 12:03:17 +0100 Subject: [PATCH 62/64] module_mpris: Fixed multi threading issues regarding 'identity_list' This addresses changes requested by @dnkl --- modules/mpris.c | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/modules/mpris.c b/modules/mpris.c index 79f1980..6a03e8c 100644 --- a/modules/mpris.c +++ b/modules/mpris.c @@ -86,6 +86,7 @@ struct context { tll(struct client *) clients; struct client *current_client; + const string_array *identity_list_ref; bool has_update; }; @@ -96,13 +97,12 @@ struct private int refresh_abort_fd; size_t timeout_ms; + string_array identity_list; struct particle *label; struct context context; }; -static tll(const char *) identity_list; - #if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG static void __attribute__((unused)) debug_print_argument_type(sd_bus_message *message) @@ -166,12 +166,6 @@ clients_free_by_unique_name(struct context *context, const char *unique_name) } } -static void -client_free_all(struct context *context) -{ - tll_free_and_free(context->clients, client_free); -} - static void client_add(struct context *context, const char *name, const char *unique_name) { @@ -211,9 +205,9 @@ client_change_unique_name(struct client *client, const char *new_name) } static bool -verify_bus_name(const char *name) +verify_bus_name(const string_array *identity_list, const char *name) { - tll_foreach(identity_list, it) + tll_foreach(*identity_list, it) { const char *ident = it->item; @@ -438,11 +432,10 @@ destroy(struct module *mod) struct private *m = mod->private; struct context *context = &m->context; - client_free_all(context); - + tll_free_and_free(context->clients, client_free); sd_bus_close(context->monitor_connection); - tll_foreach(identity_list, it) { free((char *)it->item); } + tll_free_and_free(m->identity_list, free); m->label->destroy(m->label); free(m); @@ -509,7 +502,7 @@ context_event_handle_name_acquired(sd_bus_message *message, struct context *cont return; } - if (verify_bus_name(name)) { + if (verify_bus_name(context->identity_list_ref, name)) { const char *unique_name = sd_bus_message_get_destination(message); LOG_DBG("'NameAcquired': Acquired new client: %s unique: %s", name, unique_name); client_add(context, name, unique_name); @@ -652,6 +645,7 @@ context_new(struct private *m, struct context *context) sd_bus_message_unref(reply); (*context) = (struct context){ + .identity_list_ref = &m->identity_list, .monitor_connection = connection, .clients = tll_init(), }; @@ -1055,12 +1049,18 @@ description(const struct module *mod) } static struct module * -mpris_new(size_t timeout_ms, struct particle *label) +mpris_new(const struct yml_node *ident_list, size_t timeout_ms, struct particle *label) { struct private *priv = calloc(1, sizeof(*priv)); priv->label = label; priv->timeout_ms = timeout_ms; + size_t i = 0; + for (struct yml_list_iter iter = yml_list_iter(ident_list); iter.node != NULL; yml_list_next(&iter), i++) { + char *string = strdup(yml_value_as_string(iter.node)); + tll_push_back(priv->identity_list, string); + } + struct module *mod = module_common_new(); mod->private = priv; mod->run = &run; @@ -1082,14 +1082,8 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited) if (query_timeout != NULL) timeout_ms = yml_value_as_int(query_timeout) * 1000; - // FIXME: This is a redundant copy - size_t i = 0; - for (struct yml_list_iter iter = yml_list_iter(ident_list); iter.node != NULL; yml_list_next(&iter), i++) { - const char *string = strdup(yml_value_as_string(iter.node)); - tll_push_back(identity_list, string); - } - return mpris_new(timeout_ms, conf_to_particle(c, inherited)); + return mpris_new(ident_list, timeout_ms, conf_to_particle(c, inherited)); } static bool From ca0f565237656dd5bf3af8b5e2d429357545b614 Mon Sep 17 00:00:00 2001 From: haruInDisguise Date: Tue, 18 Mar 2025 12:05:02 +0100 Subject: [PATCH 63/64] module_mpris: Refactoring + Fixed mutex usage - Addressed inconsistens variable naming and removed redundant code. - Mutex locks are now used correctly. - The context struct now references the modules config, making config access less awkward --- modules/mpris.c | 60 +++++++++++++++++++------------------------------ 1 file changed, 23 insertions(+), 37 deletions(-) diff --git a/modules/mpris.c b/modules/mpris.c index 6a03e8c..5ddf6e0 100644 --- a/modules/mpris.c +++ b/modules/mpris.c @@ -81,12 +81,13 @@ struct client { }; struct context { + const struct private *mpd_config; + sd_bus *monitor_connection; sd_bus_message *update_message; tll(struct client *) clients; struct client *current_client; - const string_array *identity_list_ref; bool has_update; }; @@ -98,9 +99,8 @@ struct private size_t timeout_ms; string_array identity_list; - struct particle *label; - struct context context; + struct particle *label; }; #if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG @@ -135,31 +135,23 @@ metadata_clear(struct metadata *metadata) } } -static void -property_clear(struct property *property) -{ - metadata_clear(&property->metadata); - memset(property, 0, sizeof(*property)); -} - static void client_free(struct client *client) { - property_clear(&client->property); - free((void *)client->bus_name); free((void *)client->bus_unique_name); free(client); } static void -clients_free_by_unique_name(struct context *context, const char *unique_name) +client_free_by_unique_name(struct context *context, const char *unique_name) { tll_foreach(context->clients, it) { struct client *client = it->item; if (strcmp(client->bus_unique_name, unique_name) == 0) { LOG_DBG("client_remove: Removing client %s", client->bus_name); + client_free(client); tll_remove(context->clients, it); } @@ -430,10 +422,9 @@ static void destroy(struct module *mod) { struct private *m = mod->private; - struct context *context = &m->context; - tll_free_and_free(context->clients, client_free); - sd_bus_close(context->monitor_connection); + tll_free_and_free(m->context.clients, client_free); + sd_bus_close(m->context.monitor_connection); tll_free_and_free(m->identity_list, free); m->label->destroy(m->label); @@ -464,7 +455,7 @@ context_event_handle_name_owner_changed(sd_bus_message *message, struct context return; LOG_DBG("event_handler: 'NameOwnerChanged': Target bus disappeared: %s", client->bus_name); - clients_free_by_unique_name(context, client->bus_unique_name); + client_free_by_unique_name(context, client->bus_unique_name); if (context->current_client == client) context->current_client = NULL; @@ -502,7 +493,7 @@ context_event_handle_name_acquired(sd_bus_message *message, struct context *cont return; } - if (verify_bus_name(context->identity_list_ref, name)) { + if (verify_bus_name(&context->mpd_config->identity_list, name)) { const char *unique_name = sd_bus_message_get_destination(message); LOG_DBG("'NameAcquired': Acquired new client: %s unique: %s", name, unique_name); client_add(context, name, unique_name); @@ -591,15 +582,17 @@ context_process_events(struct context *context, uint32_t timeout_ms) } static bool -context_new(struct private *m, struct context *context) +context_setup(struct context *context) { int status = true; sd_bus *connection; if ((status = sd_bus_default_user(&connection)) < 0) { LOG_ERR("Failed to connect to the desktop bus. errno: %d", status); - return -1; + return false; } + context->monitor_connection = connection; + /* Turn this connection into a monitor */ sd_bus_message *message; status = sd_bus_message_new_method_call(connection, &message, DBUS_SERVICE, DBUS_PATH, DBUS_INTERFACE_MONITORING, @@ -633,23 +626,16 @@ context_new(struct private *m, struct context *context) sd_bus_message *reply = NULL; sd_bus_error error = {}; - status = sd_bus_call(NULL, message, m->timeout_ms, &error, &reply); + status = sd_bus_call(NULL, message, context->mpd_config->timeout_ms, &error, &reply); if (status < 0 && sd_bus_error_is_set(&error)) { - LOG_ERR("context_new: got error response: %s: %s (%d)", error.name, error.message, - sd_bus_error_get_errno(&error)); + LOG_ERR("context_setup: got error: %s: %s (%d)", error.name, error.message, sd_bus_error_get_errno(&error)); return false; } sd_bus_message_unref(message); sd_bus_message_unref(reply); - (*context) = (struct context){ - .identity_list_ref = &m->identity_list, - .monitor_connection = connection, - .clients = tll_init(), - }; - sd_bus_add_filter(connection, NULL, context_event_handler, context); return status >= 0; @@ -765,6 +751,7 @@ static struct exposable * content_empty(struct module *mod) { struct private *m = mod->private; + mtx_lock(&mod->lock); struct tag_set tags = { .tags = (struct tag *[]){ @@ -784,11 +771,10 @@ content_empty(struct module *mod) .count = 10, }; + struct exposable *exposable = m->label->instantiate(m->label, &tags); + tag_set_destroy(&tags); mtx_unlock(&mod->lock); - struct exposable *exposable = m->label->instantiate(m->label, &tags); - - tag_set_destroy(&tags); return exposable; } @@ -862,6 +848,7 @@ content(struct module *mod) const enum tag_realtime_unit realtime_unit = (client->has_seeked_support && client->status == STATUS_PLAYING) ? TAG_REALTIME_MSECS : TAG_REALTIME_NONE; + mtx_lock(&mod->lock); struct tag_set tags = { .tags = (struct tag *[]){ tag_new_bool(mod, "has_seeked_support", client->has_seeked_support), @@ -880,11 +867,10 @@ content(struct module *mod) .count = 11, }; + struct exposable *exposable = m->label->instantiate(m->label, &tags); + tag_set_destroy(&tags); mtx_unlock(&mod->lock); - struct exposable *exposable = m->label->instantiate(m->label, &tags); - - tag_set_destroy(&tags); return exposable; } @@ -990,7 +976,7 @@ run(struct module *mod) const struct bar *bar = mod->bar; struct private *m = mod->private; - if (!context_new(m, &m->context)) { + if (!context_setup(&m->context)) { LOG_ERR("Failed to setup context"); return -1; } @@ -1054,6 +1040,7 @@ mpris_new(const struct yml_node *ident_list, size_t timeout_ms, struct particle struct private *priv = calloc(1, sizeof(*priv)); priv->label = label; priv->timeout_ms = timeout_ms; + priv->context.mpd_config = priv; size_t i = 0; for (struct yml_list_iter iter = yml_list_iter(ident_list); iter.node != NULL; yml_list_next(&iter), i++) { @@ -1082,7 +1069,6 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited) if (query_timeout != NULL) timeout_ms = yml_value_as_int(query_timeout) * 1000; - return mpris_new(ident_list, timeout_ms, conf_to_particle(c, inherited)); } From abeffbd9a9fd0b2133343e1149e65d4a795a43d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 5 May 2025 08:14:47 +0200 Subject: [PATCH 64/64] readme: goodbye! --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index 48566dc..4e825b3 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,27 @@ # Yambar +**This project is not developed anymore, and this repository will be +archived in a near future**. + +I do not have neither the time nor the will to work on this +anymore. Mainly because I do not use a bar myself anymore. + +There are also technical difficulties, caused by the fact that yambar +was initially X11, and only later was Wayland support added. + +This means I have to maintain a backend (X11) I have not used myself +in many years, as well as trying to work around technical limitations +imposed by the way both X11 and Wayland is supported, As my own use of +a bar has dwindled, the will to refactor and improve the backends has +disappeared. + +Yambar has seen a lot of contributions, for which I am very +grateful. I hope that means someone is willing to pick up where I left +of, and continue working on yambar. If not, we at least had a good +run; the first commit was in late 2018, roughly 6½ years ago! + + [![Packaging status](https://repology.org/badge/vertical-allrepos/yambar.svg?columns=4)](https://repology.org/project/yambar/versions)