From 53917a4df4d896d852aba9466bdfdab6e5de5a11 Mon Sep 17 00:00:00 2001 From: Archim Jhunjhunwala Date: Sat, 30 Nov 2024 18:59:30 -0500 Subject: [PATCH 01/18] Works, still need \n and emojis; EDIT: emojis "just work." I'll take it. EDIT2: everything good now --- inc/cfg.h | 4 +++ inc/enum.h | 8 +++++ src/args.c | 8 +++++ src/cfg.c | 33 ++++++++++++++++++++ src/layout.c | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 137 insertions(+), 1 deletion(-) diff --git a/inc/cfg.h b/inc/cfg.h index 96bae8d..70fc9a1 100644 --- a/inc/cfg.h +++ b/inc/cfg.h @@ -41,6 +41,8 @@ #define BORDER_COLOR_UNFOCUSED_DEFAULT "0x586e75" +#define LAYOUT_FORMAT_LEN 90 + // never null extern const struct Cfg * const cfg; @@ -61,6 +63,7 @@ struct Cfg { char border_color_focused[11]; char border_color_focused_monocle[11]; char border_color_unfocused[11]; + char layout_format[LAYOUT_FORMAT_LEN]; }; // returns false if not valid @@ -77,6 +80,7 @@ bool cfg_set_inner_gaps(const char *s); bool cfg_set_outer_gaps(const char *s); bool cfg_set_border_width(const char *s); bool cfg_set_border_width_monocle(const char *s); +bool cfg_set_layout_format(const char *s); // returns false if not 0xRRGGBB bool cfg_set_border_color_focused(const char *s); diff --git a/inc/enum.h b/inc/enum.h index 217415a..ebe3953 100644 --- a/inc/enum.h +++ b/inc/enum.h @@ -27,6 +27,14 @@ enum LogThreshold { LOG_THRESHOLD_DEFAULT = INFO, }; +enum LayoutFormatElem { + LAYOUT = 'l', + COUNT = 'c', + RATIO = 'r', +}; + +#define DEFAULT_LAYOUT_FORMAT "{l}" + const char *layout_name(const enum Layout layout); enum Layout layout_val(const char *name); diff --git a/src/args.c b/src/args.c index 28b7066..4ce1a69 100644 --- a/src/args.c +++ b/src/args.c @@ -34,6 +34,7 @@ static struct option cli_long_options[] = { { "help-defaults", no_argument, 0, 0, }, // 18 { "log-threshold", required_argument, 0, 0, }, // 19 { "version", no_argument, 0, 0, }, // 20 + { "layout-format", required_argument, 0, 0, }, // 21 { 0, 0, 0, 0, } }; @@ -179,6 +180,13 @@ void args_cli(int argc, char **argv) { fprintf(stdout, "wideriver version %s\n", VERSION); exit(EXIT_SUCCESS); return; + case 21: + if (!cfg_set_layout_format(optarg)) { + log_error("invalid --layout-format '%s'\n", optarg); + usage(EXIT_FAILURE); + return; + } + break; default: fprintf(stderr, "\n"); usage(EXIT_FAILURE); diff --git a/src/cfg.c b/src/cfg.c index ea6acd3..23a2a9b 100644 --- a/src/cfg.c +++ b/src/cfg.c @@ -24,6 +24,7 @@ struct Cfg c = { .border_color_focused = BORDER_COLOR_FOCUSED_DEFAULT, .border_color_focused_monocle = BORDER_COLOR_FOCUSED_MONOCLE_DEFAULT, .border_color_unfocused = BORDER_COLOR_UNFOCUSED_DEFAULT, + .layout_format = DEFAULT_LAYOUT_FORMAT, }; const struct Cfg * const cfg = &c; @@ -190,6 +191,38 @@ bool cfg_set_border_width_monocle(const char *s) { } } +bool cfg_set_layout_format(const char *s) { + if (strlen(s) > LAYOUT_FORMAT_LEN) + return false; + + int escaped = 0; + bool in_brackets = false; + + for (int i = 0; s[i] != '\0'; i++) { + if (s[i] == '{' && escaped == 0) + in_brackets = true; + else if (s[i] == '}' && escaped == 0 && in_brackets) { + if ((s[i-1] != LAYOUT && + s[i-1] != COUNT && + s[i-1] != RATIO) || + s[i-2] != '{' + ) { + return false; + } + in_brackets = false; + } else if (s[i] == '\\' && escaped == 0 && !in_brackets) { + escaped = 2; + } + + escaped -= 1; + if (escaped < 0) + escaped = 0; + } + + strcpy(c.layout_format, s); + return true; +} + bool cfg_set_border_color_focused(const char *s) { if (valid_colour(s)) { snprintf(c.border_color_focused, 11, "%s", s); diff --git a/src/layout.c b/src/layout.c index 67c5741..e8df23f 100644 --- a/src/layout.c +++ b/src/layout.c @@ -8,10 +8,11 @@ #include "log.h" #include "slist.h" #include "tag.h" +#include "cfg.h" #include "layout.h" -const char *description_info(const struct Demand* const demand, const struct Tag* const tag) { +const char *layout_image(const struct Demand* const demand, const struct Tag* const tag) { static char desc[20]; switch(tag->layout_cur) { @@ -54,6 +55,88 @@ const char *description_info(const struct Demand* const demand, const struct Tag return desc; } +// nix run .#wideriver -- --layout left --ratio-master 0.5 --count-wide-left 0 --border-width 3 --border-color-focused 0xe0def4 --border-color-unfocused 0x6e6a86 --layout-format "{r}\n{l}\n{c}" +const char *description_info(const struct Demand* const demand, const struct Tag* const tag) { + double ratio; + int count; + const char *image = layout_image(demand, tag); + switch(tag->layout_cur) { + case LEFT: + case RIGHT: + case TOP: + case BOTTOM: + count = tag->count_master; + ratio = tag->ratio_master; + break; + case WIDE: + count = tag->count_wide_left; + ratio = tag->ratio_wide; + break; + case MONOCLE: + count = demand->view_count; + ratio = 1.0; + break; + } + + static char desc[LAYOUT_FORMAT_LEN+5]; + int j = 0; + int escaped = 0; + bool in_brackets = false; + for (int i = 0; cfg->layout_format[i] != '\0'; i++) { + if (escaped > 0) { + switch (cfg->layout_format[i]) { + case 'n': + desc[j] = '\n'; + break; + case 't': + desc[j] = '\t'; + break; + case 'r': + desc[j] = '\r'; + break; + case 'v': + desc[j] = '\v'; + break; + case '{': + desc[j] = '{'; + break; + case '}': + desc[j] = '}'; + break; + default: + break; + } + j++; + } else if (cfg->layout_format[i] == '{') + in_brackets = true; + else if (cfg->layout_format[i] == '}' && in_brackets) { + switch (cfg->layout_format[i-1]) { + case RATIO: + j += snprintf(desc+j, sizeof(desc)-j, "%g", ratio); + break; + case COUNT: + j += snprintf(desc+j, sizeof(desc)-j, "%u", count); + break; + case LAYOUT: + j += snprintf(desc+j, sizeof(desc)-j, "%s", image); + break; + } + in_brackets = false; + } else if (cfg->layout_format[i] == '\\' && !in_brackets) + escaped = 2; + else if (!in_brackets) { + desc[j] = cfg->layout_format[i]; + j++; + } + + escaped -= 1; + if (escaped < 0) + escaped = 0; + } + + return desc; +} + const char *description_debug(const struct Demand* const demand, const struct Tag* const tag) { static char desc[128]; From a5a76270629be2977f0f1372acd41bb9a6b5a342 Mon Sep 17 00:00:00 2001 From: Archim Jhunjhunwala Date: Sat, 30 Nov 2024 19:33:27 -0500 Subject: [PATCH 02/18] lint / check includes / updated help --- flake.lock | 27 ++++++++++++++++ flake.nix | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++ result | 1 + src/cmd.c | 1 - src/enum.c | 3 +- src/layout.c | 5 +-- src/log.c | 2 +- src/main.c | 2 ++ src/usage.c | 2 ++ 9 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 flake.lock create mode 100644 flake.nix create mode 120000 result diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..c1cae03 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1732780316, + "narHash": "sha256-NskLIz0ue4Uqbza+1+8UGHuPVr8DrUiLfZu5VS4VQxw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "226216574ada4c3ecefcbbec41f39ce4655f78ef", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..0d57487 --- /dev/null +++ b/flake.nix @@ -0,0 +1,89 @@ +{ + description = "A simple C program using jansson built with Nix flakes"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + }; + + outputs = { self, nixpkgs }: { + packages = nixpkgs.lib.genAttrs [ "aarch64-darwin" "x86_64-linux" ] (system: + let pkgs = import nixpkgs { inherit system; }; + in rec { + wideriver = pkgs.stdenv.mkDerivation { + pname = "wideriver"; + version = "1.0"; + + src = ./.; + + nativeBuildInputs = with pkgs; [ + wayland-scanner + ]; + buildInputs = with pkgs; [ + clang + wayland + wayland-scanner + wlr-protocols + wlroots + pkg-config + ]; + + buildPhase = '' + make + ''; + + installFlags = [ "PREFIX=$(out)" ]; + + installPhase = '' + mkdir -p $out/bin + cp wideriver $out/bin/ + ''; + + meta = with pkgs.lib; { + description = "A simple program using jansson"; + license = licenses.mit; + maintainers = [ maintainers.yourname ]; + platforms = platforms.unix; + }; + }; + }); + + devShell = nixpkgs.lib.genAttrs [ "aarch64-darwin" "x86_64-linux" ] (system: + let pkgs = import nixpkgs { inherit system; }; + in pkgs.mkShell { + nativeBuildInputs = with pkgs; [ + wayland-scanner + ]; + buildInputs = with pkgs; [ + clang + clang-tools # Includes clangd + cmocka + include-what-you-use + valgrind + wayland + wayland-scanner + wlr-protocols + wlroots + pkg-config + ]; + + shellHook = '' + echo "Development environment loaded." + ''; + }); + + defaultPackage = { + aarch64-darwin = self.packages.aarch64-darwin.wideriver; + x86_64-linux = self.packages.x86_64-linux.wideriver; + }; + + defaultApp = { + forAllSystems = nixpkgs.lib.mapAttrs' (system: pkg: { + inherit system; + defaultApp = { + type = "app"; + program = "${pkg.wideriver}/bin/wideriver"; + }; + }) self.packages; + }; + }; +} diff --git a/result b/result new file mode 120000 index 0000000..e0fe0fc --- /dev/null +++ b/result @@ -0,0 +1 @@ +/nix/store/p98n306sq7xk0yvc0px9yzkgcasrl1jr-wideriver-1.0 \ No newline at end of file diff --git a/src/cmd.c b/src/cmd.c index ebf8b4a..ca0023d 100644 --- a/src/cmd.c +++ b/src/cmd.c @@ -1,5 +1,4 @@ #include -#include #include #include diff --git a/src/enum.c b/src/enum.c index c3b0fcd..ce40af7 100644 --- a/src/enum.c +++ b/src/enum.c @@ -1,6 +1,5 @@ -#include #include - +#include #include "enum.h" struct NameVal { diff --git a/src/layout.c b/src/layout.c index e8df23f..bd8cef1 100644 --- a/src/layout.c +++ b/src/layout.c @@ -1,4 +1,5 @@ #include +#include #include #include "river-layout-v3.h" @@ -57,8 +58,8 @@ const char *layout_image(const struct Demand* const demand, const struct Tag* co // nix run .#wideriver -- --layout left --ratio-master 0.5 --count-wide-left 0 --border-width 3 --border-color-focused 0xe0def4 --border-color-unfocused 0x6e6a86 --layout-format "{r}\n{l}\n{c}" const char *description_info(const struct Demand* const demand, const struct Tag* const tag) { - double ratio; - int count; + double ratio = 0; + int count = 0; const char *image = layout_image(demand, tag); switch(tag->layout_cur) { case LEFT: diff --git a/src/log.c b/src/log.c index 16fc230..757d48e 100644 --- a/src/log.c +++ b/src/log.c @@ -1,8 +1,8 @@ #include #include -#include #include #include +#include #include #include "enum.h" diff --git a/src/main.c b/src/main.c index 0584c6b..f36b919 100644 --- a/src/main.c +++ b/src/main.c @@ -6,6 +6,8 @@ #include #include #include +#include "asm-generic/errno-base.h" +#include "sys/poll.h" #include "args.h" #include "displ.h" diff --git a/src/usage.c b/src/usage.c index ed22531..3f06ffe 100644 --- a/src/usage.c +++ b/src/usage.c @@ -39,6 +39,8 @@ void usage(const int status) { " --log-threshold %s|%s|%s|%s %s\n" " --version\n" "\n" + " --layout-format format {l} = layout image {c} = count, {r} = ratio" + "\n" "COMMANDS, sent via riverctl(1):\n" "\n" " --layout %s|%s|%s|%s|%s|%s\n" From 00126897b54d8c50bbf82408bc62efdc670cf06d Mon Sep 17 00:00:00 2001 From: Archim Jhunjhunwala Date: Sun, 1 Dec 2024 21:56:22 -0500 Subject: [PATCH 03/18] linting --- flake.nix | 1 + src/layout.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 0d57487..fca7545 100644 --- a/flake.nix +++ b/flake.nix @@ -56,6 +56,7 @@ buildInputs = with pkgs; [ clang clang-tools # Includes clangd + cppcheck cmocka include-what-you-use valgrind diff --git a/src/layout.c b/src/layout.c index bd8cef1..dea841b 100644 --- a/src/layout.c +++ b/src/layout.c @@ -59,7 +59,7 @@ const char *layout_image(const struct Demand* const demand, const struct Tag* co // nix run .#wideriver -- --layout left --ratio-master 0.5 --count-wide-left 0 --border-width 3 --border-color-focused 0xe0def4 --border-color-unfocused 0x6e6a86 --layout-format "{r}\n{l}\n{c}" const char *description_info(const struct Demand* const demand, const struct Tag* const tag) { double ratio = 0; - int count = 0; + unsigned int count = 0; const char *image = layout_image(demand, tag); switch(tag->layout_cur) { case LEFT: From b063e162e4eb9288b525033a3add542b73fc7df7 Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Mon, 2 Dec 2024 15:04:19 +1100 Subject: [PATCH 04/18] add iwyu mappings --- .iwyu.mappings | 4 ++++ GNUmakefile | 2 +- src/main.c | 2 -- 3 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 .iwyu.mappings diff --git a/.iwyu.mappings b/.iwyu.mappings new file mode 100644 index 0000000..5e4b731 --- /dev/null +++ b/.iwyu.mappings @@ -0,0 +1,4 @@ +[ +{ symbol: [ va_start, private, , public] }, +{ symbol: [ va_end, private, , public] } +] diff --git a/GNUmakefile b/GNUmakefile index f41726e..7afb7fc 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -69,7 +69,7 @@ doc: wideriver # iwyu: override CC = $(IWYU) -Xiwyu --check_also="inc/*h" iwyu: clean $(SRC_O) $(TST_O) -IWYU = include-what-you-use -Xiwyu --no_fwd_decls -Xiwyu --error=1 -Xiwyu --verbose=3 +IWYU = include-what-you-use -Xiwyu --no_fwd_decls -Xiwyu --error=1 -Xiwyu --verbose=3 -Xiwyu --mapping_file=.iwyu.mappings # # cppcheck diff --git a/src/main.c b/src/main.c index f36b919..0584c6b 100644 --- a/src/main.c +++ b/src/main.c @@ -6,8 +6,6 @@ #include #include #include -#include "asm-generic/errno-base.h" -#include "sys/poll.h" #include "args.h" #include "displ.h" From 70b94694430b8b31666d0aa8fc1d3c220349fd19 Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Mon, 2 Dec 2024 16:36:46 +1100 Subject: [PATCH 05/18] add layout test and add layout-format to args test --- inc/layout.h | 6 ++ src/args.c | 1 + tst/tst-args_cli.c | 19 +++++- tst/tst-layout.c | 164 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 tst/tst-layout.c diff --git a/inc/layout.h b/inc/layout.h index 3d54577..35afd1f 100644 --- a/inc/layout.h +++ b/inc/layout.h @@ -30,5 +30,11 @@ struct SList *layout(const struct Demand *demand, const struct Tag *tag); // push Box views void push(const struct SList *views, struct river_layout_v3 *river_layout_v3, const uint32_t serial); +// layout image +const char *layout_image(const struct Demand* const demand, const struct Tag* const tag); + +// fully rendered layout +const char *description_info(const struct Demand* const demand, const struct Tag* const tag); + #endif // LAYOUT_H diff --git a/src/args.c b/src/args.c index 4ce1a69..61dc82d 100644 --- a/src/args.c +++ b/src/args.c @@ -210,6 +210,7 @@ void args_cli(int argc, char **argv) { log_info("--border-color-focused %s", cfg->border_color_focused); log_info("--border-color-focused-monocle %s", cfg->border_color_focused_monocle); log_info("--border-color-unfocused %s", cfg->border_color_unfocused); + log_info("--layout-format %s", cfg->layout_format); log_info("--log-threshold %s", log_threshold_name(log_get_threshold())); } diff --git a/tst/tst-args_cli.c b/tst/tst-args_cli.c index 183c93c..d7480eb 100644 --- a/tst/tst-args_cli.c +++ b/tst/tst-args_cli.c @@ -32,7 +32,7 @@ int after_each(void **state) { } void args_parse_cli__valid(void **state) { - int argc = 34; + int argc = 36; char *argv[] = { "dummy", "--layout", "left", "--layout-alt", "right", @@ -51,6 +51,7 @@ void args_parse_cli__valid(void **state) { "--border-color-focused-monocle", "0xDDEEFFA9", "--border-color-unfocused", "0x001122", "--log-threshold", "ERROR", + "--layout-format", "{l} {r} {c}", }; args_cli(argc, argv); @@ -71,6 +72,7 @@ void args_parse_cli__valid(void **state) { assert_str_equal(cfg->border_color_focused, "0xAABBCC"); assert_str_equal(cfg->border_color_focused_monocle, "0xDDEEFFA9"); assert_str_equal(cfg->border_color_unfocused, "0x001122"); + assert_str_equal(cfg->layout_format, "{l} {r} {c}"); assert_log(INFO, "--layout left\n" @@ -89,6 +91,7 @@ void args_parse_cli__valid(void **state) { "--border-color-focused 0xAABBCC\n" "--border-color-focused-monocle 0xDDEEFFA9\n" "--border-color-unfocused 0x001122\n" + "--layout-format {l} {r} {c}\n" "--log-threshold error\n" ); } @@ -288,6 +291,19 @@ void args_parse_cli__bad_border_color_unfocused(void **state) { assert_log(ERROR, "invalid --border-color-unfocused 'bar'\n\n"); } +void args_parse_cli__bad_layout_format(void **state) { + int argc = 3; + char *argv[] = { "dummy", + "--layout-format", "{foo} {c} {r}", + }; + + expect_value(__wrap_usage, status, EXIT_FAILURE); + + args_cli(argc, argv); + + assert_log(ERROR, "invalid --layout-format '{foo} {c} {r}'\n\n"); +} + void args_parse_cli__bad_log_threshold(void **state) { int argc = 3; char *argv[] = { "dummy", @@ -319,6 +335,7 @@ int main(void) { TEST(args_parse_cli__bad_border_color_focused), TEST(args_parse_cli__bad_border_color_focused_monocle), TEST(args_parse_cli__bad_border_color_unfocused), + TEST(args_parse_cli__bad_layout_format), TEST(args_parse_cli__bad_log_threshold), }; diff --git a/tst/tst-layout.c b/tst/tst-layout.c new file mode 100644 index 0000000..fc040ac --- /dev/null +++ b/tst/tst-layout.c @@ -0,0 +1,164 @@ +#include "tst.h" +#include "asserts.h" + +#include +#include +#include + +#include "cfg.h" + +#include "layout.h" + +int before_all(void **state) { + return 0; +} + +int after_all(void **state) { + return 0; +} + +int before_each(void **state) { + return 0; +} + +int after_each(void **state) { + return 0; +} + +void layout_image__left(void **state) { + struct Demand demand = { 0 }; + struct Tag tag = { .layout_cur = LEFT, }; + + tag.count_master = 0; + assert_str_equal(layout_image(&demand, &tag), "│├──┤"); + + tag.count_master = 1; + assert_str_equal(layout_image(&demand, &tag), "│ ├─┤"); +} + +void layout_image__right(void **state) { + struct Demand demand = { 0 }; + struct Tag tag = { .layout_cur = RIGHT, }; + + tag.count_master = 0; + assert_str_equal(layout_image(&demand, &tag), "├──┤│"); + + tag.count_master = 1; + assert_str_equal(layout_image(&demand, &tag), "├─┤ │"); +} + +void layout_image__top(void **state) { + struct Demand demand = { 0 }; + struct Tag tag = { .layout_cur = TOP, }; + + assert_str_equal(layout_image(&demand, &tag), "├─┬─┤"); +} + +void layout_image__bottom(void **state) { + struct Demand demand = { 0 }; + struct Tag tag = { .layout_cur = BOTTOM, }; + + assert_str_equal(layout_image(&demand, &tag), "├─┴─┤"); +} + +void layout_image__wide(void **state) { + struct Demand demand = { 0 }; + struct Tag tag = { .layout_cur = WIDE, }; + + tag.count_wide_left = 0; + assert_str_equal(layout_image(&demand, &tag), "││ ├─┤"); + + tag.count_wide_left = 1; + assert_str_equal(layout_image(&demand, &tag), "├─┤ ├─┤"); +} + +void layout_image__monocle(void **state) { + struct Tag tag = { .layout_cur = MONOCLE, }; + + struct Demand demand1 = { .view_count = 1 }; + assert_str_equal(layout_image(&demand1, &tag), "│ │"); + + struct Demand demand2 = { .view_count = 2 }; + assert_str_equal(layout_image(&demand2, &tag), "│ 2 │"); +} + +void layout_description_info__none(void **state) { + struct Demand demand = { 0 }; + struct Tag tag = { 0 }; + + assert_true(cfg_set_layout_format("foo")); + + assert_str_equal(description_info(&demand, &tag), "foo"); +} + +void layout_description_info__monocle(void **state) { + struct Demand demand = { .view_count = 9, }; + struct Tag tag = { .layout_cur = MONOCLE, }; + + assert_true(cfg_set_layout_format("image: {l} count: {c} ratio: {r} end")); + + // TODO #21 remove the monocle count + assert_str_equal(description_info(&demand, &tag), "image: │ 9 │ count: 9 ratio: 1 end"); +} + +void layout_description_info__lrtb(void **state) { + struct Demand demand = { .view_count = 9, }; + struct Tag tag = { .layout_cur = LEFT, .count_master = 2, .count_wide_left = 3, .ratio_master = 0.4, .ratio_wide = 0.5, }; + + assert_true(cfg_set_layout_format("image: {l} count: {c} ratio: {r} end")); + + assert_str_equal(description_info(&demand, &tag), "image: │ ├─┤ count: 2 ratio: 0.4 end"); +} + +void layout_description_info__wide(void **state) { + struct Demand demand = { .view_count = 9, }; + struct Tag tag = { .layout_cur = WIDE, .count_master = 2, .count_wide_left = 3, .ratio_master = 0.4, .ratio_wide = 0.5, }; + + assert_true(cfg_set_layout_format("image: {l} count: {c} ratio: {r} end")); + + assert_str_equal(description_info(&demand, &tag), "image: ├─┤ ├─┤ count: 3 ratio: 0.5 end"); +} + +void layout_description_info__invalids(void **state) { + struct Demand demand = { .view_count = 9, }; + struct Tag tag = { .layout_cur = WIDE, .count_master = 2, .count_wide_left = 3, .ratio_master = 0.4, .ratio_wide = 0.5, }; + + // manually set an invalid layout + strcpy(((struct Cfg*)cfg)->layout_format, "image: {foo} count: {c c} ratio: {rATIO} end"); + + // TODO #21 this is not correct + assert_str_equal(description_info(&demand, &tag), "image: count: 3 ratio: endunt: 3 ratio: 0.5 end"); + // should be + // assert_str_equal(description_info(&demand, &tag), "image: count: ratio: end"); +} + +void layout_description_info__escapes(void **state) { + struct Demand demand = { .view_count = 9, }; + struct Tag tag = { .layout_cur = WIDE, .count_master = 2, .count_wide_left = 3, .ratio_master = 0.4, .ratio_wide = 0.5, }; + + assert_true(cfg_set_layout_format("i: {l}\\tcount: {c}\\tratio: {r} end")); + + // TODO #21 this is not correct - it looks like the static desc in description_info is not being cleared or a \0 is not appended + assert_str_equal(description_info(&demand, &tag), "i: ├─┤ ├─┤\tcount: 3\tratio: 0.5 end5 end"); +} + +int main(void) { + const struct CMUnitTest tests[] = { + TEST(layout_image__left), + TEST(layout_image__right), + TEST(layout_image__top), + TEST(layout_image__bottom), + TEST(layout_image__wide), + TEST(layout_image__monocle), + + TEST(layout_description_info__none), + TEST(layout_description_info__monocle), + TEST(layout_description_info__lrtb), + TEST(layout_description_info__wide), + TEST(layout_description_info__invalids), + TEST(layout_description_info__escapes), + }; + + return RUN(tests); +} + From 88a82134f1e45fed95ed5ca9ee0b00600a270a76 Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Mon, 2 Dec 2024 17:05:11 +1100 Subject: [PATCH 06/18] terminate static layout desc string --- inc/layout.h | 2 +- src/layout.c | 3 +++ tst/tst-layout.c | 11 ++++------- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/inc/layout.h b/inc/layout.h index 35afd1f..2b78568 100644 --- a/inc/layout.h +++ b/inc/layout.h @@ -21,7 +21,7 @@ struct Box { uint32_t height; }; -// return a river layout name, caller frees +// return a river layout name const char *layout_description(const struct Demand *demand, const struct Tag *tag); // populate views with Box for river layout push dimensions, caller frees diff --git a/src/layout.c b/src/layout.c index dea841b..35fcfdf 100644 --- a/src/layout.c +++ b/src/layout.c @@ -135,6 +135,9 @@ const char *description_info(const struct Demand* const demand, const struct Tag escaped = 0; } + // terminate the string + desc[j] = '\0'; + return desc; } diff --git a/tst/tst-layout.c b/tst/tst-layout.c index fc040ac..f6df874 100644 --- a/tst/tst-layout.c +++ b/tst/tst-layout.c @@ -126,20 +126,17 @@ void layout_description_info__invalids(void **state) { // manually set an invalid layout strcpy(((struct Cfg*)cfg)->layout_format, "image: {foo} count: {c c} ratio: {rATIO} end"); - // TODO #21 this is not correct - assert_str_equal(description_info(&demand, &tag), "image: count: 3 ratio: endunt: 3 ratio: 0.5 end"); - // should be - // assert_str_equal(description_info(&demand, &tag), "image: count: ratio: end"); + // TODO #21 this is not correct; count should not be substituted + assert_str_equal(description_info(&demand, &tag), "image: count: 3 ratio: end"); } void layout_description_info__escapes(void **state) { struct Demand demand = { .view_count = 9, }; struct Tag tag = { .layout_cur = WIDE, .count_master = 2, .count_wide_left = 3, .ratio_master = 0.4, .ratio_wide = 0.5, }; - assert_true(cfg_set_layout_format("i: {l}\\tcount: {c}\\tratio: {r} end")); + assert_true(cfg_set_layout_format("i: {l}\\ncount:\\t{c}\\rratio:\\v{r} end")); - // TODO #21 this is not correct - it looks like the static desc in description_info is not being cleared or a \0 is not appended - assert_str_equal(description_info(&demand, &tag), "i: ├─┤ ├─┤\tcount: 3\tratio: 0.5 end5 end"); + assert_str_equal(description_info(&demand, &tag), "i: ├─┤ ├─┤\ncount:\t3\rratio:\v0.5 end"); } int main(void) { From 572f7025d3781a5135ca30f1f22e8eb479f104c2 Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Mon, 2 Dec 2024 17:29:19 +1100 Subject: [PATCH 07/18] add default layout to usage --- src/usage.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/usage.c b/src/usage.c index 3f06ffe..5cd301f 100644 --- a/src/usage.c +++ b/src/usage.c @@ -35,12 +35,12 @@ void usage(const int status) { " --border-color-focused-monocle 0xRRGGBB[AA] %s\n" " --border-color-unfocused 0xRRGGBB[AA] %s\n" "\n" + " --layout-format format {l} {l} = layout image, {c} = count, {r} = ratio\n" + "\n" " --help\n" " --log-threshold %s|%s|%s|%s %s\n" " --version\n" "\n" - " --layout-format format {l} = layout image {c} = count, {r} = ratio" - "\n" "COMMANDS, sent via riverctl(1):\n" "\n" " --layout %s|%s|%s|%s|%s|%s\n" From f6edd4aded9ba8ab153f625193d3b4cef5ce147b Mon Sep 17 00:00:00 2001 From: Archim Jhunjhunwala Date: Wed, 4 Dec 2024 21:11:38 -0500 Subject: [PATCH 08/18] added {n} for name (left/right/monocle/etc) --- inc/enum.h | 1 + src/cfg.c | 9 +++++---- src/layout.c | 24 +++++++++++++++++++++++- src/usage.c | 2 +- 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/inc/enum.h b/inc/enum.h index ebe3953..e78830f 100644 --- a/inc/enum.h +++ b/inc/enum.h @@ -31,6 +31,7 @@ enum LayoutFormatElem { LAYOUT = 'l', COUNT = 'c', RATIO = 'r', + NAME = 'n', }; #define DEFAULT_LAYOUT_FORMAT "{l}" diff --git a/src/cfg.c b/src/cfg.c index 23a2a9b..a749c8c 100644 --- a/src/cfg.c +++ b/src/cfg.c @@ -203,10 +203,11 @@ bool cfg_set_layout_format(const char *s) { in_brackets = true; else if (s[i] == '}' && escaped == 0 && in_brackets) { if ((s[i-1] != LAYOUT && - s[i-1] != COUNT && - s[i-1] != RATIO) || - s[i-2] != '{' - ) { + s[i-1] != COUNT && + s[i-1] != NAME && + s[i-1] != RATIO) || + s[i-2] != '{' + ) { return false; } in_brackets = false; diff --git a/src/layout.c b/src/layout.c index dea841b..ed43797 100644 --- a/src/layout.c +++ b/src/layout.c @@ -56,8 +56,28 @@ const char *layout_image(const struct Demand* const demand, const struct Tag* co return desc; } -// nix run .#wideriver -- --layout left --ratio-master 0.5 --count-wide-left 0 --border-width 3 --border-color-focused 0xe0def4 --border-color-unfocused 0x6e6a86 --layout-format "{r}\n{l}\n{c}" const char *description_info(const struct Demand* const demand, const struct Tag* const tag) { + char name[8] = ""; + switch (tag->layout_cur) { + case LEFT: + snprintf(name, sizeof(name), "left"); + break; + case RIGHT: + snprintf(name, sizeof(name), "right"); + break; + case TOP: + snprintf(name, sizeof(name), "top"); + break; + case BOTTOM: + snprintf(name, sizeof(name), "bottom"); + break; + case WIDE: + snprintf(name, sizeof(name), "wide"); + break; + case MONOCLE: + snprintf(name, sizeof(name), "monocle"); + break; + } double ratio = 0; unsigned int count = 0; const char *image = layout_image(demand, tag); @@ -121,6 +141,8 @@ const char *description_info(const struct Demand* const demand, const struct Tag case LAYOUT: j += snprintf(desc+j, sizeof(desc)-j, "%s", image); break; + case NAME: + j += snprintf(desc+j, sizeof(desc)-j, "%s", name); } in_brackets = false; } else if (cfg->layout_format[i] == '\\' && !in_brackets) diff --git a/src/usage.c b/src/usage.c index 3f06ffe..21d9b8e 100644 --- a/src/usage.c +++ b/src/usage.c @@ -39,7 +39,7 @@ void usage(const int status) { " --log-threshold %s|%s|%s|%s %s\n" " --version\n" "\n" - " --layout-format format {l} = layout image {c} = count, {r} = ratio" + " --layout-format format {l} = layout image {c} = count, {r} = ratio, {n} = name" "\n" "COMMANDS, sent via riverctl(1):\n" "\n" From 28d675f4f3eeb4ce1c41e64f7dc5973ee03348fe Mon Sep 17 00:00:00 2001 From: Archim Jhunjhunwala Date: Wed, 4 Dec 2024 21:21:30 -0500 Subject: [PATCH 09/18] string vulnerability things --- src/cfg.c | 2 +- src/layout.c | 42 +---------- src/usage.c | 101 ++++++++++++++----------- src/usage.c.orig | 189 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 252 insertions(+), 82 deletions(-) create mode 100644 src/usage.c.orig diff --git a/src/cfg.c b/src/cfg.c index a749c8c..37eb6f6 100644 --- a/src/cfg.c +++ b/src/cfg.c @@ -220,7 +220,7 @@ bool cfg_set_layout_format(const char *s) { escaped = 0; } - strcpy(c.layout_format, s); + strncpy(c.layout_format, s, sizeof(c.layout_format)); return true; } diff --git a/src/layout.c b/src/layout.c index ed43797..be81c91 100644 --- a/src/layout.c +++ b/src/layout.c @@ -56,7 +56,10 @@ const char *layout_image(const struct Demand* const demand, const struct Tag* co return desc; } -const char *description_info(const struct Demand* const demand, const struct Tag* const tag) { +const char *layout_description(const struct Demand* const demand, const struct Tag* const tag) { + if (!demand || !tag) + return ""; + char name[8] = ""; switch (tag->layout_cur) { case LEFT: @@ -160,43 +163,6 @@ const char *description_info(const struct Demand* const demand, const struct Tag return desc; } -const char *description_debug(const struct Demand* const demand, const struct Tag* const tag) { - static char desc[128]; - - switch(tag->layout_cur) { - case LEFT: - case RIGHT: - case TOP: - case BOTTOM: - snprintf(desc, sizeof(desc), "%s %u %g ", description_info(demand, tag), tag->count_master, tag->ratio_master); - break; - case WIDE: - snprintf(desc, sizeof(desc), "%s %u %g ", description_info(demand, tag), tag->count_wide_left, tag->ratio_wide); - break; - case MONOCLE: - snprintf(desc, sizeof(desc), "%s", description_info(demand, tag)); - break; - } - - return desc; -} - -const char *layout_description(const struct Demand* const demand, const struct Tag* const tag) { - - if (!demand || !tag) - return ""; - - switch (log_get_threshold()) { - case DEBUG: - return description_debug(demand, tag); - case INFO: - case WARNING: - case ERROR: - default: - return description_info(demand, tag); - } -} - struct SList *layout(const struct Demand *demand, const struct Tag *tag) { if (!demand || !tag) return NULL; diff --git a/src/usage.c b/src/usage.c index 21d9b8e..75a16fd 100644 --- a/src/usage.c +++ b/src/usage.c @@ -17,29 +17,42 @@ void usage(const int status) { "\n" " --stack %s|%s|%s %s\n" "\n" - " --count-master count %d %d <= count\n" - " --ratio-master ratio %.02f %.1g <= ratio <= %.1g\n" + " --count-master count " + " %d %d <= count\n" + " --ratio-master ratio " + " %.02f %.1g <= ratio <= %.1g\n" "\n" - " --count-wide-left count %d %d <= count\n" - " --ratio-wide ratio %.02f %.1g <= ratio <= %.1g\n" + " --count-wide-left count " + " %d %d <= count\n" + " --ratio-wide ratio " + " %.02f %.1g <= ratio <= %.1g\n" "\n" " --(no-)smart-gaps\n" - " --inner-gaps pixels %d %d <= gap size\n" - " --outer-gaps pixels %d %d <= gap size\n" + " --inner-gaps pixels " + " %d %d <= gap size\n" + " --outer-gaps pixels " + " %d %d <= gap size\n" "\n" - " --border-width pixels %d %d <= width\n" - " --border-width-monocle pixels %d %d <= width\n" - " --border-width-smart-gaps pixels %d %d <= width\n" + " --border-width pixels " + " %d %d <= width\n" + " --border-width-monocle pixels " + " %d %d <= width\n" + " --border-width-smart-gaps pixels " + " %d %d <= width\n" "\n" - " --border-color-focused 0xRRGGBB[AA] %s\n" - " --border-color-focused-monocle 0xRRGGBB[AA] %s\n" - " --border-color-unfocused 0xRRGGBB[AA] %s\n" + " --border-color-focused 0xRRGGBB[AA] " + " %s\n" + " --border-color-focused-monocle 0xRRGGBB[AA] " + " %s\n" + " --border-color-unfocused 0xRRGGBB[AA] " + " %s\n" "\n" " --help\n" " --log-threshold %s|%s|%s|%s %s\n" " --version\n" "\n" - " --layout-format format {l} = layout image {c} = count, {r} = ratio, {n} = name" + " --layout-format format {l} = " + "layout image {c} = count, {r} = ratio, {n} = name" "\n" "COMMANDS, sent via riverctl(1):\n" "\n" @@ -48,30 +61,33 @@ void usage(const int status) { "\n" " --stack %s|%s|%s\n" "\n" - " --count [+-]count %d <= count\n" - " --ratio [+-]ratio %.1g <= ratio <= %.1g\n" + " --count [+-]count " + " %d <= count\n" + " --ratio [+-]ratio " + " %.1g <= ratio <= %.1g\n" "\n", - layout_name(MONOCLE), layout_name(LEFT), layout_name(RIGHT), layout_name(TOP), layout_name(BOTTOM), layout_name(WIDE), layout_name(LAYOUT_DEFAULT), - layout_name(MONOCLE), layout_name(LEFT), layout_name(RIGHT), layout_name(TOP), layout_name(BOTTOM), layout_name(WIDE), layout_name(LAYOUT_ALT_DEFAULT), - stack_name(EVEN), stack_name(DIMINISH), stack_name(DWINDLE), stack_name(STACK_DEFAULT), - COUNT_MASTER_DEFAULT, COUNT_MIN, - RATIO_MASTER_DEFAULT, RATIO_MIN, RATIO_MAX, - COUNT_WIDE_LEFT_DEFAULT, COUNT_MIN, - RATIO_WIDE_DEFAULT, RATIO_MIN, RATIO_MAX, - INNER_GAPS_DEFAULT, INNER_GAPS_MIN, - OUTER_GAPS_DEFAULT, OUTER_GAPS_MIN, - BORDER_WIDTH_DEFAULT, BORDER_WIDTH_MIN, - BORDER_WIDTH_MONOCLE_DEFAULT, BORDER_WIDTH_MONOCLE_MIN, - BORDER_WIDTH_SMART_GAPS_DEFAULT, BORDER_WIDTH_SMART_GAPS_MIN, - BORDER_COLOR_FOCUSED_DEFAULT, - BORDER_COLOR_FOCUSED_MONOCLE_DEFAULT, - BORDER_COLOR_UNFOCUSED_DEFAULT, - log_threshold_name(DEBUG), log_threshold_name(INFO), log_threshold_name(WARNING), log_threshold_name(ERROR), log_threshold_name(LOG_THRESHOLD_DEFAULT), + layout_name(MONOCLE), layout_name(LEFT), layout_name(RIGHT), + layout_name(TOP), layout_name(BOTTOM), layout_name(WIDE), + layout_name(LAYOUT_DEFAULT), layout_name(MONOCLE), layout_name(LEFT), + layout_name(RIGHT), layout_name(TOP), layout_name(BOTTOM), + layout_name(WIDE), layout_name(LAYOUT_ALT_DEFAULT), stack_name(EVEN), + stack_name(DIMINISH), stack_name(DWINDLE), stack_name(STACK_DEFAULT), + COUNT_MASTER_DEFAULT, COUNT_MIN, RATIO_MASTER_DEFAULT, RATIO_MIN, + RATIO_MAX, COUNT_WIDE_LEFT_DEFAULT, COUNT_MIN, RATIO_WIDE_DEFAULT, + RATIO_MIN, RATIO_MAX, INNER_GAPS_DEFAULT, INNER_GAPS_MIN, + OUTER_GAPS_DEFAULT, OUTER_GAPS_MIN, BORDER_WIDTH_DEFAULT, + BORDER_WIDTH_MIN, BORDER_WIDTH_MONOCLE_DEFAULT, + BORDER_WIDTH_MONOCLE_MIN, BORDER_WIDTH_SMART_GAPS_DEFAULT, + BORDER_WIDTH_SMART_GAPS_MIN, BORDER_COLOR_FOCUSED_DEFAULT, + BORDER_COLOR_FOCUSED_MONOCLE_DEFAULT, BORDER_COLOR_UNFOCUSED_DEFAULT, + log_threshold_name(DEBUG), log_threshold_name(INFO), + log_threshold_name(WARNING), log_threshold_name(ERROR), + log_threshold_name(LOG_THRESHOLD_DEFAULT), - layout_name(MONOCLE), layout_name(LEFT), layout_name(RIGHT), layout_name(TOP), layout_name(BOTTOM), layout_name(WIDE), + layout_name(MONOCLE), layout_name(LEFT), layout_name(RIGHT), + layout_name(TOP), layout_name(BOTTOM), layout_name(WIDE), stack_name(DIMINISH), stack_name(DWINDLE), stack_name(STACK_DEFAULT), - COUNT_MIN, - RATIO_MIN, RATIO_MAX); + COUNT_MIN, RATIO_MIN, RATIO_MAX); exit(status); } @@ -97,15 +113,14 @@ void usage_defaults(void) { " --border-color-unfocused \"%s\" \\\n" " --log-threshold %s \\\n" " > \"/tmp/wideriver.${XDG_VTNR}.${USER}.log\" 2>&1 &\n", - layout_name(LAYOUT_DEFAULT), layout_name(LAYOUT_ALT_DEFAULT), stack_name(STACK_DEFAULT), - COUNT_MASTER_DEFAULT, RATIO_MASTER_DEFAULT, - COUNT_WIDE_LEFT_DEFAULT, RATIO_WIDE_DEFAULT, - INNER_GAPS_DEFAULT, - OUTER_GAPS_DEFAULT, - BORDER_WIDTH_DEFAULT, BORDER_WIDTH_MONOCLE_DEFAULT, BORDER_WIDTH_SMART_GAPS_DEFAULT, - BORDER_COLOR_FOCUSED_DEFAULT, BORDER_COLOR_FOCUSED_MONOCLE_DEFAULT, BORDER_COLOR_UNFOCUSED_DEFAULT, - log_threshold_name(LOG_THRESHOLD_DEFAULT) - ); + layout_name(LAYOUT_DEFAULT), layout_name(LAYOUT_ALT_DEFAULT), + stack_name(STACK_DEFAULT), COUNT_MASTER_DEFAULT, RATIO_MASTER_DEFAULT, + COUNT_WIDE_LEFT_DEFAULT, RATIO_WIDE_DEFAULT, INNER_GAPS_DEFAULT, + OUTER_GAPS_DEFAULT, BORDER_WIDTH_DEFAULT, + BORDER_WIDTH_MONOCLE_DEFAULT, BORDER_WIDTH_SMART_GAPS_DEFAULT, + BORDER_COLOR_FOCUSED_DEFAULT, BORDER_COLOR_FOCUSED_MONOCLE_DEFAULT, + BORDER_COLOR_UNFOCUSED_DEFAULT, + log_threshold_name(LOG_THRESHOLD_DEFAULT)); exit(EXIT_SUCCESS); } diff --git a/src/usage.c.orig b/src/usage.c.orig new file mode 100644 index 0000000..c1a2531 --- /dev/null +++ b/src/usage.c.orig @@ -0,0 +1,189 @@ +#include +#include + +#include "cfg.h" +#include "enum.h" + +#include "usage.h" + +void usage(const int status) { +<<<<<<< HEAD + fprintf(status == EXIT_SUCCESS ? stdout : stderr, + "Usage: wideriver [OPTIONS...|COMMANDS...]\n" + "\n" + "OPTIONS, startup:\n" + "\n" + " --layout %s|%s|%s|%s|%s|%s %s\n" + " --layout-alt %s|%s|%s|%s|%s|%s %s\n" + "\n" + " --stack %s|%s|%s %s\n" + "\n" + " --count-master count " + " %d %d <= count\n" + " --ratio-master ratio " + " %.02f %.1g <= ratio <= %.1g\n" + "\n" + " --count-wide-left count " + " %d %d <= count\n" + " --ratio-wide ratio " + " %.02f %.1g <= ratio <= %.1g\n" + "\n" + " --(no-)smart-gaps\n" + " --inner-gaps pixels " + " %d %d <= gap size\n" + " --outer-gaps pixels " + " %d %d <= gap size\n" + "\n" + " --border-width pixels " + " %d %d <= width\n" + " --border-width-monocle pixels " + " %d %d <= width\n" + " --border-width-smart-gaps pixels " + " %d %d <= width\n" + "\n" + " --border-color-focused 0xRRGGBB[AA] " + " %s\n" + " --border-color-focused-monocle 0xRRGGBB[AA] " + " %s\n" + " --border-color-unfocused 0xRRGGBB[AA] " + " %s\n" + "\n" + " --help\n" + " --log-threshold %s|%s|%s|%s %s\n" + " --version\n" + "\n" + " --layout-format format {l} = " + "layout image {c} = count, {r} = ratio, {n} = name" + "\n" + "COMMANDS, sent via riverctl(1):\n" + "\n" + " --layout %s|%s|%s|%s|%s|%s\n" + " --layout-toggle \n" + "\n" + " --stack %s|%s|%s\n" + "\n" + " --count [+-]count " + " %d <= count\n" + " --ratio [+-]ratio " + " %.1g <= ratio <= %.1g\n" + "\n", + layout_name(MONOCLE), layout_name(LEFT), layout_name(RIGHT), + layout_name(TOP), layout_name(BOTTOM), layout_name(WIDE), + layout_name(LAYOUT_DEFAULT), layout_name(MONOCLE), layout_name(LEFT), + layout_name(RIGHT), layout_name(TOP), layout_name(BOTTOM), + layout_name(WIDE), layout_name(LAYOUT_ALT_DEFAULT), stack_name(EVEN), + stack_name(DIMINISH), stack_name(DWINDLE), stack_name(STACK_DEFAULT), + COUNT_MASTER_DEFAULT, COUNT_MIN, RATIO_MASTER_DEFAULT, RATIO_MIN, + RATIO_MAX, COUNT_WIDE_LEFT_DEFAULT, COUNT_MIN, RATIO_WIDE_DEFAULT, + RATIO_MIN, RATIO_MAX, INNER_GAPS_DEFAULT, INNER_GAPS_MIN, + OUTER_GAPS_DEFAULT, OUTER_GAPS_MIN, BORDER_WIDTH_DEFAULT, + BORDER_WIDTH_MIN, BORDER_WIDTH_MONOCLE_DEFAULT, + BORDER_WIDTH_MONOCLE_MIN, BORDER_WIDTH_SMART_GAPS_DEFAULT, + BORDER_WIDTH_SMART_GAPS_MIN, BORDER_COLOR_FOCUSED_DEFAULT, + BORDER_COLOR_FOCUSED_MONOCLE_DEFAULT, BORDER_COLOR_UNFOCUSED_DEFAULT, + log_threshold_name(DEBUG), log_threshold_name(INFO), + log_threshold_name(WARNING), log_threshold_name(ERROR), + log_threshold_name(LOG_THRESHOLD_DEFAULT), +======= + fprintf(status == EXIT_SUCCESS ? stdout : stderr, + "Usage: wideriver [OPTIONS...|COMMANDS...]\n" + "\n" + "OPTIONS, startup:\n" + "\n" + " --layout %s|%s|%s|%s|%s|%s %s\n" + " --layout-alt %s|%s|%s|%s|%s|%s %s\n" + "\n" + " --stack %s|%s|%s %s\n" + "\n" + " --count-master count %d %d <= count\n" + " --ratio-master ratio %.02f %.1g <= ratio <= %.1g\n" + "\n" + " --count-wide-left count %d %d <= count\n" + " --ratio-wide ratio %.02f %.1g <= ratio <= %.1g\n" + "\n" + " --(no-)smart-gaps\n" + " --inner-gaps pixels %d %d <= gap size\n" + " --outer-gaps pixels %d %d <= gap size\n" + "\n" + " --border-width pixels %d %d <= width\n" + " --border-width-monocle pixels %d %d <= width\n" + " --border-width-smart-gaps pixels %d %d <= width\n" + "\n" + " --border-color-focused 0xRRGGBB[AA] %s\n" + " --border-color-focused-monocle 0xRRGGBB[AA] %s\n" + " --border-color-unfocused 0xRRGGBB[AA] %s\n" + "\n" + " --layout-format format {l} {l} = layout image, {c} = count, {r} = ratio\n" + "\n" + " --help\n" + " --log-threshold %s|%s|%s|%s %s\n" + " --version\n" + "\n" + "COMMANDS, sent via riverctl(1):\n" + "\n" + " --layout %s|%s|%s|%s|%s|%s\n" + " --layout-toggle \n" + "\n" + " --stack %s|%s|%s\n" + "\n" + " --count [+-]count %d <= count\n" + " --ratio [+-]ratio %.1g <= ratio <= %.1g\n" + "\n", + layout_name(MONOCLE), layout_name(LEFT), layout_name(RIGHT), layout_name(TOP), layout_name(BOTTOM), layout_name(WIDE), layout_name(LAYOUT_DEFAULT), + layout_name(MONOCLE), layout_name(LEFT), layout_name(RIGHT), layout_name(TOP), layout_name(BOTTOM), layout_name(WIDE), layout_name(LAYOUT_ALT_DEFAULT), + stack_name(EVEN), stack_name(DIMINISH), stack_name(DWINDLE), stack_name(STACK_DEFAULT), + COUNT_MASTER_DEFAULT, COUNT_MIN, + RATIO_MASTER_DEFAULT, RATIO_MIN, RATIO_MAX, + COUNT_WIDE_LEFT_DEFAULT, COUNT_MIN, + RATIO_WIDE_DEFAULT, RATIO_MIN, RATIO_MAX, + INNER_GAPS_DEFAULT, INNER_GAPS_MIN, + OUTER_GAPS_DEFAULT, OUTER_GAPS_MIN, + BORDER_WIDTH_DEFAULT, BORDER_WIDTH_MIN, + BORDER_WIDTH_MONOCLE_DEFAULT, BORDER_WIDTH_MONOCLE_MIN, + BORDER_WIDTH_SMART_GAPS_DEFAULT, BORDER_WIDTH_SMART_GAPS_MIN, + BORDER_COLOR_FOCUSED_DEFAULT, + BORDER_COLOR_FOCUSED_MONOCLE_DEFAULT, + BORDER_COLOR_UNFOCUSED_DEFAULT, + log_threshold_name(DEBUG), log_threshold_name(INFO), log_threshold_name(WARNING), log_threshold_name(ERROR), log_threshold_name(LOG_THRESHOLD_DEFAULT), +>>>>>>> refs/remotes/origin/master + + layout_name(MONOCLE), layout_name(LEFT), layout_name(RIGHT), + layout_name(TOP), layout_name(BOTTOM), layout_name(WIDE), + stack_name(DIMINISH), stack_name(DWINDLE), stack_name(STACK_DEFAULT), + COUNT_MIN, RATIO_MIN, RATIO_MAX); + + exit(status); +} + +void usage_defaults(void) { + fprintf(stdout, + "wideriver \\\n" + " --layout %s \\\n" + " --layout-alt %s \\\n" + " --stack %s \\\n" + " --count-master %d \\\n" + " --ratio-master %.02f \\\n" + " --count-wide-left %d \\\n" + " --ratio-wide %.02f \\\n" + " --no-smart-gaps \\\n" + " --inner-gaps %d \\\n" + " --outer-gaps %d \\\n" + " --border-width %d \\\n" + " --border-width-monocle %d \\\n" + " --border-width-smart-gaps %d \\\n" + " --border-color-focused \"%s\" \\\n" + " --border-color-focused-monocle \"%s\" \\\n" + " --border-color-unfocused \"%s\" \\\n" + " --log-threshold %s \\\n" + " > \"/tmp/wideriver.${XDG_VTNR}.${USER}.log\" 2>&1 &\n", + layout_name(LAYOUT_DEFAULT), layout_name(LAYOUT_ALT_DEFAULT), + stack_name(STACK_DEFAULT), COUNT_MASTER_DEFAULT, RATIO_MASTER_DEFAULT, + COUNT_WIDE_LEFT_DEFAULT, RATIO_WIDE_DEFAULT, INNER_GAPS_DEFAULT, + OUTER_GAPS_DEFAULT, BORDER_WIDTH_DEFAULT, + BORDER_WIDTH_MONOCLE_DEFAULT, BORDER_WIDTH_SMART_GAPS_DEFAULT, + BORDER_COLOR_FOCUSED_DEFAULT, BORDER_COLOR_FOCUSED_MONOCLE_DEFAULT, + BORDER_COLOR_UNFOCUSED_DEFAULT, + log_threshold_name(LOG_THRESHOLD_DEFAULT)); + + exit(EXIT_SUCCESS); +} From 7f47ae5bcc4b6a6eae5b06c0072e7135545133a1 Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Sun, 8 Dec 2024 12:45:44 +1100 Subject: [PATCH 10/18] add string_replace --- inc/util.h | 5 +++ src/util.c | 47 ++++++++++++++++++++ tst/GNUmakefile | 2 +- tst/{util.c => file.c} | 0 tst/{util.h => file.h} | 0 tst/tst-util.c | 99 ++++++++++++++++++++++++++++++++++++++++++ tst/wrap-log.c | 2 +- 7 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 inc/util.h create mode 100644 src/util.c rename tst/{util.c => file.c} (100%) rename tst/{util.h => file.h} (100%) create mode 100644 tst/tst-util.c diff --git a/inc/util.h b/inc/util.h new file mode 100644 index 0000000..6ffa7d9 --- /dev/null +++ b/inc/util.h @@ -0,0 +1,5 @@ +// return a copy of src with instances of target substituted with replacement +// returns unmodified on zero length target +// returns NULL on any NULL parameter +// caller frees +char *string_replace(const char * const src, const char * const target, const char * const replacement); diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..0fbd026 --- /dev/null +++ b/src/util.c @@ -0,0 +1,47 @@ +#include +#include +#include + +char *string_replace(const char * const src, const char * const target, const char * const replacement) { + if (!src || !target || !replacement) + return NULL; + + size_t len_res = strlen(src) + 1; + size_t len_target = strlen(target); + size_t len_replacement = strlen(replacement); + + // return unmodified on empty target + if (len_target == 0) { + return strdup(src); + } + + // calculate result length + char *match = (char*)src; + while ((match = strstr(match, target))) { + match += len_target; + len_res += len_replacement - len_target; + } + + // allocate result + char *res = calloc(len_res, sizeof(char)); + char *r = res; + + // copy matches + char *s = (char*)src; + while ((match = strstr(s, target))) { + + // copy up to match + r = stpncpy(r, s, match - s); + + // copy replacement + r = stpncpy(r, replacement, len_replacement); + + // advance src to after match + s = match + len_target; + } + + // copy remainder + r = stpncpy(r, s, len_res - (r - res) - 1); + + return res; +} diff --git a/tst/GNUmakefile b/tst/GNUmakefile index 75dac28..20f1761 100644 --- a/tst/GNUmakefile +++ b/tst/GNUmakefile @@ -4,7 +4,7 @@ PKGS_TST = cmocka CFLAGS += $(foreach p,$(PKGS_TST),$(shell pkg-config --cflags $(p))) LDLIBS += $(foreach p,$(PKGS_TST),$(shell pkg-config --libs $(p))) -OBJS = tst/util.o \ +OBJS = tst/file.o \ $(patsubst %.c,%.o,$(wildcard tst/wrap-*.c)) \ $(filter-out src/main.o,$(SRC_O)) \ $(PRO_O) $(LIB_O) diff --git a/tst/util.c b/tst/file.c similarity index 100% rename from tst/util.c rename to tst/file.c diff --git a/tst/util.h b/tst/file.h similarity index 100% rename from tst/util.h rename to tst/file.h diff --git a/tst/tst-util.c b/tst/tst-util.c new file mode 100644 index 0000000..18e63a8 --- /dev/null +++ b/tst/tst-util.c @@ -0,0 +1,99 @@ +#include "tst.h" +#include "asserts.h" + +#include +#include + +#include "util.h" + +int before_all(void **state) { + return 0; +} + +int after_all(void **state) { + return 0; +} + +int before_each(void **state) { + return 0; +} + +int after_each(void **state) { + return 0; +} + +void string_replace__null(void **state) { + assert_nul(string_replace(NULL, NULL, NULL)); +} + +void string_replace__matches_shorter(void **state) { + char *actual = string_replace("AAA target FOO target BAR target ZZZ", "target", "rep"); + + assert_str_equal(actual, "AAA rep FOO rep BAR rep ZZZ"); + + free(actual); +} + +void string_replace__matches_longer(void **state) { + char *actual = string_replace("target FOO target BAR target", "target", "replacement"); + + assert_str_equal(actual, "replacement FOO replacement BAR replacement"); + + free(actual); +} + +void string_replace__no_matches(void **state) { + char *actual = string_replace("FOO BAR", "target", "replacement"); + + assert_str_equal(actual, "FOO BAR"); + + free(actual); +} + +void string_replace__empty_replacement(void **state) { + char *actual = string_replace("target", "target", ""); + + assert_str_equal(actual, ""); + + free(actual); +} + +void string_replace__empty_target(void **state) { + char *actual = string_replace("target", "", "replacement"); + + assert_str_equal(actual, "target"); + + free(actual); +} + +void string_replace__same(void **state) { + char *actual = string_replace("target FOO target BAR target", "target", "target"); + + assert_str_equal(actual, "target FOO target BAR target"); + + free(actual); +} + +void string_replace__replacement_matches(void **state) { + char *actual = string_replace("target", "target", "FOO target BAR"); + + assert_str_equal(actual, "FOO target BAR"); + + free(actual); +} + +int main(void) { + const struct CMUnitTest tests[] = { + TEST(string_replace__null), + TEST(string_replace__matches_shorter), + TEST(string_replace__matches_longer), + TEST(string_replace__no_matches), + TEST(string_replace__empty_replacement), + TEST(string_replace__empty_target), + TEST(string_replace__same), + TEST(string_replace__replacement_matches), + }; + + return RUN(tests); +} + diff --git a/tst/wrap-log.c b/tst/wrap-log.c index b7b1188..c035e09 100644 --- a/tst/wrap-log.c +++ b/tst/wrap-log.c @@ -7,7 +7,7 @@ #include #include "enum.h" -#include "util.h" +#include "file.h" // 0 unused, 1 DEBUG, 4 ERROR static char b[5][262144] = { 0 }; From 7c8cc9e4d2ca1cb4a770c87f7220fe50096b9b0a Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Mon, 9 Dec 2024 14:35:01 +1100 Subject: [PATCH 11/18] layout_description uses string_replace --- src/layout.c | 123 ++++++++++++++--------------------------------- tst/tst-layout.c | 30 ++++++++---- 2 files changed, 55 insertions(+), 98 deletions(-) diff --git a/src/layout.c b/src/layout.c index b47a87e..ff36fdd 100644 --- a/src/layout.c +++ b/src/layout.c @@ -1,6 +1,7 @@ #include #include #include +#include #include "river-layout-v3.h" @@ -10,6 +11,7 @@ #include "slist.h" #include "tag.h" #include "cfg.h" +#include "util.h" #include "layout.h" @@ -51,6 +53,9 @@ const char *layout_image(const struct Demand* const demand, const struct Tag* co snprintf(desc, sizeof(desc), "├─┤ ├─┤"); } break; + default: + return NULL; + break; } return desc; @@ -60,108 +65,50 @@ const char *layout_description(const struct Demand* const demand, const struct T if (!demand || !tag) return ""; - char name[8] = ""; - switch (tag->layout_cur) { - case LEFT: - snprintf(name, sizeof(name), "left"); - break; - case RIGHT: - snprintf(name, sizeof(name), "right"); - break; - case TOP: - snprintf(name, sizeof(name), "top"); - break; - case BOTTOM: - snprintf(name, sizeof(name), "bottom"); - break; - case WIDE: - snprintf(name, sizeof(name), "wide"); - break; - case MONOCLE: - snprintf(name, sizeof(name), "monocle"); - break; - } - double ratio = 0; - unsigned int count = 0; - const char *image = layout_image(demand, tag); + char count[10] = "{c}"; + char ratio[10] = "{r}"; switch(tag->layout_cur) { case LEFT: case RIGHT: case TOP: case BOTTOM: - count = tag->count_master; - ratio = tag->ratio_master; + snprintf(count, 10, "%d", tag->count_master); + snprintf(ratio, 10, "%g", tag->ratio_master); break; case WIDE: - count = tag->count_wide_left; - ratio = tag->ratio_wide; + snprintf(count, 10, "%d", tag->count_wide_left); + snprintf(ratio, 10, "%g", tag->ratio_wide); break; case MONOCLE: - count = demand->view_count; - ratio = 1.0; + snprintf(count, 10, "%d", demand->view_count); + snprintf(ratio, 10, "%g", 1.0); + break; + default: break; } - static char desc[LAYOUT_FORMAT_LEN+5]; - int j = 0; - int escaped = 0; - bool in_brackets = false; - for (int i = 0; cfg->layout_format[i] != '\0'; i++) { - if (escaped > 0) { - switch (cfg->layout_format[i]) { - case 'n': - desc[j] = '\n'; - break; - case 't': - desc[j] = '\t'; - break; - case 'r': - desc[j] = '\r'; - break; - case 'v': - desc[j] = '\v'; - break; - case '{': - desc[j] = '{'; - break; - case '}': - desc[j] = '}'; - break; - default: - break; - } - j++; - } else if (cfg->layout_format[i] == '{') - in_brackets = true; - else if (cfg->layout_format[i] == '}' && in_brackets) { - switch (cfg->layout_format[i-1]) { - case RATIO: - j += snprintf(desc+j, sizeof(desc)-j, "%g", ratio); - break; - case COUNT: - j += snprintf(desc+j, sizeof(desc)-j, "%u", count); - break; - case LAYOUT: - j += snprintf(desc+j, sizeof(desc)-j, "%s", image); - break; - case NAME: - j += snprintf(desc+j, sizeof(desc)-j, "%s", name); - } - in_brackets = false; - } else if (cfg->layout_format[i] == '\\' && !in_brackets) - escaped = 2; - else if (!in_brackets) { - desc[j] = cfg->layout_format[i]; - j++; - } + const char *image = layout_image(demand, tag); + const char *name = layout_name(tag->layout_cur); - escaped -= 1; - if (escaped < 0) - escaped = 0; - } + static char desc[LAYOUT_FORMAT_LEN + 5]; + + char *counted, *ratioed, *imaged, *named; + char *escaped_n, *escaped_t, *escaped_r, *escaped_v; + + counted = string_replace(cfg->layout_format, "{c}", count); + ratioed = string_replace(counted, "{r}", ratio); + imaged = string_replace(ratioed, "{l}", image ? image : "{l}"); + named = string_replace(imaged, "{n}", name ? name : "{n}"); + + escaped_n = string_replace(named, "\\n", "\n"); + escaped_t = string_replace(escaped_n, "\\t", "\t"); + escaped_r = string_replace(escaped_t, "\\r", "\r"); + escaped_v = string_replace(escaped_r, "\\v", "\v"); + + strncpy(desc, escaped_v, LAYOUT_FORMAT_LEN + 5); - // terminate the string - desc[j] = '\0'; + free(counted); free(ratioed); free(imaged); free(named); + free(escaped_n); free(escaped_t); free(escaped_r); free(escaped_v); return desc; } diff --git a/tst/tst-layout.c b/tst/tst-layout.c index e7c7729..53439a2 100644 --- a/tst/tst-layout.c +++ b/tst/tst-layout.c @@ -82,41 +82,51 @@ void layout_image__monocle(void **state) { assert_str_equal(layout_image(&demand2, &tag), "│ 2 │"); } +void layout_image__invalid(void **state) { + struct Tag tag = { .layout_cur = 0, }; + + struct Demand demand1 = { .view_count = 1 }; + assert_nul(layout_image(&demand1, &tag)); + + struct Demand demand2 = { .view_count = 2 }; + assert_nul(layout_image(&demand2, &tag)); +} + void layout_description_info__none(void **state) { struct Demand demand = { 0 }; struct Tag tag = { 0 }; - assert_true(cfg_set_layout_format("foo")); + assert_true(cfg_set_layout_format("{c}{l}{r}{n}")); - assert_str_equal(layout_description(&demand, &tag), "foo"); + assert_str_equal(layout_description(&demand, &tag), "{c}{l}{r}{n}"); } void layout_description_info__monocle(void **state) { struct Demand demand = { .view_count = 9, }; struct Tag tag = { .layout_cur = MONOCLE, }; - assert_true(cfg_set_layout_format("image: {l} count: {c} ratio: {r} end")); + assert_true(cfg_set_layout_format("image: {l} count: {c} ratio: {r} name: {n} end")); // TODO #21 remove the monocle count - assert_str_equal(layout_description(&demand, &tag), "image: │ 9 │ count: 9 ratio: 1 end"); + assert_str_equal(layout_description(&demand, &tag), "image: │ 9 │ count: 9 ratio: 1 name: monocle end"); } void layout_description_info__lrtb(void **state) { struct Demand demand = { .view_count = 9, }; struct Tag tag = { .layout_cur = LEFT, .count_master = 2, .count_wide_left = 3, .ratio_master = 0.4, .ratio_wide = 0.5, }; - assert_true(cfg_set_layout_format("image: {l} count: {c} ratio: {r} end")); + assert_true(cfg_set_layout_format("image: {l} count: {c} ratio: {r} name: {n} end")); - assert_str_equal(layout_description(&demand, &tag), "image: │ ├─┤ count: 2 ratio: 0.4 end"); + assert_str_equal(layout_description(&demand, &tag), "image: │ ├─┤ count: 2 ratio: 0.4 name: left end"); } void layout_description_info__wide(void **state) { struct Demand demand = { .view_count = 9, }; struct Tag tag = { .layout_cur = WIDE, .count_master = 2, .count_wide_left = 3, .ratio_master = 0.4, .ratio_wide = 0.5, }; - assert_true(cfg_set_layout_format("image: {l} count: {c} ratio: {r} end")); + assert_true(cfg_set_layout_format("image: {l} count: {c} ratio: {r} name: {n} end")); - assert_str_equal(layout_description(&demand, &tag), "image: ├─┤ ├─┤ count: 3 ratio: 0.5 end"); + assert_str_equal(layout_description(&demand, &tag), "image: ├─┤ ├─┤ count: 3 ratio: 0.5 name: wide end"); } void layout_description_info__invalids(void **state) { @@ -126,8 +136,7 @@ void layout_description_info__invalids(void **state) { // manually set an invalid layout strcpy(((struct Cfg*)cfg)->layout_format, "image: {foo} count: {c c} ratio: {rATIO} end"); - // TODO #21 this is not correct; count should not be substituted - assert_str_equal(layout_description(&demand, &tag), "image: count: 3 ratio: end"); + assert_str_equal(layout_description(&demand, &tag), "image: {foo} count: {c c} ratio: {rATIO} end"); } void layout_description_info__escapes(void **state) { @@ -147,6 +156,7 @@ int main(void) { TEST(layout_image__bottom), TEST(layout_image__wide), TEST(layout_image__monocle), + TEST(layout_image__invalid), TEST(layout_description_info__none), TEST(layout_description_info__monocle), From b26038b3adbeaa9b3b7dd6038fd4c2e830d889be Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Mon, 9 Dec 2024 15:43:43 +1100 Subject: [PATCH 12/18] layout_description uses string_replace --- .cppcheck.supp | 2 +- src/layout.c | 91 ++++++++++++++++++++++++++++---------------------- 2 files changed, 53 insertions(+), 40 deletions(-) diff --git a/.cppcheck.supp b/.cppcheck.supp index d355204..a5d36f2 100644 --- a/.cppcheck.supp +++ b/.cppcheck.supp @@ -1,7 +1,7 @@ unusedFunction:pro/*.h unusedFunction:tst/asserts.h -unusedFunction:tst/util.c +unusedFunction:tst/file.c unusedFunction:tst/wrap-*.c # TODO use --check-level=exhaustive once available in CI diff --git a/src/layout.c b/src/layout.c index ff36fdd..c9d196a 100644 --- a/src/layout.c +++ b/src/layout.c @@ -9,6 +9,7 @@ #include "enum.h" #include "log.h" #include "slist.h" +#include "stable.h" #include "tag.h" #include "cfg.h" #include "util.h" @@ -16,41 +17,41 @@ #include "layout.h" const char *layout_image(const struct Demand* const demand, const struct Tag* const tag) { - static char desc[20]; + static char image[20]; switch(tag->layout_cur) { case LEFT: if (tag->count_master == 0 ) { - snprintf(desc, sizeof(desc), "│├──┤"); + snprintf(image, sizeof(image), "│├──┤"); } else { - snprintf(desc, sizeof(desc), "│ ├─┤"); + snprintf(image, sizeof(image), "│ ├─┤"); } break; case RIGHT: if (tag->count_master == 0 ) { - snprintf(desc, sizeof(desc), "├──┤│"); + snprintf(image, sizeof(image), "├──┤│"); } else { - snprintf(desc, sizeof(desc), "├─┤ │"); + snprintf(image, sizeof(image), "├─┤ │"); } break; case TOP: - snprintf(desc, sizeof(desc), "├─┬─┤"); + snprintf(image, sizeof(image), "├─┬─┤"); break; case BOTTOM: - snprintf(desc, sizeof(desc), "├─┴─┤"); + snprintf(image, sizeof(image), "├─┴─┤"); break; case MONOCLE: if (demand->view_count > 1) { - snprintf(desc, sizeof(desc), "│ %u │", demand->view_count); + snprintf(image, sizeof(image), "│ %u │", demand->view_count); } else { - snprintf(desc, sizeof(desc), "│ │"); + snprintf(image, sizeof(image), "│ │"); } break; case WIDE: if (tag->count_wide_left == 0) { - snprintf(desc, sizeof(desc), "││ ├─┤"); + snprintf(image, sizeof(image), "││ ├─┤"); } else { - snprintf(desc, sizeof(desc), "├─┤ ├─┤"); + snprintf(image, sizeof(image), "├─┤ ├─┤"); } break; default: @@ -58,57 +59,69 @@ const char *layout_image(const struct Demand* const demand, const struct Tag* co break; } - return desc; + return image; } const char *layout_description(const struct Demand* const demand, const struct Tag* const tag) { if (!demand || !tag) return ""; - char count[10] = "{c}"; - char ratio[10] = "{r}"; + const struct STable *replacements = stable_init(8, 8, true); + stable_put(replacements, "\\n", "\n"); + stable_put(replacements, "\\t", "\t"); + stable_put(replacements, "\\r", "\r"); + stable_put(replacements, "\\v", "\v"); + + // layout details + char ratio[4], count[3]; switch(tag->layout_cur) { case LEFT: case RIGHT: case TOP: case BOTTOM: - snprintf(count, 10, "%d", tag->count_master); - snprintf(ratio, 10, "%g", tag->ratio_master); + snprintf(count, sizeof(count), "%u", tag->count_master); + stable_put(replacements, "{c}", count); + + snprintf(ratio, sizeof(ratio), "%g", tag->ratio_master); + stable_put(replacements, "{r}", ratio); break; case WIDE: - snprintf(count, 10, "%d", tag->count_wide_left); - snprintf(ratio, 10, "%g", tag->ratio_wide); + snprintf(count, sizeof(count), "%u", tag->count_wide_left); + stable_put(replacements, "{c}", count); + + snprintf(ratio, sizeof(ratio), "%g", tag->ratio_wide); + stable_put(replacements, "{r}", ratio); break; case MONOCLE: - snprintf(count, 10, "%d", demand->view_count); - snprintf(ratio, 10, "%g", 1.0); + snprintf(count, sizeof(count), "%u", demand->view_count); + stable_put(replacements, "{c}", count); + + stable_put(replacements, "{r}", "1"); break; default: break; } - const char *image = layout_image(demand, tag); - const char *name = layout_name(tag->layout_cur); + // layout + stable_put(replacements, "{l}", layout_image(demand, tag)); + stable_put(replacements, "{n}", layout_name(tag->layout_cur)); + + // perform all replacements + char *res = strdup(cfg->layout_format); + for (const struct STableIter *i = stable_iter(replacements); i; i = stable_next(i)) { + if (i->val) { + char *next = string_replace(res, i->key, i->val); + free(res); + res = next; + } + } + // populate the output static char desc[LAYOUT_FORMAT_LEN + 5]; + strncpy(desc, res, LAYOUT_FORMAT_LEN + 5); + free(res); - char *counted, *ratioed, *imaged, *named; - char *escaped_n, *escaped_t, *escaped_r, *escaped_v; - - counted = string_replace(cfg->layout_format, "{c}", count); - ratioed = string_replace(counted, "{r}", ratio); - imaged = string_replace(ratioed, "{l}", image ? image : "{l}"); - named = string_replace(imaged, "{n}", name ? name : "{n}"); - - escaped_n = string_replace(named, "\\n", "\n"); - escaped_t = string_replace(escaped_n, "\\t", "\t"); - escaped_r = string_replace(escaped_t, "\\r", "\r"); - escaped_v = string_replace(escaped_r, "\\v", "\v"); - - strncpy(desc, escaped_v, LAYOUT_FORMAT_LEN + 5); - - free(counted); free(ratioed); free(imaged); free(named); - free(escaped_n); free(escaped_t); free(escaped_r); free(escaped_v); + stable_free(replacements); return desc; } From 40046c243f49288d1fe709b6f251eff51a6ad98a Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Mon, 9 Dec 2024 16:11:45 +1100 Subject: [PATCH 13/18] layout_description and image are caller frees --- inc/layout.h | 6 ++- src/layout.c | 61 ++++++++++++++------------- src/listener_river_layout.c | 4 +- tst/tst-layout.c | 82 +++++++++++++++++++++++++++++-------- 4 files changed, 105 insertions(+), 48 deletions(-) diff --git a/inc/layout.h b/inc/layout.h index 2b78568..e23e574 100644 --- a/inc/layout.h +++ b/inc/layout.h @@ -22,7 +22,8 @@ struct Box { }; // return a river layout name -const char *layout_description(const struct Demand *demand, const struct Tag *tag); +// caller frees +char *layout_description(const struct Demand *demand, const struct Tag *tag); // populate views with Box for river layout push dimensions, caller frees struct SList *layout(const struct Demand *demand, const struct Tag *tag); @@ -31,7 +32,8 @@ struct SList *layout(const struct Demand *demand, const struct Tag *tag); void push(const struct SList *views, struct river_layout_v3 *river_layout_v3, const uint32_t serial); // layout image -const char *layout_image(const struct Demand* const demand, const struct Tag* const tag); +// caller frees +char *layout_image(const struct Demand* const demand, const struct Tag* const tag); // fully rendered layout const char *description_info(const struct Demand* const demand, const struct Tag* const tag); diff --git a/src/layout.c b/src/layout.c index c9d196a..b4039c0 100644 --- a/src/layout.c +++ b/src/layout.c @@ -16,56 +16,58 @@ #include "layout.h" -const char *layout_image(const struct Demand* const demand, const struct Tag* const tag) { - static char image[20]; +char *layout_image(const struct Demand* const demand, const struct Tag* const tag) { + char *image = calloc(20, sizeof(char)); switch(tag->layout_cur) { case LEFT: if (tag->count_master == 0 ) { - snprintf(image, sizeof(image), "│├──┤"); + snprintf(image, 20, "│├──┤"); } else { - snprintf(image, sizeof(image), "│ ├─┤"); + snprintf(image, 20, "│ ├─┤"); } break; case RIGHT: if (tag->count_master == 0 ) { - snprintf(image, sizeof(image), "├──┤│"); + snprintf(image, 20, "├──┤│"); } else { - snprintf(image, sizeof(image), "├─┤ │"); + snprintf(image, 20, "├─┤ │"); } break; case TOP: - snprintf(image, sizeof(image), "├─┬─┤"); + snprintf(image, 20, "├─┬─┤"); break; case BOTTOM: - snprintf(image, sizeof(image), "├─┴─┤"); + snprintf(image, 20, "├─┴─┤"); break; case MONOCLE: if (demand->view_count > 1) { - snprintf(image, sizeof(image), "│ %u │", demand->view_count); + snprintf(image, 20, "│ %u │", demand->view_count); } else { - snprintf(image, sizeof(image), "│ │"); + snprintf(image, 20, "│ │"); } break; case WIDE: if (tag->count_wide_left == 0) { - snprintf(image, sizeof(image), "││ ├─┤"); + snprintf(image, 20, "││ ├─┤"); } else { - snprintf(image, sizeof(image), "├─┤ ├─┤"); + snprintf(image, 20, "├─┤ ├─┤"); } break; default: - return NULL; + free(image); + image = NULL; break; } return image; } -const char *layout_description(const struct Demand* const demand, const struct Tag* const tag) { +char *layout_description(const struct Demand* const demand, const struct Tag* const tag) { if (!demand || !tag) return ""; + // escapes const struct STable *replacements = stable_init(8, 8, true); stable_put(replacements, "\\n", "\n"); stable_put(replacements, "\\t", "\t"); @@ -73,7 +75,7 @@ const char *layout_description(const struct Demand* const demand, const struct T stable_put(replacements, "\\v", "\v"); // layout details - char ratio[4], count[3]; + char ratio[13], count[3]; switch(tag->layout_cur) { case LEFT: case RIGHT: @@ -102,25 +104,26 @@ const char *layout_description(const struct Demand* const demand, const struct T break; } - // layout - stable_put(replacements, "{l}", layout_image(demand, tag)); - stable_put(replacements, "{n}", layout_name(tag->layout_cur)); + // layout info + char *image = layout_image(demand, tag); + if (image) { + stable_put(replacements, "{l}", image); + } + + const char *name = layout_name(tag->layout_cur); + if (name) { + stable_put(replacements, "{n}", name); + } // perform all replacements - char *res = strdup(cfg->layout_format); + char *desc = strdup(cfg->layout_format); for (const struct STableIter *i = stable_iter(replacements); i; i = stable_next(i)) { - if (i->val) { - char *next = string_replace(res, i->key, i->val); - free(res); - res = next; - } + char *next = string_replace(desc, i->key, i->val); + free(desc); + desc = next; } - // populate the output - static char desc[LAYOUT_FORMAT_LEN + 5]; - strncpy(desc, res, LAYOUT_FORMAT_LEN + 5); - free(res); - + free(image); stable_free(replacements); return desc; diff --git a/src/listener_river_layout.c b/src/listener_river_layout.c index 82950f6..f301818 100644 --- a/src/listener_river_layout.c +++ b/src/listener_river_layout.c @@ -48,13 +48,15 @@ static void layout_handle_layout_demand(void *data, slist_free_vals(&boxes, NULL); // river layout name - const char *layout_name = layout_description(&demand, tag); + char *layout_name = layout_description(&demand, tag); // commit river_layout_v3_commit(output->river_layout, layout_name, serial); // maybe style displ_request_style(output, tag, view_count); + + free(layout_name); } static void layout_handle_namespace_in_use(void *data, diff --git a/tst/tst-layout.c b/tst/tst-layout.c index 53439a2..647cbb9 100644 --- a/tst/tst-layout.c +++ b/tst/tst-layout.c @@ -28,58 +28,84 @@ int after_each(void **state) { void layout_image__left(void **state) { struct Demand demand = { 0 }; struct Tag tag = { .layout_cur = LEFT, }; + char *actual; tag.count_master = 0; - assert_str_equal(layout_image(&demand, &tag), "│├──┤"); + actual = layout_image(&demand, &tag); + assert_str_equal(actual, "│├──┤"); + free(actual); tag.count_master = 1; - assert_str_equal(layout_image(&demand, &tag), "│ ├─┤"); + actual = layout_image(&demand, &tag); + assert_str_equal(actual, "│ ├─┤"); + free(actual); } void layout_image__right(void **state) { struct Demand demand = { 0 }; struct Tag tag = { .layout_cur = RIGHT, }; + char *actual; tag.count_master = 0; - assert_str_equal(layout_image(&demand, &tag), "├──┤│"); + actual = layout_image(&demand, &tag); + assert_str_equal(actual, "├──┤│"); + free(actual); tag.count_master = 1; - assert_str_equal(layout_image(&demand, &tag), "├─┤ │"); + actual = layout_image(&demand, &tag); + assert_str_equal(actual, "├─┤ │"); + free(actual); } void layout_image__top(void **state) { struct Demand demand = { 0 }; struct Tag tag = { .layout_cur = TOP, }; + char *actual; - assert_str_equal(layout_image(&demand, &tag), "├─┬─┤"); + actual = layout_image(&demand, &tag); + assert_str_equal(actual, "├─┬─┤"); + free(actual); } void layout_image__bottom(void **state) { struct Demand demand = { 0 }; struct Tag tag = { .layout_cur = BOTTOM, }; + char *actual; - assert_str_equal(layout_image(&demand, &tag), "├─┴─┤"); + actual = layout_image(&demand, &tag); + assert_str_equal(actual, "├─┴─┤"); + free(actual); } void layout_image__wide(void **state) { struct Demand demand = { 0 }; struct Tag tag = { .layout_cur = WIDE, }; + char *actual; tag.count_wide_left = 0; - assert_str_equal(layout_image(&demand, &tag), "││ ├─┤"); + actual = layout_image(&demand, &tag); + assert_str_equal(actual, "││ ├─┤"); + free(actual); tag.count_wide_left = 1; - assert_str_equal(layout_image(&demand, &tag), "├─┤ ├─┤"); + actual = layout_image(&demand, &tag); + assert_str_equal(actual, "├─┤ ├─┤"); + free(actual); } void layout_image__monocle(void **state) { struct Tag tag = { .layout_cur = MONOCLE, }; + char *actual; struct Demand demand1 = { .view_count = 1 }; - assert_str_equal(layout_image(&demand1, &tag), "│ │"); + actual = layout_image(&demand1, &tag); + assert_str_equal(actual, "│ │"); + free(actual); struct Demand demand2 = { .view_count = 2 }; - assert_str_equal(layout_image(&demand2, &tag), "│ 2 │"); + actual = layout_image(&demand2, &tag); + assert_str_equal(actual, "│ 2 │"); + free(actual); } void layout_image__invalid(void **state) { @@ -98,7 +124,11 @@ void layout_description_info__none(void **state) { assert_true(cfg_set_layout_format("{c}{l}{r}{n}")); - assert_str_equal(layout_description(&demand, &tag), "{c}{l}{r}{n}"); + char *actual = layout_description(&demand, &tag); + + assert_str_equal(actual, "{c}{l}{r}{n}"); + + free(actual); } void layout_description_info__monocle(void **state) { @@ -107,8 +137,12 @@ void layout_description_info__monocle(void **state) { assert_true(cfg_set_layout_format("image: {l} count: {c} ratio: {r} name: {n} end")); + char *actual = layout_description(&demand, &tag); + // TODO #21 remove the monocle count - assert_str_equal(layout_description(&demand, &tag), "image: │ 9 │ count: 9 ratio: 1 name: monocle end"); + // assert_str_equal(actual, "image: │ 9 │ count: 9 ratio: 1 name: monocle end"); + + free(actual); } void layout_description_info__lrtb(void **state) { @@ -117,7 +151,11 @@ void layout_description_info__lrtb(void **state) { assert_true(cfg_set_layout_format("image: {l} count: {c} ratio: {r} name: {n} end")); - assert_str_equal(layout_description(&demand, &tag), "image: │ ├─┤ count: 2 ratio: 0.4 name: left end"); + char *actual = layout_description(&demand, &tag); + + assert_str_equal(actual, "image: │ ├─┤ count: 2 ratio: 0.4 name: left end"); + + free(actual); } void layout_description_info__wide(void **state) { @@ -126,7 +164,11 @@ void layout_description_info__wide(void **state) { assert_true(cfg_set_layout_format("image: {l} count: {c} ratio: {r} name: {n} end")); - assert_str_equal(layout_description(&demand, &tag), "image: ├─┤ ├─┤ count: 3 ratio: 0.5 name: wide end"); + char *actual = layout_description(&demand, &tag); + + assert_str_equal(actual, "image: ├─┤ ├─┤ count: 3 ratio: 0.5 name: wide end"); + + free(actual); } void layout_description_info__invalids(void **state) { @@ -136,7 +178,11 @@ void layout_description_info__invalids(void **state) { // manually set an invalid layout strcpy(((struct Cfg*)cfg)->layout_format, "image: {foo} count: {c c} ratio: {rATIO} end"); - assert_str_equal(layout_description(&demand, &tag), "image: {foo} count: {c c} ratio: {rATIO} end"); + char *actual = layout_description(&demand, &tag); + + assert_str_equal(actual, "image: {foo} count: {c c} ratio: {rATIO} end"); + + free(actual); } void layout_description_info__escapes(void **state) { @@ -145,7 +191,11 @@ void layout_description_info__escapes(void **state) { assert_true(cfg_set_layout_format("i: {l}\\ncount:\\t{c}\\rratio:\\v{r} end")); - assert_str_equal(layout_description(&demand, &tag), "i: ├─┤ ├─┤\ncount:\t3\rratio:\v0.5 end"); + char *actual = layout_description(&demand, &tag); + + assert_str_equal(actual, "i: ├─┤ ├─┤\ncount:\t3\rratio:\v0.5 end"); + + free(actual); } int main(void) { From bf749b98276a182bf58f01368de63e659e07f4cf Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Mon, 9 Dec 2024 16:18:53 +1100 Subject: [PATCH 14/18] temporarily disable gcc due to false positive stringop-truncation --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 74e201c..ed89771 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,8 +16,8 @@ jobs: run: | sudo add-apt-repository universe sudo apt install clang wayland-protocols libwlroots-dev libcmocka-dev cppcheck valgrind -y - - name: gcc - run: make CC=gcc CXX=g++ clean wideriver + # - name: gcc + # run: make CC=gcc CXX=g++ clean wideriver - name: clang run: make CC=clang CXX=clang++ clean wideriver - name: test From 136b995987404c91833ce4fe42730802a977f7f7 Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Mon, 9 Dec 2024 16:25:02 +1100 Subject: [PATCH 15/18] temporarily use clang for test-vg due to false positive stringop-truncation --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed89771..a048478 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,5 +25,5 @@ jobs: - name: cppcheck run: make CC=clang CXX=clang++ cppcheck - name: test valgrind - run: make CC=gcc CXX=g++ clean test-vg + run: make CC=clang CXX=clang++ clean test-vg From f63c976fb24779971855b7fab3d523d76df7fd0a Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Mon, 9 Dec 2024 16:31:05 +1100 Subject: [PATCH 16/18] Revert "temporarily use clang for test-vg due to false positive stringop-truncation" This reverts commit 136b995987404c91833ce4fe42730802a977f7f7. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a048478..ed89771 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,5 +25,5 @@ jobs: - name: cppcheck run: make CC=clang CXX=clang++ cppcheck - name: test valgrind - run: make CC=clang CXX=clang++ clean test-vg + run: make CC=gcc CXX=g++ clean test-vg From 93008bbfb046c31db362624a8ee323077895dad0 Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Fri, 13 Dec 2024 19:27:59 +1100 Subject: [PATCH 17/18] Revert "temporarily disable gcc due to false positive stringop-truncation" This reverts commit bf749b98276a182bf58f01368de63e659e07f4cf. --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed89771..74e201c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,8 +16,8 @@ jobs: run: | sudo add-apt-repository universe sudo apt install clang wayland-protocols libwlroots-dev libcmocka-dev cppcheck valgrind -y - # - name: gcc - # run: make CC=gcc CXX=g++ clean wideriver + - name: gcc + run: make CC=gcc CXX=g++ clean wideriver - name: clang run: make CC=clang CXX=clang++ clean wideriver - name: test From 0f6b118fbe9010c10940eeb00407b9e1f0ed4963 Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Fri, 13 Dec 2024 19:28:46 +1100 Subject: [PATCH 18/18] use memcpy (recommended) instead of stpncpy to avoid spurious gcc warnings --- src/util.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/util.c b/src/util.c index 0fbd026..a0cb1dd 100644 --- a/src/util.c +++ b/src/util.c @@ -31,17 +31,20 @@ char *string_replace(const char * const src, const char * const target, const ch while ((match = strstr(s, target))) { // copy up to match - r = stpncpy(r, s, match - s); + memcpy(r, s, match - s); + r += match - s; // copy replacement - r = stpncpy(r, replacement, len_replacement); + memcpy(r, replacement, len_replacement); + r += len_replacement; // advance src to after match s = match + len_target; } - // copy remainder - r = stpncpy(r, s, len_res - (r - res) - 1); + // copy remainder, ensuring terminated + memcpy(r, s, len_res - (r - res) - 1); + res[len_res - 1] = '\0'; return res; }