From 1b2dee55efa246d1a020fff4345988ded02123f0 Mon Sep 17 00:00:00 2001 From: fraktal Date: Wed, 4 Sep 2024 15:33:25 +0200 Subject: [PATCH 01/22] 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 02/22] 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 03/22] 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 04/22] 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 05/22] 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 06/22] 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 07/22] 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 08/22] 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 09/22] 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 10/22] 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 11/22] 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 12/22] 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 13/22] 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 14/22] 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 15/22] 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 16/22] 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 17/22] 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 18/22] 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 19/22] 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 20/22] 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 21/22] 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 22/22] 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