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/.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/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..fca7545 --- /dev/null +++ b/flake.nix @@ -0,0 +1,90 @@ +{ + 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 + cppcheck + 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/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..e78830f 100644 --- a/inc/enum.h +++ b/inc/enum.h @@ -27,6 +27,15 @@ enum LogThreshold { LOG_THRESHOLD_DEFAULT = INFO, }; +enum LayoutFormatElem { + LAYOUT = 'l', + COUNT = 'c', + RATIO = 'r', + NAME = 'n', +}; + +#define DEFAULT_LAYOUT_FORMAT "{l}" + const char *layout_name(const enum Layout layout); enum Layout layout_val(const char *name); diff --git a/inc/layout.h b/inc/layout.h index 3d54577..e23e574 100644 --- a/inc/layout.h +++ b/inc/layout.h @@ -21,8 +21,9 @@ struct Box { uint32_t height; }; -// return a river layout name, caller frees -const char *layout_description(const struct Demand *demand, const struct Tag *tag); +// return a river layout name +// 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); @@ -30,5 +31,12 @@ 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 +// 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); + #endif // LAYOUT_H 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/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/args.c b/src/args.c index 28b7066..61dc82d 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); @@ -202,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/src/cfg.c b/src/cfg.c index ea6acd3..37eb6f6 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,39 @@ 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] != NAME && + 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; + } + + strncpy(c.layout_format, s, sizeof(c.layout_format)); + 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/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 67c5741..b4039c0 100644 --- a/src/layout.c +++ b/src/layout.c @@ -1,5 +1,7 @@ #include +#include #include +#include #include "river-layout-v3.h" @@ -7,88 +9,124 @@ #include "enum.h" #include "log.h" #include "slist.h" +#include "stable.h" #include "tag.h" +#include "cfg.h" +#include "util.h" #include "layout.h" -const char *description_info(const struct Demand* const demand, const struct Tag* const tag) { - static char desc[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(desc, sizeof(desc), "│├──┤"); + snprintf(image, 20, "│├──┤"); } else { - snprintf(desc, sizeof(desc), "│ ├─┤"); + snprintf(image, 20, "│ ├─┤"); } break; case RIGHT: if (tag->count_master == 0 ) { - snprintf(desc, sizeof(desc), "├──┤│"); + snprintf(image, 20, "├──┤│"); } else { - snprintf(desc, sizeof(desc), "├─┤ │"); + snprintf(image, 20, "├─┤ │"); } break; case TOP: - snprintf(desc, sizeof(desc), "├─┬─┤"); + snprintf(image, 20, "├─┬─┤"); break; case BOTTOM: - snprintf(desc, sizeof(desc), "├─┴─┤"); + snprintf(image, 20, "├─┴─┤"); break; case MONOCLE: if (demand->view_count > 1) { - snprintf(desc, sizeof(desc), "│ %u │", demand->view_count); + snprintf(image, 20, "│ %u │", demand->view_count); } else { - snprintf(desc, sizeof(desc), "│ │"); + snprintf(image, 20, "│ │"); } break; case WIDE: if (tag->count_wide_left == 0) { - snprintf(desc, sizeof(desc), "││ ├─┤"); + snprintf(image, 20, "││ ├─┤"); } else { - snprintf(desc, sizeof(desc), "├─┤ ├─┤"); + snprintf(image, 20, "├─┤ ├─┤"); } break; + default: + free(image); + image = NULL; + break; } - return desc; + return image; } -const char *description_debug(const struct Demand* const demand, const struct Tag* const tag) { - static char desc[128]; +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"); + stable_put(replacements, "\\r", "\r"); + stable_put(replacements, "\\v", "\v"); + // layout details + char ratio[13], count[3]; 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); + 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(desc, sizeof(desc), "%s %u %g ", description_info(demand, tag), tag->count_wide_left, 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(desc, sizeof(desc), "%s", description_info(demand, tag)); + snprintf(count, sizeof(count), "%u", demand->view_count); + stable_put(replacements, "{c}", count); + + stable_put(replacements, "{r}", "1"); + break; + default: break; } - return desc; -} - -const char *layout_description(const struct Demand* const demand, const struct Tag* const tag) { + // layout info + char *image = layout_image(demand, tag); + if (image) { + stable_put(replacements, "{l}", image); + } - if (!demand || !tag) - return ""; + const char *name = layout_name(tag->layout_cur); + if (name) { + stable_put(replacements, "{n}", name); + } - switch (log_get_threshold()) { - case DEBUG: - return description_debug(demand, tag); - case INFO: - case WARNING: - case ERROR: - default: - return description_info(demand, tag); + // perform all replacements + char *desc = strdup(cfg->layout_format); + for (const struct STableIter *i = stable_iter(replacements); i; i = stable_next(i)) { + char *next = string_replace(desc, i->key, i->val); + free(desc); + desc = next; } + + free(image); + stable_free(replacements); + + return desc; } struct SList *layout(const struct Demand *demand, const struct Tag *tag) { 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/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/usage.c b/src/usage.c index ed22531..f668acf 100644 --- a/src/usage.c +++ b/src/usage.c @@ -17,28 +17,43 @@ 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" "\n" + " --layout-format format {l} {l} = layout image, {c} = count, {r} = ratio, {n} = name\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" + "\n" "COMMANDS, sent via riverctl(1):\n" "\n" " --layout %s|%s|%s|%s|%s|%s\n" @@ -46,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); } @@ -95,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/util.c b/src/util.c new file mode 100644 index 0000000..a0cb1dd --- /dev/null +++ b/src/util.c @@ -0,0 +1,50 @@ +#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 + memcpy(r, s, match - s); + r += match - s; + + // copy replacement + memcpy(r, replacement, len_replacement); + r += len_replacement; + + // advance src to after match + s = match + len_target; + } + + // copy remainder, ensuring terminated + memcpy(r, s, len_res - (r - res) - 1); + res[len_res - 1] = '\0'; + + 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-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..647cbb9 --- /dev/null +++ b/tst/tst-layout.c @@ -0,0 +1,221 @@ +#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, }; + char *actual; + + tag.count_master = 0; + actual = layout_image(&demand, &tag); + assert_str_equal(actual, "│├──┤"); + free(actual); + + tag.count_master = 1; + 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; + actual = layout_image(&demand, &tag); + assert_str_equal(actual, "├──┤│"); + free(actual); + + tag.count_master = 1; + 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; + + 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; + + 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; + actual = layout_image(&demand, &tag); + assert_str_equal(actual, "││ ├─┤"); + free(actual); + + tag.count_wide_left = 1; + 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 }; + actual = layout_image(&demand1, &tag); + assert_str_equal(actual, "│ │"); + free(actual); + + struct Demand demand2 = { .view_count = 2 }; + actual = layout_image(&demand2, &tag); + assert_str_equal(actual, "│ 2 │"); + free(actual); +} + +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("{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) { + struct Demand demand = { .view_count = 9, }; + struct Tag tag = { .layout_cur = MONOCLE, }; + + 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(actual, "image: │ 9 │ count: 9 ratio: 1 name: monocle end"); + + free(actual); +} + +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} name: {n} 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) { + 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} name: {n} 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) { + 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"); + + 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) { + 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}\\ncount:\\t{c}\\rratio:\\v{r} end")); + + char *actual = layout_description(&demand, &tag); + + assert_str_equal(actual, "i: ├─┤ ├─┤\ncount:\t3\rratio:\v0.5 end"); + + free(actual); +} + +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_image__invalid), + + 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); +} + 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 };