├── .ccls ├── .cppcheck.supp ├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── feature_request.yml └── workflows │ ├── ci.yml │ └── packages │ ├── base │ └── install.sh │ └── include-what-you-use │ ├── build.sh │ └── install.sh ├── .gitignore ├── CONTRIBUTING.md ├── Dockerfile ├── GNUmakefile ├── LICENSE ├── README.md ├── config.mk ├── doc ├── GNUmakefile └── templ │ ├── 10.man.md │ ├── 10.readme.md │ ├── 20.man.readme.md │ ├── 30.readme.md │ ├── 40.man.readme.md │ └── 80.man.md ├── inc ├── args.h ├── arrange.h ├── cfg.h ├── cmd.h ├── control.h ├── displ.h ├── enum.h ├── layout.h ├── listener_registry.h ├── listener_river_command_callback.h ├── listener_river_layout.h ├── listener_river_output_status.h ├── listener_river_seat_status.h ├── log.h ├── mem.h ├── output.h ├── tag.h └── usage.h ├── lib └── col │ ├── .source │ ├── inc │ ├── fn.h │ ├── itable.h │ ├── oset.h │ ├── ptable.h │ ├── slist.h │ └── stable.h │ └── src │ ├── fn.c │ ├── itable.c │ ├── oset.c │ ├── ptable.c │ ├── slist.c │ └── stable.c ├── man └── wideriver.1 ├── pro ├── river-control-unstable-v1.xml ├── river-layout-v3.xml └── river-status-unstable-v1.xml ├── src ├── args.c ├── arrange.c ├── cfg.c ├── cmd.c ├── control.c ├── displ.c ├── enum.c ├── layout.c ├── listener_registry.c ├── listener_river_command_callback.c ├── listener_river_layout.c ├── listener_river_output_status.c ├── listener_river_seat_status.c ├── log.c ├── main.c ├── mem.c ├── output.c ├── tag.c └── usage.c └── tst ├── GNUmakefile ├── asserts.h ├── tst-args_cli.c ├── tst-args_cmd.c ├── tst-arrange.c ├── tst-arrange_count.c ├── tst-arrange_master_stack.c ├── tst-arrange_mid.c ├── tst-arrange_views.c ├── tst-cfg.c ├── tst-cmd.c ├── tst-output_apply_cmd.c ├── tst-tag.c ├── tst.h ├── util.c ├── util.h └── wrap-log.c /.ccls: -------------------------------------------------------------------------------- 1 | clang 2 | 3 | -Iinc 4 | -Ipro 5 | -Itst 6 | -Ilib/col/inc 7 | 8 | -D_GNU_SOURCE 9 | -DVERSION="0.0.1-SNAPSHOT" 10 | -DRIVER_LAYOUT_V3_VERSION=2 11 | 12 | # TODO reinstate in config.mk and remove -Wno-format-zero-length 13 | -pedantic 14 | -Wall 15 | -Wextra 16 | -Wimplicit-fallthrough 17 | -Wno-unused-parameter 18 | -Wno-format-zero-length 19 | 20 | %c -Wold-style-definition 21 | %c -Wstrict-prototypes 22 | 23 | %c -std=gnu17 24 | -------------------------------------------------------------------------------- /.cppcheck.supp: -------------------------------------------------------------------------------- 1 | unusedFunction:src/log.c 2 | 3 | unusedFunction:pro/*.h 4 | 5 | unusedFunction:tst/asserts.h 6 | unusedFunction:tst/util.c 7 | unusedFunction:tst/wrap-*.c 8 | 9 | staticFunction:tst/*c 10 | 11 | # this is just not practical 12 | nullPointerOutOfMemory 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = tab 8 | trim_trailing_whitespace = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | 13 | [*.{yml,yaml}] 14 | indent_style = space 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Report a problem with wideriver 3 | labels: [bug] 4 | body: 5 | - type: textarea 6 | attributes: 7 | label: "Description" 8 | description: "A short description of the problem you are reporting. You can attach screenshots or videos. [grim](https://git.sr.ht/~emersion/grim/) and [wf-recorder](https://github.com/ammen99/wf-recorder) may be useful." 9 | validations: 10 | required: true 11 | - type: input 12 | attributes: 13 | label: "river version" 14 | description: "Output of `river -version`" 15 | validations: 16 | required: true 17 | - type: input 18 | attributes: 19 | label: "wideriver version" 20 | description: "Output of `wideriver --version`" 21 | validations: 22 | required: true 23 | - type: textarea 24 | attributes: 25 | label: "Logs" 26 | description: "Logs showing at minimum the startup arguments. Likely at `/tmp/wideriver.${XDG_VTNR}.${USER}.log`" 27 | render: text 28 | validations: 29 | required: true 30 | - type: textarea 31 | attributes: 32 | label: "Steps to reproduce" 33 | description: "Steps to reproduce using the configuration shown in the logs" 34 | validations: 35 | required: true 36 | 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest new wideriver functionality 3 | labels: [feature] 4 | body: 5 | - type: textarea 6 | attributes: 7 | label: "Missing behavior" 8 | description: "Describe the behavior that is not available." 9 | validations: 10 | required: true 11 | - type: textarea 12 | attributes: 13 | label: "Desired solution" 14 | description: "Describe the solution you'd like." 15 | validations: 16 | required: true 17 | - type: textarea 18 | attributes: 19 | label: "Ideas" 20 | description: "A \"sketch\" of the solution. You could attach an image or draft `wideriver` arguments or `riverctl` commands." 21 | 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | branches: 5 | - '*' 6 | push: 7 | branches: 8 | - master 9 | workflow_dispatch: {} 10 | 11 | jobs: 12 | all: 13 | runs-on: ubuntu-latest 14 | 15 | container: 16 | image: archlinux:multilib-devel 17 | 18 | defaults: 19 | run: 20 | shell: sh -ec "runuser runner {0}" # assume the runner user except where overridden by root 21 | 22 | env: 23 | DEBUGINFOD_URLS: "https://debuginfod.archlinux.org" # profile thus debuginfod.sh is not executed 24 | DEBUGINFOD_CACHE_PATH: "/tmp/debuginfod_client" 25 | 26 | steps: 27 | - uses: actions/checkout@v4 28 | 29 | - name: create user 30 | shell: sh -e {0} 31 | # magic numbers and names not available in environment 32 | run: | 33 | groupadd --gid 118 docker 34 | useradd -m -s /bin/sh --uid 1001 --gid docker runner 35 | 36 | - name: set source permissions 37 | run: chown -R runner:docker . 38 | shell: sh -e {0} 39 | 40 | - name: install packages 41 | shell: sh -e {0} 42 | run: .github/workflows/packages/base/install.sh 43 | 44 | - run: make clean 45 | - run: make CC=gcc all 46 | - run: make CC=gcc test 47 | - run: make CC=gcc test-vg 48 | 49 | - run: make clean cppcheck 50 | 51 | - run: make clean 52 | - run: make CC=clang all 53 | - run: make CC=clang test 54 | - run: make CC=clang test-vg 55 | 56 | - name: build include-what-you-use package 57 | run: .github/workflows/packages/include-what-you-use/build.sh 58 | - name: install include-what-you-use package 59 | run: .github/workflows/packages/include-what-you-use/install.sh 60 | shell: sh -e {0} 61 | 62 | - run: make clean iwyu 63 | 64 | -------------------------------------------------------------------------------- /.github/workflows/packages/base/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | pacman --noconfirm -Syu \ 4 | clang \ 5 | cmake \ 6 | cmocka \ 7 | cppcheck \ 8 | git \ 9 | llvm \ 10 | llvm-libs \ 11 | ninja \ 12 | valgrind \ 13 | wayland \ 14 | wayland-protocols 15 | -------------------------------------------------------------------------------- /.github/workflows/packages/include-what-you-use/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # must be run as a user 4 | 5 | rm -rf /tmp/include-what-you-use 6 | 7 | git clone https://aur.archlinux.org/include-what-you-use.git /tmp/include-what-you-use 8 | 9 | makepkg OPTIONS=-debug PKGEXT='.pkg.tar' --dir /tmp/include-what-you-use 10 | -------------------------------------------------------------------------------- /.github/workflows/packages/include-what-you-use/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | pacman -U --noconfirm /tmp/include-what-you-use/*.pkg.tar 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.o 2 | 3 | pro/*.h 4 | pro/*.c 5 | 6 | /wideriver 7 | 8 | /tst-* 9 | 10 | /.ccls-cache 11 | 12 | /log.* 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to https://github.com/alex-courtis/wideriver 2 | 3 | 4 | 5 | - [Dependencies](#dependencies) 6 | * [Build](#build) 7 | * [Test](#test) 8 | * [Doc](#doc) 9 | - [Development](#development) 10 | * [Compile](#compile) 11 | * [Test](#test-1) 12 | * [Install](#install) 13 | * [Uninstall](#uninstall) 14 | * [Lint](#lint) 15 | * [Check Includes](#check-includes) 16 | - [Documentation](#documentation) 17 | - [Style](#style) 18 | 19 | 20 | 21 | Thank you for contribution! 22 | 23 | Ideas, bug fixes and enhancements are always welcome. 24 | 25 | Please raise an [issue](https://github.com/alex-courtis/wideriver/issues), fork the repository and raise a [PR](https://github.com/alex-courtis/wideriver/pulls). 26 | 27 | [ci.yml](.github/workflows/ci.yml) must pass. 28 | 29 | ## Dependencies 30 | 31 | Most will be available if you are running river. 32 | 33 | ### Build 34 | * GNU make 35 | * gcc or clang 36 | * wayland 37 | * wayland-protocols 38 | * wlroots 39 | 40 | ### Test 41 | * valgrind 42 | * [cmocka](https://cmocka.org/) 43 | * [include-what-you-use](https://include-what-you-use.org/) 44 | 45 | ### Doc 46 | * [pandoc](https://pandoc.org) 47 | * [markdown-toc](https://github.com/jonschlinkert/markdown-toc) 48 | 49 | ## Development 50 | 51 | gcc is the default for packaging reasons, however clang is preferred. 52 | 53 | Set CC when invoking make: 54 | 55 | `make CC=clang ...` 56 | 57 | [ccls](https://github.com/MaskRay/ccls) using clang is configured via `.ccls`, for editors that support the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/). 58 | 59 | ### Compile 60 | 61 | `make` 62 | 63 | ### Test 64 | 65 | `make test` 66 | 67 | cmocka is used for unit testing. Individual tests with `--wrap` definitions are defined in `tst/GNUmakefile`. 68 | 69 | Please add tests when defining new functionality. 70 | 71 | Individual tests may be run via test-name e.g. 72 | `make test-cfg` 73 | 74 | Valgrind test by appending `-vg` e.g. 75 | `make test-vg` 76 | `make test-cfg-vg` 77 | 78 | ### Install 79 | 80 | Installs under `$(DESTDIR)$(PREFIX)`, usually `/usr/local` 81 | 82 | ```sh 83 | make install 84 | ``` 85 | 86 | ### Uninstall 87 | 88 | ```sh 89 | make uninstall 90 | ``` 91 | 92 | ### Lint 93 | 94 | `make cppcheck` 95 | 96 | Please resolve all issues before committing. 97 | 98 | ### Check Includes 99 | 100 | include-what-you-use is configured to run for `src` and `tst`. 101 | 102 | `make iwyu` 103 | 104 | Necessary changes will be indicated in the output with "should". 105 | 106 | See all violations: 107 | 108 | `make -k iwyu > /dev/null` 109 | 110 | ### Developing On The (CI) Arch Image 111 | 112 | `Dockerfile` defines an image similar to that used by the docker container in `ci.yml` 113 | 114 | It is intended to run in detached mode, thus the `ENTRYPOINT [ "sleep", "infinity" ]` 115 | 116 | Build the image: 117 | ```sh 118 | make docker-build 119 | ``` 120 | 121 | Run a detached container: 122 | ```sh 123 | make docker-run 124 | ``` 125 | 126 | Build and install the AUR include-what-you-use package: 127 | ```sh 128 | make docker-packages 129 | ``` 130 | 131 | Execute a command in the container e.g.: 132 | ```sh 133 | docker exec wide-river make clean test-vg 134 | ``` 135 | 136 | OR run a shell in the container: 137 | ```sh 138 | docker exec -it wide-river /bin/bash 139 | ``` 140 | 141 | Stop and remove the container: 142 | ```sh 143 | make docker-stop 144 | ``` 145 | 146 | ## Documentation 147 | 148 | Please ensure that documentation is updated when adding new features or changing CLI arguments, including defaults. 149 | 150 | pandoc is used to generate readme and man page from templates in `doc/templ/*md` 151 | 152 | It's sometimes easier to use the pandoc docker image than to install it: 153 | ``` 154 | #!/bin/sh 155 | docker run --rm --volume "${PWD}:/data" --volume "/tmp:/tmp" --user "$(id -u):$(id -g)" pandoc/latex "$@" 156 | ``` 157 | 158 | [markdown-toc](https://github.com/jonschlinkert/markdown-toc) is used to inject the table of contents into `README.md` 159 | 160 | Update the templates and run `make doc`. 161 | 162 | Preview the man page via `man -l man/wideriver.1` 163 | 164 | Commit `README.md` and `man/wideriver.1` 165 | 166 | ## Style 167 | 168 | Please match the style of the surrounding code and obey `.editorconfig`. Default vim C-indenting `gg=G` is preferred. 169 | 170 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # docker image for local dev 2 | # Keep this in sync with ci.yml 3 | # GH CI doesn't allow use of a Dockerfile, hence we maintain this separately 4 | 5 | FROM archlinux:multilib-devel 6 | 7 | COPY .github/workflows/packages/base/install.sh /usr/local/bin/packages-base-install.sh 8 | 9 | RUN packages-base-install.sh 10 | 11 | ENV DEBUGINFOD_URLS="https://debuginfod.archlinux.org" 12 | ENV DEBUGINFOD_CACHE_PATH="/tmp/debuginfod_client" 13 | 14 | ENTRYPOINT [ "sleep", "infinity" ] 15 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | include config.mk 2 | 3 | # 4 | # dependencies 5 | # 6 | SRC_H = $(wildcard inc/*.h) 7 | SRC_C = $(wildcard src/*.c) 8 | SRC_O = $(SRC_C:.c=.o) 9 | 10 | PRO_X = $(wildcard pro/*.xml) 11 | PRO_H = $(PRO_X:.xml=.h) 12 | PRO_C = $(PRO_X:.xml=.c) 13 | PRO_O = $(PRO_X:.xml=.o) 14 | 15 | LIB_H = $(wildcard lib/col/inc/*.h) 16 | LIB_C = $(wildcard lib/col/src/*.c) 17 | LIB_O = $(LIB_C:.c=.o) 18 | 19 | TST_H = $(wildcard tst/*.h) 20 | TST_C = $(wildcard tst/*.c) 21 | TST_O = $(TST_C:.c=.o) 22 | TST_E = $(patsubst tst/%.c,%,$(wildcard tst/tst-*.c)) 23 | TST_T = $(patsubst tst%,test%,$(TST_E)) 24 | 25 | # 26 | # build 27 | # 28 | all: wideriver 29 | 30 | $(SRC_O): $(SRC_H) $(PRO_H) 31 | $(PRO_O): $(PRO_H) 32 | $(LIB_O): $(LIB_H) 33 | 34 | wideriver: $(SRC_O) $(PRO_O) $(LIB_O) 35 | $(CC) -o $(@) $(^) $(LDFLAGS) $(LDLIBS) 36 | @test -x ../deploy.sh && ../deploy.sh || true 37 | 38 | $(PRO_H): $(PRO_X) 39 | wayland-scanner client-header $(@:.h=.xml) $@ 40 | 41 | $(PRO_C): $(PRO_X) 42 | wayland-scanner private-code $(@:.c=.xml) $@ 43 | 44 | clean: 45 | rm -f wideriver $(SRC_O) $(PRO_O) $(PRO_H) $(PRO_C) $(LIB_O) $(TST_O) $(TST_E) 46 | 47 | # 48 | # install 49 | # 50 | install: wideriver man/wideriver.1 51 | mkdir -p $(DESTDIR)$(PREFIX)/bin 52 | cp -f wideriver $(DESTDIR)$(PREFIX)/bin 53 | chmod 755 $(DESTDIR)$(PREFIX)/bin/wideriver 54 | mkdir -p $(DESTDIR)$(PREFIX)/share/man/man1 55 | cp -f man/wideriver.1 $(DESTDIR)$(PREFIX)/share/man/man1 56 | chmod 644 $(DESTDIR)$(PREFIX)/share/man/man1/wideriver.1 57 | 58 | uninstall: 59 | rm -f $(DESTDIR)$(PREFIX)/bin/wideriver 60 | rm -f $(DESTDIR)$(PREFIX)/share/man/man1/wideriver.1 61 | 62 | # 63 | # doc 64 | # 65 | doc: wideriver 66 | $(MAKE) -f doc/GNUmakefile 67 | 68 | # 69 | # iwyu 70 | # 71 | iwyu: override CC = $(IWYU) -Xiwyu --check_also="inc/*h" 72 | iwyu: clean $(SRC_O) $(TST_O) 73 | IWYU = include-what-you-use -Xiwyu --no_fwd_decls -Xiwyu --error=1 -Xiwyu --verbose=3 74 | 75 | # 76 | # cppcheck 77 | # 78 | cppcheck: $(SRC_H) $(SRC_C) $(TST_H) $(TST_C) 79 | cppcheck $(^)\ 80 | --enable=warning,unusedFunction,performance,portability \ 81 | --check-level=exhaustive \ 82 | --suppressions-list=.cppcheck.supp \ 83 | --error-exitcode=1 \ 84 | $(CPPFLAGS) 85 | 86 | # 87 | # valgrind 88 | # 89 | %-vg: VALGRIND = valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all --errors-for-leak-kinds=all --gen-suppressions=all 90 | %-vg: % ; 91 | 92 | # 93 | # test 94 | # 95 | test: $(TST_T) 96 | test-vg: $(TST_T) 97 | 98 | $(TST_T): EXE = $(patsubst test%,tst%,$(@)) 99 | $(TST_T): $(SRC_O) $(PRO_O) $(LIB_O) 100 | $(MAKE) -f tst/GNUmakefile $(EXE) 101 | $(VALGRIND) ./$(EXE) 102 | 103 | # 104 | # local docker dev 105 | # 106 | docker-build: 107 | docker build --tag "wide-river:latest" . 108 | 109 | docker-stop: 110 | docker rm -f wide-river || true 111 | 112 | docker-run: docker-stop 113 | docker run \ 114 | --name="wide-river" \ 115 | --volume "${PWD}:/wide-river" \ 116 | --workdir="/wide-river" \ 117 | --user "`id -u`:`id -g`" \ 118 | --detach \ 119 | "wide-river:latest" 120 | 121 | docker-packages: 122 | docker exec wide-river .github/workflows/packages/include-what-you-use/build.sh 123 | docker exec --user "root:root" wide-river .github/workflows/packages/include-what-you-use/install.sh 124 | 125 | # 126 | # targets 127 | # 128 | .PHONY: all clean install uninstall doc iwyu cppcheck test test-vg $(TST_T) 129 | 130 | .NOTPARALLEL: iwyu test test-vg 131 | -------------------------------------------------------------------------------- /config.mk: -------------------------------------------------------------------------------- 1 | VERSION ?= "1.2.1-SNAPSHOT" 2 | 3 | RIVER_LAYOUT_V3_VERSION = 2 4 | 5 | PREFIX ?= /usr/local 6 | 7 | INCS = -Iinc -Ipro -Ilib/col/inc 8 | 9 | CPPFLAGS += $(INCS) -D_GNU_SOURCE -DVERSION=\"$(VERSION)\" -DRIVER_LAYOUT_V3_VERSION=$(RIVER_LAYOUT_V3_VERSION) 10 | 11 | OFLAGS = -O3 12 | WFLAGS = -pedantic -Wall -Wextra -Werror -Wimplicit-fallthrough -Wno-unused-parameter -Wno-format-zero-length 13 | DFLAGS = -g 14 | COMPFLAGS = $(WFLAGS) $(OFLAGS) $(DFLAGS) 15 | 16 | CFLAGS += $(COMPFLAGS) -std=gnu17 -Wold-style-definition -Wstrict-prototypes 17 | 18 | LDFLAGS += 19 | 20 | ifeq (,$(filter-out DragonFly FreeBSD NetBSD OpenBSD,$(shell uname -s))) 21 | PKGS += epoll-shim 22 | endif 23 | 24 | PKGS += wayland-client 25 | PKG_CONFIG ?= pkg-config 26 | CFLAGS += $(foreach p,$(PKGS),$(shell $(PKG_CONFIG) --cflags $(p))) 27 | LDLIBS += $(foreach p,$(PKGS),$(shell $(PKG_CONFIG) --libs $(p))) 28 | 29 | CC = gcc 30 | -------------------------------------------------------------------------------- /doc/GNUmakefile: -------------------------------------------------------------------------------- 1 | include GNUmakefile 2 | 3 | all: README.md man/wideriver.1 CONTRIBUTING.md 4 | rm doc/help*.out doc/*pandoc.md 5 | 6 | doc/help.out: 7 | ./wideriver --help > $(@) 8 | 9 | doc/help-defaults.out: 10 | ./wideriver --help-defaults > $(@) 11 | 12 | doc/readme.pandoc.md: doc/help.out doc/help-defaults.out 13 | cat doc/templ/*readme*md | sed \ 14 | -e "/@HELP@/{r doc/help.out" \ 15 | -e "d}" \ 16 | -e "/@HELP_DEFAULTS@/{r doc/help-defaults.out" \ 17 | -e "d}" \ 18 | > $(@) 19 | 20 | doc/man.pandoc.md: doc/help-defaults.out 21 | cat doc/templ/*man*md | sed \ 22 | -e "s#@DATE@#`date +%Y/%m/%d`#g" \ 23 | -e "/@HELP_DEFAULTS@/{r doc/help-defaults.out" \ 24 | -e "d}" \ 25 | > $(@) 26 | 27 | README.md: doc/readme.pandoc.md 28 | pandoc -s --wrap=none --from=markdown --to=gfm --output $(@) doc/readme.pandoc.md 29 | markdown-toc --maxdepth=3 -i $(@) 30 | 31 | man/wideriver.1: doc/man.pandoc.md 32 | pandoc -s --wrap=none --from=markdown --to=man --shift-heading-level-by -1 --output $(@) doc/man.pandoc.md 33 | 34 | CONTRIBUTING.md: 35 | markdown-toc -i $(@) 36 | 37 | .PHONY: all README.md man/wideriver.1 CONTRIBUTING.md 38 | -------------------------------------------------------------------------------- /doc/templ/10.man.md: -------------------------------------------------------------------------------- 1 | % WIDERIVER(1) wideriver | User Manuals 2 | % Alexander Courtis 3 | % @DATE@ 4 | 5 | ## NAME 6 | 7 | `wideriver` - tiling window manager for the river wayland compositor 8 | 9 | ## SYNOPSIS 10 | 11 | `wideriver` [*OPTION*...|*COMMAND*...] 12 | 13 | ## DESCRIPTION 14 | 15 | [//]: # vim: set filetype=markdown ts=4 sw=4 et : 16 | -------------------------------------------------------------------------------- /doc/templ/10.readme.md: -------------------------------------------------------------------------------- 1 | # wideriver 2 | 3 | [//]: # vim: set filetype=markdown ts=4 sw=4 et : 4 | -------------------------------------------------------------------------------- /doc/templ/30.readme.md: -------------------------------------------------------------------------------- 1 | ## INSTALL 2 | 3 | ### Package Manager 4 | 5 | [![Packaging status](https://repology.org/badge/vertical-allrepos/wideriver.svg)](https://repology.org/project/wideriver/versions) 6 | 7 | ### From Source 8 | 9 | [![CI](https://github.com/alex-courtis/wideriver/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/alex-courtis/wideriver/actions/workflows/ci.yml?query=branch%3Amaster) 10 | 11 | See [CONTRIBUTING.md](CONTRIBUTING.md) 12 | 13 | ``` sh 14 | git clone clone git@github.com:alex-courtis/wideriver.git 15 | cd wideriver 16 | make 17 | sudo make install 18 | ``` 19 | 20 | Should install under `/usr/local` 21 | 22 | ## USAGE 23 | 24 | ``` 25 | @HELP@ 26 | ``` 27 | 28 | [//]: # vim: set filetype=markdown ts=4 sw=4 et : 29 | -------------------------------------------------------------------------------- /doc/templ/40.man.readme.md: -------------------------------------------------------------------------------- 1 | ## OPTIONS 2 | 3 | `--layout` `monocle`|`left`|`right`|`top`|`bottom`|`wide` 4 | : Initial layout, default `left`. 5 | 6 | `--layout-alt` `monocle`|`left`|`right`|`top`|`bottom`|`wide` 7 | : Initial alternate layout, default `monocle`. 8 | Use `--layout-toggle` to switch to alternate layout. 9 | 10 | `--stack` `even`|`diminish`|`dwindle` 11 | : Initial stacking method, default `dwindle`. 12 | 13 | `--count-master` *count* 14 | : Initial number of views in the master area, default `1`, minimum `0`. 15 | Does not apply to wide layout. 16 | 17 | `--ratio-master` *ratio* 18 | : Initial proportion of the width or height the master area occupies, default `0.5`, minimum `0.1`, maximum `0.9`. 19 | Does not apply to wide layout. 20 | 21 | `--count-wide-left` *count* 22 | : Initial number of views in the wide layout's left stack area, default `1`, minimum `0`. 23 | You may wish to set this to 0 for a more natural or intuitive feel when launching the first two views. 24 | 25 | `--ratio-wide` *ratio* 26 | : Initial proportion of the width the wide layout's master area occupies, default `0.35`, minimum `0.1`, maximum `0.9`. 27 | The default value is best suited to ultrawide monitors, a value of `0.5` may be more useful for 16:9 monitors. 28 | 29 | `--(no-)smart-gaps` 30 | : Automatically hides the gaps when there is only one view or monocle layout. 31 | 32 | `--inner-gaps` *pixels* 33 | : Inner gaps width, default `0`, minimum `0`. 34 | 35 | `--outer-gaps` *pixels* 36 | : Outer gaps width, default `0`, minimum `0`. 37 | 38 | `--border-width` *pixels* 39 | : Border width for all layouts except monocle, default `2`, minimum `0`. 40 | 41 | `--border-width-monocle` *pixels* 42 | : Border width for monocle layout, default `0`, minimum `0`. 43 | 44 | `--border-width-smart-gaps` *pixels* 45 | : Border width for when smart gaps hides the gaps for all layouts except monocle, default `0`, minimum `0`. 46 | Has no effect if `--no-smart-gaps`. 47 | 48 | `--border-color-focused` `0x`*RRGGBB*[*AA*] 49 | : Border color for focused views in all layouts excluding monocle, default `0x93a1a1`. 50 | 51 | `--border-color-focused-monocle` `0x`*RRGGBB*[*AA*] 52 | : Border color for focused view in monocle layout, default `0x586e75`. 53 | It is recommended to set this to the unfocused color or a darker colour as an always focused border can be distracting. 54 | 55 | `--border-color-unfocused` `0x`*RRGGBB*[*AA*] 56 | : Border color for unfocused views in all layouts, default `0x586e75`. 57 | Does not apply for monocle layout. 58 | 59 | `--log-threshold` `debug`|`info`|`warning`|`error`|`fatal` 60 | : Minimum log level, default `info`. 61 | 62 | ## COMMANDS 63 | 64 | When multiple tags are focused, the command is applied to and persisted for only the lowest tag. 65 | 66 | `--layout` `monocle`|`left`|`right`|`top`|`bottom`|`wide` 67 | : Set layout persistently for the tag, updating the alternate layout. 68 | 69 | `--layout-toggle` 70 | : Set layout to the alternate (previous) for the tag. 71 | 72 | `--stack` `diminish`|`dwindle`|`dwindle` 73 | : Set stacking method persistently for the tag. 74 | Applies to all layouts for the tag. 75 | 76 | `--count` [`+-`]*count* 77 | : Increment, decrement or set the master count, minimum `0`. 78 | For wide layout this is instead the left stack count. 79 | Discrete value for wide and all other layouts are persisted per tag. 80 | Prefix with `+` to increment, `-` to decrement, or an absolute value. 81 | 82 | `--ratio` [`+-`]*pixels* 83 | : Increase, decrease or set the master ratio: the proportion of the width or height the master area occupies, minimum `0.1`, maximum `0.9`. 84 | Discrete tiling and wide values persisted per tag. 85 | Prefix with `+` to increase, `-` to decrease, or an absolute value. 86 | 87 | ## RECIPES 88 | 89 | ### Wide Shuffling 90 | 91 | You can "shuffle" views through master, focusing the new master using: 92 | 93 | `riverctl send-layout-cmd wideriver '--count +1' && riverctl focus-view next"` 94 | 95 | `riverctl send-layout-cmd wideriver '--count -1' && riverctl focus-view previous"` 96 | 97 | ## FAQ 98 | 99 | ### Name Does Not Always Update 100 | 101 | The layout name will not update when there are no views for the selected tags. This can occurs when setting a tag with no views or changing the layout for a tag with no views. 102 | 103 | This may be resolved with a river enhancement: [#1004](https://github.com/riverwm/river/issues/1002) 104 | 105 | ### Borders Are Not Shown 106 | 107 | Please ensure you have enabled server side decorations i.e. the borders: 108 | 109 | ``` sh 110 | riverctl rule-add ssd 111 | ``` 112 | 113 | You can still use client side decorations for specific applications e.g.: 114 | 115 | ``` sh 116 | riverctl rule-add -app-id audacity csd 117 | ``` 118 | 119 | ### Random Pixels In Gaps When Using Fractional Scaling 120 | 121 | This is a known river and wlroots issue: https://codeberg.org/river/river/issues/816 122 | 123 | Workaround: set a river background colour other than default black `0x000000` 124 | 125 | `riverctl background-color "0x010101"` is sufficiently close. 126 | 127 | ## ISSUES 128 | 129 | ### Problems 130 | 131 | Please raise a [Bug Report](https://github.com/alex-courtis/wideriver/issues/new?assignees=&labels=bug&projects=&template=bug_report.yml) 132 | 133 | ### Ideas 134 | 135 | Please create a [Feature Request](https://github.com/alex-courtis/wideriver/issues/new?assignees=&labels=feature&projects=&template=feature_request.yml) 136 | 137 | ### Questions or Discussions 138 | 139 | Please raise an [Issue](https://github.com/alex-courtis/wideriver/issues/new) 140 | 141 | ### Contributions 142 | 143 | [CONTRIBUTING.md](doc/CONTRIBUTING.md) is most gratefully appreciated. 144 | 145 | [//]: # vim: set filetype=markdown ts=4 sw=4 et : 146 | -------------------------------------------------------------------------------- /doc/templ/80.man.md: -------------------------------------------------------------------------------- 1 | ## SEE ALSO 2 | 3 | https://github.com/alex-courtis/wideriver 4 | 5 | [//]: # vim: set filetype=markdown ts=4 sw=4 et : 6 | -------------------------------------------------------------------------------- /inc/args.h: -------------------------------------------------------------------------------- 1 | #ifndef ARGS_H 2 | #define ARGS_H 3 | 4 | // parse river command args, NULL on failure 5 | const struct Cmd *args_cmd(int argc, char **argv); 6 | 7 | // populate cfg 8 | void args_cli(int argc, char **argv); 9 | 10 | #endif // ARGS_H 11 | -------------------------------------------------------------------------------- /inc/arrange.h: -------------------------------------------------------------------------------- 1 | #ifndef ARRANGE_H 2 | #define ARRANGE_H 3 | 4 | #include 5 | 6 | #include "enum.h" 7 | #include "layout.h" 8 | #include "slist.h" 9 | #include "tag.h" 10 | 11 | enum Cardinal { 12 | N, 13 | S, 14 | E, 15 | W, 16 | }; 17 | 18 | enum Ordinal { 19 | NE, 20 | SE, 21 | SW, 22 | NW, 23 | }; 24 | 25 | // count master/stacks views 26 | void arrange_count(const uint32_t view_count, 27 | const struct Tag* const tag, 28 | uint32_t *before, 29 | uint32_t *master, 30 | uint32_t *after); 31 | 32 | // calculate LRUD master/stack areas for Tag 33 | void arrange_master_stack(const struct Demand *demand, 34 | const struct Tag *tag, 35 | const uint32_t num_master, 36 | const uint32_t num_stack, 37 | struct Box *master, 38 | struct Box *stack); 39 | 40 | // calculate WIDE master/stacks areas for Tag 41 | void arrange_wide(const struct Demand *demand, 42 | const struct Tag* const tag, 43 | const uint32_t num_before, 44 | const uint32_t num_master, 45 | const uint32_t num_after, 46 | struct Box *before, 47 | struct Box *master, 48 | struct Box *after); 49 | 50 | // append many new Box to views with full usable area 51 | void arrange_monocle(const struct Demand *demand, 52 | const struct Tag* const tag, 53 | struct SList **views); 54 | 55 | // recursively append new Box to views as per stack 56 | void arrange_views(const struct Demand *demand, 57 | const enum Stack stack, 58 | const enum Cardinal dir_cur, 59 | const enum Cardinal dir_next, 60 | const uint32_t num_total, 61 | const uint32_t num_remaining, 62 | const uint32_t inner_gap, 63 | const struct Box box_total, 64 | const struct Box box_remaining, 65 | struct SList **views); 66 | 67 | #endif // ARRANGE_H 68 | 69 | -------------------------------------------------------------------------------- /inc/cfg.h: -------------------------------------------------------------------------------- 1 | #ifndef CFG_H 2 | #define CFG_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "enum.h" 9 | 10 | #define COUNT_MIN 0 11 | 12 | #define COUNT_MASTER_DEFAULT 1 13 | #define COUNT_WIDE_LEFT_DEFAULT 0 14 | 15 | #define RATIO_MIN 0.1 16 | #define RATIO_MAX 0.9 17 | 18 | #define RATIO_MASTER_DEFAULT 0.5 19 | #define RATIO_WIDE_DEFAULT 0.35 20 | 21 | #define SMART_GAPS_DEFAULT false 22 | 23 | #define BORDER_WIDTH_SMART_GAPS_MIN 0 24 | #define BORDER_WIDTH_SMART_GAPS_DEFAULT 0 25 | 26 | #define INNER_GAPS_MIN 0 27 | #define INNER_GAPS_DEFAULT 0 28 | 29 | #define OUTER_GAPS_MIN 0 30 | #define OUTER_GAPS_DEFAULT 0 31 | 32 | #define BORDER_WIDTH_MIN 0 33 | #define BORDER_WIDTH_DEFAULT 2 34 | 35 | #define BORDER_WIDTH_MONOCLE_MIN 0 36 | #define BORDER_WIDTH_MONOCLE_DEFAULT 0 37 | 38 | #define BORDER_COLOR_FOCUSED_DEFAULT "0x93a1a1" 39 | 40 | #define BORDER_COLOR_FOCUSED_MONOCLE_DEFAULT "0x586e75" 41 | 42 | #define BORDER_COLOR_UNFOCUSED_DEFAULT "0x586e75" 43 | 44 | // never null 45 | extern const struct Cfg * const cfg; 46 | 47 | struct Cfg { 48 | enum Layout layout; 49 | enum Layout layout_alt; 50 | enum Stack stack; 51 | uint32_t count_master; 52 | double ratio_master; 53 | uint32_t count_wide_left; 54 | double ratio_wide; 55 | bool smart_gaps; 56 | uint32_t border_width_smart_gaps; 57 | uint32_t inner_gaps; 58 | uint32_t outer_gaps; 59 | size_t border_width; 60 | size_t border_width_monocle; 61 | char border_color_focused[11]; 62 | char border_color_focused_monocle[11]; 63 | char border_color_unfocused[11]; 64 | }; 65 | 66 | // returns false if not valid 67 | bool cfg_set_layout(const char *s); 68 | bool cfg_set_layout_alt(const char *s); 69 | bool cfg_set_stack(const char *s); 70 | bool cfg_set_count_master(const char *s); 71 | bool cfg_set_ratio_master(const char *s); 72 | bool cfg_set_count_wide_left(const char *s); 73 | bool cfg_set_ratio_wide(const char *s); 74 | void cfg_set_smart_gaps(bool smart_gaps); 75 | bool cfg_set_border_width_smart_gaps(const char *s); 76 | bool cfg_set_inner_gaps(const char *s); 77 | bool cfg_set_outer_gaps(const char *s); 78 | bool cfg_set_border_width(const char *s); 79 | bool cfg_set_border_width_monocle(const char *s); 80 | 81 | // returns false if not 0xRRGGBB 82 | bool cfg_set_border_color_focused(const char *s); 83 | bool cfg_set_border_color_focused_monocle(const char *s); 84 | bool cfg_set_border_color_unfocused(const char *s); 85 | 86 | #endif // CFG_H 87 | -------------------------------------------------------------------------------- /inc/cmd.h: -------------------------------------------------------------------------------- 1 | #ifndef CMD_H 2 | #define CMD_H 3 | 4 | #include 5 | #include 6 | 7 | #include "enum.h" 8 | 9 | struct Cmd { 10 | enum Layout layout; 11 | bool layout_toggle; 12 | enum Stack stack; 13 | uint32_t *count; 14 | int32_t *count_delta; 15 | double *ratio; 16 | double *ratio_delta; 17 | }; 18 | 19 | // instantiate cmd from river command args, NULL on any failure 20 | const struct Cmd *cmd_init(const char *args); 21 | 22 | void cmd_destroy(const struct Cmd *cmd); 23 | 24 | // false on failure 25 | bool cmd_set_layout(struct Cmd *cmd, const char *s); 26 | bool cmd_set_layout_toggle(struct Cmd *cmd); 27 | bool cmd_set_stack(struct Cmd *cmd, const char *s); 28 | bool cmd_set_count(struct Cmd *cmd, const char *s); 29 | bool cmd_set_ratio(struct Cmd *cmd, const char *s); 30 | 31 | #endif // CMD_H 32 | -------------------------------------------------------------------------------- /inc/control.h: -------------------------------------------------------------------------------- 1 | #ifndef CONTROL_H 2 | #define CONTROL_H 3 | 4 | #include "slist.h" 5 | 6 | typedef void (*control_complete_fn)(void); 7 | 8 | // execute a river control command, executing the complete on success and failure 9 | void control_execute(const struct SList *args, control_complete_fn); 10 | 11 | #endif // CONTROL_H 12 | -------------------------------------------------------------------------------- /inc/displ.h: -------------------------------------------------------------------------------- 1 | #ifndef DISPL_H 2 | #define DISPL_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "output.h" 9 | #include "tag.h" 10 | 11 | // never null 12 | extern const struct Displ * const displ; 13 | 14 | struct Style { 15 | size_t border_width; 16 | const char *border_color_focused; 17 | const char *border_color_unfocused; 18 | }; 19 | 20 | struct Displ { 21 | // handles 22 | struct wl_display *wl_display; 23 | struct wl_registry *wl_registry; 24 | struct wl_seat *wl_seat; 25 | struct river_layout_manager_v3 *river_layout_manager; 26 | struct zriver_status_manager_v1 *river_status_manager; 27 | struct zriver_seat_status_v1 *river_seat_status; 28 | struct zriver_control_v1 *river_control; 29 | 30 | // Outputs by wl_output 31 | const struct PTable *outputs; 32 | 33 | // state 34 | const struct wl_output *wl_output_focused; 35 | struct Style style_desired; 36 | struct Style style_current; 37 | }; 38 | 39 | // connect, register and round trip, false on failure 40 | bool displ_init(void); 41 | 42 | // disconnect the display, free resources and destroy 43 | void displ_destroy(void); 44 | 45 | // request style as per tag, output must be focused or no focus reported (startup case) 46 | void displ_request_style(const struct Output *output, const struct Tag *tag, const uint32_t view_count); 47 | 48 | #endif // DISPL_H 49 | -------------------------------------------------------------------------------- /inc/enum.h: -------------------------------------------------------------------------------- 1 | #ifndef ENUM_H 2 | #define ENUM_H 3 | 4 | enum Layout { 5 | LEFT = 1, 6 | RIGHT, 7 | TOP, 8 | BOTTOM, 9 | MONOCLE, 10 | WIDE, 11 | LAYOUT_DEFAULT = LEFT, 12 | LAYOUT_ALT_DEFAULT = MONOCLE, 13 | }; 14 | 15 | enum Stack { 16 | EVEN = 1, 17 | DIMINISH, 18 | DWINDLE, 19 | STACK_DEFAULT = DWINDLE, 20 | }; 21 | 22 | enum LogThreshold { 23 | DEBUG = 1, 24 | INFO, 25 | WARNING, 26 | ERROR, 27 | FATAL, 28 | LOG_THRESHOLD_DEFAULT = INFO, 29 | }; 30 | 31 | const char *layout_name(const enum Layout layout); 32 | enum Layout layout_val(const char *name); 33 | 34 | const char *stack_name(const enum Stack stack); 35 | enum Stack stack_val(const char *name); 36 | 37 | const char *log_threshold_name(const enum LogThreshold log_threshold); 38 | enum LogThreshold log_threshold_val(const char *name); 39 | 40 | #endif // ENUM_H 41 | -------------------------------------------------------------------------------- /inc/layout.h: -------------------------------------------------------------------------------- 1 | #ifndef LAYOUT_H 2 | #define LAYOUT_H 3 | 4 | #include 5 | #include "river-layout-v3.h" 6 | 7 | #include "slist.h" 8 | 9 | #include "tag.h" 10 | 11 | struct Demand { 12 | const uint32_t view_count; 13 | const uint32_t usable_width; 14 | const uint32_t usable_height; 15 | }; 16 | 17 | struct Box { 18 | int32_t x; 19 | int32_t y; 20 | uint32_t width; 21 | uint32_t height; 22 | }; 23 | 24 | // return a river layout name, caller frees 25 | const char *layout_description(const struct Demand *demand, const struct Tag *tag); 26 | 27 | // populate views with Box for river layout push dimensions, caller frees 28 | struct SList *layout(const struct Demand *demand, const struct Tag *tag); 29 | 30 | // push Box views 31 | void push(const struct SList *views, struct river_layout_v3 *river_layout_v3, const uint32_t serial); 32 | 33 | #endif // LAYOUT_H 34 | 35 | -------------------------------------------------------------------------------- /inc/listener_registry.h: -------------------------------------------------------------------------------- 1 | #ifndef LISTENER_REGISTRY_H 2 | #define LISTENER_REGISTRY_H 3 | 4 | const struct wl_registry_listener *registry_listener(void); 5 | 6 | #endif // LISTENER_REGISTRY_H 7 | 8 | -------------------------------------------------------------------------------- /inc/listener_river_command_callback.h: -------------------------------------------------------------------------------- 1 | #ifndef LISTENER_RIVER_COMMAND_CALLBACK_H 2 | #define LISTENER_RIVER_COMMAND_CALLBACK_H 3 | 4 | const struct zriver_command_callback_v1_listener *river_command_callback_listener(void); 5 | 6 | #endif // LISTENER_RIVER_COMMAND_CALLBACK_H 7 | 8 | -------------------------------------------------------------------------------- /inc/listener_river_layout.h: -------------------------------------------------------------------------------- 1 | #ifndef LISTENER_RIVER_LAYOUT_H 2 | #define LISTENER_RIVER_LAYOUT_H 3 | 4 | const struct river_layout_v3_listener *river_layout_listener(void); 5 | 6 | #endif // LISTENER_RIVER_LAYOUT_H 7 | 8 | -------------------------------------------------------------------------------- /inc/listener_river_output_status.h: -------------------------------------------------------------------------------- 1 | #ifndef LISTENER_RIVER_OUTPUT_STATUS_H 2 | #define LISTENER_RIVER_OUTPUT_STATUS_H 3 | 4 | const struct zriver_output_status_v1_listener *river_output_status_listener(void); 5 | 6 | #endif // LISTENER_RIVER_OUTPUT_STATUS_H 7 | 8 | -------------------------------------------------------------------------------- /inc/listener_river_seat_status.h: -------------------------------------------------------------------------------- 1 | #ifndef LISTENER_RIVER_SEAT_STATUS_H 2 | #define LISTENER_RIVER_SEAT_STATUS_H 3 | 4 | const struct zriver_seat_status_v1_listener *river_seat_status_listener(void); 5 | 6 | #endif // LISTENER_RIVER_SEAT_STATUS_H 7 | 8 | -------------------------------------------------------------------------------- /inc/log.h: -------------------------------------------------------------------------------- 1 | #ifndef LOG_H 2 | #define LOG_H 3 | 4 | #include 5 | 6 | #include "enum.h" 7 | 8 | void log_d(const char *__restrict __format, ...) __attribute__ ((__format__ (__printf__, 1, 2))); 9 | 10 | void log_d_c_s(const char *__restrict __format, ...) __attribute__ ((__format__ (__printf__, 1, 2))); 11 | 12 | void log_d_c(const char *__restrict __format, ...) __attribute__ ((__format__ (__printf__, 1, 2))); 13 | 14 | void log_d_c_e(const char *__restrict __format, ...) __attribute__ ((__format__ (__printf__, 1, 2))); 15 | 16 | void log_i(const char *__restrict __format, ...) __attribute__ ((__format__ (__printf__, 1, 2))); 17 | 18 | void log_w(const char *__restrict __format, ...) __attribute__ ((__format__ (__printf__, 1, 2))); 19 | 20 | void log_e(const char *__restrict __format, ...) __attribute__ ((__format__ (__printf__, 1, 2))); 21 | 22 | void log_e_errno(const char *__restrict __format, ...) __attribute__ ((__format__ (__printf__, 1, 2))); 23 | 24 | void log_f(const char *__restrict __format, ...) __attribute__ ((__format__ (__printf__, 1, 2))); 25 | 26 | void log_f_errno(const char *__restrict __format, ...) __attribute__ ((__format__ (__printf__, 1, 2))); 27 | 28 | enum LogThreshold log_get_threshold(void); 29 | 30 | // false if not valid 31 | bool log_set_threshold(const char *s); 32 | 33 | #endif // LOG_H 34 | 35 | -------------------------------------------------------------------------------- /inc/mem.h: -------------------------------------------------------------------------------- 1 | #ifndef MEM_H 2 | #define MEM_H 3 | 4 | #include 5 | 6 | double *doubledup(const double d); 7 | 8 | int32_t *int32dup(const int32_t i); 9 | 10 | uint32_t *uint32dup(const uint32_t i); 11 | 12 | #endif // MEM_H 13 | 14 | -------------------------------------------------------------------------------- /inc/output.h: -------------------------------------------------------------------------------- 1 | #ifndef OUTPUT_H 2 | #define OUTPUT_H 3 | 4 | #include 5 | #include 6 | 7 | #include "river-layout-v3.h" 8 | #include "river-status-unstable-v1.h" 9 | 10 | #include "cmd.h" 11 | 12 | struct Output { 13 | struct wl_output *wl_output; 14 | uint32_t name; 15 | struct river_layout_v3 *river_layout; 16 | struct zriver_output_status_v1 *river_output_status; 17 | struct SList *tags; 18 | uint32_t command_tags_mask; 19 | }; 20 | 21 | struct Output *output_init(struct wl_output *wl_output, 22 | const uint32_t name, 23 | struct river_layout_manager_v3 *river_layout_manager, 24 | struct zriver_status_manager_v1 *river_status_manager); 25 | 26 | void output_destroy(const void *o); 27 | 28 | // apply cmd to command_tags_mask 29 | void output_apply_cmd(const struct Output *output, const struct Cmd *cmd); 30 | 31 | #endif // OUTPUT_H 32 | -------------------------------------------------------------------------------- /inc/tag.h: -------------------------------------------------------------------------------- 1 | #ifndef TAG_H 2 | #define TAG_H 3 | 4 | #include 5 | #include 6 | 7 | #include "enum.h" 8 | #include "slist.h" 9 | 10 | struct Tag { 11 | uint32_t mask; 12 | enum Layout layout_cur; 13 | enum Layout layout_prev; 14 | enum Stack stack; 15 | uint32_t count_master; 16 | double ratio_master; 17 | uint32_t count_wide_left; 18 | double ratio_wide; 19 | bool smart_gaps; 20 | uint32_t inner_gaps; 21 | uint32_t outer_gaps; 22 | }; 23 | 24 | struct Tag *tag_init(const uint32_t mask); 25 | 26 | struct SList *tags_init(void); 27 | 28 | void tag_destroy(const void *t); 29 | 30 | void tags_destroy(const struct SList *tags); 31 | 32 | // return the first tag that fits mask otherwise first 33 | struct Tag *tag_first(const struct SList *tags, const uint32_t mask); 34 | 35 | // return all tags matching mask 36 | struct SList *tag_all(const struct SList *tags, const uint32_t mask); 37 | 38 | #endif // TAG_H 39 | 40 | -------------------------------------------------------------------------------- /inc/usage.h: -------------------------------------------------------------------------------- 1 | #ifndef USAGE_H 2 | #define USAGE_H 3 | 4 | void usage(const int status); 5 | 6 | void usage_defaults(void); 7 | 8 | #endif // USAGE_H 9 | 10 | -------------------------------------------------------------------------------- /lib/col/.source: -------------------------------------------------------------------------------- 1 | git@github.com:alex-courtis/alex-c-collections.git v1.4.0 2 | -------------------------------------------------------------------------------- /lib/col/inc/fn.h: -------------------------------------------------------------------------------- 1 | #ifndef FN_H 2 | #define FN_H 3 | 4 | #include 5 | 6 | // 7 | // a is generally the value from the collection, b is user supplied 8 | // 9 | typedef bool (*fn_equals)(const void* const a, const void* const b); 10 | 11 | // true if both NULL or strcmp(a, b) == 0 12 | bool fn_comp_equals_strcmp(const void* const a, const void* const b); 13 | 14 | // true if both NULL or strstr(a, b) 15 | bool fn_comp_equals_strstr(const void* const a, const void* const b); 16 | 17 | // 18 | // a < b 19 | // 20 | typedef bool (*fn_less_than)(const void* const a, const void* const b); 21 | 22 | // 23 | // arbitrary test 24 | // 25 | typedef bool (*fn_test)(const void* const val); 26 | 27 | // 28 | // free 29 | // 30 | typedef void (*fn_free_val)(const void* const val); 31 | 32 | #endif // FN_H 33 | 34 | -------------------------------------------------------------------------------- /lib/col/inc/itable.h: -------------------------------------------------------------------------------- 1 | #ifndef ITABLE_H 2 | #define ITABLE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "fn.h" 9 | 10 | /* 11 | * Array backed integer indexed table. 12 | * Entries preserve insertion order. 13 | * Operations linearly traverse keys. 14 | * NULL values permitted. 15 | * Not thread safe. 16 | */ 17 | struct ITable; 18 | 19 | /* 20 | * Entry iterator. 21 | */ 22 | struct ITableIter { 23 | const uint64_t key; 24 | const void* const val; 25 | }; 26 | 27 | /* 28 | * Lifecycle 29 | */ 30 | 31 | // construct a table with initial size, growing as necessary, NULL on zero param 32 | const struct ITable *itable_init(const size_t initial, const size_t grow); 33 | 34 | // free table 35 | void itable_free(const void* const tab); 36 | 37 | // free table and vals, null fn_free_val uses free() 38 | void itable_free_vals(const struct ITable* const tab, fn_free_val); 39 | 40 | // free iter 41 | void itable_iter_free(const struct ITableIter* const iter); 42 | 43 | /* 44 | * Access 45 | */ 46 | 47 | // return val, NULL not present 48 | const void *itable_get(const struct ITable* const tab, const uint64_t key); 49 | 50 | // create an iterator, caller must itable_iter_free or invoke itable_next until NULL 51 | const struct ITableIter *itable_iter(const struct ITable* const tab); 52 | 53 | // next iterator value, NULL at end of list 54 | const struct ITableIter *itable_next(const struct ITableIter* const iter); 55 | 56 | /* 57 | * Mutate 58 | */ 59 | 60 | // set key/val, return old val if overwritten 61 | const void *itable_put(const struct ITable* const tab, const uint64_t key, const void* const val); 62 | 63 | // remove key, return old val if present 64 | const void *itable_remove(const struct ITable* const tab, const uint64_t key); 65 | 66 | /* 67 | * Comparison 68 | */ 69 | 70 | // same length, keys and vals equal in order, NULL equal compares pointers 71 | bool itable_equal(const struct ITable* const a, const struct ITable* const b, fn_equals); 72 | 73 | /* 74 | * Conversion 75 | */ 76 | 77 | // ordered uint64_t* key pointers to table, caller frees list only 78 | struct SList *itable_keys_slist(const struct ITable* const tab); 79 | 80 | // ordered val pointers to table, caller frees list only 81 | struct SList *itable_vals_slist(const struct ITable* const tab); 82 | 83 | /* 84 | * Info 85 | */ 86 | 87 | // number of entries with val 88 | size_t itable_size(const struct ITable* const tab); 89 | 90 | // current capacity: initial + n * grow 91 | size_t itable_capacity(const struct ITable* const tab); 92 | 93 | #endif // ITABLE_H 94 | 95 | -------------------------------------------------------------------------------- /lib/col/inc/oset.h: -------------------------------------------------------------------------------- 1 | #ifndef OSET_H 2 | #define OSET_H 3 | 4 | #include 5 | #include 6 | 7 | #include "fn.h" 8 | 9 | /* 10 | * Array backed ordered set. 11 | * Operations linearly traverse values. 12 | * NULL not permitted. 13 | * Not thread safe. 14 | */ 15 | struct OSet; 16 | 17 | /* 18 | * Entry iterator. 19 | */ 20 | struct OSetIter { 21 | const void* const val; 22 | }; 23 | 24 | /* 25 | * Lifecycle 26 | */ 27 | 28 | // construct a set with initial size, grow as needed, NULL on zero param 29 | const struct OSet *oset_init(const size_t initial, const size_t grow); 30 | 31 | // free set 32 | void oset_free(const void* const set); 33 | 34 | // free map and vals, NULL fn_free_val uses free() 35 | void oset_free_vals(const struct OSet* const set, fn_free_val); 36 | 37 | // free iter 38 | void oset_iter_free(const struct OSetIter* const iter); 39 | 40 | /* 41 | * Access 42 | */ 43 | 44 | // true if this set contains the specified element 45 | bool oset_contains(const struct OSet* const set, const void* const val); 46 | 47 | // create an iterator, caller must oset_iter_free or invoke oset_next until NULL 48 | const struct OSetIter *oset_iter(const struct OSet* const set); 49 | 50 | // next iterator value, NULL at end of set 51 | const struct OSetIter *oset_next(const struct OSetIter* const iter); 52 | 53 | /* 54 | * Mutate 55 | */ 56 | 57 | // true if this set did not already contain the specified element 58 | bool oset_add(const struct OSet* const set, const void* const val); 59 | 60 | // true if this set contained the element 61 | bool oset_remove(const struct OSet* const set, const void* const val); 62 | 63 | /* 64 | * Comparison 65 | */ 66 | 67 | // same length, vals equal in order, NULL equal compares pointers 68 | bool oset_equal(const struct OSet* const a, const struct OSet* const b, bool (*equal)(const void *a, const void *b)); 69 | 70 | /* 71 | * Conversion 72 | */ 73 | 74 | // ordered val pointers to table, caller frees list only 75 | struct SList *oset_vals_slist(const struct OSet* const set); 76 | 77 | /* 78 | * Info 79 | */ 80 | 81 | // number of values 82 | size_t oset_size(const struct OSet* const set); 83 | 84 | // current capacity: initial + n * grow 85 | size_t oset_capacity(const struct OSet* const set); 86 | 87 | #endif // OSET_H 88 | 89 | -------------------------------------------------------------------------------- /lib/col/inc/ptable.h: -------------------------------------------------------------------------------- 1 | #ifndef PTABLE_H 2 | #define PTABLE_H 3 | 4 | #include 5 | #include 6 | 7 | #include "fn.h" 8 | 9 | /* 10 | * ITable convenience wrapper with pointer key. 11 | */ 12 | struct PTable; 13 | 14 | /* 15 | * Entry iterator. 16 | */ 17 | struct PTableIter { 18 | const void *key; 19 | const void *val; 20 | }; 21 | 22 | /* 23 | * Lifecycle 24 | */ 25 | 26 | // construct a table with initial size, growing as necessary, NULL on zero param 27 | const struct PTable *ptable_init(const size_t initial, const size_t grow); 28 | 29 | // free table 30 | void ptable_free(const void* const tab); 31 | 32 | // free table and vals, null fn_free_val uses free() 33 | void ptable_free_vals(const struct PTable* const tab, fn_free_val); 34 | 35 | // free iter 36 | void ptable_iter_free(const struct PTableIter* const iter); 37 | 38 | /* 39 | * Access 40 | */ 41 | 42 | // return val, NULL not present 43 | const void *ptable_get(const struct PTable* const tab, const void* const key); 44 | 45 | // create an iterator, caller must ptable_iter_free or invoke ptable_next until NULL 46 | const struct PTableIter *ptable_iter(const struct PTable* const tab); 47 | 48 | // next iterator value, NULL at end of list 49 | const struct PTableIter *ptable_next(const struct PTableIter* const iter); 50 | 51 | /* 52 | * Mutate 53 | */ 54 | 55 | // set key/val, return old val if overwritten, NULL val to remove 56 | const void *ptable_put(const struct PTable* const tab, const void* const key, const void* const val); 57 | 58 | // remove key, return old val if present 59 | const void *ptable_remove(const struct PTable* const tab, const void* const key); 60 | 61 | /* 62 | * Comparison 63 | */ 64 | 65 | // same length, keys and vals equal in order, NULL equal compares pointers 66 | bool ptable_equal(const struct PTable* const a, const struct PTable* const b, fn_equals); 67 | 68 | /* 69 | * Conversion 70 | */ 71 | 72 | // ordered key pointers to table, caller frees list only 73 | struct SList *ptable_keys_slist(const struct PTable* const tab); 74 | 75 | // ordered val pointers to table, caller frees list only 76 | struct SList *ptable_vals_slist(const struct PTable* const tab); 77 | 78 | /* 79 | * Info 80 | */ 81 | 82 | // number of entries with val 83 | size_t ptable_size(const struct PTable* const tab); 84 | 85 | // current capacity: initial + n * grow 86 | size_t ptable_capacity(const struct PTable* const tab); 87 | 88 | #endif // PTABLE_H 89 | 90 | -------------------------------------------------------------------------------- /lib/col/inc/slist.h: -------------------------------------------------------------------------------- 1 | #ifndef SLIST_H 2 | #define SLIST_H 3 | 4 | #include 5 | #include 6 | 7 | #include "fn.h" 8 | 9 | struct SList { 10 | void *val; 11 | struct SList *nex; 12 | }; 13 | 14 | /* 15 | * Lifecycle 16 | */ 17 | 18 | // clone the list, setting val pointers 19 | struct SList *slist_shallow_clone(struct SList *head); 20 | 21 | // free list 22 | void slist_free(struct SList **head); 23 | 24 | // free list and vals, NULL fn_free_val uses free() 25 | void slist_free_vals(struct SList **head, fn_free_val); 26 | 27 | /* 28 | * Mutate 29 | */ 30 | 31 | // append val to a list 32 | struct SList *slist_append(struct SList **head, void *val); 33 | 34 | // remove an item, returning the val 35 | void *slist_remove(struct SList **head, struct SList **item); 36 | 37 | // remove items, NULL fn_equals is val pointer comparison 38 | size_t slist_remove_all(struct SList **head, fn_equals, const void *b); 39 | 40 | // remove items and free vals, NULL equals is val pointer comparison, NULL fn_free_val calls free() 41 | size_t slist_remove_all_free(struct SList **head, fn_equals, const void *b, fn_free_val); 42 | 43 | /* 44 | * Access 45 | */ 46 | 47 | // val at position 48 | void *slist_at(struct SList *head, size_t index); 49 | 50 | // find 51 | struct SList *slist_find(struct SList *head, fn_test); 52 | 53 | // find a val 54 | void *slist_find_val(struct SList *head, fn_test); 55 | 56 | // find, NULL fn_equals is val pointer comparison 57 | struct SList *slist_find_equal(struct SList *head, fn_equals, const void *b); 58 | 59 | // find a val, NULL fn_equals is val pointer comparison 60 | void *slist_find_equal_val(struct SList *head, fn_equals, const void *b); 61 | 62 | /* 63 | * Comparison 64 | */ 65 | 66 | // same length and every item equal in order, NULL fn_equals compares pointers 67 | bool slist_equal(struct SList *a, struct SList *b, fn_equals); 68 | 69 | /* 70 | * Utility 71 | */ 72 | 73 | // sort into a new list 74 | struct SList *slist_sort(struct SList *head, fn_less_than); 75 | 76 | // move items between lists where from value equals b, NULL fn_equals does nothing 77 | void slist_move(struct SList **to, struct SList **from, fn_equals, const void *b); 78 | 79 | // move items between lists where from value equals b, NULL fn_equals does nothing 80 | void slist_move(struct SList **to, struct SList **from, fn_equals, const void *b); 81 | 82 | /* 83 | * Info 84 | */ 85 | 86 | // length 87 | size_t slist_length(struct SList *head); 88 | 89 | #endif // SLIST_H 90 | 91 | -------------------------------------------------------------------------------- /lib/col/inc/stable.h: -------------------------------------------------------------------------------- 1 | #ifndef STABLE_H 2 | #define STABLE_H 3 | 4 | #include 5 | #include 6 | 7 | #include "fn.h" 8 | 9 | /* 10 | * Array backed string indexed table. 11 | * Entries preserve insertion order. 12 | * Operations linearly traverse keys. 13 | * NULL values permitted. 14 | * Not thread safe. 15 | */ 16 | struct STable; 17 | 18 | /* 19 | * Entry iterator. 20 | */ 21 | struct STableIter { 22 | const char* const key; 23 | const void* const val; 24 | }; 25 | 26 | /* 27 | * Lifecycle 28 | */ 29 | 30 | // construct a table with initial size, growing as necessary, NULL on zero param 31 | const struct STable *stable_init(const size_t initial, const size_t grow, const bool case_insensitive); 32 | 33 | // free table 34 | void stable_free(const void* const tab); 35 | 36 | // free table and vals, null fn_free_val uses free() 37 | void stable_free_vals(const struct STable* const tab, fn_free_val); 38 | 39 | // free iter 40 | void stable_iter_free(const struct STableIter* const iter); 41 | 42 | /* 43 | * Access 44 | */ 45 | 46 | // return val, NULL if not present 47 | const void *stable_get(const struct STable* const tab, const char* const key); 48 | 49 | // create an iterator, caller must stable_iter_free or invoke stable_next until NULL 50 | const struct STableIter *stable_iter(const struct STable* const tab); 51 | 52 | // next iterator value, NULL at end of list 53 | const struct STableIter *stable_next(const struct STableIter* const iter); 54 | 55 | /* 56 | * Mutate 57 | */ 58 | 59 | // set key/val, return old val if overwritten 60 | const void *stable_put(const struct STable* const tab, const char* const key, const void* const val); 61 | 62 | // remove key, return old val if present 63 | const void *stable_remove(const struct STable* const tab, const char* const key); 64 | 65 | /* 66 | * Comparison 67 | */ 68 | 69 | // same length, keys and vals equal, NULL equal compares pointers 70 | bool stable_equal(const struct STable* const a, const struct STable* const b, fn_equals); 71 | 72 | /* 73 | * Conversion 74 | */ 75 | 76 | // ordered key pointers to table, caller frees list only 77 | struct SList *stable_keys_slist(const struct STable* const tab); 78 | 79 | // ordered val pointers to table, caller frees list only 80 | struct SList *stable_vals_slist(const struct STable* const tab); 81 | 82 | /* 83 | * Info 84 | */ 85 | 86 | // number of entries 87 | size_t stable_size(const struct STable* const tab); 88 | 89 | // current capacity: initial + n * grow 90 | size_t stable_capacity(const struct STable* const tab); 91 | 92 | #endif // STABLE_H 93 | 94 | -------------------------------------------------------------------------------- /lib/col/src/fn.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "fn.h" 5 | 6 | bool fn_comp_equals_strcmp(const void* const a, const void* const b) { 7 | if (a == b) 8 | return true; 9 | 10 | if (!a || !b) 11 | return false; 12 | 13 | return strcmp(a, b) == 0; 14 | } 15 | 16 | bool fn_comp_equals_strstr(const void* const a, const void* const b) { 17 | if (a == b) 18 | return true; 19 | 20 | if (!a || !b) 21 | return false; 22 | 23 | return strstr(a, b); 24 | } 25 | -------------------------------------------------------------------------------- /lib/col/src/itable.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "fn.h" 7 | #include "slist.h" 8 | 9 | #include "itable.h" 10 | 11 | struct ITable { 12 | uint64_t *keys; 13 | const void **vals; 14 | size_t capacity; 15 | size_t grow; 16 | size_t size; 17 | }; 18 | 19 | struct ITableIterP { 20 | /* 21 | * Public, removed const 22 | */ 23 | uint64_t key; 24 | const void *val; 25 | 26 | /* 27 | * Private 28 | */ 29 | const struct ITable *tab; 30 | const uint64_t *k; 31 | const void **v; 32 | }; 33 | 34 | // grow to capacity + grow 35 | void grow_itable(struct ITable *tab) { 36 | 37 | // grow new arrays 38 | uint64_t *new_keys = calloc(tab->capacity + tab->grow, sizeof(uint64_t)); 39 | const void **new_vals = calloc(tab->capacity + tab->grow, sizeof(void*)); 40 | 41 | // copy old arrays 42 | memcpy(new_keys, tab->keys, tab->capacity * sizeof(uint64_t)); 43 | memcpy(new_vals, tab->vals, tab->capacity * sizeof(void*)); 44 | 45 | // free old arrays 46 | free(tab->keys); 47 | free(tab->vals); 48 | 49 | // lock in new 50 | tab->keys = new_keys; 51 | tab->vals = new_vals; 52 | tab->capacity += tab->grow; 53 | } 54 | 55 | const struct ITable *itable_init(const size_t initial, const size_t grow) { 56 | if (initial == 0 || grow == 0) 57 | return NULL; 58 | 59 | struct ITable *tab = calloc(1, sizeof(struct ITable)); 60 | tab->capacity = initial; 61 | tab->grow = grow; 62 | tab->keys = calloc(tab->capacity, sizeof(uint64_t)); 63 | tab->vals = calloc(tab->capacity, sizeof(void*)); 64 | 65 | return tab; 66 | } 67 | 68 | void itable_free(const void* const cvtab) { 69 | if (!cvtab) 70 | return; 71 | 72 | struct ITable *tab = (struct ITable*)cvtab; 73 | 74 | free(tab->keys); 75 | free(tab->vals); 76 | 77 | free(tab); 78 | } 79 | 80 | void itable_free_vals(const struct ITable* const tab, fn_free_val free_val) { 81 | if (!tab) 82 | return; 83 | 84 | for (const void **v = tab->vals; v < tab->vals + tab->capacity; v++) { 85 | if (*v) { 86 | if (free_val) { 87 | free_val(*v); 88 | } else { 89 | free((void*)*v); 90 | } 91 | } 92 | } 93 | 94 | itable_free(tab); 95 | } 96 | 97 | void itable_iter_free(const struct ITableIter* const iter) { 98 | if (!iter) 99 | return; 100 | 101 | free((void*)iter); 102 | } 103 | 104 | const void *itable_get(const struct ITable* const tab, const uint64_t key) { 105 | if (!tab) 106 | return NULL; 107 | 108 | // loop over keys 109 | uint64_t *k; 110 | const void **v; 111 | for (k = tab->keys, v = tab->vals; 112 | k < tab->keys + tab->size; 113 | k++, v++) { 114 | if (*k == key) { 115 | return *v; 116 | } 117 | } 118 | 119 | return NULL; 120 | } 121 | 122 | const struct ITableIter *itable_iter(const struct ITable* const tab) { 123 | if (!tab) 124 | return NULL; 125 | 126 | // loop over keys and vals 127 | uint64_t *k; 128 | const void **v; 129 | for (k = tab->keys, v = tab->vals; 130 | v < tab->vals + tab->size && k < tab->keys + tab->size; 131 | k++, v++) { 132 | if (*v) { 133 | struct ITableIterP *iterp = calloc(1, sizeof(struct ITableIterP)); 134 | 135 | iterp->tab = tab; 136 | iterp->key = *k; 137 | iterp->val = *v; 138 | iterp->k = k; 139 | iterp->v = v; 140 | 141 | return (struct ITableIter*)iterp; 142 | } 143 | } 144 | 145 | return NULL; 146 | } 147 | 148 | const struct ITableIter *itable_next(const struct ITableIter* const iter) { 149 | if (!iter) 150 | return NULL; 151 | 152 | struct ITableIterP *iterp = (struct ITableIterP*)iter; 153 | 154 | if (!iterp || !iterp->tab) { 155 | itable_iter_free(iter); 156 | return NULL; 157 | } 158 | 159 | // loop over keys and vals 160 | while (++iterp->v < iterp->tab->vals + iterp->tab->size && 161 | ++iterp->k < iterp->tab->keys + iterp->tab->size) { 162 | if (*iterp->v) { 163 | iterp->key = *(iterp->k); 164 | iterp->val = *(iterp->v); 165 | return iter; 166 | } 167 | } 168 | 169 | itable_iter_free(iter); 170 | return NULL; 171 | } 172 | 173 | const void *itable_put(const struct ITable* const ctab, const uint64_t key, const void* const val) { 174 | if (!ctab) 175 | return NULL; 176 | 177 | struct ITable *tab = (struct ITable*)ctab; 178 | 179 | // loop over existing keys 180 | uint64_t *k; 181 | const void **v; 182 | for (k = tab->keys, v = tab->vals; k < tab->keys + tab->size; k++, v++) { 183 | 184 | // overwrite existing values 185 | if (*k == key) { 186 | const void *prev = *v; 187 | *v = val; 188 | return prev; 189 | } 190 | } 191 | 192 | // grow for new entry 193 | if (tab->size >= tab->capacity) { 194 | grow_itable(tab); 195 | k = &tab->keys[tab->size]; 196 | v = &tab->vals[tab->size]; 197 | } 198 | 199 | // new 200 | *k = key; 201 | *v = val; 202 | tab->size++; 203 | 204 | return NULL; 205 | } 206 | 207 | const void *itable_remove(const struct ITable* const ctab, const uint64_t key) { 208 | if (!ctab) 209 | return NULL; 210 | 211 | struct ITable *tab = (struct ITable*)ctab; 212 | 213 | // loop over existing keys 214 | uint64_t *k; 215 | const void **v; 216 | for (k = tab->keys, v = tab->vals; k < tab->keys + tab->size; k++, v++) { 217 | 218 | if (*k == key) { 219 | const void* prev = *v; 220 | *v = NULL; 221 | tab->size--; 222 | 223 | // shift down over removed 224 | uint64_t *mk; 225 | const void **mv; 226 | for (mk = k, mv = v; mk < tab->keys + tab->size; mk++, mv++) { 227 | *mk = *(mk + 1); 228 | *mv = *(mv + 1); 229 | } 230 | *mk = 0; 231 | *mv = NULL; 232 | 233 | return prev; 234 | } 235 | } 236 | 237 | return NULL; 238 | } 239 | 240 | bool itable_equal(const struct ITable* const a, const struct ITable* const b, fn_equals equals) { 241 | if (!a || !b || a->size != b->size) 242 | return false; 243 | 244 | uint64_t *ak, *bk; 245 | const void **av, **bv; 246 | 247 | for (ak = a->keys, bk = b->keys, av = a->vals, bv = b->vals; 248 | ak < a->keys + a->size; 249 | ak++, bk++, av++, bv++) { 250 | 251 | // key 252 | if (*ak != *bk) { 253 | return false; 254 | } 255 | 256 | // value 257 | if (equals) { 258 | if (!equals(*av, *bv)) { 259 | return false; 260 | } 261 | } else if (*av != *bv) { 262 | return false; 263 | } 264 | } 265 | 266 | return true; 267 | } 268 | 269 | struct SList *itable_keys_slist(const struct ITable* const tab) { 270 | if (!tab) 271 | return NULL; 272 | 273 | struct SList *list = NULL; 274 | 275 | uint64_t *k; 276 | for (k = tab->keys; k < tab->keys + tab->size; k++) { 277 | slist_append(&list, (void*)*k); 278 | } 279 | 280 | return list; 281 | } 282 | 283 | struct SList *itable_vals_slist(const struct ITable* const tab) { 284 | if (!tab) 285 | return NULL; 286 | 287 | struct SList *list = NULL; 288 | 289 | uint64_t *k; 290 | const void **v; 291 | for (k = tab->keys, v = tab->vals; k < tab->keys + tab->size; k++, v++) { 292 | slist_append(&list, (void*)*v); 293 | } 294 | 295 | return list; 296 | } 297 | 298 | size_t itable_size(const struct ITable* const tab) { 299 | return tab ? tab->size : 0; 300 | } 301 | 302 | size_t itable_capacity(const struct ITable* const tab) { 303 | return tab ? tab->capacity : 0; 304 | } 305 | -------------------------------------------------------------------------------- /lib/col/src/oset.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "fn.h" 6 | #include "slist.h" 7 | 8 | #include "oset.h" 9 | 10 | struct OSet { 11 | const void **vals; 12 | size_t capacity; 13 | size_t grow; 14 | size_t size; 15 | }; 16 | 17 | struct OSetIterP { 18 | /* 19 | * Public, removed const 20 | */ 21 | const void* val; 22 | 23 | /* 24 | * Private 25 | */ 26 | const struct OSet *set; 27 | const void **v; 28 | }; 29 | 30 | // grow to capacity + grow 31 | void grow_oset(struct OSet *set) { 32 | 33 | // grow new arrays 34 | const void **new_vals = calloc(set->capacity + set->grow, sizeof(void*)); 35 | 36 | // copy old arrays 37 | memcpy(new_vals, set->vals, set->capacity * sizeof(void*)); 38 | 39 | // free old arrays 40 | free(set->vals); 41 | 42 | // lock in new 43 | set->vals = new_vals; 44 | set->capacity += set->grow; 45 | } 46 | 47 | const struct OSet *oset_init(const size_t initial, const size_t grow) { 48 | if (initial == 0 || grow == 0) 49 | return NULL; 50 | 51 | struct OSet *set = calloc(1, sizeof(struct OSet)); 52 | set->capacity = initial; 53 | set->grow = grow; 54 | set->vals = calloc(set->capacity, sizeof(void*)); 55 | 56 | return set; 57 | } 58 | 59 | void oset_free(const void* const cvset) { 60 | if (!cvset) 61 | return; 62 | 63 | struct OSet *set = (struct OSet*)cvset; 64 | 65 | free(set->vals); 66 | 67 | free(set); 68 | } 69 | 70 | void oset_free_vals(const struct OSet* const set, fn_free_val free_val) { 71 | if (!set) 72 | return; 73 | 74 | // loop over vals 75 | for (const void **v = set->vals; v < set->vals + set->capacity; v++) { 76 | if (*v) { 77 | if (free_val) { 78 | free_val(*v); 79 | } else { 80 | free((void*)*v); 81 | } 82 | } 83 | } 84 | 85 | oset_free(set); 86 | } 87 | 88 | void oset_iter_free(const struct OSetIter* const iter) { 89 | if (!iter) 90 | return; 91 | 92 | free((struct OSetIterP*)iter); 93 | } 94 | 95 | bool oset_contains(const struct OSet* const set, const void* const val) { 96 | if (!set || !val) 97 | return false; 98 | 99 | // loop over vals 100 | for (const void **v = set->vals; v < set->vals + set->size; v++) { 101 | if (*v == val) { 102 | return true; 103 | } 104 | } 105 | 106 | return false; 107 | } 108 | 109 | const struct OSetIter *oset_iter(const struct OSet* const set) { 110 | if (!set) 111 | return NULL; 112 | 113 | // loop over vals 114 | for (const void **v = set->vals; v < set->vals + set->size; v++) { 115 | if (*v) { 116 | struct OSetIterP *iterp = calloc(1, sizeof(struct OSetIterP)); 117 | 118 | iterp->set = set; 119 | iterp->val = *v; 120 | iterp->v = v; 121 | 122 | return (struct OSetIter*)iterp; 123 | } 124 | } 125 | 126 | return NULL; 127 | } 128 | 129 | const struct OSetIter *oset_next(const struct OSetIter* const iter) { 130 | if (!iter) 131 | return NULL; 132 | 133 | struct OSetIterP *iterp = (struct OSetIterP*)iter; 134 | 135 | if (!iterp || !iterp->set) { 136 | oset_iter_free(iter); 137 | return NULL; 138 | } 139 | 140 | // loop over vals 141 | while (++iterp->v < iterp->set->vals + iterp->set->size) { 142 | if (*iterp->v) { 143 | iterp->val = *(iterp->v); 144 | return iter; 145 | } 146 | } 147 | 148 | oset_iter_free(iter); 149 | return NULL; 150 | } 151 | 152 | bool oset_add(const struct OSet* const cset, const void* const val) { 153 | if (!cset || !val) 154 | return false; 155 | 156 | struct OSet *set = (struct OSet*)cset; 157 | 158 | // loop over vals 159 | const void **v; 160 | for (v = set->vals; v < set->vals + set->size; v++) { 161 | 162 | // already present 163 | if (*v == val) { 164 | return false; 165 | } 166 | } 167 | 168 | // maybe grow for new entry 169 | if (set->size >= set->capacity) { 170 | grow_oset(set); 171 | v = &set->vals[set->size]; 172 | } 173 | 174 | // new value 175 | *v = (void*)val; 176 | set->size++; 177 | 178 | return true; 179 | } 180 | 181 | bool oset_remove(const struct OSet* const cset, const void* const val) { 182 | if (!cset || !val) 183 | return false; 184 | 185 | struct OSet *set = (struct OSet*)cset; 186 | 187 | // loop over vals 188 | for (const void **v = set->vals; v < set->vals + set->size; v++) { 189 | if (*v == val) { 190 | 191 | *v = NULL; 192 | set->size--; 193 | 194 | // shift down over removed 195 | const void **m; 196 | for (m = v; m < v + set->size; m++) { 197 | *m = *(m + 1); 198 | } 199 | *m = NULL; 200 | 201 | return true; 202 | } 203 | } 204 | 205 | return false; 206 | } 207 | 208 | bool oset_equal(const struct OSet* const a, const struct OSet* const b, fn_equals equals) { 209 | if (!a || !b || a->size != b->size) 210 | return false; 211 | 212 | for (const void **av = a->vals, **bv = b->vals; av < (a->vals + a->size); av++, bv++) { 213 | 214 | // value 215 | if (equals) { 216 | if (!equals(*av, *bv)) { 217 | return false; 218 | } 219 | } else if (*av != *bv) { 220 | return false; 221 | } 222 | } 223 | 224 | return true; 225 | } 226 | 227 | struct SList *oset_vals_slist(const struct OSet* const set) { 228 | if (!set) 229 | return NULL; 230 | 231 | struct SList *list = NULL; 232 | 233 | for (const void **v = set->vals; v < set->vals + set->size; v++) { 234 | slist_append(&list, (void*)*v); 235 | } 236 | 237 | return list; 238 | } 239 | 240 | size_t oset_size(const struct OSet* const set) { 241 | return set ? set->size : 0; 242 | } 243 | 244 | size_t oset_capacity(const struct OSet* const set) { 245 | return set ? set->capacity : 0; 246 | } 247 | -------------------------------------------------------------------------------- /lib/col/src/ptable.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "fn.h" 7 | #include "itable.h" 8 | 9 | #include "ptable.h" 10 | 11 | const struct PTable *ptable_init(const size_t initial, const size_t grow) { 12 | 13 | // pointer has to fit into ITable key 14 | assert(sizeof(void*) <= sizeof(uint64_t)); 15 | 16 | return (struct PTable *)itable_init(initial, grow); 17 | } 18 | 19 | void ptable_free(const void* const tab) { 20 | itable_free(tab); 21 | } 22 | 23 | void ptable_free_vals(const struct PTable* const tab, fn_free_val free_val) { 24 | itable_free_vals((const struct ITable* const)tab, free_val); 25 | } 26 | 27 | void ptable_iter_free(const struct PTableIter* const iter) { 28 | itable_iter_free((const struct ITableIter* const)iter); 29 | } 30 | 31 | const void *ptable_get(const struct PTable* const tab, const void* const key) { 32 | return itable_get((const struct ITable* const)tab, (const uint64_t)key); 33 | } 34 | 35 | const struct PTableIter *ptable_iter(const struct PTable* const tab) { 36 | return (struct PTableIter*)itable_iter((struct ITable* const)tab); 37 | } 38 | 39 | const struct PTableIter *ptable_next(const struct PTableIter* const iter) { 40 | return (struct PTableIter*)itable_next((struct ITableIter* const)iter); 41 | } 42 | 43 | const void *ptable_put(const struct PTable* const tab, const void* const key, const void* const val) { 44 | return itable_put((const struct ITable* const)tab, (const uint64_t)key, val); 45 | } 46 | 47 | const void *ptable_remove(const struct PTable* const tab, const void* const key) { 48 | return itable_remove((const struct ITable* const)tab, (const uint64_t)key); 49 | } 50 | 51 | bool ptable_equal(const struct PTable* const a, const struct PTable* const b, fn_equals equals) { 52 | return itable_equal((const struct ITable* const)a, (const struct ITable* const)b, equals); 53 | } 54 | 55 | struct SList *ptable_keys_slist(const struct PTable* const tab) { 56 | return itable_keys_slist((const struct ITable* const)tab); 57 | } 58 | 59 | struct SList *ptable_vals_slist(const struct PTable* const tab) { 60 | return itable_vals_slist((const struct ITable* const)tab); 61 | } 62 | 63 | size_t ptable_size(const struct PTable* const tab) { 64 | return itable_size((struct ITable* const)tab); 65 | } 66 | 67 | size_t ptable_capacity(const struct PTable* const tab) { 68 | return itable_capacity((struct ITable* const)tab); 69 | } 70 | 71 | -------------------------------------------------------------------------------- /lib/col/src/slist.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "fn.h" 5 | 6 | #include "slist.h" 7 | 8 | struct SList *slist_shallow_clone(struct SList *head) { 9 | struct SList *c, *i; 10 | 11 | c = NULL; 12 | for (i = head; i; i = i->nex) { 13 | slist_append(&c, i->val); 14 | } 15 | 16 | return c; 17 | } 18 | 19 | void slist_free(struct SList **head) { 20 | struct SList *i, *f; 21 | 22 | i = *head; 23 | while (i) { 24 | f = i; 25 | i = i->nex; 26 | free(f); 27 | } 28 | 29 | *head = NULL; 30 | } 31 | 32 | void slist_free_vals(struct SList **head, fn_free_val free_val) { 33 | struct SList *i; 34 | 35 | for (i = *head; i; i = i->nex) { 36 | if (free_val) { 37 | free_val(i->val); 38 | } else { 39 | free(i->val); 40 | } 41 | } 42 | 43 | slist_free(head); 44 | } 45 | 46 | struct SList *slist_append(struct SList **head, void *val) { 47 | struct SList *i, *l; 48 | 49 | i = calloc(1, sizeof(struct SList)); 50 | i->val = val; 51 | 52 | if (*head) { 53 | for (l = *head; l->nex; l = l->nex); 54 | l->nex = i; 55 | } else { 56 | *head = i; 57 | } 58 | 59 | return i; 60 | } 61 | 62 | void *slist_remove(struct SList **head, struct SList **item) { 63 | struct SList *i, *f, *p; 64 | void *removed = NULL; 65 | 66 | i = *head; 67 | p = NULL; 68 | f = NULL; 69 | 70 | for (i = *head; i; i = i->nex) { 71 | if (i == *item) { 72 | f = *item; 73 | break; 74 | } 75 | p = i; 76 | } 77 | 78 | if (f) { 79 | if (p) { 80 | p->nex = f->nex; 81 | } else { 82 | *head = f->nex; 83 | } 84 | removed = f->val; 85 | free(f); 86 | *item = NULL; 87 | } 88 | 89 | return removed; 90 | } 91 | 92 | size_t slist_remove_all(struct SList **head, fn_equals equals, const void *b) { 93 | struct SList *i; 94 | size_t removed = 0; 95 | 96 | while ((i = slist_find_equal(*head, equals, b))) { 97 | slist_remove(head, &i); 98 | removed++; 99 | } 100 | 101 | return removed; 102 | } 103 | 104 | size_t slist_remove_all_free(struct SList **head, fn_equals equals, const void *b, fn_free_val free_val) { 105 | struct SList *i; 106 | size_t removed = 0; 107 | 108 | while ((i = slist_find_equal(*head, equals, b))) { 109 | if (free_val) { 110 | free_val(i->val); 111 | } else { 112 | free(i->val); 113 | } 114 | slist_remove(head, &i); 115 | removed++; 116 | } 117 | 118 | return removed; 119 | } 120 | 121 | void *slist_at(struct SList *head, size_t index) { 122 | size_t c = 0; 123 | for (struct SList *i = head; i; i = i->nex, c++) { 124 | if (c == index) { 125 | return i->val; 126 | } 127 | } 128 | 129 | return NULL; 130 | } 131 | 132 | struct SList *slist_find(struct SList *head, fn_test test) { 133 | struct SList *i; 134 | 135 | if (!test) 136 | return NULL; 137 | 138 | for (i = head; i; i = i->nex) { 139 | if (test(i->val)) { 140 | return i; 141 | } 142 | } 143 | 144 | return NULL; 145 | } 146 | 147 | void *slist_find_val(struct SList *head, fn_test test) { 148 | struct SList *f = slist_find(head, test); 149 | if (f) 150 | return f->val; 151 | else 152 | return NULL; 153 | } 154 | 155 | struct SList *slist_find_equal(struct SList *head, fn_equals equals, const void *b) { 156 | struct SList *i; 157 | 158 | for (i = head; i; i = i->nex) { 159 | if (equals) { 160 | if (equals(i->val, b)) { 161 | return i; 162 | } 163 | } else if (i->val == b) { 164 | return i; 165 | } 166 | } 167 | 168 | return NULL; 169 | } 170 | 171 | void *slist_find_equal_val(struct SList *head, fn_equals equals, const void *b) { 172 | struct SList *f = slist_find_equal(head, equals, b); 173 | if (f) 174 | return f->val; 175 | else 176 | return NULL; 177 | } 178 | 179 | bool slist_equal(struct SList *a, struct SList *b, fn_equals equals) { 180 | struct SList *ai, *bi; 181 | 182 | for (ai = a, bi = b; ai && bi; ai = ai->nex, bi = bi->nex) { 183 | if (equals) { 184 | if (!equals(ai->val, bi->val)) { 185 | return false; 186 | } 187 | } else if (ai->val != bi->val) { 188 | return false; 189 | } 190 | } 191 | 192 | if (ai || bi) { 193 | return false; 194 | } 195 | 196 | return true; 197 | } 198 | 199 | size_t slist_length(struct SList *head) { 200 | size_t length = 0; 201 | 202 | for (struct SList *i = head; i; i = i->nex) { 203 | length++; 204 | } 205 | 206 | return length; 207 | } 208 | 209 | struct SList *slist_sort(struct SList *head, fn_less_than less_than) { 210 | struct SList *sorted = NULL; 211 | 212 | if (!head || !less_than) { 213 | return sorted; 214 | } 215 | 216 | if (!head->nex) { 217 | slist_append(&sorted, head->val); 218 | return sorted; 219 | } 220 | 221 | struct SList *sorting = slist_shallow_clone(head); 222 | 223 | struct SList *sorting_head = sorting; 224 | struct SList **sorted_trail = &sorted; 225 | 226 | while (sorting != NULL) { 227 | sorting_head = sorting; 228 | sorted_trail = &sorted; 229 | 230 | sorting = sorting->nex; 231 | 232 | while (!(*sorted_trail == NULL || less_than(sorting_head->val, (*sorted_trail)->val))) { 233 | sorted_trail = &(*sorted_trail)->nex; 234 | } 235 | 236 | sorting_head->nex = *sorted_trail; 237 | *sorted_trail = sorting_head; 238 | } 239 | 240 | slist_free(&sorting); 241 | return sorted; 242 | } 243 | 244 | void slist_move(struct SList **to, struct SList **from, fn_equals equals, const void *b) { 245 | if (!to || !from || !equals) 246 | return; 247 | 248 | struct SList *f = *from; 249 | while (f) { 250 | struct SList *r = f; 251 | void *val = f->val; 252 | f = f->nex; 253 | if (equals(val, b)) { 254 | slist_append(to, val); 255 | slist_remove(from, &r); 256 | } 257 | } 258 | } 259 | 260 | -------------------------------------------------------------------------------- /lib/col/src/stable.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "fn.h" 7 | 8 | #include "slist.h" 9 | 10 | #include "stable.h" 11 | 12 | typedef int (*comparator)(const char *s1, const char *s2); 13 | 14 | struct STable { 15 | const char **keys; 16 | const void **vals; 17 | size_t capacity; 18 | size_t grow; 19 | size_t size; 20 | comparator cmp; 21 | }; 22 | 23 | struct STableIterP { 24 | /* 25 | * Public, removed const 26 | */ 27 | const char *key; 28 | const void *val; 29 | 30 | /* 31 | * Private 32 | */ 33 | const struct STable *tab; 34 | const char **k; 35 | const void **v; 36 | }; 37 | 38 | // grow to capacity + grow 39 | void grow_stable(struct STable *tab) { 40 | 41 | // grow new arrays 42 | const char **new_keys = calloc(tab->capacity + tab->grow, sizeof(char*)); 43 | const void **new_vals = calloc(tab->capacity + tab->grow, sizeof(void*)); 44 | 45 | // copy old arrays 46 | memcpy(new_keys, tab->keys, tab->capacity * sizeof(char*)); 47 | memcpy(new_vals, tab->vals, tab->capacity * sizeof(void*)); 48 | 49 | // free old arrays 50 | free(tab->keys); 51 | free(tab->vals); 52 | 53 | // lock in new 54 | tab->keys = new_keys; 55 | tab->vals = new_vals; 56 | tab->capacity += tab->grow; 57 | } 58 | 59 | const struct STable *stable_init(const size_t initial, const size_t grow, const bool case_insensitive) { 60 | if (initial == 0 || grow == 0) 61 | return NULL; 62 | 63 | struct STable *tab = calloc(1, sizeof(struct STable)); 64 | tab->capacity = initial; 65 | tab->grow = grow; 66 | if (case_insensitive) { 67 | tab->cmp = strcasecmp; 68 | } else { 69 | tab->cmp = strcmp; 70 | } 71 | tab->keys = calloc(tab->capacity, sizeof(char*)); 72 | tab->vals = calloc(tab->capacity, sizeof(void*)); 73 | 74 | return tab; 75 | } 76 | 77 | void stable_free(const void* const cvtab) { 78 | if (!cvtab) 79 | return; 80 | 81 | struct STable *tab = (struct STable*)cvtab; 82 | 83 | for (const char **k = tab->keys; k < tab->keys + tab->capacity; k++) { 84 | free((void*)*k); 85 | } 86 | free(tab->keys); 87 | free(tab->vals); 88 | 89 | free(tab); 90 | } 91 | 92 | void stable_free_vals(const struct STable* const tab, fn_free_val free_val) { 93 | if (!tab) 94 | return; 95 | 96 | for (const void **v = tab->vals; v < tab->vals + tab->capacity; v++) { 97 | if (*v) { 98 | if (free_val) { 99 | free_val(*v); 100 | } else { 101 | free((void*)*v); 102 | } 103 | } 104 | } 105 | 106 | stable_free(tab); 107 | } 108 | 109 | void stable_iter_free(const struct STableIter* const iter) { 110 | if (!iter) 111 | return; 112 | 113 | free((void*)iter); 114 | } 115 | 116 | const void *stable_get(const struct STable* const tab, const char* const key) { 117 | if (!tab) 118 | return NULL; 119 | 120 | // loop over keys 121 | const char **k; 122 | const void **v; 123 | for (k = tab->keys, v = tab->vals; 124 | k < tab->keys + tab->size; 125 | k++, v++) { 126 | if (tab->cmp(*k, key) == 0) { 127 | return *v; 128 | } 129 | } 130 | 131 | return NULL; 132 | } 133 | 134 | const struct STableIter *stable_iter(const struct STable* const tab) { 135 | if (!tab) 136 | return NULL; 137 | 138 | // loop over keys and vals 139 | const char **k; 140 | const void **v; 141 | for (k = tab->keys, v = tab->vals; 142 | v < tab->vals + tab->size && k < tab->keys + tab->size; 143 | k++, v++) { 144 | if (*v) { 145 | struct STableIterP *iterp = calloc(1, sizeof(struct STableIterP)); 146 | 147 | iterp->tab = tab; 148 | iterp->key = *k; 149 | iterp->val = *v; 150 | iterp->k = k; 151 | iterp->v = v; 152 | 153 | return (struct STableIter*)iterp; 154 | } 155 | } 156 | 157 | return NULL; 158 | } 159 | 160 | const struct STableIter *stable_next(const struct STableIter* const iter) { 161 | if (!iter) 162 | return NULL; 163 | 164 | struct STableIterP *iterp = (struct STableIterP*)iter; 165 | 166 | if (!iterp || !iterp->tab) { 167 | stable_iter_free(iter); 168 | return NULL; 169 | } 170 | 171 | // loop over keys and vals 172 | while (++iterp->v < iterp->tab->vals + iterp->tab->size && 173 | ++iterp->k < iterp->tab->keys + iterp->tab->size) { 174 | if (*iterp->v) { 175 | iterp->key = *(iterp->k); 176 | iterp->val = *(iterp->v); 177 | return iter; 178 | } 179 | } 180 | 181 | stable_iter_free(iter); 182 | return NULL; 183 | } 184 | 185 | const void *stable_put(const struct STable* const ctab, const char* const key, const void* const val) { 186 | if (!ctab || !key) 187 | return NULL; 188 | 189 | struct STable *tab = (struct STable*)ctab; 190 | 191 | // loop over existing keys 192 | const char **k; 193 | const void **v; 194 | for (k = tab->keys, v = tab->vals; k < tab->keys + tab->size; k++, v++) { 195 | 196 | // overwrite existing values 197 | if (tab->cmp(*k, key) == 0) { 198 | const void *prev = *v; 199 | *v = val; 200 | return prev; 201 | } 202 | } 203 | 204 | // grow for new entry 205 | if (tab->size >= tab->capacity) { 206 | grow_stable(tab); 207 | k = &tab->keys[tab->size]; 208 | v = &tab->vals[tab->size]; 209 | } 210 | 211 | // new 212 | *k = strdup(key); 213 | *v = val; 214 | tab->size++; 215 | 216 | return NULL; 217 | } 218 | 219 | const void *stable_remove(const struct STable* const ctab, const char* const key) { 220 | if (!ctab) 221 | return NULL; 222 | 223 | struct STable *tab = (struct STable*)ctab; 224 | 225 | // loop over existing keys 226 | const char **k; 227 | const void **v; 228 | for (k = tab->keys, v = tab->vals; k < tab->keys + tab->size; k++, v++) { 229 | 230 | if (tab->cmp(*k, key) == 0) { 231 | free((void*)*k); 232 | *k = NULL; 233 | const void* prev = *v; 234 | *v = NULL; 235 | tab->size--; 236 | 237 | // shift down over removed 238 | const char **mk; 239 | const void **mv; 240 | for (mk = k, mv = v; mk < tab->keys + tab->size; mk++, mv++) { 241 | *mk = *(mk + 1); 242 | *mv = *(mv + 1); 243 | } 244 | *mk = NULL; 245 | *mv = NULL; 246 | 247 | return prev; 248 | } 249 | } 250 | 251 | return NULL; 252 | } 253 | 254 | bool stable_equal(const struct STable* const a, const struct STable* const b, fn_equals equals) { 255 | if (!a || !b || a->size != b->size) 256 | return false; 257 | 258 | const char **ak, **bk; 259 | const void **av, **bv; 260 | 261 | for (ak = a->keys, bk = b->keys, av = a->vals, bv = b->vals; 262 | ak < a->keys + a->size; 263 | ak++, bk++, av++, bv++) { 264 | 265 | // key 266 | if (a->cmp == strcasecmp || b->cmp == strcasecmp) { 267 | if (strcasecmp(*ak, *bk) != 0) { 268 | return false; 269 | } 270 | } else { 271 | if (strcmp(*ak, *bk) != 0) { 272 | return false; 273 | } 274 | } 275 | 276 | // value 277 | if (equals) { 278 | if (!equals(*av, *bv)) { 279 | return false; 280 | } 281 | } else if (*av != *bv) { 282 | return false; 283 | } 284 | } 285 | 286 | return true; 287 | } 288 | 289 | struct SList *stable_keys_slist(const struct STable* const tab) { 290 | if (!tab) 291 | return NULL; 292 | 293 | struct SList *list = NULL; 294 | 295 | const char **k; 296 | for (k = tab->keys; k < tab->keys + tab->size; k++) { 297 | slist_append(&list, (void*)*k); 298 | } 299 | 300 | return list; 301 | } 302 | 303 | struct SList *stable_vals_slist(const struct STable* const tab) { 304 | if (!tab) 305 | return NULL; 306 | 307 | struct SList *list = NULL; 308 | 309 | const char **k; 310 | const void **v; 311 | for (k = tab->keys, v = tab->vals; k < tab->keys + tab->size; k++, v++) { 312 | slist_append(&list, (void*)*v); 313 | } 314 | 315 | return list; 316 | } 317 | 318 | size_t stable_size(const struct STable* const tab) { 319 | return tab ? tab->size : 0; 320 | } 321 | 322 | size_t stable_capacity(const struct STable* const tab) { 323 | return tab ? tab->capacity : 0; 324 | } 325 | 326 | -------------------------------------------------------------------------------- /pro/river-control-unstable-v1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright 2020 The River Developers 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | 18 | 19 | 20 | 21 | This interface allows clients to run compositor commands and receive a 22 | success/failure response with output or a failure message respectively. 23 | 24 | Each command is built up in a series of add_argument requests and 25 | executed with a run_command request. The first argument is the command 26 | to be run. 27 | 28 | A complete list of commands should be made available in the man page of 29 | the compositor. 30 | 31 | 32 | 33 | 34 | This request indicates that the client will not use the 35 | river_control object any more. Objects that have been created 36 | through this instance are not affected. 37 | 38 | 39 | 40 | 41 | 42 | Arguments are stored by the server in the order they were sent until 43 | the run_command request is made. 44 | 45 | 46 | 47 | 48 | 49 | 50 | Execute the command built up using the add_argument request for the 51 | given seat. 52 | 53 | 54 | 56 | 57 | 58 | 59 | 60 | 61 | This object is created by the run_command request. Exactly one of the 62 | success or failure events will be sent. This object will be destroyed 63 | by the compositor after one of the events is sent. 64 | 65 | 66 | 67 | 68 | Sent when the command has been successfully received and executed by 69 | the compositor. Some commands may produce output, in which case the 70 | output argument will be a non-empty string. 71 | 72 | 73 | 74 | 75 | 76 | 77 | Sent when the command could not be carried out. This could be due to 78 | sending a non-existent command, no command, not enough arguments, too 79 | many arguments, invalid arguments, etc. 80 | 81 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /pro/river-status-unstable-v1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright 2020 The River Developers 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | 18 | 19 | 20 | 21 | A global factory for objects that receive status information specific 22 | to river. It could be used to implement, for example, a status bar. 23 | 24 | 25 | 26 | 27 | This request indicates that the client will not use the 28 | river_status_manager object any more. Objects that have been created 29 | through this instance are not affected. 30 | 31 | 32 | 33 | 34 | 35 | This creates a new river_output_status object for the given wl_output. 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | This creates a new river_seat_status object for the given wl_seat. 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | This interface allows clients to receive information about the current 53 | windowing state of an output. 54 | 55 | 56 | 57 | 58 | This request indicates that the client will not use the 59 | river_output_status object any more. 60 | 61 | 62 | 63 | 64 | 65 | Sent once binding the interface and again whenever the tag focus of 66 | the output changes. 67 | 68 | 69 | 70 | 71 | 72 | 73 | Sent once on binding the interface and again whenever the tag state 74 | of the output changes. 75 | 76 | 77 | 78 | 79 | 80 | 81 | Sent once on binding the interface and again whenever the set of 82 | tags with at least one urgent view changes. 83 | 84 | 85 | 86 | 87 | 88 | 89 | Sent once on binding the interface should a layout name exist and again 90 | whenever the name changes. 91 | 92 | 93 | 94 | 95 | 96 | 97 | Sent when the current layout name has been removed without a new one 98 | being set, for example when the active layout generator disconnects. 99 | 100 | 101 | 102 | 103 | 104 | 105 | This interface allows clients to receive information about the current 106 | focus of a seat. Note that (un)focused_output events will only be sent 107 | if the client has bound the relevant wl_output globals. 108 | 109 | 110 | 111 | 112 | This request indicates that the client will not use the 113 | river_seat_status object any more. 114 | 115 | 116 | 117 | 118 | 119 | Sent on binding the interface and again whenever an output gains focus. 120 | 121 | 122 | 123 | 124 | 125 | 126 | Sent whenever an output loses focus. 127 | 128 | 129 | 130 | 131 | 132 | 133 | Sent once on binding the interface and again whenever the focused 134 | view or a property thereof changes. The title may be an empty string 135 | if no view is focused or the focused view did not set a title. 136 | 137 | 138 | 139 | 140 | 141 | 142 | Sent once on binding the interface and again whenever a new mode 143 | is entered (e.g. with riverctl enter-mode foobar). 144 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /src/args.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "cfg.h" 8 | #include "cmd.h" 9 | #include "enum.h" 10 | #include "log.h" 11 | #include "usage.h" 12 | 13 | #include "args.h" 14 | 15 | static struct option cli_long_options[] = { 16 | { "layout", required_argument, 0, 0, }, // 0 17 | { "layout-alt", required_argument, 0, 0, }, // 1 18 | { "stack", required_argument, 0, 0, }, // 2 19 | { "count-master", required_argument, 0, 0, }, // 3 20 | { "ratio-master", required_argument, 0, 0, }, // 4 21 | { "count-wide-left", required_argument, 0, 0, }, // 5 22 | { "ratio-wide", required_argument, 0, 0, }, // 6 23 | { "smart-gaps", no_argument, 0, 0, }, // 7 24 | { "no-smart-gaps", no_argument, 0, 0, }, // 8 25 | { "border-width-smart-gaps", required_argument, 0, 0, }, // 9 26 | { "inner-gaps", required_argument, 0, 0, }, // 10 27 | { "outer-gaps", required_argument, 0, 0, }, // 11 28 | { "border-width", required_argument, 0, 0, }, // 12 29 | { "border-width-monocle", required_argument, 0, 0, }, // 13 30 | { "border-color-focused", required_argument, 0, 0, }, // 14 31 | { "border-color-focused-monocle", required_argument, 0, 0, }, // 15 32 | { "border-color-unfocused", required_argument, 0, 0, }, // 16 33 | { "help", no_argument, 0, 0, }, // 17 34 | { "help-defaults", no_argument, 0, 0, }, // 18 35 | { "log-threshold", required_argument, 0, 0, }, // 19 36 | { "version", no_argument, 0, 0, }, // 20 37 | { 0, 0, 0, 0, } 38 | }; 39 | 40 | void args_cli(int argc, char **argv) { 41 | optind = 0; 42 | 43 | while (1) { 44 | int long_index = 0; 45 | int c = getopt_long(argc, argv, "", cli_long_options, &long_index); 46 | if (c == -1) { 47 | break; 48 | } 49 | if (c == '?') { 50 | fprintf(stderr, "\n"); 51 | usage(EXIT_FAILURE); 52 | } 53 | switch (long_index) { 54 | case 0: 55 | if (!cfg_set_layout(optarg)) { 56 | log_f("invalid --layout '%s'\n", optarg); 57 | usage(EXIT_FAILURE); 58 | return; 59 | } 60 | break; 61 | case 1: 62 | if (!cfg_set_layout_alt(optarg)) { 63 | log_f("invalid --layout-alt '%s'\n", optarg); 64 | usage(EXIT_FAILURE); 65 | return; 66 | } 67 | break; 68 | case 2: 69 | if (!cfg_set_stack(optarg)) { 70 | log_f("invalid --stack '%s'\n", optarg); 71 | usage(EXIT_FAILURE); 72 | return; 73 | } 74 | break; 75 | case 3: 76 | if (!cfg_set_count_master(optarg)) { 77 | log_f("invalid --count-master '%s'\n", optarg); 78 | usage(EXIT_FAILURE); 79 | return; 80 | } 81 | break; 82 | case 4: 83 | if (!cfg_set_ratio_master(optarg)) { 84 | log_f("invalid --ratio-master '%s'\n", optarg); 85 | usage(EXIT_FAILURE); 86 | return; 87 | } 88 | break; 89 | case 5: 90 | if (!cfg_set_count_wide_left(optarg)) { 91 | log_f("invalid --count-wide-left '%s'\n", optarg); 92 | usage(EXIT_FAILURE); 93 | return; 94 | } 95 | break; 96 | case 6: 97 | if (!cfg_set_ratio_wide(optarg)) { 98 | log_f("invalid --ratio-wide '%s'\n", optarg); 99 | usage(EXIT_FAILURE); 100 | return; 101 | } 102 | break; 103 | case 7: 104 | cfg_set_smart_gaps(true); 105 | break; 106 | case 8: 107 | cfg_set_smart_gaps(false); 108 | break; 109 | case 9: 110 | if (!cfg_set_border_width_smart_gaps(optarg)) { 111 | log_f("invalid --border-width-smart-gaps '%s'\n", optarg); 112 | usage(EXIT_FAILURE); 113 | return; 114 | } 115 | break; 116 | case 10: 117 | if (!cfg_set_inner_gaps(optarg)) { 118 | log_f("invalid --inner-gaps '%s'\n", optarg); 119 | usage(EXIT_FAILURE); 120 | return; 121 | } 122 | break; 123 | case 11: 124 | if (!cfg_set_outer_gaps(optarg)) { 125 | log_f("invalid --outer-gaps '%s'\n", optarg); 126 | usage(EXIT_FAILURE); 127 | return; 128 | } 129 | break; 130 | case 12: 131 | if (!cfg_set_border_width(optarg)) { 132 | log_f("invalid --border-width '%s'\n", optarg); 133 | usage(EXIT_FAILURE); 134 | return; 135 | } 136 | break; 137 | case 13: 138 | if (!cfg_set_border_width_monocle(optarg)) { 139 | log_f("invalid --border-width-monocle '%s'\n", optarg); 140 | usage(EXIT_FAILURE); 141 | return; 142 | } 143 | break; 144 | case 14: 145 | if (!cfg_set_border_color_focused(optarg)) { 146 | log_f("invalid --border-color-focused '%s'\n", optarg); 147 | usage(EXIT_FAILURE); 148 | return; 149 | } 150 | break; 151 | case 15: 152 | if (!cfg_set_border_color_focused_monocle(optarg)) { 153 | log_f("invalid --border-color-focused-monocle '%s'\n", optarg); 154 | usage(EXIT_FAILURE); 155 | return; 156 | } 157 | break; 158 | case 16: 159 | if (!cfg_set_border_color_unfocused(optarg)) { 160 | log_f("invalid --border-color-unfocused '%s'\n", optarg); 161 | usage(EXIT_FAILURE); 162 | return; 163 | } 164 | break; 165 | case 17: 166 | usage(EXIT_SUCCESS); 167 | return; 168 | case 18: 169 | usage_defaults(); 170 | return; 171 | case 19: 172 | if (!log_set_threshold(optarg)) { 173 | log_f("invalid --log-threshold '%s'\n", optarg); 174 | usage(EXIT_FAILURE); 175 | return; 176 | } 177 | break; 178 | case 20: 179 | fprintf(stdout, "wideriver version %s\n", VERSION); 180 | exit(EXIT_SUCCESS); 181 | return; 182 | default: 183 | fprintf(stderr, "\n"); 184 | usage(EXIT_FAILURE); 185 | return; 186 | } 187 | } 188 | 189 | log_i("--layout %s", layout_name(cfg->layout)); 190 | log_i("--layout-alt %s", layout_name(cfg->layout_alt)); 191 | log_i("--stack %s", stack_name(cfg->stack)); 192 | log_i("--count-master %u", cfg->count_master); 193 | log_i("--ratio-master %g", cfg->ratio_master); 194 | log_i("--count-wide-left %u", cfg->count_wide_left); 195 | log_i("--ratio-wide %g", cfg->ratio_wide); 196 | log_i("--%ssmart-gaps", cfg->smart_gaps ? "" : "no-"); 197 | log_i("--inner-gaps %u", cfg->inner_gaps); 198 | log_i("--outer-gaps %u", cfg->outer_gaps); 199 | log_i("--border-width %zu", cfg->border_width); 200 | log_i("--border-width-monocle %zu", cfg->border_width_monocle); 201 | log_i("--border-width-smart-gaps %u", cfg->border_width_smart_gaps); 202 | log_i("--border-color-focused %s", cfg->border_color_focused); 203 | log_i("--border-color-focused-monocle %s", cfg->border_color_focused_monocle); 204 | log_i("--border-color-unfocused %s", cfg->border_color_unfocused); 205 | log_i("--log-threshold %s", log_threshold_name(log_get_threshold())); 206 | } 207 | 208 | static struct option cmd_long_options[] = { 209 | { "layout", required_argument, 0, 0, }, // 0 210 | { "layout-toggle", no_argument, 0, 0, }, // 1 211 | { "count", required_argument, 0, 0, }, // 2 212 | { "ratio", required_argument, 0, 0, }, // 3 213 | { "stack", required_argument, 0, 0, }, // 4 214 | { 0, 0, 0, 0, } 215 | }; 216 | 217 | const struct Cmd *args_cmd(int argc, char **argv) { 218 | struct Cmd *cmd = calloc(1, sizeof(struct Cmd)); 219 | 220 | optind = 0; 221 | 222 | // suppress printing errors 223 | opterr = 0; 224 | 225 | while (1) { 226 | int long_index = 0; 227 | int c = getopt_long(argc, argv, "", cmd_long_options, &long_index); 228 | if (c == -1) { 229 | break; 230 | } 231 | if (c == '?') { 232 | goto err; 233 | } 234 | switch (long_index) { 235 | case 0: 236 | if (!cmd_set_layout(cmd, optarg)) { 237 | log_e("invalid --layout '%s'", optarg); 238 | goto err; 239 | } 240 | break; 241 | case 1: 242 | cmd_set_layout_toggle(cmd); 243 | break; 244 | case 2: 245 | if (!cmd_set_count(cmd, optarg)) { 246 | log_e("invalid --count '%s'", optarg); 247 | goto err; 248 | } 249 | break; 250 | case 3: 251 | if (!cmd_set_ratio(cmd, optarg)) { 252 | log_e("invalid --ratio '%s'", optarg); 253 | goto err; 254 | } 255 | break; 256 | case 4: 257 | if (!cmd_set_stack(cmd, optarg)) { 258 | log_e("invalid --stack '%s'", optarg); 259 | goto err; 260 | } 261 | break; 262 | default: 263 | log_e("invalid command"); 264 | goto err; 265 | } 266 | } 267 | 268 | return cmd; 269 | 270 | err: 271 | cmd_destroy(cmd); 272 | return NULL; 273 | } 274 | -------------------------------------------------------------------------------- /src/cfg.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "enum.h" 7 | 8 | #include "cfg.h" 9 | 10 | struct Cfg c = { 11 | .layout = LAYOUT_DEFAULT, 12 | .layout_alt = LAYOUT_ALT_DEFAULT, 13 | .stack = STACK_DEFAULT, 14 | .count_master = COUNT_MASTER_DEFAULT, 15 | .ratio_master = RATIO_MASTER_DEFAULT, 16 | .count_wide_left = COUNT_WIDE_LEFT_DEFAULT, 17 | .ratio_wide = RATIO_WIDE_DEFAULT, 18 | .smart_gaps = SMART_GAPS_DEFAULT, 19 | .border_width_smart_gaps = BORDER_WIDTH_SMART_GAPS_DEFAULT, 20 | .inner_gaps = INNER_GAPS_DEFAULT, 21 | .outer_gaps = OUTER_GAPS_DEFAULT, 22 | .border_width = BORDER_WIDTH_DEFAULT, 23 | .border_width_monocle = BORDER_WIDTH_MONOCLE_DEFAULT, 24 | .border_color_focused = BORDER_COLOR_FOCUSED_DEFAULT, 25 | .border_color_focused_monocle = BORDER_COLOR_FOCUSED_MONOCLE_DEFAULT, 26 | .border_color_unfocused = BORDER_COLOR_UNFOCUSED_DEFAULT, 27 | }; 28 | 29 | const struct Cfg * const cfg = &c; 30 | 31 | // s is 0xRRGGBB or 0xRRGGBBAA 32 | bool valid_colour(const char *s) { 33 | if (!s) 34 | return false; 35 | 36 | size_t len = strlen(s); 37 | 38 | if (len != 8 && len != 10) 39 | return false; 40 | 41 | if (strncmp(s, "0x", 2) != 0) { 42 | return false; 43 | } 44 | 45 | for (const char *c = s + 2; c < s + len; c++) { 46 | if (*c >= 'A' && *c <= 'F') 47 | continue; 48 | if (*c >= 'a' && *c <= 'f') 49 | continue; 50 | if (*c >= '0' && *c <= '9') 51 | continue; 52 | return false; 53 | } 54 | 55 | return true; 56 | } 57 | 58 | bool cfg_set_layout(const char *s) { 59 | enum Layout layout = layout_val(s); 60 | if (layout) { 61 | c.layout = layout; 62 | return true; 63 | } 64 | return false; 65 | } 66 | 67 | bool cfg_set_layout_alt(const char *s) { 68 | enum Layout layout = layout_val(s); 69 | if (layout) { 70 | c.layout_alt = layout; 71 | return true; 72 | } 73 | return false; 74 | } 75 | 76 | bool cfg_set_stack(const char *s) { 77 | enum Stack stack = stack_val(s); 78 | if (stack) { 79 | c.stack = stack; 80 | return true; 81 | } 82 | return false; 83 | } 84 | 85 | bool cfg_set_count_master(const char *s) { 86 | char *endptr; 87 | long l = strtol(s, &endptr, 10); 88 | 89 | if (*s == '\0' || endptr == s || *endptr != '\0' || l < COUNT_MIN) { 90 | return false; 91 | } else { 92 | c.count_master = l; 93 | return true; 94 | } 95 | } 96 | 97 | bool cfg_set_ratio_master(const char *s) { 98 | double ratio_master = strtod(s, NULL); 99 | if (ratio_master < RATIO_MIN || ratio_master > RATIO_MAX) { 100 | return false; 101 | } else { 102 | c.ratio_master = ratio_master; 103 | return true; 104 | } 105 | } 106 | 107 | bool cfg_set_count_wide_left(const char *s) { 108 | char *endptr; 109 | long l = strtol(s, &endptr, 10); 110 | 111 | if (*s == '\0' || endptr == s || *endptr != '\0' || l < COUNT_MIN) { 112 | return false; 113 | } else { 114 | c.count_wide_left = l; 115 | return true; 116 | } 117 | } 118 | 119 | bool cfg_set_ratio_wide(const char *s) { 120 | double ratio_wide = strtod(s, NULL); 121 | if (ratio_wide < RATIO_MIN || ratio_wide > RATIO_MAX) { 122 | return false; 123 | } else { 124 | c.ratio_wide = ratio_wide; 125 | return true; 126 | } 127 | } 128 | 129 | void cfg_set_smart_gaps(bool smart_gaps) { 130 | c.smart_gaps = smart_gaps; 131 | } 132 | 133 | bool cfg_set_border_width_smart_gaps(const char *s) { 134 | char *endptr; 135 | long l = strtol(s, &endptr, 10); 136 | 137 | if (*s == '\0' || endptr == s || *endptr != '\0' || l < BORDER_WIDTH_SMART_GAPS_MIN) { 138 | return false; 139 | } else { 140 | c.border_width_smart_gaps = l; 141 | return true; 142 | } 143 | } 144 | 145 | bool cfg_set_inner_gaps(const char *s) { 146 | char *endptr; 147 | long l = strtol(s, &endptr, 10); 148 | 149 | if (*s == '\0' || endptr == s || *endptr != '\0' || l < INNER_GAPS_MIN) { 150 | return false; 151 | } else { 152 | c.inner_gaps = l; 153 | return true; 154 | } 155 | } 156 | 157 | bool cfg_set_outer_gaps(const char *s) { 158 | char *endptr; 159 | long l = strtol(s, &endptr, 10); 160 | 161 | if (*s == '\0' || endptr == s || *endptr != '\0' || l < OUTER_GAPS_MIN) { 162 | return false; 163 | } else { 164 | c.outer_gaps = l; 165 | return true; 166 | } 167 | } 168 | 169 | bool cfg_set_border_width(const char *s) { 170 | char *endptr; 171 | long l = strtol(s, &endptr, 10); 172 | 173 | if (*s == '\0' || endptr == s || *endptr != '\0' || l < BORDER_WIDTH_MIN) { 174 | return false; 175 | } else { 176 | c.border_width = l; 177 | return true; 178 | } 179 | } 180 | 181 | bool cfg_set_border_width_monocle(const char *s) { 182 | char *endptr; 183 | long l = strtol(s, &endptr, 10); 184 | 185 | if (*s == '\0' || endptr == s || *endptr != '\0' || l < BORDER_WIDTH_MONOCLE_MIN) { 186 | return false; 187 | } else { 188 | c.border_width_monocle = l; 189 | return true; 190 | } 191 | } 192 | 193 | bool cfg_set_border_color_focused(const char *s) { 194 | if (valid_colour(s)) { 195 | snprintf(c.border_color_focused, 11, "%s", s); 196 | return true; 197 | } 198 | return false; 199 | } 200 | 201 | bool cfg_set_border_color_focused_monocle(const char *s) { 202 | if (valid_colour(s)) { 203 | snprintf(c.border_color_focused_monocle, 11, "%s", s); 204 | return true; 205 | } 206 | return false; 207 | } 208 | 209 | bool cfg_set_border_color_unfocused(const char *s) { 210 | if (valid_colour(s)) { 211 | snprintf(c.border_color_unfocused, 11, "%s", s); 212 | return true; 213 | } 214 | return false; 215 | } 216 | -------------------------------------------------------------------------------- /src/cmd.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "args.h" 7 | #include "cfg.h" 8 | #include "enum.h" 9 | #include "mem.h" 10 | #include "slist.h" 11 | #include "log.h" 12 | 13 | #include "cmd.h" 14 | 15 | const struct Cmd *cmd_init(const char *args) { 16 | static char *dummy = "dummy"; 17 | 18 | int argc = 0; 19 | 20 | // tokenise 21 | struct SList *tokens = NULL; 22 | char *str = strdup(args); 23 | for (char *t = strtok(str, " "); t; t = strtok(NULL, " ")) { 24 | slist_append(&tokens, strdup(t)); 25 | argc++; 26 | } 27 | free(str); 28 | 29 | if (argc == 0) { 30 | log_e("command empty '%s'", args); 31 | return NULL; 32 | } 33 | 34 | // build argv, dummy at 0 35 | char *argv[++argc]; 36 | argv[0] = dummy; 37 | int i = 1; 38 | for (struct SList *token = tokens; token; token = token->nex) { 39 | argv[i++] = token->val; 40 | } 41 | 42 | // retain tokens whilst parsing 43 | const struct Cmd *cmd = args_cmd(argc, argv); 44 | if (!cmd) { 45 | log_e("command invalid '%s'", args); 46 | } 47 | 48 | slist_free_vals(&tokens, NULL); 49 | 50 | return cmd; 51 | } 52 | 53 | void cmd_destroy(const struct Cmd *cmd) { 54 | if (!cmd) 55 | return; 56 | 57 | free(cmd->count); 58 | free(cmd->count_delta); 59 | free(cmd->ratio); 60 | free(cmd->ratio_delta); 61 | 62 | free((struct Cmd*)cmd); 63 | } 64 | 65 | bool cmd_set_layout(struct Cmd *cmd, const char *s) { 66 | enum Layout layout = layout_val(s); 67 | if (layout) { 68 | cmd->layout = layout; 69 | return true; 70 | } 71 | return false; 72 | } 73 | 74 | bool cmd_set_layout_toggle(struct Cmd *cmd) { 75 | cmd->layout_toggle = true; 76 | return true; 77 | } 78 | 79 | bool cmd_set_count(struct Cmd * cmd, const char *s) { 80 | char *endptr; 81 | long int l = strtol(s, &endptr, 10); 82 | 83 | if (*s == '\0' || endptr == s || *endptr != '\0') { 84 | return false; 85 | } else if (*s == '-' || *s == '+') { 86 | cmd->count_delta = int32dup(l); 87 | return true; 88 | } else if (l < COUNT_MIN) { 89 | return false; 90 | } else { 91 | cmd->count = uint32dup(l); 92 | return true; 93 | } 94 | } 95 | 96 | bool cmd_set_ratio(struct Cmd *cmd, const char *s) { 97 | char *endptr; 98 | double d = strtod(s, &endptr); 99 | if (*s == '\0' || endptr == s) { 100 | return false; 101 | } else if (*s == '-' || *s == '+') { 102 | cmd->ratio_delta = doubledup(d); 103 | return true; 104 | } else if (d < RATIO_MIN || d > RATIO_MAX) { 105 | return false; 106 | } else { 107 | cmd->ratio = doubledup(d); 108 | return true; 109 | } 110 | } 111 | 112 | bool cmd_set_stack(struct Cmd *cmd, const char *s) { 113 | enum Stack stack = stack_val(s); 114 | if (stack) { 115 | cmd->stack = stack; 116 | return true; 117 | } 118 | return false; 119 | } 120 | -------------------------------------------------------------------------------- /src/control.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "river-control-unstable-v1.h" 5 | 6 | #include "displ.h" 7 | #include "listener_river_command_callback.h" 8 | #include "log.h" 9 | #include "slist.h" 10 | 11 | #include "control.h" 12 | 13 | static char buf[1024]; 14 | 15 | void control_execute(const struct SList *args, control_complete_fn complete) { 16 | if (!args) 17 | return; 18 | 19 | char *arg = NULL; 20 | char *b = buf; 21 | 22 | // args create the control 23 | for (const struct SList *i = args; i; i = i->nex) { 24 | 25 | arg = (char*)i->val; 26 | if (b + strlen(arg) < buf + sizeof(buf)) { 27 | b += sprintf(b, "'%s' ", arg); 28 | } 29 | 30 | zriver_control_v1_add_argument(displ->river_control, i->val); 31 | } 32 | 33 | log_d_c_s(" control"); log_d_c_e("%s", buf); 34 | 35 | // execute 36 | struct zriver_command_callback_v1 *cb = zriver_control_v1_run_command(displ->river_control, displ->wl_seat); 37 | 38 | // listen 39 | zriver_command_callback_v1_add_listener(cb, river_command_callback_listener(), *(void**)&complete); 40 | } 41 | 42 | -------------------------------------------------------------------------------- /src/displ.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "river-control-unstable-v1.h" 9 | #include "river-layout-v3.h" 10 | #include "river-status-unstable-v1.h" 11 | 12 | #include "cfg.h" 13 | #include "control.h" 14 | #include "enum.h" 15 | #include "listener_registry.h" 16 | #include "log.h" 17 | #include "output.h" 18 | #include "ptable.h" 19 | #include "slist.h" 20 | #include "tag.h" 21 | 22 | #include "displ.h" 23 | 24 | struct Displ d = { 0 }; 25 | 26 | const struct Displ * const displ = &d; 27 | 28 | static void complete_border_width(void) { 29 | log_d_c_s(" complete"); log_d_c("border_width"); log_d_c_e("%zu->%zu", d.style_current.border_width, d.style_desired.border_width); 30 | d.style_current.border_width = d.style_desired.border_width; 31 | } 32 | 33 | static void complete_border_color_focused(void) { 34 | log_d_c_s(" complete"); log_d_c("border_color_focused"); log_d_c_e("%s->%s", d.style_current.border_color_focused, d.style_desired.border_color_focused); 35 | d.style_current.border_color_focused = d.style_desired.border_color_focused; 36 | } 37 | 38 | static void complete_border_color_unfocused(void) { 39 | log_d_c_s(" complete"); log_d_c("border_color_unfocused"); log_d_c_e("%s->%s", d.style_current.border_color_unfocused, d.style_desired.border_color_unfocused); 40 | d.style_current.border_color_unfocused = d.style_desired.border_color_unfocused; 41 | } 42 | 43 | static void desire_style(const struct Tag *tag, const uint32_t view_count) { 44 | if (tag) { 45 | if (tag->layout_cur == MONOCLE) { 46 | d.style_desired.border_width = cfg->border_width_monocle; 47 | d.style_desired.border_color_focused = cfg->border_color_focused_monocle; 48 | } else if (view_count == 1 && tag->smart_gaps) { 49 | d.style_desired.border_width = cfg->border_width_smart_gaps; 50 | d.style_desired.border_color_focused = cfg->border_color_focused; 51 | } else { 52 | d.style_desired.border_width = cfg->border_width; 53 | d.style_desired.border_color_focused = cfg->border_color_focused; 54 | } 55 | d.style_desired.border_color_unfocused = cfg->border_color_unfocused; 56 | } 57 | } 58 | 59 | static void control_command_style(const struct Style desired, const struct Style current) { 60 | static char buf[8]; 61 | 62 | struct SList *args = NULL; 63 | 64 | if (desired.border_width != current.border_width) { 65 | slist_append(&args, strdup("border-width")); 66 | snprintf(buf, sizeof(buf), "%zu", desired.border_width); 67 | slist_append(&args, strdup(buf)); 68 | control_execute(args, complete_border_width); 69 | slist_free_vals(&args, NULL); 70 | } 71 | 72 | if (desired.border_color_focused != current.border_color_focused) { 73 | slist_append(&args, strdup("border-color-focused")); 74 | slist_append(&args, strdup(desired.border_color_focused)); 75 | control_execute(args, complete_border_color_focused); 76 | slist_free_vals(&args, NULL); 77 | } 78 | 79 | if (desired.border_color_unfocused != current.border_color_unfocused) { 80 | slist_append(&args, strdup("border-color-unfocused")); 81 | slist_append(&args, strdup(desired.border_color_unfocused)); 82 | control_execute(args, complete_border_color_unfocused); 83 | slist_free_vals(&args, NULL); 84 | } 85 | } 86 | 87 | bool displ_init(void) { 88 | d.outputs = ptable_init(5, 5); 89 | 90 | // ensure that border width of 0 is set on first run 91 | d.style_current.border_width = -1; 92 | 93 | d.wl_display = wl_display_connect(NULL); 94 | if (!d.wl_display) { 95 | log_f("Unable to connect to the compositor. Check or set the WAYLAND_DISPLAY environment variable."); 96 | goto err; 97 | } 98 | 99 | d.wl_registry = wl_display_get_registry(d.wl_display); 100 | if (!d.wl_registry) { 101 | log_f("wl_display_get_registry failed, exiting"); 102 | goto err; 103 | } 104 | 105 | wl_registry_add_listener(d.wl_registry, registry_listener(), &d); 106 | 107 | if (wl_display_roundtrip(d.wl_display) == -1) { 108 | log_f("Initial wl_display_roundtrip failed to retrieve wl_display, exiting"); 109 | goto err; 110 | } 111 | 112 | if (!d.river_layout_manager) { 113 | log_f("Compositor did not provide river_layout_manager_v3, exiting"); 114 | goto err; 115 | } 116 | if (!d.river_status_manager) { 117 | log_f("Compositor did not provide zriver_status_manager_v1, exiting"); 118 | goto err; 119 | } 120 | if (!d.river_seat_status) { 121 | log_f("Compositor did not provide zriver_seat_status_v1, exiting"); 122 | goto err; 123 | } 124 | if (!d.river_control) { 125 | log_f("Compositor did not provide zriver_control_v1, exiting"); 126 | goto err; 127 | } 128 | 129 | return true; 130 | 131 | err: 132 | displ_destroy(); 133 | return false; 134 | } 135 | 136 | void displ_destroy(void) { 137 | log_d("displ_destroy"); 138 | 139 | ptable_free_vals(d.outputs, output_destroy); 140 | 141 | if (d.river_layout_manager) { 142 | log_d_c_s("displ_destroy"); log_d_c(""); log_d_c("river_layout_manager"); log_d_c_e("%p", (void*)d.river_layout_manager); 143 | river_layout_manager_v3_destroy(d.river_layout_manager); 144 | } 145 | if (d.river_status_manager) { 146 | log_d_c_s("displ_destroy"); log_d_c(""); log_d_c("river_status_manager"); log_d_c_e("%p", (void*)d.river_status_manager); 147 | zriver_status_manager_v1_destroy(d.river_status_manager); 148 | } 149 | if (d.river_seat_status) { 150 | log_d_c_s("displ_destroy"); log_d_c(""); log_d_c("river_seat_status"); log_d_c_e("%p", (void*)d.river_seat_status); 151 | zriver_seat_status_v1_destroy(d.river_seat_status); 152 | } 153 | if (d.river_control) { 154 | log_d_c_s("displ_destroy"); log_d_c(""); log_d_c("river_control"); log_d_c_e("%p", (void*)d.river_control); 155 | zriver_control_v1_destroy(d.river_control); 156 | } 157 | if (d.wl_seat) { 158 | log_d_c_s("displ_destroy"); log_d_c(""); log_d_c("wl_seat"); log_d_c_e("%p", (void*)d.wl_seat); 159 | wl_seat_destroy(d.wl_seat); 160 | } 161 | if (d.wl_registry) { 162 | log_d_c_s("displ_destroy"); log_d_c(""); log_d_c("wl_registry"); log_d_c_e("%p", (void*)d.wl_registry); 163 | wl_registry_destroy(d.wl_registry); 164 | } 165 | if (d.wl_display) { 166 | log_d_c_s("displ_destroy"); log_d_c(""); log_d_c("wl_display"); log_d_c_e("%p", (void*)d.wl_display); 167 | wl_display_disconnect(d.wl_display); 168 | } 169 | 170 | memset(&d, 0, sizeof(struct Displ)); 171 | } 172 | 173 | void displ_request_style(const struct Output *output, const struct Tag *tag, const uint32_t view_count) { 174 | if (!output || !tag) 175 | return; 176 | 177 | const struct Output * const output_focused = (struct Output*)ptable_get(displ->outputs, d.wl_output_focused); 178 | 179 | log_d_c_s(" displ_request_style"); log_d_c("output"); log_d_c("%d", output ? output->name : 0); log_d_c_e("%p", (void*)output->wl_output); 180 | log_d_c_s(" displ_request_style"); log_d_c("output_focused"); log_d_c("%d", output_focused ? output_focused->name : 0); log_d_c_e("%p", (void*)d.wl_output_focused); 181 | 182 | // output must be focused or no focus reported 183 | if (d.wl_output_focused != output->wl_output && d.wl_output_focused) { 184 | log_d_c_s(" displ_request_style"); log_d_c_e("not focused or no focus"); 185 | return; 186 | } 187 | 188 | // desire 189 | desire_style(tag, view_count); 190 | 191 | // execute 192 | control_command_style(d.style_desired, d.style_current); 193 | } 194 | 195 | -------------------------------------------------------------------------------- /src/enum.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "enum.h" 5 | 6 | struct NameVal { 7 | const unsigned int val; 8 | const char *name; 9 | }; 10 | 11 | static struct NameVal layouts[] = { 12 | { .val = MONOCLE, .name = "monocle", }, 13 | { .val = LEFT, .name = "left", }, 14 | { .val = RIGHT, .name = "right", }, 15 | { .val = TOP, .name = "top", }, 16 | { .val = BOTTOM, .name = "bottom", }, 17 | { .val = WIDE, .name = "wide", }, 18 | { .val = 0, .name = NULL, }, 19 | }; 20 | 21 | static struct NameVal stacks[] = { 22 | { .val = EVEN, .name = "even", }, 23 | { .val = DIMINISH, .name = "diminish", }, 24 | { .val = DWINDLE, .name = "dwindle", }, 25 | { .val = 0, .name = NULL, }, 26 | }; 27 | 28 | static struct NameVal log_thresholds[] = { 29 | { .val = DEBUG, .name = "debug", }, 30 | { .val = INFO, .name = "info", }, 31 | { .val = WARNING, .name = "warning", }, 32 | { .val = ERROR, .name = "error", }, 33 | { .val = FATAL, .name = "fatal", }, 34 | { .val = 0, .name = NULL, }, 35 | }; 36 | 37 | const char *name(const struct NameVal *name_vals, const unsigned int val) { 38 | if (!name_vals) { 39 | return NULL; 40 | } 41 | for (int i = 0; name_vals[i].name; i++) { 42 | if (val == name_vals[i].val) { 43 | return name_vals[i].name; 44 | } 45 | } 46 | return NULL; 47 | } 48 | 49 | static unsigned int val(const struct NameVal *name_vals, const char *name) { 50 | if (!name_vals || !name) { 51 | return 0; 52 | } 53 | for (int i = 0; name_vals[i].name; i++) { 54 | if (strcasecmp(name_vals[i].name, name) == 0) { 55 | return name_vals[i].val; 56 | } 57 | } 58 | return 0; 59 | } 60 | 61 | const char *layout_name(const enum Layout layout) { 62 | return name(layouts, layout); 63 | } 64 | 65 | enum Layout layout_val(const char *name) { 66 | return val(layouts, name); 67 | } 68 | 69 | const char *stack_name(const enum Stack stack) { 70 | return name(stacks, stack); 71 | } 72 | 73 | enum Stack stack_val(const char *name) { 74 | return val(stacks, name); 75 | } 76 | 77 | const char *log_threshold_name(const enum LogThreshold log_threshold) { 78 | return name(log_thresholds, log_threshold); 79 | } 80 | 81 | enum LogThreshold log_threshold_val(const char *name) { 82 | return val(log_thresholds, name); 83 | } 84 | -------------------------------------------------------------------------------- /src/layout.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "river-layout-v3.h" 5 | 6 | #include "arrange.h" 7 | #include "enum.h" 8 | #include "log.h" 9 | #include "slist.h" 10 | #include "tag.h" 11 | 12 | #include "layout.h" 13 | 14 | static const char *description_info(const struct Demand* const demand, const struct Tag* const tag) { 15 | static char desc[20]; 16 | 17 | switch(tag->layout_cur) { 18 | case LEFT: 19 | if (tag->count_master == 0 ) { 20 | snprintf(desc, sizeof(desc), "│├──┤"); 21 | } else { 22 | snprintf(desc, sizeof(desc), "│ ├─┤"); 23 | } 24 | break; 25 | case RIGHT: 26 | if (tag->count_master == 0 ) { 27 | snprintf(desc, sizeof(desc), "├──┤│"); 28 | } else { 29 | snprintf(desc, sizeof(desc), "├─┤ │"); 30 | } 31 | break; 32 | case TOP: 33 | snprintf(desc, sizeof(desc), "├─┬─┤"); 34 | break; 35 | case BOTTOM: 36 | snprintf(desc, sizeof(desc), "├─┴─┤"); 37 | break; 38 | case MONOCLE: 39 | if (demand->view_count > 1) { 40 | snprintf(desc, sizeof(desc), "│ %u │", demand->view_count); 41 | } else { 42 | snprintf(desc, sizeof(desc), "│ │"); 43 | } 44 | break; 45 | case WIDE: 46 | if (tag->count_wide_left == 0) { 47 | snprintf(desc, sizeof(desc), "││ ├─┤"); 48 | } else { 49 | snprintf(desc, sizeof(desc), "├─┤ ├─┤"); 50 | } 51 | break; 52 | } 53 | 54 | return desc; 55 | } 56 | 57 | static const char *description_debug(const struct Demand* const demand, const struct Tag* const tag) { 58 | static char desc[128]; 59 | 60 | switch(tag->layout_cur) { 61 | case LEFT: 62 | case RIGHT: 63 | case TOP: 64 | case BOTTOM: 65 | snprintf(desc, sizeof(desc), "%s %u %g ", description_info(demand, tag), tag->count_master, tag->ratio_master); 66 | break; 67 | case WIDE: 68 | snprintf(desc, sizeof(desc), "%s %u %g ", description_info(demand, tag), tag->count_wide_left, tag->ratio_wide); 69 | break; 70 | case MONOCLE: 71 | snprintf(desc, sizeof(desc), "%s", description_info(demand, tag)); 72 | break; 73 | } 74 | 75 | return desc; 76 | } 77 | 78 | const char *layout_description(const struct Demand* const demand, const struct Tag* const tag) { 79 | const char *description; 80 | 81 | if (!demand || !tag) { 82 | description = ""; 83 | } else { 84 | switch (log_get_threshold()) { 85 | case DEBUG: 86 | description = description_debug(demand, tag); 87 | break; 88 | case INFO: 89 | case WARNING: 90 | case ERROR: 91 | default: 92 | description = description_info(demand, tag); 93 | break; 94 | } 95 | } 96 | 97 | log_d_c_s(" layout_description"); log_d_c_e("'%s'", description); 98 | 99 | return description; 100 | } 101 | 102 | struct SList *layout(const struct Demand *demand, const struct Tag *tag) { 103 | if (!demand || !tag) 104 | return NULL; 105 | 106 | struct SList *views = NULL; 107 | 108 | struct Box box_before = { 0 }; 109 | struct Box box_master = { 0 }; 110 | struct Box box_after = { 0 }; 111 | 112 | // count 113 | uint32_t num_before, num_master, num_after; 114 | arrange_count(demand->view_count, tag, &num_before, &num_master, &num_after); 115 | 116 | // areas 117 | switch (tag->layout_cur) { 118 | case LEFT: 119 | case RIGHT: 120 | case TOP: 121 | case BOTTOM: 122 | arrange_master_stack(demand, tag, num_master, num_after, &box_master, &box_after); 123 | break; 124 | case MONOCLE: 125 | // NOP 126 | break; 127 | case WIDE: 128 | arrange_wide(demand, tag, num_before, num_master, num_after, &box_before, &box_master, &box_after); 129 | break; 130 | } 131 | 132 | log_d_c_s(" layout"); log_d_c_e("%s", layout_name(tag->layout_cur)); 133 | 134 | log_d_c_s(" layout"); log_d_c("before"); log_d_c("%u", num_before); log_d_c("%u,%u", box_before.x, box_before.y); log_d_c_e("%ux%u", box_before.width, box_before.height); 135 | log_d_c_s(" layout"); log_d_c("master"); log_d_c("%u", num_master); log_d_c("%u,%u", box_master.x, box_master.y); log_d_c_e("%ux%u", box_master.width, box_master.height); 136 | log_d_c_s(" layout"); log_d_c("after"); log_d_c("%u", num_after); log_d_c("%u,%u", box_after.x, box_after.y); log_d_c_e("%ux%u", box_after.width, box_after.height); 137 | 138 | // populate 139 | switch(tag->layout_cur) { 140 | case LEFT: 141 | // top to bottom, dwindle right down 142 | arrange_views(demand, EVEN, S, S, num_master, num_master, tag->inner_gaps, box_master, box_master, &views); 143 | arrange_views(demand, tag->stack, S, E, num_after, num_after, tag->inner_gaps, box_after, box_after, &views); 144 | break; 145 | case RIGHT: 146 | // top to bottom, dwindle left down 147 | arrange_views(demand, EVEN, S, S, num_master, num_master, tag->inner_gaps, box_master, box_master, &views); 148 | arrange_views(demand, tag->stack, S, W, num_after, num_after, tag->inner_gaps, box_after, box_after, &views); 149 | break; 150 | case TOP: 151 | // left to right, dwindle down right 152 | arrange_views(demand, EVEN, E, E, num_master, num_master, tag->inner_gaps, box_master, box_master, &views); 153 | arrange_views(demand, tag->stack, E, S, num_after, num_after, tag->inner_gaps, box_after, box_after, &views); 154 | break; 155 | case BOTTOM: 156 | // left to right, dwindle up right 157 | arrange_views(demand, EVEN, E, E, num_master, num_master, tag->inner_gaps, box_master, box_master, &views); 158 | arrange_views(demand, tag->stack, E, N, num_after, num_after, tag->inner_gaps, box_after, box_after, &views); 159 | break; 160 | case MONOCLE: 161 | arrange_monocle(demand, tag, &views); 162 | break; 163 | case WIDE: 164 | // left stack dwindle left up 165 | arrange_views(demand, tag->stack, N, W, num_before, num_before, tag->inner_gaps, box_before, box_before, &views); 166 | 167 | // reverse to push first view farthest away 168 | struct SList *views_reversed = NULL; 169 | for (int i = slist_length(views) - 1; i >= 0; i--) 170 | slist_append(&views_reversed, slist_at(views, i)); 171 | slist_free(&views); 172 | views = views_reversed; 173 | 174 | // only one master 175 | arrange_views(demand, EVEN, S, S, num_master, num_master, tag->inner_gaps, box_master, box_master, &views); 176 | 177 | // right stack dwindle right down 178 | arrange_views(demand, tag->stack, S, E, num_after, num_after, tag->inner_gaps, box_after, box_after, &views); 179 | break; 180 | } 181 | 182 | return views; 183 | } 184 | 185 | void push(const struct SList *views, struct river_layout_v3 *river_layout_v3, const uint32_t serial) { 186 | uint32_t n = 0; 187 | for (const struct SList *i = views; i; i = i->nex) { 188 | struct Box *box = i->val; 189 | log_d_c_s(" push"); log_d_c("view"); log_d_c("%02u", n++); log_d_c("%u,%u", box->x, box->x); log_d_c_e("%ux%u", box->width, box->height); 190 | river_layout_v3_push_view_dimensions(river_layout_v3, box->x, box->y, box->width, box->height, serial); 191 | } 192 | } 193 | 194 | -------------------------------------------------------------------------------- /src/listener_registry.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "river-control-unstable-v1.h" 7 | #include "river-layout-v3.h" 8 | #include "river-status-unstable-v1.h" 9 | 10 | #include "displ.h" 11 | #include "listener_river_seat_status.h" 12 | #include "log.h" 13 | #include "output.h" 14 | #include "ptable.h" 15 | 16 | #include "listener_registry.h" 17 | 18 | // 19 | // Displ data 20 | // 21 | 22 | static void global(void *data, 23 | struct wl_registry *wl_registry, 24 | uint32_t name, 25 | const char *interface, 26 | uint32_t version) { 27 | struct Displ *displ = data; 28 | 29 | log_d_c_s("global"); log_d_c("%d", name); log_d_c_e("%s", interface); 30 | 31 | if (strcmp(interface, wl_seat_interface.name) == 0) { 32 | 33 | displ->wl_seat = wl_registry_bind(wl_registry, name, &wl_seat_interface, version); 34 | log_d_c_s("bind"); log_d_c("%d", name); log_d_c("wl_seat"); log_d_c_e("%p", (void*)displ->wl_seat); 35 | 36 | } else if (strcmp(interface, river_layout_manager_v3_interface.name) == 0) { 37 | 38 | if (version <= RIVER_LAYOUT_V3_VERSION) { 39 | displ->river_layout_manager = wl_registry_bind(wl_registry, name, &river_layout_manager_v3_interface, RIVER_LAYOUT_V3_VERSION); 40 | log_d_c_s("bind"); log_d_c("%d", name); log_d_c("river_layout_manager"); log_d_c_e("%p", (void*)displ->river_layout_manager); 41 | } else { 42 | log_f("Invalid river_layout_manager_v3_interface version %d expected %d, exiting", version, RIVER_LAYOUT_V3_VERSION); 43 | return; 44 | } 45 | 46 | } else if (strcmp(interface, zriver_control_v1_interface.name) == 0) { 47 | 48 | // unstable interface, check version once stable 49 | displ->river_control = wl_registry_bind(wl_registry, name, &zriver_control_v1_interface, version); 50 | log_d_c_s("bind"); log_d_c("%d", name); log_d_c("river_control"); log_d_c_e("%p", (void*)displ->river_control); 51 | 52 | } else if (strcmp(interface, zriver_status_manager_v1_interface.name) == 0) { 53 | 54 | // unstable interface, check version once stable 55 | displ->river_status_manager = wl_registry_bind(wl_registry, name, &zriver_status_manager_v1_interface, version); 56 | log_d_c_s("bind"); log_d_c("%d", name); log_d_c("river_status_manager"); log_d_c_e("%p", (void*)displ->river_status_manager); 57 | 58 | displ->river_seat_status = zriver_status_manager_v1_get_river_seat_status(displ->river_status_manager, displ->wl_seat); 59 | 60 | zriver_seat_status_v1_add_listener(displ->river_seat_status, river_seat_status_listener(), displ); 61 | 62 | } else if (strcmp(interface, wl_output_interface.name) == 0) { 63 | 64 | struct wl_output *wl_output = wl_registry_bind(wl_registry, name, &wl_output_interface, version); 65 | log_d_c_s("bind"); log_d_c("%d", name); log_d_c("wl_output"); log_d_c_e("%p", (void*)wl_output); 66 | 67 | struct Output *output = output_init(wl_output, name, displ->river_layout_manager, displ->river_status_manager); 68 | if (output) { 69 | ptable_put(displ->outputs, wl_output, output); 70 | } 71 | } 72 | } 73 | 74 | static void global_remove(void *data, 75 | struct wl_registry *wl_registry, 76 | uint32_t name) { 77 | struct Displ *displ = data; 78 | 79 | log_d_c_s("global_remove"); log_d_c_e("%d", name); 80 | 81 | const struct PTableIter *i; 82 | for (i = ptable_iter(displ->outputs); i; i = ptable_next(i)) { 83 | struct Output *output = (struct Output*)i->val; 84 | if (output->name == name) { 85 | 86 | output_destroy(output); 87 | 88 | ptable_remove(displ->outputs, i->key); 89 | 90 | break; 91 | } 92 | } 93 | 94 | ptable_iter_free(i); 95 | } 96 | 97 | static const struct wl_registry_listener listener = { 98 | .global = global, 99 | .global_remove = global_remove, 100 | }; 101 | 102 | const struct wl_registry_listener *registry_listener(void) { 103 | return &listener; 104 | } 105 | 106 | -------------------------------------------------------------------------------- /src/listener_river_command_callback.c: -------------------------------------------------------------------------------- 1 | #include "river-control-unstable-v1.h" 2 | 3 | #include "control.h" 4 | #include "log.h" 5 | 6 | #include "listener_river_command_callback.h" 7 | 8 | // 9 | // control_complete data 10 | // 11 | 12 | static void river_command_handle_success(void *data, 13 | struct zriver_command_callback_v1 *zriver_command_callback_v1, 14 | const char *output) { 15 | 16 | log_d_c_s("command_success"); log_d_c_e("'%s'", output); 17 | 18 | zriver_command_callback_v1_destroy(zriver_command_callback_v1); 19 | 20 | if (data) 21 | (*(control_complete_fn*)&data)(); 22 | } 23 | 24 | static void river_command_handle_failure(void *data, 25 | struct zriver_command_callback_v1 *zriver_command_callback_v1, 26 | const char *failure_message) { 27 | 28 | log_e("command_failure '%s'", failure_message); 29 | 30 | zriver_command_callback_v1_destroy(zriver_command_callback_v1); 31 | 32 | if (data) 33 | (*(control_complete_fn*)&data)(); 34 | } 35 | 36 | static const struct zriver_command_callback_v1_listener listener = { 37 | .success = river_command_handle_success, 38 | .failure = river_command_handle_failure, 39 | }; 40 | 41 | const struct zriver_command_callback_v1_listener *river_command_callback_listener(void) { 42 | return &listener; 43 | } 44 | 45 | -------------------------------------------------------------------------------- /src/listener_river_layout.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "river-layout-v3.h" 4 | 5 | #include "cmd.h" 6 | #include "displ.h" 7 | #include "layout.h" 8 | #include "log.h" 9 | #include "output.h" 10 | #include "slist.h" 11 | #include "tag.h" 12 | 13 | #include "listener_river_layout.h" 14 | 15 | // 16 | // Output data 17 | // 18 | 19 | static void layout_handle_layout_demand(void *data, 20 | struct river_layout_v3 *river_layout_v3, 21 | uint32_t view_count, 22 | uint32_t usable_width, 23 | uint32_t usable_height, 24 | uint32_t tags, 25 | uint32_t serial) { 26 | 27 | struct Output *output = (struct Output*)data; 28 | 29 | log_d_c_s("layout_layout_demand"); log_d_c("%d", output ? output->name : 0); log_d_c("view_count"); log_d_c_e("%02u", view_count); 30 | log_d_c_s("layout_layout_demand"); log_d_c("%d", output ? output->name : 0); log_d_c("usable"); log_d_c_e("%ux%u", usable_width, usable_height); 31 | log_d_c_s("layout_layout_demand"); log_d_c("%d", output ? output->name : 0); log_d_c("tags"); log_d_c_e("0x%u", tags); 32 | 33 | const struct Demand demand = { 34 | .view_count = view_count, 35 | .usable_width = usable_width, 36 | .usable_height = usable_height, 37 | }; 38 | 39 | // use lowest tag's layout 40 | const struct Tag *tag = tag_first(output->tags, tags); 41 | 42 | // position all views 43 | struct SList *boxes = layout(&demand, tag); 44 | 45 | // push all views 46 | push(boxes, river_layout_v3, serial); 47 | slist_free_vals(&boxes, NULL); 48 | 49 | // river layout name 50 | const char *layout_name = layout_description(&demand, tag); 51 | 52 | // commit 53 | river_layout_v3_commit(output->river_layout, layout_name, serial); 54 | 55 | // maybe style 56 | displ_request_style(output, tag, view_count); 57 | } 58 | 59 | static void layout_handle_namespace_in_use(void *data, 60 | struct river_layout_v3 *river_layout_v3) { 61 | log_e("river namespace in use, exiting"); 62 | 63 | exit(EXIT_FAILURE); 64 | } 65 | 66 | static void layout_handle_user_command_tags(void *data, 67 | struct river_layout_v3 *river_layout_manager_v3, 68 | uint32_t tags) { 69 | struct Output *output = (struct Output*)data; 70 | 71 | if (!output) { 72 | log_e("layout_user_command_tags 0x%ux missing output", tags); 73 | return; 74 | } 75 | 76 | log_d_c_s("layout_user_command_tags"); log_d_c("%d", output->name); log_d_c("tags"); log_d_c_e("0x%u", tags); 77 | 78 | output->command_tags_mask = tags; 79 | } 80 | 81 | static void layout_handle_user_command(void *data, 82 | struct river_layout_v3 *river_layout_manager_v3, 83 | const char *command) { 84 | 85 | struct Output *output = (struct Output*)data; 86 | if (!output) { 87 | log_e("layout_user_command '%s' missing output", command); 88 | return; 89 | } 90 | 91 | log_d_c_s("layout_user_command"); log_d_c("%d", output->name); log_d_c("command"); log_d_c_e("'%s'", command); 92 | 93 | // version 2 guarantees tags will always be sent before command 94 | if (!output->command_tags_mask) { 95 | log_e("layout_user_command '%s' not sent command tags", command); 96 | return; 97 | } 98 | 99 | // maybe apply 100 | const struct Cmd *cmd = cmd_init(command); 101 | if (cmd) { 102 | output_apply_cmd(output, cmd); 103 | cmd_destroy(cmd); 104 | } 105 | } 106 | 107 | static const struct river_layout_v3_listener listener = { 108 | .namespace_in_use = layout_handle_namespace_in_use, 109 | .layout_demand = layout_handle_layout_demand, 110 | .user_command_tags = layout_handle_user_command_tags, 111 | .user_command = layout_handle_user_command, 112 | }; 113 | 114 | const struct river_layout_v3_listener *river_layout_listener(void) { 115 | return &listener; 116 | } 117 | 118 | -------------------------------------------------------------------------------- /src/listener_river_output_status.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "river-status-unstable-v1.h" 5 | 6 | #include "log.h" 7 | #include "output.h" 8 | 9 | #include "listener_river_output_status.h" 10 | 11 | // 12 | // Output data 13 | // 14 | 15 | static void output_status_handle_focused_tags(void *data, 16 | struct zriver_output_status_v1 *zriver_output_status_v1, 17 | uint32_t tags) { 18 | struct Output *output = (struct Output*)data; 19 | 20 | log_d_c_s("output_focused_tags"); log_d_c("%d", output ? output->name : 0); log_d_c("tags"); log_d_c_e("0x%u", tags); 21 | } 22 | 23 | static void output_status_handle_view_tags(void *data, 24 | struct zriver_output_status_v1 *zriver_output_status_v1, 25 | struct wl_array *tags) { 26 | struct Output *output = (struct Output*)data; 27 | 28 | log_d_c_s("output_view_tags"); log_d_c_e("%d", output ? output->name : 0); 29 | } 30 | 31 | static void output_status_handle_urgent_tags(void *data, 32 | struct zriver_output_status_v1 *zriver_output_status_v1, 33 | uint32_t tags) { 34 | struct Output *output = (struct Output*)data; 35 | 36 | log_d_c_s("output_urgent_tags"); log_d_c("%d", output ? output->name : 0); log_d_c("tags"); log_d_c_e("0x%u", tags); 37 | } 38 | 39 | static void output_status_handle_layout_name(void *data, 40 | struct zriver_output_status_v1 *zriver_output_status_v1, 41 | const char *name) { 42 | struct Output *output = (struct Output*)data; 43 | 44 | log_d_c_s("output_layout_name"); log_d_c("%d", output ? output->name : 0); log_d_c("name"); log_d_c_e("'%s'", name); 45 | } 46 | 47 | static void output_status_handle_layout_name_clear(void *data, 48 | struct zriver_output_status_v1 *zriver_output_status_v1) { 49 | struct Output *output = (struct Output*)data; 50 | 51 | log_d_c_s("output_layout_name_clear"); log_d_c_e("%d", output ? output->name : 0); 52 | } 53 | 54 | static const struct zriver_output_status_v1_listener listener = { 55 | .focused_tags = output_status_handle_focused_tags, 56 | .view_tags = output_status_handle_view_tags, 57 | .urgent_tags = output_status_handle_urgent_tags, 58 | .layout_name = output_status_handle_layout_name, 59 | .layout_name_clear = output_status_handle_layout_name_clear, 60 | }; 61 | 62 | const struct zriver_output_status_v1_listener *river_output_status_listener(void) { 63 | return &listener; 64 | } 65 | 66 | -------------------------------------------------------------------------------- /src/listener_river_seat_status.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "river-status-unstable-v1.h" 4 | 5 | #include "displ.h" 6 | #include "log.h" 7 | #include "output.h" 8 | #include "ptable.h" 9 | 10 | #include "listener_river_seat_status.h" 11 | 12 | // 13 | // Displ data 14 | // 15 | 16 | static void handle_focused_output(void *data, 17 | struct zriver_seat_status_v1 *zriver_seat_status_v1, 18 | struct wl_output *wl_output) { 19 | if (!data || !wl_output) 20 | return; 21 | 22 | struct Displ *displ = data; 23 | 24 | struct Output *output = (struct Output*)ptable_get(displ->outputs, wl_output); 25 | 26 | log_d_c_s("seat_focused_output"); log_d_c_e("%d", output ? output->name : 0); 27 | 28 | displ->wl_output_focused = wl_output; 29 | } 30 | 31 | static void handle_unfocused_output(void *data, 32 | struct zriver_seat_status_v1 *zriver_seat_status_v1, 33 | struct wl_output *wl_output) { 34 | 35 | struct Displ *displ = data; 36 | 37 | struct Output *output = (struct Output*)ptable_get(displ->outputs, wl_output); 38 | 39 | log_d_c_s("seat_unfocused_output"); log_d_c_e("%d", output ? output->name : 0); 40 | } 41 | 42 | static void handle_focused_view(void *data, 43 | struct zriver_seat_status_v1 *zriver_seat_status_v1, 44 | const char *title) { 45 | log_d_c_s("seat_focused_view"); log_d_c("title"); log_d_c_e("%s", title); 46 | } 47 | 48 | static void handle_mode(void *data, 49 | struct zriver_seat_status_v1 *zriver_seat_status_v1, 50 | const char *name) { 51 | log_d_c_s("seat_mode"); log_d_c("name"); log_d_c_e("%s", name); 52 | } 53 | 54 | static const struct zriver_seat_status_v1_listener listener = { 55 | .focused_output = handle_focused_output, 56 | .unfocused_output = handle_unfocused_output, 57 | .focused_view = handle_focused_view, 58 | .mode = handle_mode, 59 | }; 60 | 61 | const struct zriver_seat_status_v1_listener *river_seat_status_listener(void) { 62 | return &listener; 63 | } 64 | 65 | -------------------------------------------------------------------------------- /src/log.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "enum.h" 9 | 10 | #include "log.h" 11 | 12 | #define COL_WIDTH 24 13 | static char col_buf[COL_WIDTH]; 14 | 15 | char THRESHOLD_CHAR[] = { 16 | '?', 17 | 'D', 18 | 'I', 19 | 'W', 20 | 'E', 21 | 'F', 22 | }; 23 | 24 | enum LogThreshold log_threshold = LOG_THRESHOLD_DEFAULT; 25 | 26 | enum LogMode { 27 | NORMAL = 0, 28 | COL_START, 29 | COL, 30 | COL_END, 31 | }; 32 | 33 | static void print_prefix(const enum LogThreshold threshold, FILE *__restrict __stream) { 34 | static char buf[16]; 35 | static time_t t; 36 | 37 | t = time(NULL); 38 | 39 | strftime(buf, sizeof(buf), "%H:%M:%S", localtime(&t)); 40 | 41 | fprintf(__stream, "%c [%s] ", THRESHOLD_CHAR[threshold], buf); 42 | } 43 | 44 | static void log_(const enum LogThreshold threshold, const enum LogMode mode, const int eno, const char *__restrict __format, va_list __args) { 45 | if (threshold < log_threshold) { 46 | return; 47 | } 48 | 49 | static FILE *stream; 50 | 51 | stream = threshold >= ERROR ? stderr : stdout; 52 | 53 | switch (mode) { 54 | case NORMAL: 55 | print_prefix(threshold, stream); 56 | __attribute__((fallthrough)); 57 | case COL_END: 58 | vfprintf(stream, __format, __args); 59 | if (eno) { 60 | fprintf(stream, " %d: %s", eno, strerror(eno)); 61 | } 62 | fprintf(stream, "\n"); 63 | break; 64 | case COL_START: 65 | print_prefix(threshold, stream); 66 | __attribute__((fallthrough)); 67 | case COL: 68 | vsnprintf(col_buf, COL_WIDTH, __format, __args); 69 | fprintf(stream, "%-*s", COL_WIDTH, col_buf); 70 | break; 71 | default: 72 | break; 73 | } 74 | 75 | fflush(stream); 76 | } 77 | 78 | void log_d(const char *__restrict __format, ...) { 79 | va_list args; 80 | va_start(args, __format); 81 | log_(DEBUG, NORMAL, 0, __format, args); 82 | va_end(args); 83 | } 84 | 85 | void log_d_c_s(const char *__restrict __format, ...) { 86 | va_list args; 87 | va_start(args, __format); 88 | log_(DEBUG, COL_START, 0, __format, args); 89 | va_end(args); 90 | } 91 | 92 | void log_d_c(const char *__restrict __format, ...) { 93 | va_list args; 94 | va_start(args, __format); 95 | log_(DEBUG, COL, 0, __format, args); 96 | va_end(args); 97 | } 98 | 99 | void log_d_c_e(const char *__restrict __format, ...) { 100 | va_list args; 101 | va_start(args, __format); 102 | log_(DEBUG, COL_END, 0, __format, args); 103 | va_end(args); 104 | } 105 | 106 | void log_i(const char *__restrict __format, ...) { 107 | va_list args; 108 | va_start(args, __format); 109 | log_(INFO, NORMAL, 0, __format, args); 110 | va_end(args); 111 | } 112 | 113 | void log_w(const char *__restrict __format, ...) { 114 | va_list args; 115 | va_start(args, __format); 116 | log_(WARNING, NORMAL, 0, __format, args); 117 | va_end(args); 118 | } 119 | 120 | void log_e(const char *__restrict __format, ...) { 121 | va_list args; 122 | va_start(args, __format); 123 | log_(ERROR, NORMAL, 0, __format, args); 124 | va_end(args); 125 | } 126 | 127 | void log_e_errno(const char *__restrict __format, ...) { 128 | va_list args; 129 | va_start(args, __format); 130 | log_(ERROR, NORMAL, errno, __format, args); 131 | va_end(args); 132 | } 133 | 134 | void log_f(const char *__restrict __format, ...) { 135 | va_list args; 136 | va_start(args, __format); 137 | log_(FATAL, NORMAL, 0, __format, args); 138 | va_end(args); 139 | } 140 | 141 | void log_f_errno(const char *__restrict __format, ...) { 142 | va_list args; 143 | va_start(args, __format); 144 | log_(FATAL, NORMAL, errno, __format, args); 145 | va_end(args); 146 | } 147 | 148 | enum LogThreshold log_get_threshold(void) { 149 | return log_threshold; 150 | } 151 | 152 | bool log_set_threshold(const char *s) { 153 | enum LogThreshold t = log_threshold_val(s); 154 | if (t) { 155 | log_threshold = t; 156 | return true; 157 | } 158 | return false; 159 | } 160 | 161 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "args.h" 12 | #include "displ.h" 13 | #include "log.h" 14 | 15 | #define NPFDS 2 16 | static struct pollfd pfds[NPFDS]; 17 | static struct pollfd *pfd_wayland = NULL; 18 | static struct pollfd *pfd_signal = NULL; 19 | 20 | static void init_pfds(void) { 21 | 22 | // wayland FD 23 | if (!pfd_wayland) { 24 | pfds[0].fd = wl_display_get_fd(displ->wl_display); 25 | pfds[0].events = POLLIN; 26 | pfds[0].revents = 0; 27 | pfd_wayland = &pfds[0]; 28 | } 29 | 30 | // interesting signals 31 | if (!pfd_signal) { 32 | sigset_t mask; 33 | sigemptyset(&mask); 34 | sigaddset(&mask, SIGINT); 35 | sigaddset(&mask, SIGQUIT); 36 | sigaddset(&mask, SIGTERM); 37 | sigprocmask(SIG_BLOCK, &mask, NULL); 38 | 39 | pfds[1].fd = signalfd(-1, &mask, 0); 40 | pfds[1].events = POLLIN; 41 | pfds[1].revents = 0; 42 | pfd_signal = &pfds[1]; 43 | } 44 | } 45 | 46 | // EXIT_SUCCESS on EPIPE otherwise error with op and errno 47 | static int rc_errno_pipe_ok(const char *op) { 48 | if (errno == EPIPE) { 49 | log_i("Wayland display terminated, exiting."); 50 | return EXIT_SUCCESS; 51 | } else { 52 | log_f_errno("%s failed, exiting", op); 53 | return errno; 54 | } 55 | } 56 | 57 | // see Wayland Protocol docs Appendix B wl_display_prepare_read_queue 58 | static int loop(void) { 59 | 60 | while (true) { 61 | init_pfds(); 62 | 63 | // dispatch and prepare for next wayland event 64 | while (wl_display_prepare_read(displ->wl_display) != 0) { 65 | if (wl_display_dispatch_pending(displ->wl_display) == -1) { 66 | return rc_errno_pipe_ok("wl_display_dispatch_pending"); 67 | } 68 | } 69 | 70 | // send dispatched 71 | if (wl_display_flush(displ->wl_display) == -1) { 72 | return rc_errno_pipe_ok("wl_display_read_events"); 73 | } 74 | 75 | // poll for all events 76 | if (poll(pfds, NPFDS, -1) < 0) { 77 | log_f_errno("poll failed, exiting"); 78 | return EXIT_FAILURE; 79 | } 80 | 81 | if (pfd_signal->revents & pfd_signal->events) { 82 | 83 | // signal received: int, quit, term 84 | struct signalfd_siginfo fdsi; 85 | if (read(pfd_signal->fd, &fdsi, sizeof(fdsi)) == sizeof(fdsi)) { 86 | log_i("Received signal %d %s", fdsi.ssi_signo, strsignal(fdsi.ssi_signo)); 87 | return EXIT_SUCCESS; 88 | } 89 | } else if (pfd_wayland->revents & pfd_wayland->events) { 90 | 91 | // wayland events 92 | if (wl_display_read_events(displ->wl_display) == -1) { 93 | return rc_errno_pipe_ok("wl_display_read_events"); 94 | } 95 | } else { 96 | log_f("Unknown event received, exiting"); 97 | return EXIT_FAILURE; 98 | } 99 | } 100 | 101 | return EXIT_SUCCESS; 102 | } 103 | 104 | int main(int argc, char **argv) { 105 | int rc = EXIT_SUCCESS; 106 | 107 | if (!getenv("WAYLAND_DISPLAY")) { 108 | log_f("Environment variable WAYLAND_DISPLAY not set, exiting"); 109 | exit(EXIT_FAILURE); 110 | } 111 | 112 | args_cli(argc, argv); 113 | 114 | if (!displ_init()) { 115 | rc = EXIT_FAILURE; 116 | goto done; 117 | } 118 | 119 | log_i("wideriver started"); 120 | 121 | rc = loop(); 122 | 123 | log_i("wideriver done %d", rc); 124 | 125 | done: 126 | displ_destroy(); 127 | 128 | return rc; 129 | } 130 | -------------------------------------------------------------------------------- /src/mem.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "mem.h" 5 | 6 | double *doubledup(const double d) { 7 | double *p = calloc(1, sizeof(double)); 8 | *p = d; 9 | return p; 10 | } 11 | 12 | int32_t *int32dup(const int32_t i) { 13 | int32_t *p = calloc(1, sizeof(int32_t)); 14 | *p = i; 15 | return p; 16 | } 17 | 18 | uint32_t *uint32dup(const uint32_t i) { 19 | uint32_t *p = calloc(1, sizeof(uint32_t)); 20 | *p = i; 21 | return p; 22 | } 23 | -------------------------------------------------------------------------------- /src/output.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "river-layout-v3.h" 8 | #include "river-status-unstable-v1.h" 9 | 10 | #include "cfg.h" 11 | #include "cmd.h" 12 | #include "enum.h" 13 | #include "listener_river_layout.h" 14 | #include "listener_river_output_status.h" 15 | #include "log.h" 16 | #include "slist.h" 17 | #include "tag.h" 18 | 19 | #include "output.h" 20 | 21 | struct Output *output_init(struct wl_output *wl_output, 22 | const uint32_t name, 23 | struct river_layout_manager_v3 *river_layout_manager, 24 | struct zriver_status_manager_v1 *river_status_manager) { 25 | 26 | log_d_c_s("output_init"); log_d_c("%d", name); log_d_c("wl_output"); log_d_c_e("%p", (void*)wl_output); 27 | 28 | if (!wl_output) { 29 | log_w("Cannot create output %d, missing wayland output.", name); 30 | return NULL; 31 | } 32 | if (!river_layout_manager) { 33 | log_w("Cannot create output %d, missing river layout manager.", name); 34 | return NULL; 35 | } 36 | if (!river_status_manager) { 37 | log_w("Cannot create output %d, missing river status manager.", name); 38 | return NULL; 39 | } 40 | 41 | struct river_layout_v3 *river_layout = river_layout_manager_v3_get_layout(river_layout_manager, wl_output, "wideriver"); 42 | if (!river_layout) { 43 | log_w("Failed to create river layout, ignoring output %d", name); 44 | return NULL; 45 | } 46 | log_d_c_s("output_init"); log_d_c("%d", name); log_d_c("river_layout"); log_d_c_e("%p", (void*)river_layout); 47 | 48 | struct zriver_output_status_v1 *river_output_status = zriver_status_manager_v1_get_river_output_status(river_status_manager, wl_output); 49 | if (!river_output_status) { 50 | log_w("Failed to create river status manager, ignoring output %d", name); 51 | return NULL; 52 | } 53 | log_d_c_s("output_init"); log_d_c("%d", name); log_d_c("river_output_status"); log_d_c_e("%p", (void*)river_output_status); 54 | 55 | struct Output *output = calloc(1, sizeof(struct Output)); 56 | output->wl_output = wl_output; 57 | output->name = name; 58 | output->river_layout = river_layout; 59 | output->river_output_status = river_output_status; 60 | output->tags = tags_init(); 61 | 62 | river_layout_v3_add_listener(output->river_layout, river_layout_listener(), output); 63 | 64 | zriver_output_status_v1_add_listener(output->river_output_status, river_output_status_listener(), output); 65 | 66 | return output; 67 | } 68 | 69 | void output_destroy(const void *o) { 70 | if (!o) 71 | return; 72 | 73 | const struct Output* const output = o; 74 | log_d_c_s("output_destroy"); log_d_c_e("%d %s", output->name, ""); 75 | 76 | if (output->river_layout) { 77 | river_layout_v3_destroy(output->river_layout); 78 | log_d_c_s("output_destroy"); log_d_c("%d", output->name); log_d_c("river_layout"); log_d_c_e("%p", (void*)output->river_layout); 79 | } 80 | if (output->river_output_status) { 81 | zriver_output_status_v1_destroy(output->river_output_status); 82 | log_d_c_s("output_destroy"); log_d_c("%d", output->name); log_d_c("river_output_status"); log_d_c_e("%p", (void*)output->river_output_status); 83 | } 84 | if (output->wl_output) { 85 | log_d_c_s("output_destroy"); log_d_c("%d", output->name); log_d_c("wl_output"); log_d_c_e("%p", (void*)output->wl_output); 86 | wl_output_destroy(output->wl_output); 87 | } 88 | tags_destroy(output->tags); 89 | 90 | free((void*)output); 91 | } 92 | 93 | static void apply_layout(const struct Cmd *cmd, struct Tag *tag) { 94 | if (cmd->layout) { 95 | if (tag->layout_cur != cmd->layout) { 96 | tag->layout_prev = tag->layout_cur; 97 | tag->layout_cur = cmd->layout; 98 | } 99 | } else if (cmd->layout_toggle) { 100 | enum Layout cur = tag->layout_cur; 101 | tag->layout_cur = tag->layout_prev; 102 | tag->layout_prev = cur; 103 | } 104 | } 105 | 106 | static void apply_stack(const struct Cmd *cmd, struct Tag *tag) { 107 | if (cmd->stack) { 108 | tag->stack = cmd->stack; 109 | } 110 | } 111 | 112 | static void apply_count_ratio(const struct Cmd *cmd, struct Tag *tag) { 113 | uint32_t *count = NULL; 114 | double *ratio = NULL; 115 | 116 | switch (tag->layout_cur) { 117 | case WIDE: 118 | count = &tag->count_wide_left; 119 | ratio = &tag->ratio_wide; 120 | break; 121 | case LEFT: 122 | case RIGHT: 123 | case TOP: 124 | case BOTTOM: 125 | count = &tag->count_master; 126 | ratio = &tag->ratio_master; 127 | break; 128 | case MONOCLE: 129 | return; 130 | } 131 | 132 | if (cmd->count) { 133 | *count = *cmd->count; 134 | } else if (cmd->count_delta) { 135 | const int32_t new = *count + *cmd->count_delta; 136 | if (new < COUNT_MIN) { 137 | *count = COUNT_MIN; 138 | } else { 139 | *count = new; 140 | } 141 | } 142 | 143 | if (cmd->ratio) { 144 | *ratio = *cmd->ratio; 145 | } else if (cmd->ratio_delta) { 146 | *ratio = *ratio + *cmd->ratio_delta; 147 | } 148 | if (cmd->ratio || cmd->ratio_delta) { 149 | *ratio = MAX(*ratio, RATIO_MIN); 150 | *ratio = MIN(*ratio, RATIO_MAX); 151 | } 152 | } 153 | 154 | void output_apply_cmd(const struct Output *output, const struct Cmd *cmd) { 155 | struct SList *all = tag_all(output->tags, output->command_tags_mask); 156 | for (struct SList *i = all; i; i = i->nex) { 157 | struct Tag *tag = i->val; 158 | 159 | apply_layout(cmd, tag); 160 | apply_stack(cmd, tag); 161 | apply_count_ratio(cmd, tag); 162 | } 163 | slist_free(&all); 164 | } 165 | 166 | -------------------------------------------------------------------------------- /src/tag.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "cfg.h" 5 | #include "slist.h" 6 | 7 | #include "tag.h" 8 | 9 | struct Tag *tag_init(const uint32_t mask) { 10 | struct Tag *tag = calloc(1, sizeof(struct Tag)); 11 | 12 | tag->layout_cur = cfg->layout; 13 | tag->layout_prev = cfg->layout_alt; 14 | 15 | tag->stack = cfg->stack; 16 | 17 | tag->count_master = cfg->count_master; 18 | tag->ratio_master = cfg->ratio_master; 19 | tag->count_wide_left = cfg->count_wide_left; 20 | tag->ratio_wide = cfg->ratio_wide; 21 | 22 | tag->smart_gaps = cfg->smart_gaps; 23 | tag->inner_gaps = cfg->inner_gaps; 24 | tag->outer_gaps = cfg->outer_gaps; 25 | 26 | tag->mask = mask; 27 | 28 | return tag; 29 | } 30 | 31 | struct SList *tags_init(void) { 32 | struct SList *tags = NULL; 33 | 34 | // tag for all 32 bits of the uint32_t masks 35 | for (int i = 0; i < 32; i++) { 36 | slist_append(&tags, tag_init(1LL << i)); 37 | } 38 | 39 | return tags; 40 | } 41 | 42 | void tag_destroy(const void *t) { 43 | if (!t) 44 | return; 45 | 46 | free((struct Tag*)t); 47 | } 48 | 49 | void tags_destroy(const struct SList *tags) { 50 | if (!tags) 51 | return; 52 | 53 | slist_free_vals((struct SList**)&tags, tag_destroy); 54 | } 55 | 56 | struct Tag *tag_first(const struct SList *tags, const uint32_t mask) { 57 | if (!tags) { 58 | return NULL; 59 | } 60 | 61 | for (const struct SList *i = tags; i; i = i->nex) { 62 | const struct Tag *tag = i->val; 63 | if (mask & tag->mask) { 64 | return i->val; 65 | } 66 | } 67 | 68 | // default to first 69 | return tags->val; 70 | } 71 | 72 | struct SList *tag_all(const struct SList *tags, const uint32_t mask) { 73 | if (!tags) { 74 | return NULL; 75 | } 76 | 77 | struct SList *all = NULL; 78 | 79 | for (const struct SList *i = tags; i; i = i->nex) { 80 | const struct Tag *tag = i->val; 81 | if (mask & tag->mask) { 82 | slist_append(&all, i->val); 83 | } 84 | } 85 | 86 | return all; 87 | } 88 | 89 | -------------------------------------------------------------------------------- /src/usage.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "cfg.h" 5 | #include "enum.h" 6 | 7 | #include "usage.h" 8 | 9 | void usage(const int status) { 10 | fprintf(status == EXIT_SUCCESS ? stdout : stderr, 11 | "Usage: wideriver [OPTIONS...|COMMANDS...]\n" 12 | "\n" 13 | "OPTIONS, startup:\n" 14 | "\n" 15 | " --layout %s|%s|%s|%s|%s|%s %s\n" 16 | " --layout-alt %s|%s|%s|%s|%s|%s %s\n" 17 | "\n" 18 | " --stack %s|%s|%s %s\n" 19 | "\n" 20 | " --count-master count %d %d <= count\n" 21 | " --ratio-master ratio %.02f %.1g <= ratio <= %.1g\n" 22 | "\n" 23 | " --count-wide-left count %d %d <= count\n" 24 | " --ratio-wide ratio %.02f %.1g <= ratio <= %.1g\n" 25 | "\n" 26 | " --(no-)smart-gaps\n" 27 | " --inner-gaps pixels %d %d <= gap size\n" 28 | " --outer-gaps pixels %d %d <= gap size\n" 29 | "\n" 30 | " --border-width pixels %d %d <= width\n" 31 | " --border-width-monocle pixels %d %d <= width\n" 32 | " --border-width-smart-gaps pixels %d %d <= width\n" 33 | "\n" 34 | " --border-color-focused 0xRRGGBB[AA] %s\n" 35 | " --border-color-focused-monocle 0xRRGGBB[AA] %s\n" 36 | " --border-color-unfocused 0xRRGGBB[AA] %s\n" 37 | "\n" 38 | " --help\n" 39 | " --log-threshold %s|%s|%s|%s|%s %s\n" 40 | " --version\n" 41 | "\n" 42 | "COMMANDS, sent via riverctl(1):\n" 43 | "\n" 44 | " --layout %s|%s|%s|%s|%s|%s\n" 45 | " --layout-toggle \n" 46 | "\n" 47 | " --stack %s|%s|%s\n" 48 | "\n" 49 | " --count [+-]count %d <= count\n" 50 | " --ratio [+-]ratio %.1g <= ratio <= %.1g\n" 51 | "\n", 52 | layout_name(MONOCLE), layout_name(LEFT), layout_name(RIGHT), layout_name(TOP), layout_name(BOTTOM), layout_name(WIDE), layout_name(LAYOUT_DEFAULT), 53 | layout_name(MONOCLE), layout_name(LEFT), layout_name(RIGHT), layout_name(TOP), layout_name(BOTTOM), layout_name(WIDE), layout_name(LAYOUT_ALT_DEFAULT), 54 | stack_name(EVEN), stack_name(DIMINISH), stack_name(DWINDLE), stack_name(STACK_DEFAULT), 55 | COUNT_MASTER_DEFAULT, COUNT_MIN, 56 | RATIO_MASTER_DEFAULT, RATIO_MIN, RATIO_MAX, 57 | COUNT_WIDE_LEFT_DEFAULT, COUNT_MIN, 58 | RATIO_WIDE_DEFAULT, RATIO_MIN, RATIO_MAX, 59 | INNER_GAPS_DEFAULT, INNER_GAPS_MIN, 60 | OUTER_GAPS_DEFAULT, OUTER_GAPS_MIN, 61 | BORDER_WIDTH_DEFAULT, BORDER_WIDTH_MIN, 62 | BORDER_WIDTH_MONOCLE_DEFAULT, BORDER_WIDTH_MONOCLE_MIN, 63 | BORDER_WIDTH_SMART_GAPS_DEFAULT, BORDER_WIDTH_SMART_GAPS_MIN, 64 | BORDER_COLOR_FOCUSED_DEFAULT, 65 | BORDER_COLOR_FOCUSED_MONOCLE_DEFAULT, 66 | BORDER_COLOR_UNFOCUSED_DEFAULT, 67 | log_threshold_name(DEBUG), log_threshold_name(INFO), log_threshold_name(WARNING), log_threshold_name(ERROR), log_threshold_name(FATAL), log_threshold_name(LOG_THRESHOLD_DEFAULT), 68 | 69 | layout_name(MONOCLE), layout_name(LEFT), layout_name(RIGHT), layout_name(TOP), layout_name(BOTTOM), layout_name(WIDE), 70 | stack_name(DIMINISH), stack_name(DWINDLE), stack_name(STACK_DEFAULT), 71 | COUNT_MIN, 72 | RATIO_MIN, RATIO_MAX); 73 | 74 | exit(status); 75 | } 76 | 77 | void usage_defaults(void) { 78 | fprintf(stdout, 79 | "wideriver \\\n" 80 | " --layout %s \\\n" 81 | " --layout-alt %s \\\n" 82 | " --stack %s \\\n" 83 | " --count-master %d \\\n" 84 | " --ratio-master %.02f \\\n" 85 | " --count-wide-left %d \\\n" 86 | " --ratio-wide %.02f \\\n" 87 | " --no-smart-gaps \\\n" 88 | " --inner-gaps %d \\\n" 89 | " --outer-gaps %d \\\n" 90 | " --border-width %d \\\n" 91 | " --border-width-monocle %d \\\n" 92 | " --border-width-smart-gaps %d \\\n" 93 | " --border-color-focused \"%s\" \\\n" 94 | " --border-color-focused-monocle \"%s\" \\\n" 95 | " --border-color-unfocused \"%s\" \\\n" 96 | " --log-threshold %s \\\n" 97 | " > \"/tmp/wideriver.${XDG_VTNR}.${USER}.log\" 2>&1 &\n", 98 | layout_name(LAYOUT_DEFAULT), layout_name(LAYOUT_ALT_DEFAULT), stack_name(STACK_DEFAULT), 99 | COUNT_MASTER_DEFAULT, RATIO_MASTER_DEFAULT, 100 | COUNT_WIDE_LEFT_DEFAULT, RATIO_WIDE_DEFAULT, 101 | INNER_GAPS_DEFAULT, 102 | OUTER_GAPS_DEFAULT, 103 | BORDER_WIDTH_DEFAULT, BORDER_WIDTH_MONOCLE_DEFAULT, BORDER_WIDTH_SMART_GAPS_DEFAULT, 104 | BORDER_COLOR_FOCUSED_DEFAULT, BORDER_COLOR_FOCUSED_MONOCLE_DEFAULT, BORDER_COLOR_UNFOCUSED_DEFAULT, 105 | log_threshold_name(LOG_THRESHOLD_DEFAULT) 106 | ); 107 | 108 | exit(EXIT_SUCCESS); 109 | } 110 | -------------------------------------------------------------------------------- /tst/GNUmakefile: -------------------------------------------------------------------------------- 1 | include GNUmakefile 2 | 3 | PKGS_TST = cmocka 4 | CFLAGS += $(foreach p,$(PKGS_TST),$(shell pkg-config --cflags $(p))) 5 | LDLIBS += $(foreach p,$(PKGS_TST),$(shell pkg-config --libs $(p))) 6 | 7 | OBJS = tst/util.o \ 8 | $(patsubst %.c,%.o,$(wildcard tst/wrap-*.c)) \ 9 | $(filter-out src/main.o,$(SRC_O)) \ 10 | $(PRO_O) $(LIB_O) 11 | 12 | WRAPS_COMMON = -Wl,$\ 13 | --wrap=log_f,--wrap=log_e,--wrap=log_w,--wrap=log_i,--wrap=log_d 14 | 15 | tst-args_cli: WRAPS=,$\ 16 | --wrap=usage 17 | 18 | tst-cmd: WRAPS=,$\ 19 | --wrap=args_cmd 20 | 21 | tst-output_apply_cmd: WRAPS=,$\ 22 | --wrap=tag_all 23 | 24 | $(TST_O): $(TST_H) 25 | 26 | $(TST_E): $(TST_O) $(OBJS) 27 | $(CC) -o $(@) tst/$(@).o $(OBJS) $(LDFLAGS) $(LDLIBS) $(WRAPS_COMMON)$(WRAPS) 28 | 29 | -------------------------------------------------------------------------------- /tst/asserts.h: -------------------------------------------------------------------------------- 1 | #ifndef ASSERTS_H 2 | #define ASSERTS_H 3 | 4 | #include 5 | #include 6 | 7 | #include "layout.h" 8 | 9 | void _assert_nul(const void *a, const char * const ae, const char * const file, const int line) { 10 | if (a) { 11 | cm_print_error("%s is not NULL\n", ae); 12 | _fail(file, line); 13 | } 14 | } 15 | #define assert_nul(a) _assert_nul(a, #a, __FILE__, __LINE__) 16 | 17 | void _assert_non_nul(const void *a, const char * const ae, const char * const file, const int line) { 18 | if (!a) { 19 | cm_print_error("%s is NULL\n", ae); 20 | _fail(file, line); 21 | } 22 | } 23 | #define assert_non_nul(a) _assert_non_nul(a, #a, __FILE__, __LINE__) 24 | 25 | void _assert_str_equal(const char * const a, const char * const ae, const char * const b, const char * const be, const char * const file, const int line) { 26 | if (!a && !b) 27 | return; 28 | _assert_non_nul(a, ae, file, line); 29 | _assert_non_nul(b, be, file, line); 30 | _assert_string_equal(a, b, file, line); 31 | } 32 | #define assert_str_equal(a, b) _assert_str_equal(a, #a, b, #b, __FILE__, __LINE__) 33 | 34 | void _assert_str_equal_n(const char * const a, const char * const ae, const char * const b, const char * const be, const size_t n, const char * const file, const int line) { 35 | if (!a && !b) 36 | return; 37 | _assert_non_nul(a, ae, file, line); 38 | _assert_non_nul(b, be, file, line); 39 | if (strncmp(a, b, n) != 0) { 40 | cm_print_error("\"%.*s\" != \"%.*s\"\n", (int)n, a, (int)n, b); 41 | _fail(file, line); 42 | } 43 | } 44 | #define assert_str_equal_n(a, b, n) _assert_str_equal_n(a, #a, b, #b, n, __FILE__, __LINE__) 45 | 46 | void _assert_log(enum LogThreshold t, const char* s, const char * const file, const int line); 47 | #define assert_log(t, s) _assert_log(t, s, __FILE__, __LINE__) 48 | 49 | void _assert_logs_empty(const char * const file, const int line); 50 | #define assert_logs_empty() _assert_logs_empty(__FILE__, __LINE__) 51 | 52 | void _assert_boxes_equal(struct Box *a, struct Box *b, 53 | const char * const file, const int line) { 54 | if (!a) { 55 | print_error("Box a is null\n"); 56 | _fail(file, line); 57 | } 58 | if (!b) { 59 | print_error("Box b is null\n"); 60 | _fail(file, line); 61 | } 62 | if (memcmp(a, b, sizeof(struct Box)) != 0) { 63 | print_error("struct Box a = { .x = %d, .y = %d .width = %d, .height = %d, };\n" 64 | "struct Box b = { .x = %d, .y = %d .width = %d, .height = %d, };\n", 65 | a->x, a->y, a->width, a->height, b->x, b->y, b->width, b->height); 66 | _fail(file, line); 67 | } 68 | } 69 | #define assert_boxes_equal(a, b) _assert_boxes_equal(a, b, __FILE__, __LINE__) 70 | 71 | #define assert_box_equal(a, x, y, width, height) \ 72 | { \ 73 | struct Box expected = { x, y, width, height, }; \ 74 | _assert_boxes_equal(a, &expected, __FILE__, __LINE__); \ 75 | } 76 | 77 | #endif // ASSERTS_H 78 | -------------------------------------------------------------------------------- /tst/tst-args_cli.c: -------------------------------------------------------------------------------- 1 | #include "tst.h" 2 | #include "asserts.h" 3 | 4 | #include 5 | #include 6 | 7 | #include "cfg.h" 8 | #include "enum.h" 9 | 10 | #include "args.h" 11 | 12 | void __wrap_usage(int status) { 13 | check_expected(status); 14 | } 15 | 16 | int before_all(void **state) { 17 | return 0; 18 | } 19 | 20 | int after_all(void **state) { 21 | return 0; 22 | } 23 | 24 | int before_each(void **state) { 25 | assert_logs_empty(); 26 | return 0; 27 | } 28 | 29 | int after_each(void **state) { 30 | assert_logs_empty(); 31 | return 0; 32 | } 33 | 34 | void args_parse_cli__valid(void **state) { 35 | int argc = 34; 36 | char *argv[] = { "dummy", 37 | "--layout", "left", 38 | "--layout-alt", "right", 39 | "--stack", "dwindle", 40 | "--count-master", "2", 41 | "--ratio-master", "0.2", 42 | "--count-wide-left", "8", 43 | "--ratio-wide", "0.8", 44 | "--smart-gaps", 45 | "--border-width-smart-gaps", "8", 46 | "--inner-gaps", "6", 47 | "--outer-gaps", "6", 48 | "--border-width", "5", 49 | "--border-width-monocle", "10", 50 | "--border-color-focused", "0xAABBCC", 51 | "--border-color-focused-monocle", "0xDDEEFFA9", 52 | "--border-color-unfocused", "0x001122", 53 | "--log-threshold", "ERROR", 54 | }; 55 | 56 | args_cli(argc, argv); 57 | 58 | assert_int_equal(cfg->layout, LEFT); 59 | assert_int_equal(cfg->layout_alt, RIGHT); 60 | assert_int_equal(cfg->stack, DWINDLE); 61 | assert_int_equal(cfg->count_master, 2); 62 | assert_float_equal(cfg->ratio_master, 0.2, 0.001); 63 | assert_int_equal(cfg->count_wide_left, 8); 64 | assert_float_equal(cfg->ratio_wide, 0.8, 0.001); 65 | assert_true(cfg->smart_gaps); 66 | assert_int_equal(cfg->border_width_smart_gaps, 8); 67 | assert_int_equal(cfg->inner_gaps, 6); 68 | assert_int_equal(cfg->outer_gaps, 6); 69 | assert_int_equal(cfg->border_width, 5); 70 | assert_int_equal(cfg->border_width_monocle, 10); 71 | assert_str_equal(cfg->border_color_focused, "0xAABBCC"); 72 | assert_str_equal(cfg->border_color_focused_monocle, "0xDDEEFFA9"); 73 | assert_str_equal(cfg->border_color_unfocused, "0x001122"); 74 | 75 | assert_log(INFO, 76 | "--layout left\n" 77 | "--layout-alt right\n" 78 | "--stack dwindle\n" 79 | "--count-master 2\n" 80 | "--ratio-master 0.2\n" 81 | "--count-wide-left 8\n" 82 | "--ratio-wide 0.8\n" 83 | "--smart-gaps\n" 84 | "--inner-gaps 6\n" 85 | "--outer-gaps 6\n" 86 | "--border-width 5\n" 87 | "--border-width-monocle 10\n" 88 | "--border-width-smart-gaps 8\n" 89 | "--border-color-focused 0xAABBCC\n" 90 | "--border-color-focused-monocle 0xDDEEFFA9\n" 91 | "--border-color-unfocused 0x001122\n" 92 | "--log-threshold error\n" 93 | ); 94 | } 95 | 96 | void args_parse_cli__bad_layout(void **state) { 97 | int argc = 3; 98 | char *argv[] = { "dummy", 99 | "--layout", "bleh", 100 | }; 101 | 102 | expect_value(__wrap_usage, status, EXIT_FAILURE); 103 | 104 | args_cli(argc, argv); 105 | 106 | assert_log(FATAL, "invalid --layout 'bleh'\n\n"); 107 | } 108 | 109 | void args_parse_cli__bad_layout_alt(void **state) { 110 | int argc = 3; 111 | char *argv[] = { "dummy", 112 | "--layout-alt", "foo", 113 | }; 114 | 115 | expect_value(__wrap_usage, status, EXIT_FAILURE); 116 | 117 | args_cli(argc, argv); 118 | 119 | assert_log(FATAL, "invalid --layout-alt 'foo'\n\n"); 120 | } 121 | 122 | void args_parse_cli__bad_stack(void **state) { 123 | int argc = 3; 124 | char *argv[] = { "dummy", 125 | "--stack", "bleh", 126 | }; 127 | 128 | expect_value(__wrap_usage, status, EXIT_FAILURE); 129 | 130 | args_cli(argc, argv); 131 | 132 | assert_log(FATAL, "invalid --stack 'bleh'\n\n"); 133 | } 134 | 135 | void args_parse_cli__bad_count_master(void **state) { 136 | int argc = 3; 137 | char *argv[] = { "dummy", 138 | "--count-master", "-1", 139 | }; 140 | 141 | expect_value(__wrap_usage, status, EXIT_FAILURE); 142 | 143 | args_cli(argc, argv); 144 | 145 | assert_log(FATAL, "invalid --count-master '-1'\n\n"); 146 | } 147 | 148 | void args_parse_cli__bad_ratio_master(void **state) { 149 | int argc = 3; 150 | char *argv[] = { "dummy", 151 | "--ratio-master", "-1", 152 | }; 153 | 154 | expect_value(__wrap_usage, status, EXIT_FAILURE); 155 | 156 | args_cli(argc, argv); 157 | 158 | assert_log(FATAL, "invalid --ratio-master '-1'\n\n"); 159 | } 160 | 161 | void args_parse_cli__bad_count_wide_left(void **state) { 162 | int argc = 3; 163 | char *argv[] = { "dummy", 164 | "--count-wide-left", "-1", 165 | }; 166 | 167 | expect_value(__wrap_usage, status, EXIT_FAILURE); 168 | 169 | args_cli(argc, argv); 170 | 171 | assert_log(FATAL, "invalid --count-wide-left '-1'\n\n"); 172 | } 173 | 174 | void args_parse_cli__bad_ratio_wide(void **state) { 175 | int argc = 3; 176 | char *argv[] = { "dummy", 177 | "--ratio-wide", "-1", 178 | }; 179 | 180 | expect_value(__wrap_usage, status, EXIT_FAILURE); 181 | 182 | args_cli(argc, argv); 183 | 184 | assert_log(FATAL, "invalid --ratio-wide '-1'\n\n"); 185 | } 186 | 187 | void args_parse_cli__bad_border_width_smart_gaps(void **state) { 188 | int argc = 3; 189 | char *argv[] = { "dummy", 190 | "--border-width-smart-gaps", "-1", 191 | }; 192 | 193 | expect_value(__wrap_usage, status, EXIT_FAILURE); 194 | 195 | args_cli(argc, argv); 196 | 197 | assert_log(FATAL, "invalid --border-width-smart-gaps '-1'\n\n"); 198 | } 199 | 200 | void args_parse_cli__bad_inner_gaps(void **state) { 201 | int argc = 3; 202 | char *argv[] = { "dummy", 203 | "--inner-gaps", "-1", 204 | }; 205 | 206 | expect_value(__wrap_usage, status, EXIT_FAILURE); 207 | 208 | args_cli(argc, argv); 209 | 210 | assert_log(FATAL, "invalid --inner-gaps '-1'\n\n"); 211 | } 212 | 213 | void args_parse_cli__bad_outer_gaps(void **state) { 214 | int argc = 3; 215 | char *argv[] = { "dummy", 216 | "--outer-gaps", "-1", 217 | }; 218 | 219 | expect_value(__wrap_usage, status, EXIT_FAILURE); 220 | 221 | args_cli(argc, argv); 222 | 223 | assert_log(FATAL, "invalid --outer-gaps '-1'\n\n"); 224 | } 225 | 226 | void args_parse_cli__bad_border_width(void **state) { 227 | int argc = 3; 228 | char *argv[] = { "dummy", 229 | "--border-width", "A", 230 | }; 231 | 232 | expect_value(__wrap_usage, status, EXIT_FAILURE); 233 | 234 | args_cli(argc, argv); 235 | 236 | assert_log(FATAL, "invalid --border-width 'A'\n\n"); 237 | } 238 | 239 | void args_parse_cli__bad_border_width_monocle(void **state) { 240 | int argc = 3; 241 | char *argv[] = { "dummy", 242 | "--border-width-monocle", "", 243 | }; 244 | 245 | expect_value(__wrap_usage, status, EXIT_FAILURE); 246 | 247 | args_cli(argc, argv); 248 | 249 | assert_log(FATAL, "invalid --border-width-monocle ''\n\n"); 250 | } 251 | 252 | void args_parse_cli__bad_border_color_focused(void **state) { 253 | int argc = 3; 254 | char *argv[] = { "dummy", 255 | "--border-color-focused", "bleh", 256 | }; 257 | 258 | expect_value(__wrap_usage, status, EXIT_FAILURE); 259 | 260 | args_cli(argc, argv); 261 | 262 | assert_log(FATAL, "invalid --border-color-focused 'bleh'\n\n"); 263 | } 264 | 265 | void args_parse_cli__bad_border_color_focused_monocle(void **state) { 266 | int argc = 3; 267 | char *argv[] = { "dummy", 268 | "--border-color-focused-monocle", "foo", 269 | }; 270 | 271 | expect_value(__wrap_usage, status, EXIT_FAILURE); 272 | 273 | args_cli(argc, argv); 274 | 275 | assert_log(FATAL, "invalid --border-color-focused-monocle 'foo'\n\n"); 276 | } 277 | 278 | void args_parse_cli__bad_border_color_unfocused(void **state) { 279 | int argc = 3; 280 | char *argv[] = { "dummy", 281 | "--border-color-unfocused", "bar", 282 | }; 283 | 284 | expect_value(__wrap_usage, status, EXIT_FAILURE); 285 | 286 | args_cli(argc, argv); 287 | 288 | assert_log(FATAL, "invalid --border-color-unfocused 'bar'\n\n"); 289 | } 290 | 291 | void args_parse_cli__bad_log_threshold(void **state) { 292 | int argc = 3; 293 | char *argv[] = { "dummy", 294 | "--log-threshold", "bleh", 295 | }; 296 | 297 | expect_value(__wrap_usage, status, EXIT_FAILURE); 298 | 299 | args_cli(argc, argv); 300 | 301 | assert_log(FATAL, "invalid --log-threshold 'bleh'\n\n"); 302 | } 303 | 304 | int main(void) { 305 | const struct CMUnitTest tests[] = { 306 | TEST(args_parse_cli__valid), 307 | TEST(args_parse_cli__bad_layout), 308 | TEST(args_parse_cli__bad_layout_alt), 309 | TEST(args_parse_cli__bad_stack), 310 | TEST(args_parse_cli__bad_count_master), 311 | TEST(args_parse_cli__bad_ratio_master), 312 | TEST(args_parse_cli__bad_count_wide_left), 313 | TEST(args_parse_cli__bad_ratio_wide), 314 | TEST(args_parse_cli__bad_border_width_smart_gaps), 315 | TEST(args_parse_cli__bad_inner_gaps), 316 | TEST(args_parse_cli__bad_outer_gaps), 317 | TEST(args_parse_cli__bad_border_width), 318 | TEST(args_parse_cli__bad_border_width_monocle), 319 | TEST(args_parse_cli__bad_border_color_focused), 320 | TEST(args_parse_cli__bad_border_color_focused_monocle), 321 | TEST(args_parse_cli__bad_border_color_unfocused), 322 | TEST(args_parse_cli__bad_log_threshold), 323 | }; 324 | 325 | return RUN(tests); 326 | } 327 | 328 | -------------------------------------------------------------------------------- /tst/tst-args_cmd.c: -------------------------------------------------------------------------------- 1 | #include "tst.h" 2 | #include "asserts.h" 3 | 4 | #include 5 | 6 | #include "cmd.h" 7 | #include "enum.h" 8 | 9 | #include "args.h" 10 | 11 | void __wrap_usage(int status) { 12 | check_expected(status); 13 | } 14 | 15 | int before_all(void **state) { 16 | return 0; 17 | } 18 | 19 | int after_all(void **state) { 20 | return 0; 21 | } 22 | 23 | int before_each(void **state) { 24 | assert_logs_empty(); 25 | return 0; 26 | } 27 | 28 | int after_each(void **state) { 29 | assert_logs_empty(); 30 | return 0; 31 | } 32 | 33 | void args_parse_cmd__valid(void **state) { 34 | int argc = 10; 35 | char *argv[] = { "dummy", 36 | "--layout", "monocle", 37 | "--layout-toggle", 38 | "--count", "2", 39 | "--ratio", "0.25", 40 | "--stack", "dwindle", 41 | }; 42 | 43 | const struct Cmd *cmd = args_cmd(argc, argv); 44 | 45 | assert_non_nul(cmd); 46 | 47 | assert_int_equal(cmd->layout, MONOCLE); 48 | assert_true(cmd->layout_toggle); 49 | 50 | assert_non_nul(cmd->count); 51 | assert_int_equal(*cmd->count, 2); 52 | 53 | assert_non_nul(cmd->ratio); 54 | assert_float_equal(*cmd->ratio, 0.25, 0.001); 55 | 56 | assert_int_equal(cmd->stack, DWINDLE); 57 | 58 | cmd_destroy(cmd); 59 | } 60 | 61 | void args_parse_cmd__invalid(void **state) { 62 | int argc = 3; 63 | char *argv[] = { "dummy", 64 | "--foo", "bar", 65 | }; 66 | 67 | const struct Cmd *cmd = args_cmd(argc, argv); 68 | 69 | assert_nul(cmd); 70 | } 71 | 72 | void args_parse_cmd__bad_layout(void **state) { 73 | int argc = 3; 74 | char *argv[] = { "dummy", 75 | "--layout", "bleh", 76 | }; 77 | 78 | const struct Cmd *cmd = args_cmd(argc, argv); 79 | 80 | assert_nul(cmd); 81 | 82 | assert_log(ERROR, "invalid --layout 'bleh'\n"); 83 | } 84 | 85 | void args_parse_cmd__bad_count_master(void **state) { 86 | int argc = 3; 87 | char *argv[] = { "dummy", 88 | "--count", "A", 89 | }; 90 | 91 | const struct Cmd *cmd = args_cmd(argc, argv); 92 | 93 | assert_nul(cmd); 94 | 95 | assert_log(ERROR, "invalid --count 'A'\n"); 96 | 97 | cmd_destroy(cmd); 98 | } 99 | 100 | void args_parse_cmd__bad_ratio_master(void **state) { 101 | int argc = 3; 102 | char *argv[] = { "dummy", 103 | "--ratio", "123", 104 | }; 105 | 106 | const struct Cmd *cmd = args_cmd(argc, argv); 107 | 108 | assert_nul(cmd); 109 | 110 | assert_log(ERROR, "invalid --ratio '123'\n"); 111 | } 112 | 113 | void args_parse_cmd__bad_stack(void **state) { 114 | int argc = 3; 115 | char *argv[] = { "dummy", 116 | "--stack", "bleh", 117 | }; 118 | 119 | const struct Cmd *cmd = args_cmd(argc, argv); 120 | 121 | assert_nul(cmd); 122 | 123 | assert_log(ERROR, "invalid --stack 'bleh'\n"); 124 | } 125 | 126 | void args_parse_cmd__count_master_delta(void **state) { 127 | int argc = 3; 128 | char *argv[] = { "dummy", 129 | "--count", "-2", 130 | }; 131 | 132 | const struct Cmd *cmd = args_cmd(argc, argv); 133 | 134 | assert_non_nul(cmd); 135 | 136 | assert_non_nul(cmd->count_delta); 137 | assert_int_equal(*cmd->count_delta, -2); 138 | 139 | cmd_destroy(cmd); 140 | } 141 | 142 | void args_parse_cmd__ratio_master_delta(void **state) { 143 | int argc = 3; 144 | char *argv[] = { "dummy", 145 | "--ratio", "+0.05", 146 | }; 147 | 148 | const struct Cmd *cmd = args_cmd(argc, argv); 149 | 150 | assert_non_nul(cmd); 151 | 152 | assert_non_nul(cmd->ratio_delta); 153 | assert_float_equal(*cmd->ratio_delta, 0.05, 0.001); 154 | 155 | cmd_destroy(cmd); 156 | } 157 | 158 | int main(void) { 159 | const struct CMUnitTest tests[] = { 160 | TEST(args_parse_cmd__valid), 161 | TEST(args_parse_cmd__invalid), 162 | TEST(args_parse_cmd__bad_layout), 163 | TEST(args_parse_cmd__bad_count_master), 164 | TEST(args_parse_cmd__bad_ratio_master), 165 | TEST(args_parse_cmd__bad_stack), 166 | TEST(args_parse_cmd__count_master_delta), 167 | TEST(args_parse_cmd__ratio_master_delta), 168 | }; 169 | 170 | return RUN(tests); 171 | } 172 | 173 | -------------------------------------------------------------------------------- /tst/tst-arrange.c: -------------------------------------------------------------------------------- 1 | #include "tst.h" 2 | #include "asserts.h" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "enum.h" 9 | #include "layout.h" 10 | #include "slist.h" 11 | #include "tag.h" 12 | 13 | #include "arrange.h" 14 | 15 | int before_all(void **state) { 16 | return 0; 17 | } 18 | 19 | int after_all(void **state) { 20 | return 0; 21 | } 22 | 23 | int before_each(void **state) { 24 | return 0; 25 | } 26 | 27 | int after_each(void **state) { 28 | return 0; 29 | } 30 | 31 | void arrange_monocle__many(void **state) { 32 | struct SList *stack = NULL; 33 | struct Demand demand = { .view_count = 2, .usable_width = 5, .usable_height = 3 }; 34 | struct Tag tag = { .layout_cur = MONOCLE, }; 35 | 36 | arrange_monocle(&demand, &tag, &stack); 37 | 38 | assert_int_equal(slist_length(stack), 2); 39 | 40 | assert_box_equal(slist_at(stack, 0), 0, 0, 5, 3); 41 | assert_box_equal(slist_at(stack, 1), 0, 0, 5, 3); 42 | 43 | slist_free_vals(&stack, NULL); 44 | } 45 | 46 | void arrange_monocle__many_with_gaps(void **state) { 47 | struct SList *stack = NULL; 48 | struct Demand demand = { .view_count = 2, .usable_width = 5, .usable_height = 3 }; 49 | struct Tag tag = { .layout_cur = MONOCLE, .smart_gaps = false, 50 | .inner_gaps = 0, .outer_gaps = 1, }; 51 | struct Tag smart_tag = { .layout_cur = MONOCLE, .smart_gaps = true, 52 | .inner_gaps = 0, .outer_gaps = 1, }; 53 | 54 | arrange_monocle(&demand, &tag, &stack); 55 | assert_int_equal(slist_length(stack), 2); 56 | assert_box_equal(slist_at(stack, 1), 1, 1, 3, 1); 57 | assert_box_equal(slist_at(stack, 0), 1, 1, 3, 1); 58 | slist_free_vals(&stack, NULL); 59 | 60 | arrange_monocle(&demand, &smart_tag, &stack); 61 | assert_int_equal(slist_length(stack), 2); 62 | assert_box_equal(slist_at(stack, 0), 0, 0, 5, 3); 63 | assert_box_equal(slist_at(stack, 1), 0, 0, 5, 3); 64 | slist_free_vals(&stack, NULL); 65 | } 66 | 67 | int main(void) { 68 | const struct CMUnitTest tests[] = { 69 | TEST(arrange_monocle__many), 70 | TEST(arrange_monocle__many_with_gaps), 71 | }; 72 | 73 | return RUN(tests); 74 | } 75 | 76 | -------------------------------------------------------------------------------- /tst/tst-arrange_count.c: -------------------------------------------------------------------------------- 1 | #include "tst.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "enum.h" 7 | #include "tag.h" 8 | 9 | #include "arrange.h" 10 | 11 | int before_all(void **state) { 12 | return 0; 13 | } 14 | 15 | int after_all(void **state) { 16 | return 0; 17 | } 18 | 19 | int before_each(void **state) { 20 | return 0; 21 | } 22 | 23 | int after_each(void **state) { 24 | return 0; 25 | } 26 | 27 | void arrange_count__monocle_bad(void **state) { 28 | uint32_t master, before, after; 29 | 30 | struct Tag tag = { .layout_cur = MONOCLE, .count_wide_left = 99, }; 31 | 32 | tag.count_master = 0; 33 | arrange_count(0, &tag, &before, &master, &after); 34 | assert_int_equal(before, 0); 35 | assert_int_equal(master, 0); 36 | assert_int_equal(after, 0); 37 | 38 | tag.count_master = 2; 39 | arrange_count(0, &tag, &before, &master, &after); 40 | assert_int_equal(before, 0); 41 | assert_int_equal(master, 0); 42 | assert_int_equal(after, 0); 43 | } 44 | 45 | void arrange_count__monocle(void **state) { 46 | uint32_t master, before, after; 47 | 48 | struct Tag tag = { .layout_cur = MONOCLE, .count_wide_left = 99, }; 49 | 50 | tag.count_master = 2; 51 | arrange_count(5, &tag, &before, &master, &after); 52 | assert_int_equal(before, 0); 53 | assert_int_equal(master, 5); 54 | assert_int_equal(after, 0); 55 | } 56 | 57 | void arrange_count__lrud_bad(void **state) { 58 | uint32_t master, before, after; 59 | 60 | struct Tag tag = { .layout_cur = LEFT, .count_wide_left = 99, }; 61 | 62 | tag.count_master = 0; 63 | arrange_count(0, &tag, &before, &master, &after); 64 | assert_int_equal(before, 0); 65 | assert_int_equal(master, 0); 66 | assert_int_equal(after, 0); 67 | 68 | tag.count_master = 5; 69 | arrange_count(0, &tag, &before, &master, &after); 70 | assert_int_equal(before, 0); 71 | assert_int_equal(master, 0); 72 | assert_int_equal(after, 0); 73 | } 74 | 75 | void arrange_count__lrud(void **state) { 76 | uint32_t master, before, after; 77 | 78 | struct Tag tag = { .layout_cur = LEFT, .count_wide_left = 99, }; 79 | 80 | tag.count_master = 2; 81 | arrange_count(5, &tag, &before, &master, &after); 82 | assert_int_equal(before, 0); 83 | assert_int_equal(master, 2); 84 | assert_int_equal(after, 3); 85 | 86 | tag.count_master = 2; 87 | arrange_count(2, &tag, &before, &master, &after); 88 | assert_int_equal(before, 0); 89 | assert_int_equal(master, 2); 90 | assert_int_equal(after, 0); 91 | 92 | tag.count_master = 5; 93 | arrange_count(2, &tag, &before, &master, &after); 94 | assert_int_equal(before, 0); 95 | assert_int_equal(master, 2); 96 | assert_int_equal(after, 0); 97 | 98 | tag.count_master = 0; 99 | arrange_count(5, &tag, &before, &master, &after); 100 | assert_int_equal(before, 0); 101 | assert_int_equal(master, 0); 102 | assert_int_equal(after, 5); 103 | } 104 | 105 | void arrange_count__wide_bad(void **state) { 106 | uint32_t master, before, after; 107 | 108 | struct Tag tag = { .layout_cur = WIDE, .count_master = 99, }; 109 | 110 | // bad 111 | tag.count_wide_left = 0; 112 | arrange_count(0, &tag, &before, &master, &after); 113 | assert_int_equal(before, 0); 114 | assert_int_equal(master, 0); 115 | assert_int_equal(after, 0); 116 | 117 | // bad 118 | tag.count_wide_left = 5; 119 | arrange_count(0, &tag, &before, &master, &after); 120 | assert_int_equal(before, 0); 121 | assert_int_equal(master, 0); 122 | assert_int_equal(after, 0); 123 | 124 | // mid, bad count 125 | tag.count_wide_left = 0; 126 | arrange_count(1, &tag, &before, &master, &after); 127 | assert_int_equal(before, 0); 128 | assert_int_equal(master, 1); 129 | assert_int_equal(after, 0); 130 | 131 | // mid and right, bad count 132 | tag.count_wide_left = 0; 133 | arrange_count(3, &tag, &before, &master, &after); 134 | assert_int_equal(before, 0); 135 | assert_int_equal(master, 1); 136 | assert_int_equal(after, 2); 137 | } 138 | 139 | void arrange_count__wide(void **state) { 140 | uint32_t master, before, after; 141 | 142 | struct Tag tag = { .layout_cur = WIDE, .count_master = 99, }; 143 | 144 | // mid 145 | tag.count_wide_left = 5; 146 | arrange_count(1, &tag, &before, &master, &after); 147 | assert_int_equal(before, 0); 148 | assert_int_equal(master, 1); 149 | assert_int_equal(after, 0); 150 | 151 | // left and mid 152 | tag.count_wide_left = 3; 153 | arrange_count(4, &tag, &before, &master, &after); 154 | assert_int_equal(before, 3); 155 | assert_int_equal(master, 1); 156 | assert_int_equal(after, 0); 157 | 158 | // mid and right 159 | tag.count_wide_left = 0; 160 | arrange_count(4, &tag, &before, &master, &after); 161 | assert_int_equal(before, 0); 162 | assert_int_equal(master, 1); 163 | assert_int_equal(after, 3); 164 | 165 | // left and mid and right 166 | tag.count_wide_left = 2; 167 | arrange_count(5, &tag, &before, &master, &after); 168 | assert_int_equal(before, 2); 169 | assert_int_equal(master, 1); 170 | assert_int_equal(after, 2); 171 | } 172 | 173 | int main(void) { 174 | const struct CMUnitTest tests[] = { 175 | TEST(arrange_count__monocle_bad), 176 | TEST(arrange_count__monocle), 177 | TEST(arrange_count__lrud_bad), 178 | TEST(arrange_count__lrud), 179 | TEST(arrange_count__wide_bad), 180 | TEST(arrange_count__wide), 181 | }; 182 | 183 | return RUN(tests); 184 | } 185 | 186 | -------------------------------------------------------------------------------- /tst/tst-arrange_master_stack.c: -------------------------------------------------------------------------------- 1 | #include "tst.h" 2 | #include "asserts.h" 3 | 4 | #include 5 | #include 6 | 7 | #include "enum.h" 8 | #include "layout.h" 9 | #include "tag.h" 10 | 11 | #include "arrange.h" 12 | 13 | int before_all(void **state) { 14 | return 0; 15 | } 16 | 17 | int after_all(void **state) { 18 | return 0; 19 | } 20 | 21 | int before_each(void **state) { 22 | return 0; 23 | } 24 | 25 | int after_each(void **state) { 26 | return 0; 27 | } 28 | 29 | void arrange_master_stack__zero(void **state) { 30 | struct Box master, stack; 31 | 32 | struct Demand demand = { .usable_width = 9, .usable_height = 5, }; 33 | struct Tag tag = { .layout_cur = LEFT, .ratio_master = 0.5, }; 34 | 35 | arrange_master_stack(&demand, &tag, 0, 0, &master, &stack); 36 | 37 | assert_box_equal(&master, 0, 0, 0, 0); 38 | 39 | assert_box_equal(&stack, 0, 0, 0, 0); 40 | } 41 | 42 | void arrange_master_stack__zero_master(void **state) { 43 | struct Box master, stack; 44 | 45 | struct Demand demand = { .usable_width = 9, .usable_height = 5, }; 46 | struct Tag tag = { .layout_cur = LEFT, .ratio_master = 0.5, }; 47 | 48 | arrange_master_stack(&demand, &tag, 0, 1, &master, &stack); 49 | 50 | assert_box_equal(&master, 0, 0, 0, 0); 51 | 52 | assert_box_equal(&stack, 0, 0, 9, 5); 53 | } 54 | 55 | void arrange_master_stack__zero_stack(void **state) { 56 | struct Box master, stack; 57 | 58 | struct Demand demand = { .usable_width = 9, .usable_height = 5, }; 59 | struct Tag tag = { .layout_cur = LEFT, .ratio_master = 0.5, }; 60 | 61 | arrange_master_stack(&demand, &tag, 1, 0, &master, &stack); 62 | 63 | assert_box_equal(&master, 0, 0, 9, 5); 64 | 65 | assert_box_equal(&stack, 0, 0, 0, 0); 66 | } 67 | 68 | void arrange_master_stack__zero_master_with_gaps(void **state) { 69 | struct Box master, stack; 70 | 71 | struct Demand demand = { .view_count = 1, .usable_width = 9, .usable_height = 5, }; 72 | struct Tag tag = { .layout_cur = LEFT, .ratio_master = 0.5, 73 | .smart_gaps = false, .inner_gaps = 1, .outer_gaps = 1, }; 74 | struct Tag smart_tag = { .layout_cur = LEFT, .ratio_master = 0.5, 75 | .smart_gaps = true, .inner_gaps = 1, .outer_gaps = 1, }; 76 | 77 | arrange_master_stack(&demand, &tag, 0, 1, &master, &stack); 78 | assert_box_equal(&master, 0, 0, 0, 0); 79 | assert_box_equal(&stack, 1, 1, 7, 3); 80 | 81 | arrange_master_stack(&demand, &smart_tag, 0, 1, &master, &stack); 82 | assert_box_equal(&master, 0, 0, 0, 0); 83 | assert_box_equal(&stack, 0, 0, 9, 5); 84 | } 85 | 86 | void arrange_master_stack__zero_stack_with_gaps(void **state) { 87 | struct Box master, stack; 88 | 89 | struct Demand demand = { .view_count = 1, .usable_width = 9, .usable_height = 5, }; 90 | struct Tag tag = { .layout_cur = LEFT, .ratio_master = 0.5, 91 | .smart_gaps = false, .inner_gaps = 1, .outer_gaps = 1, }; 92 | struct Tag smart_tag = { .layout_cur = LEFT, .ratio_master = 0.5, 93 | .smart_gaps = true, .inner_gaps = 1, .outer_gaps = 1, }; 94 | 95 | arrange_master_stack(&demand, &tag, 1, 0, &master, &stack); 96 | assert_box_equal(&master, 1, 1, 7, 3); 97 | assert_box_equal(&stack, 0, 0, 0, 0); 98 | 99 | arrange_master_stack(&demand, &smart_tag, 1, 0, &master, &stack); 100 | assert_box_equal(&master, 0, 0, 9, 5); 101 | assert_box_equal(&stack, 0, 0, 0, 0); 102 | } 103 | 104 | void arrange_master_stack__left(void **state) { 105 | struct Box master, stack; 106 | 107 | struct Demand demand = { .usable_width = 9, .usable_height = 5, }; 108 | struct Tag tag = { .layout_cur = LEFT, .ratio_master = 0.4, }; 109 | 110 | arrange_master_stack(&demand, &tag, 1, 1, &master, &stack); 111 | 112 | assert_box_equal(&master, 0, 0, 4, 5); 113 | 114 | assert_box_equal(&stack, 4, 0, 5, 5); 115 | } 116 | 117 | void arrange_master_stack__left_with_gaps(void **state) { 118 | struct Box master, stack; 119 | 120 | struct Demand demand = { .usable_width = 9, .usable_height = 5, }; 121 | struct Tag tag = { .layout_cur = LEFT, .ratio_master = 0.4, 122 | .inner_gaps = 1, .outer_gaps = 1, }; 123 | 124 | arrange_master_stack(&demand, &tag, 1, 1, &master, &stack); 125 | assert_box_equal(&master, 1, 1, 2, 3); 126 | assert_box_equal(&stack, 4, 1, 4, 3); 127 | } 128 | 129 | void arrange_master_stack__right(void **state) { 130 | struct Box master, stack; 131 | 132 | struct Demand demand = { .usable_width = 9, .usable_height = 5, }; 133 | struct Tag tag = { .layout_cur = RIGHT, .ratio_master = 0.4, }; 134 | 135 | arrange_master_stack(&demand, &tag, 1, 1, &master, &stack); 136 | 137 | assert_box_equal(&master, 5, 0, 4, 5); 138 | 139 | assert_box_equal(&stack, 0, 0, 5, 5); 140 | } 141 | 142 | void arrange_master_stack__right_with_gaps(void **state) { 143 | struct Box master, stack; 144 | 145 | struct Demand demand = { .usable_width = 9, .usable_height = 5, }; 146 | struct Tag tag = { .layout_cur = RIGHT, .ratio_master = 0.4, 147 | .inner_gaps = 1, .outer_gaps = 1, }; 148 | 149 | arrange_master_stack(&demand, &tag, 1, 1, &master, &stack); 150 | assert_box_equal(&master, 6, 1, 2, 3); 151 | assert_box_equal(&stack, 1, 1, 4, 3); 152 | } 153 | 154 | void arrange_master_stack__top(void **state) { 155 | struct Box master, stack; 156 | 157 | struct Demand demand = { .usable_width = 9, .usable_height = 5, }; 158 | struct Tag tag = { .layout_cur = TOP, .ratio_master = 0.3, }; 159 | 160 | arrange_master_stack(&demand, &tag, 1, 1, &master, &stack); 161 | 162 | assert_box_equal(&master, 0, 0, 9, 2); 163 | 164 | assert_box_equal(&stack, 0, 2, 9, 3); 165 | } 166 | 167 | void arrange_master_stack__top_with_gaps(void **state) { 168 | struct Box master, stack; 169 | 170 | struct Demand demand = { .usable_width = 9, .usable_height = 5, }; 171 | struct Tag tag = { .layout_cur = TOP, .ratio_master = 0.3, 172 | .inner_gaps = 1, .outer_gaps = 1, }; 173 | 174 | arrange_master_stack(&demand, &tag, 1, 1, &master, &stack); 175 | assert_box_equal(&master, 1, 1, 7, 1); 176 | assert_box_equal(&stack, 1, 3, 7, 1); 177 | } 178 | 179 | void arrange_master_stack__bottom(void **state) { 180 | struct Box master, stack; 181 | 182 | struct Demand demand = { .usable_width = 9, .usable_height = 5, }; 183 | struct Tag tag = { .layout_cur = BOTTOM, .ratio_master = 0.3, }; 184 | 185 | arrange_master_stack(&demand, &tag, 1, 1, &master, &stack); 186 | 187 | assert_box_equal(&master, 0, 3, 9, 2); 188 | 189 | assert_box_equal(&stack, 0, 0, 9, 3); 190 | } 191 | 192 | void arrange_master_stack__bottom_with_gaps(void **state) { 193 | struct Box master, stack; 194 | 195 | struct Demand demand = { .usable_width = 9, .usable_height = 5, }; 196 | struct Tag tag = { .layout_cur = BOTTOM, .ratio_master = 0.3, 197 | .inner_gaps = 1, .outer_gaps = 1, }; 198 | 199 | arrange_master_stack(&demand, &tag, 1, 1, &master, &stack); 200 | assert_box_equal(&master, 1, 3, 7, 1); 201 | assert_box_equal(&stack, 1, 1, 7, 1); 202 | } 203 | 204 | int main(void) { 205 | const struct CMUnitTest tests[] = { 206 | TEST(arrange_master_stack__zero), 207 | TEST(arrange_master_stack__zero_master), 208 | TEST(arrange_master_stack__zero_stack), 209 | 210 | TEST(arrange_master_stack__left), 211 | TEST(arrange_master_stack__right), 212 | TEST(arrange_master_stack__top), 213 | TEST(arrange_master_stack__bottom), 214 | 215 | TEST(arrange_master_stack__zero_master_with_gaps), 216 | TEST(arrange_master_stack__zero_stack_with_gaps), 217 | 218 | TEST(arrange_master_stack__left_with_gaps), 219 | TEST(arrange_master_stack__right_with_gaps), 220 | TEST(arrange_master_stack__top_with_gaps), 221 | TEST(arrange_master_stack__bottom_with_gaps), 222 | }; 223 | 224 | return RUN(tests); 225 | } 226 | 227 | -------------------------------------------------------------------------------- /tst/tst-arrange_mid.c: -------------------------------------------------------------------------------- 1 | #include "tst.h" 2 | #include "asserts.h" 3 | 4 | #include 5 | #include 6 | 7 | #include "layout.h" 8 | #include "tag.h" 9 | 10 | #include "arrange.h" 11 | 12 | int before_all(void **state) { 13 | return 0; 14 | } 15 | 16 | int after_all(void **state) { 17 | return 0; 18 | } 19 | 20 | int before_each(void **state) { 21 | return 0; 22 | } 23 | 24 | int after_each(void **state) { 25 | return 0; 26 | } 27 | 28 | void arrange_wide__000(void **state) { 29 | struct Box master, before, after; 30 | 31 | struct Demand demand = { .usable_width = 13, .usable_height = 5, }; 32 | struct Tag tag = { .ratio_wide = 0.1 }; 33 | 34 | arrange_wide(&demand, &tag, 0, 0, 0, &before, &master, &after); 35 | 36 | assert_box_equal(&before, 0, 0, 0, 0); 37 | assert_box_equal(&master, 0, 0, 0, 0); 38 | assert_box_equal(&after, 0, 0, 0, 0); 39 | } 40 | 41 | void arrange_wide__010(void **state) { 42 | struct Box master, before, after; 43 | 44 | struct Demand demand = { .usable_width = 13, .usable_height = 5, }; 45 | struct Tag tag = { .ratio_wide = 999 }; 46 | 47 | arrange_wide(&demand, &tag, 0, 1, 0, &before, &master, &after); 48 | 49 | assert_box_equal(&before, 0, 0, 0, 0); 50 | assert_box_equal(&master, 0, 0, 13, 5); 51 | assert_box_equal(&after, 0, 0, 0, 0); 52 | } 53 | 54 | void arrange_wide__010_with_gaps(void **state) { 55 | struct Box master, before, after; 56 | 57 | struct Demand demand = { .view_count = 1, .usable_width = 13, .usable_height = 5, }; 58 | struct Tag tag = { .ratio_wide = 999, .smart_gaps = false, 59 | .inner_gaps = 1, .outer_gaps = 1, }; 60 | struct Tag smart_tag = { .ratio_wide = 999, .smart_gaps = true, 61 | .inner_gaps = 1, .outer_gaps = 1, }; 62 | 63 | arrange_wide(&demand, &tag, 0, 1, 0, &before, &master, &after); 64 | assert_box_equal(&before, 0, 0, 0, 0); 65 | assert_box_equal(&master, 1, 1, 11, 3); 66 | assert_box_equal(&after, 0, 0, 0, 0); 67 | 68 | arrange_wide(&demand, &smart_tag, 0, 1, 0, &before, &master, &after); 69 | assert_box_equal(&before, 0, 0, 0, 0); 70 | assert_box_equal(&master, 0, 0, 13, 5); 71 | assert_box_equal(&after, 0, 0, 0, 0); 72 | } 73 | 74 | void arrange_wide__001(void **state) { 75 | struct Box master, before, after; 76 | 77 | struct Demand demand = { .usable_width = 13, .usable_height = 5, }; 78 | struct Tag tag = { .ratio_wide = 999 }; 79 | 80 | arrange_wide(&demand, &tag, 0, 0, 1, &before, &master, &after); 81 | 82 | assert_box_equal(&before, 0, 0, 0, 0); 83 | assert_box_equal(&master, 0, 0, 0, 0); 84 | assert_box_equal(&after, 0, 0, 13, 5); 85 | } 86 | 87 | void arrange_wide__001_with_gaps(void **state) { 88 | struct Box master, before, after; 89 | 90 | struct Demand demand = { .view_count = 1, .usable_width = 13, .usable_height = 5, }; 91 | struct Tag tag = { .ratio_wide = 999, .smart_gaps = false, 92 | .inner_gaps = 1, .outer_gaps = 1, }; 93 | struct Tag smart_tag = { .ratio_wide = 999, .smart_gaps = true, 94 | .inner_gaps = 1, .outer_gaps = 1, }; 95 | 96 | arrange_wide(&demand, &tag, 0, 0, 1, &before, &master, &after); 97 | assert_box_equal(&before, 0, 0, 0, 0); 98 | assert_box_equal(&master, 0, 0, 0, 0); 99 | assert_box_equal(&after, 1, 1, 11, 3); 100 | 101 | arrange_wide(&demand, &smart_tag, 0, 0, 1, &before, &master, &after); 102 | assert_box_equal(&before, 0, 0, 0, 0); 103 | assert_box_equal(&master, 0, 0, 0, 0); 104 | assert_box_equal(&after, 0, 0, 13, 5); 105 | } 106 | 107 | void arrange_wide__100(void **state) { 108 | struct Box master, before, after; 109 | 110 | struct Demand demand = { .usable_width = 13, .usable_height = 5, }; 111 | struct Tag tag = { .ratio_wide = 999 }; 112 | 113 | arrange_wide(&demand, &tag, 1, 0, 0, &before, &master, &after); 114 | 115 | assert_box_equal(&before, 0, 0, 13, 5); 116 | assert_box_equal(&master, 0, 0, 0, 0); 117 | assert_box_equal(&after, 0, 0, 0, 0); 118 | } 119 | 120 | void arrange_wide__100_with_gaps(void **state) { 121 | struct Box master, before, after; 122 | 123 | struct Demand demand = { .view_count = 1, .usable_width = 13, .usable_height = 5, }; 124 | struct Tag tag = { .ratio_wide = 999, .smart_gaps = false, 125 | .inner_gaps = 1, .outer_gaps = 1, }; 126 | struct Tag smart_tag = { .ratio_wide = 999, .smart_gaps = true, 127 | .inner_gaps = 1, .outer_gaps = 1, }; 128 | 129 | arrange_wide(&demand, &tag, 1, 0, 0, &before, &master, &after); 130 | assert_box_equal(&before, 1, 1, 11, 3); 131 | assert_box_equal(&master, 0, 0, 0, 0); 132 | assert_box_equal(&after, 0, 0, 0, 0); 133 | 134 | arrange_wide(&demand, &smart_tag, 1, 0, 0, &before, &master, &after); 135 | assert_box_equal(&before, 0, 0, 13, 5); 136 | assert_box_equal(&master, 0, 0, 0, 0); 137 | assert_box_equal(&after, 0, 0, 0, 0); 138 | } 139 | 140 | void arrange_wide__101(void **state) { 141 | struct Box master, before, after; 142 | 143 | struct Demand demand = { .usable_width = 13, .usable_height = 5, }; 144 | struct Tag tag = { .ratio_wide = 999 }; 145 | 146 | arrange_wide(&demand, &tag, 1, 0, 1, &before, &master, &after); 147 | 148 | assert_box_equal(&before, 0, 0, 7, 5); 149 | assert_box_equal(&master, 0, 0, 0, 0); 150 | assert_box_equal(&after, 7, 0, 6, 5); 151 | } 152 | 153 | void arrange_wide__101_with_gaps(void **state) { 154 | struct Box master, before, after; 155 | 156 | struct Demand demand = { .usable_width = 13, .usable_height = 5, }; 157 | struct Tag tag = { .ratio_wide = 999, .inner_gaps = 1, .outer_gaps = 1, }; 158 | 159 | arrange_wide(&demand, &tag, 1, 0, 1, &before, &master, &after); 160 | assert_box_equal(&before, 1, 1, 5, 3); 161 | assert_box_equal(&master, 0, 0, 0, 0); 162 | assert_box_equal(&after, 7, 1, 5, 3); 163 | } 164 | 165 | void arrange_wide__011(void **state) { 166 | struct Box master, before, after; 167 | 168 | struct Demand demand = { .usable_width = 13, .usable_height = 5, }; 169 | struct Tag tag = { .ratio_wide = 0.2 }; 170 | 171 | arrange_wide(&demand, &tag, 0, 1, 1, &before, &master, &after); 172 | 173 | assert_box_equal(&before, 0, 0, 0, 0); 174 | assert_box_equal(&master, 0, 0, 8, 5); 175 | assert_box_equal(&after, 8, 0, 5, 5); 176 | } 177 | 178 | void arrange_wide__011_with_gaps(void **state) { 179 | struct Box master, before, after; 180 | 181 | struct Demand demand = { .usable_width = 13, .usable_height = 5, }; 182 | struct Tag tag = { .ratio_wide = 0.2, .inner_gaps = 1, .outer_gaps = 1, }; 183 | 184 | arrange_wide(&demand, &tag, 0, 1, 1, &before, &master, &after); 185 | assert_box_equal(&before, 0, 0, 0, 0); 186 | assert_box_equal(&master, 1, 1, 6, 3); 187 | assert_box_equal(&after, 8, 1, 4, 3); 188 | } 189 | 190 | void arrange_wide__110(void **state) { 191 | struct Box master, before, after; 192 | 193 | struct Demand demand = { .usable_width = 13, .usable_height = 5, }; 194 | struct Tag tag = { .ratio_wide = 0.7 }; 195 | 196 | arrange_wide(&demand, &tag, 1, 1, 0, &before, &master, &after); 197 | 198 | assert_box_equal(&before, 0, 0, 2, 5); 199 | assert_box_equal(&master, 2, 0, 11, 5); 200 | assert_box_equal(&after, 0, 0, 0, 0); 201 | } 202 | 203 | void arrange_wide__110_with_gaps(void **state) { 204 | struct Box master, before, after; 205 | 206 | struct Demand demand = { .usable_width = 13, .usable_height = 5, }; 207 | struct Tag tag = { .ratio_wide = 0.7, .inner_gaps = 1, .outer_gaps = 1, }; 208 | 209 | arrange_wide(&demand, &tag, 1, 1, 0, &before, &master, &after); 210 | assert_box_equal(&before, 1, 1, 1, 3); 211 | assert_box_equal(&master, 3, 1, 9, 3); 212 | assert_box_equal(&after, 0, 0, 0, 0); 213 | } 214 | 215 | void arrange_wide__111(void **state) { 216 | struct Box master, before, after; 217 | 218 | struct Demand demand = { .usable_width = 13, .usable_height = 5, }; 219 | struct Tag tag = { .ratio_wide = 0.5 }; 220 | 221 | arrange_wide(&demand, &tag, 1, 1, 1, &before, &master, &after); 222 | 223 | assert_box_equal(&before, 0, 0, 3, 5); 224 | assert_box_equal(&master, 3, 0, 7, 5); 225 | assert_box_equal(&after, 10, 0, 3, 5); 226 | } 227 | 228 | void arrange_wide__111_with_gaps(void **state) { 229 | struct Box master, before, after; 230 | 231 | struct Demand demand = { .usable_width = 13, .usable_height = 5, }; 232 | struct Tag tag = { .ratio_wide = 0.5, .inner_gaps = 1, .outer_gaps = 1, }; 233 | 234 | arrange_wide(&demand, &tag, 1, 1, 1, &before, &master, &after); 235 | assert_box_equal(&before, 1, 1, 2, 3); 236 | assert_box_equal(&master, 4, 1, 5, 3); 237 | assert_box_equal(&after, 10, 1, 2, 3); 238 | } 239 | 240 | int main(void) { 241 | const struct CMUnitTest tests[] = { 242 | TEST(arrange_wide__000), 243 | 244 | TEST(arrange_wide__010), 245 | TEST(arrange_wide__001), 246 | TEST(arrange_wide__100), 247 | 248 | TEST(arrange_wide__101), 249 | 250 | TEST(arrange_wide__011), 251 | TEST(arrange_wide__110), 252 | 253 | TEST(arrange_wide__111), 254 | 255 | TEST(arrange_wide__010_with_gaps), 256 | TEST(arrange_wide__001_with_gaps), 257 | TEST(arrange_wide__100_with_gaps), 258 | TEST(arrange_wide__101_with_gaps), 259 | TEST(arrange_wide__011_with_gaps), 260 | TEST(arrange_wide__110_with_gaps), 261 | TEST(arrange_wide__111_with_gaps), 262 | }; 263 | 264 | return RUN(tests); 265 | } 266 | 267 | -------------------------------------------------------------------------------- /tst/tst-cfg.c: -------------------------------------------------------------------------------- 1 | #include "tst.h" 2 | 3 | #include 4 | #include 5 | 6 | bool valid_colour(const char * const s); 7 | 8 | int before_all(void **state) { 9 | return 0; 10 | } 11 | 12 | int after_all(void **state) { 13 | return 0; 14 | } 15 | 16 | int before_each(void **state) { 17 | return 0; 18 | } 19 | 20 | int after_each(void **state) { 21 | return 0; 22 | } 23 | 24 | void valid_colour__len(void **state) { 25 | assert_false(valid_colour("aoeu")); 26 | } 27 | 28 | void valid_colour__prefix(void **state) { 29 | assert_false(valid_colour("AA000000")); 30 | } 31 | 32 | void valid_colour__alpha(void **state) { 33 | assert_false(valid_colour("0x12345z")); 34 | assert_false(valid_colour("0x12345Z")); 35 | } 36 | 37 | void valid_colour__valid(void **state) { 38 | assert_true(valid_colour("0x09afAF")); 39 | assert_true(valid_colour("0x09afAF12")); 40 | } 41 | 42 | int main(void) { 43 | const struct CMUnitTest tests[] = { 44 | TEST(valid_colour__len), 45 | TEST(valid_colour__prefix), 46 | TEST(valid_colour__alpha), 47 | TEST(valid_colour__valid), 48 | }; 49 | 50 | return RUN(tests); 51 | } 52 | 53 | -------------------------------------------------------------------------------- /tst/tst-cmd.c: -------------------------------------------------------------------------------- 1 | #include "tst.h" 2 | #include "asserts.h" 3 | 4 | #include 5 | #include 6 | 7 | #include "enum.h" 8 | 9 | #include "cmd.h" 10 | 11 | char **argv_expected = NULL; 12 | 13 | struct Cmd *__wrap_args_cmd(int argc, char **argv) { 14 | check_expected(argc); 15 | check_expected(argv); 16 | 17 | for (int i = 0; i < argc; i++) { 18 | assert_str_equal(argv[i], argv_expected[i]); 19 | } 20 | 21 | return mock_type(struct Cmd*); 22 | } 23 | 24 | int before_all(void **state) { 25 | return 0; 26 | } 27 | 28 | int after_all(void **state) { 29 | return 0; 30 | } 31 | 32 | int before_each(void **state) { 33 | assert_logs_empty(); 34 | return 0; 35 | } 36 | 37 | int after_each(void **state) { 38 | return 0; 39 | } 40 | 41 | void cmd_init__empty(void **state) { 42 | char args[] = ""; 43 | 44 | assert_nul(cmd_init(args)); 45 | 46 | assert_log(ERROR, "command empty ''\n"); 47 | } 48 | 49 | void cmd_init__valid(void **state) { 50 | char args[] = " --foo bar baz "; 51 | 52 | char *tokens[] = { "dummy", "--foo", "bar", "baz" }; 53 | argv_expected = tokens; 54 | 55 | struct Cmd mock_cmd = { 0 }; 56 | 57 | expect_value(__wrap_args_cmd, argc, 4); 58 | expect_any(__wrap_args_cmd, argv); 59 | will_return(__wrap_args_cmd, &mock_cmd); 60 | 61 | const struct Cmd *cmd = cmd_init(args); 62 | 63 | assert_ptr_equal(cmd, &mock_cmd); 64 | } 65 | 66 | void cmd_init__invalid(void **state) { 67 | char args[] = "foo"; 68 | 69 | char *tokens[] = { "dummy", "foo", }; 70 | argv_expected = tokens; 71 | 72 | expect_value(__wrap_args_cmd, argc, 2); 73 | expect_any(__wrap_args_cmd, argv); 74 | will_return(__wrap_args_cmd, NULL); 75 | 76 | const struct Cmd *cmd = cmd_init(args); 77 | assert_nul(cmd); 78 | 79 | assert_log(ERROR, "command invalid 'foo'\n"); 80 | } 81 | 82 | int main(void) { 83 | const struct CMUnitTest tests[] = { 84 | TEST(cmd_init__empty), 85 | TEST(cmd_init__valid), 86 | TEST(cmd_init__invalid), 87 | }; 88 | 89 | return RUN(tests); 90 | } 91 | 92 | -------------------------------------------------------------------------------- /tst/tst-output_apply_cmd.c: -------------------------------------------------------------------------------- 1 | #include "tst.h" 2 | #include "asserts.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "cfg.h" 10 | #include "cmd.h" 11 | #include "enum.h" 12 | #include "mem.h" 13 | #include "slist.h" 14 | #include "tag.h" 15 | 16 | #include "output.h" 17 | 18 | struct Output output = { 0 }; 19 | struct Tag *tag = NULL; 20 | const struct Cmd *cmd = NULL; 21 | 22 | struct SList *__wrap_tag_all(const struct SList *tags, const uint32_t mask) { 23 | check_expected(tags); 24 | check_expected(mask); 25 | 26 | struct SList *all = NULL; 27 | slist_append(&all, tag); 28 | return all; 29 | } 30 | 31 | int before_all(void **state) { 32 | return 0; 33 | } 34 | 35 | int after_all(void **state) { 36 | return 0; 37 | } 38 | 39 | int before_each(void **state) { 40 | assert_logs_empty(); 41 | 42 | tag = tag_init(0); 43 | 44 | tag->layout_cur = LEFT; 45 | tag->layout_prev = RIGHT; 46 | tag->stack = DIMINISH; 47 | tag->count_master = 1; 48 | tag->ratio_master = 0.5; 49 | tag->count_wide_left = 2; 50 | tag->ratio_wide = 0.2; 51 | 52 | return 0; 53 | } 54 | 55 | int after_each(void **state) { 56 | assert_logs_empty(); 57 | 58 | tag_destroy(tag); 59 | tag = NULL; 60 | 61 | cmd_destroy(cmd); 62 | cmd = NULL; 63 | 64 | return 0; 65 | } 66 | 67 | void output_apply_cmd__vals_top(void **state) { 68 | cmd = cmd_init("--layout TOP --layout-toggle --stack EVEN --count 9 --ratio 0.9"); 69 | assert_non_nul(cmd); 70 | 71 | expect_value(__wrap_tag_all, tags, output.tags); 72 | expect_value(__wrap_tag_all, mask, output.command_tags_mask); 73 | 74 | output_apply_cmd(&output, cmd); 75 | 76 | assert_int_equal(tag->layout_cur, TOP); 77 | assert_int_equal(tag->layout_prev, LEFT); 78 | assert_int_equal(tag->stack, EVEN); 79 | assert_int_equal(tag->count_master, 9); 80 | assert_float_equal(tag->ratio_master, 0.9, 0.001); 81 | assert_int_equal(tag->count_wide_left, 2); 82 | assert_float_equal(tag->ratio_wide, 0.2, 0.001); 83 | } 84 | 85 | void output_apply_cmd__vals_wide(void **state) { 86 | cmd = cmd_init("--layout WIDE --layout-toggle --stack EVEN --count 9 --ratio 0.9"); 87 | assert_non_nul(cmd); 88 | 89 | expect_value(__wrap_tag_all, tags, output.tags); 90 | expect_value(__wrap_tag_all, mask, output.command_tags_mask); 91 | 92 | output_apply_cmd(&output, cmd); 93 | 94 | assert_int_equal(tag->layout_cur, WIDE); 95 | assert_int_equal(tag->layout_prev, LEFT); 96 | assert_int_equal(tag->stack, EVEN); 97 | assert_int_equal(tag->count_master, 1); 98 | assert_float_equal(tag->ratio_master, 0.5, 0.001); 99 | assert_int_equal(tag->count_wide_left, 9); 100 | assert_float_equal(tag->ratio_wide, 0.9, 0.001); 101 | } 102 | 103 | void output_apply_cmd__vals_monocle(void **state) { 104 | cmd = cmd_init("--layout MONOCLE --layout-toggle --stack EVEN --count 9 --ratio 0.9"); 105 | assert_non_nul(cmd); 106 | 107 | expect_value(__wrap_tag_all, tags, output.tags); 108 | expect_value(__wrap_tag_all, mask, output.command_tags_mask); 109 | 110 | output_apply_cmd(&output, cmd); 111 | 112 | assert_int_equal(tag->layout_cur, MONOCLE); 113 | assert_int_equal(tag->layout_prev, LEFT); 114 | assert_int_equal(tag->stack, EVEN); 115 | assert_int_equal(tag->count_master, 1); 116 | assert_float_equal(tag->ratio_master, 0.5, 0.001); 117 | assert_int_equal(tag->count_wide_left, 2); 118 | assert_float_equal(tag->ratio_wide, 0.2, 0.001); 119 | } 120 | 121 | void output_apply_cmd__toggle_delta(void **state) { 122 | cmd = cmd_init("--layout-toggle --count +10 --ratio +0.1"); 123 | assert_non_nul(cmd); 124 | 125 | expect_value(__wrap_tag_all, tags, output.tags); 126 | expect_value(__wrap_tag_all, mask, output.command_tags_mask); 127 | 128 | output_apply_cmd(&output, cmd); 129 | 130 | assert_int_equal(tag->layout_cur, RIGHT); 131 | assert_int_equal(tag->layout_prev, LEFT); 132 | assert_int_equal(tag->stack, DIMINISH); 133 | assert_int_equal(tag->count_master, 11); 134 | assert_float_equal(tag->ratio_master, 0.6, 0.001); 135 | assert_int_equal(tag->count_wide_left, 2); 136 | assert_float_equal(tag->ratio_wide, 0.2, 0.001); 137 | } 138 | 139 | void output_apply_cmd__layout_nop(void **state) { 140 | cmd = cmd_init("--layout LEFT --layout-toggle"); 141 | assert_non_nul(cmd); 142 | 143 | expect_value(__wrap_tag_all, tags, output.tags); 144 | expect_value(__wrap_tag_all, mask, output.command_tags_mask); 145 | 146 | output_apply_cmd(&output, cmd); 147 | 148 | assert_int_equal(tag->layout_cur, LEFT); 149 | assert_int_equal(tag->layout_prev, RIGHT); 150 | assert_int_equal(tag->stack, DIMINISH); 151 | assert_int_equal(tag->count_master, 1); 152 | assert_float_equal(tag->ratio_master, 0.5, 0.001); 153 | assert_int_equal(tag->count_wide_left, 2); 154 | assert_float_equal(tag->ratio_wide, 0.2, 0.001); 155 | } 156 | 157 | void output_apply_cmd__count_master_delta_min(void **state) { 158 | cmd = cmd_init("--count -10"); 159 | assert_non_nul(cmd); 160 | 161 | expect_value(__wrap_tag_all, tags, output.tags); 162 | expect_value(__wrap_tag_all, mask, output.command_tags_mask); 163 | 164 | output_apply_cmd(&output, cmd); 165 | 166 | assert_int_equal(tag->layout_cur, LEFT); 167 | assert_int_equal(tag->layout_prev, RIGHT); 168 | assert_int_equal(tag->stack, DIMINISH); 169 | assert_int_equal(tag->count_master, COUNT_MIN); 170 | assert_float_equal(tag->ratio_master, 0.5, 0.001); 171 | assert_int_equal(tag->count_wide_left, 2); 172 | assert_float_equal(tag->ratio_wide, 0.2, 0.001); 173 | } 174 | 175 | void output_apply_cmd__ratio_master_min(void **state) { 176 | cmd = cmd_init("--ratio 0.5"); 177 | assert_non_nul(cmd); 178 | 179 | free(cmd->ratio); 180 | ((struct Cmd*)cmd)->ratio = doubledup(-5); 181 | 182 | expect_value(__wrap_tag_all, tags, output.tags); 183 | expect_value(__wrap_tag_all, mask, output.command_tags_mask); 184 | 185 | output_apply_cmd(&output, cmd); 186 | 187 | assert_int_equal(tag->layout_cur, LEFT); 188 | assert_int_equal(tag->layout_prev, RIGHT); 189 | assert_int_equal(tag->stack, DIMINISH); 190 | assert_int_equal(tag->count_master, 1); 191 | assert_float_equal(tag->ratio_master, RATIO_MIN, 0.001); 192 | assert_int_equal(tag->count_wide_left, 2); 193 | assert_float_equal(tag->ratio_wide, 0.2, 0.001); 194 | } 195 | 196 | void output_apply_cmd__ratio_master_max(void **state) { 197 | cmd = cmd_init("--ratio 0.5"); 198 | assert_non_nul(cmd); 199 | 200 | free(cmd->ratio); 201 | ((struct Cmd*)cmd)->ratio = doubledup(5); 202 | 203 | expect_value(__wrap_tag_all, tags, output.tags); 204 | expect_value(__wrap_tag_all, mask, output.command_tags_mask); 205 | 206 | output_apply_cmd(&output, cmd); 207 | 208 | assert_int_equal(tag->layout_cur, LEFT); 209 | assert_int_equal(tag->layout_prev, RIGHT); 210 | assert_int_equal(tag->stack, DIMINISH); 211 | assert_int_equal(tag->count_master, 1); 212 | assert_float_equal(tag->ratio_master, RATIO_MAX, 0.001); 213 | assert_int_equal(tag->count_wide_left, 2); 214 | assert_float_equal(tag->ratio_wide, 0.2, 0.001); 215 | } 216 | 217 | void output_apply_cmd__ratio_master_delta_min(void **state) { 218 | cmd = cmd_init("--ratio -10.0"); 219 | assert_non_nul(cmd); 220 | 221 | expect_value(__wrap_tag_all, tags, output.tags); 222 | expect_value(__wrap_tag_all, mask, output.command_tags_mask); 223 | 224 | output_apply_cmd(&output, cmd); 225 | 226 | assert_int_equal(tag->layout_cur, LEFT); 227 | assert_int_equal(tag->layout_prev, RIGHT); 228 | assert_int_equal(tag->stack, DIMINISH); 229 | assert_int_equal(tag->count_master, 1); 230 | assert_float_equal(tag->ratio_master, RATIO_MIN, 0.001); 231 | assert_int_equal(tag->count_wide_left, 2); 232 | assert_float_equal(tag->ratio_wide, 0.2, 0.001); 233 | } 234 | 235 | void output_apply_cmd__ratio_master_delta_max(void **state) { 236 | cmd = cmd_init("--ratio +10.0"); 237 | assert_non_nul(cmd); 238 | 239 | expect_value(__wrap_tag_all, tags, output.tags); 240 | expect_value(__wrap_tag_all, mask, output.command_tags_mask); 241 | 242 | output_apply_cmd(&output, cmd); 243 | 244 | assert_int_equal(tag->layout_cur, LEFT); 245 | assert_int_equal(tag->layout_prev, RIGHT); 246 | assert_int_equal(tag->stack, DIMINISH); 247 | assert_int_equal(tag->count_master, 1); 248 | assert_float_equal(tag->ratio_master, RATIO_MAX, 0.001); 249 | assert_int_equal(tag->count_wide_left, 2); 250 | assert_float_equal(tag->ratio_wide, 0.2, 0.001); 251 | } 252 | 253 | int main(void) { 254 | const struct CMUnitTest tests[] = { 255 | TEST(output_apply_cmd__vals_top), 256 | TEST(output_apply_cmd__vals_wide), 257 | TEST(output_apply_cmd__vals_monocle), 258 | 259 | TEST(output_apply_cmd__toggle_delta), 260 | 261 | TEST(output_apply_cmd__layout_nop), 262 | 263 | TEST(output_apply_cmd__count_master_delta_min), 264 | 265 | TEST(output_apply_cmd__ratio_master_min), 266 | TEST(output_apply_cmd__ratio_master_max), 267 | 268 | TEST(output_apply_cmd__ratio_master_delta_min), 269 | TEST(output_apply_cmd__ratio_master_delta_max), 270 | }; 271 | 272 | return RUN(tests); 273 | } 274 | 275 | -------------------------------------------------------------------------------- /tst/tst-tag.c: -------------------------------------------------------------------------------- 1 | #include "tst.h" 2 | #include "asserts.h" 3 | 4 | #include 5 | 6 | #include "slist.h" 7 | 8 | #include "tag.h" 9 | 10 | int before_all(void **state) { 11 | *state = tags_init(); 12 | assert_non_nul(*state); 13 | return 0; 14 | } 15 | 16 | int after_all(void **state) { 17 | tags_destroy(*state); 18 | return 0; 19 | } 20 | 21 | int before_each(void **state) { 22 | return 0; 23 | } 24 | 25 | int after_each(void **state) { 26 | return 0; 27 | } 28 | 29 | // no mask match 30 | void tag_first__none(void **state) { 31 | const struct Tag *tag = tag_first(*state, 0x0); 32 | assert_non_nul(tag); 33 | assert_int_equal(tag->mask, 0x1); 34 | } 35 | 36 | // exact mask match 37 | void tag_first__equal(void **state) { 38 | const struct Tag *tag = tag_first(*state, 0x2); 39 | assert_non_nul(tag); 40 | assert_int_equal(tag->mask, 0x2); 41 | } 42 | 43 | // first mask match 44 | void tag_first__first(void **state) { 45 | const struct Tag *tag = tag_first(*state, 0x4 | 0x8); 46 | assert_non_nul(tag); 47 | assert_int_equal(tag->mask, 0x4); 48 | } 49 | 50 | // no mask match 51 | void tag_all__none(void **state) { 52 | struct SList *all = tag_all(*state, 0x0); 53 | 54 | assert_int_equal(slist_length(all), 0); 55 | } 56 | 57 | // one mask match 58 | void tag_all__one(void **state) { 59 | struct SList *one = tag_all(*state, 0x2); 60 | 61 | assert_int_equal(slist_length(one), 1); 62 | 63 | assert_ptr_equal(slist_at(one, 0), slist_at(*state, 1)); 64 | 65 | slist_free(&one); 66 | } 67 | 68 | // many mask match 69 | void tag_all__many(void **state) { 70 | struct SList *all = tag_all(*state, 0x4 | 0x8); 71 | 72 | assert_int_equal(slist_length(all), 2); 73 | 74 | assert_ptr_equal(slist_at(all, 0), slist_at(*state, 2)); 75 | assert_ptr_equal(slist_at(all, 1), slist_at(*state, 3)); 76 | 77 | slist_free(&all); 78 | } 79 | 80 | int main(void) { 81 | const struct CMUnitTest tests[] = { 82 | TEST(tag_first__none), 83 | TEST(tag_first__equal), 84 | TEST(tag_first__first), 85 | 86 | TEST(tag_all__none), 87 | TEST(tag_all__one), 88 | TEST(tag_all__many), 89 | }; 90 | 91 | return RUN(tests); 92 | } 93 | 94 | -------------------------------------------------------------------------------- /tst/tst.h: -------------------------------------------------------------------------------- 1 | #ifndef TST_H 2 | #define TST_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | // use cmocka_print_error and remove this proto once cmocka 1.1.7+ is widely available 11 | void cm_print_error(const char* const format, ...) CMOCKA_PRINTF_ATTRIBUTE(1, 2); 12 | 13 | // print log messages, useful when debugging tests 14 | #define LOG_PRINT false 15 | 16 | // 17 | // test definition 18 | // 19 | #define TEST(t) cmocka_unit_test_setup_teardown(t, before_each, after_each) 20 | #define RUN(t) cmocka_run_group_tests(t, before_all, after_all) 21 | 22 | #endif // TST_H 23 | -------------------------------------------------------------------------------- /tst/util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | char *read_file(const char *path) { 11 | int fd = open(path, O_RDONLY); 12 | if (fd == -1) { 13 | char *out = calloc(PATH_MAX + 64, sizeof(char)); 14 | snprintf(out, PATH_MAX + 64, "file not found: %s\n", path); 15 | return out; 16 | } 17 | 18 | int len = lseek(fd, 0, SEEK_END); 19 | 20 | if (len == 0) { 21 | return NULL; 22 | } 23 | 24 | char *out = calloc(len, sizeof(char)); 25 | 26 | if (len > 0) { 27 | memcpy(out, mmap(0, len, PROT_READ, MAP_PRIVATE, fd, 0), sizeof(char) * len - 1); 28 | } 29 | 30 | close(fd); 31 | 32 | return out; 33 | } 34 | 35 | void write_file(const char *path, const char *content) { 36 | FILE *f = fopen(path, "w"); 37 | if (!f) { 38 | fprintf(stderr, "could not write to %s\n", path); 39 | exit(1); 40 | } 41 | 42 | fprintf(f, "%s", content); 43 | 44 | fclose(f); 45 | } 46 | 47 | -------------------------------------------------------------------------------- /tst/util.h: -------------------------------------------------------------------------------- 1 | #ifndef UTIL_H 2 | #define UTIL_H 3 | 4 | char *read_file(const char *path); 5 | 6 | void write_file(const char *path, const char *content); 7 | 8 | #endif // UTIL_H 9 | -------------------------------------------------------------------------------- /tst/wrap-log.c: -------------------------------------------------------------------------------- 1 | #include "tst.h" // IWYU pragma: keep 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "enum.h" 10 | #include "util.h" 11 | 12 | // 0 unused, 1 DEBUG, 5 FATAL 13 | static char b[6][262144] = { 0 }; 14 | static char *bp[6] = { 0 }; 15 | 16 | void _assert_log(enum LogThreshold t, const char * s, const char * const file, const int line) { 17 | if (bp[t]) { 18 | bp[t] = NULL; 19 | if (strcmp(b[t], s) != 0) { 20 | cm_print_error("assert_log\nlog.actual:\n\"%s\"\nlog.expected:\n\"%s\"\n", b[t], s); 21 | write_file("log.actual", b[t]); 22 | write_file("log.expected", s); 23 | _fail(file, line); 24 | } 25 | } else { 26 | _assert_string_equal("", s, file, line); 27 | } 28 | } 29 | 30 | void _assert_logs_empty(const char * const file, const int line) { 31 | bool empty = true; 32 | for (enum LogThreshold t = DEBUG; t <= ERROR; t++) { 33 | if (bp[t]) { 34 | bp[t] = NULL; 35 | cm_print_error("\nunexpected log %s:\n\"%s\"\n", log_threshold_name(t), b[t]); 36 | empty = false; 37 | } 38 | } 39 | if (!empty) { 40 | _fail(file, line); 41 | } 42 | } 43 | 44 | void _log(enum LogThreshold t, const char *__restrict __format, va_list __args) { 45 | static char *printed; 46 | 47 | if (!bp[t]) { 48 | bp[t] = b[t]; 49 | } 50 | 51 | printed = bp[t]; 52 | 53 | bp[t] += vsnprintf(bp[t], sizeof(b[t]) - (bp[t] - b[t]), __format, __args); 54 | 55 | if (LOG_PRINT) { 56 | fprintf(stderr, "%s\n", printed); 57 | } 58 | 59 | bp[t] += snprintf(bp[t], sizeof(b[t]) - (bp[t] - b[t]), "\n"); 60 | } 61 | 62 | 63 | void __wrap_log_set_threshold(enum LogThreshold threshold, bool cli) { 64 | check_expected(threshold); 65 | check_expected(cli); 66 | } 67 | 68 | void __wrap_log_(enum LogThreshold t, const char *__restrict __format, ...) { 69 | va_list args; 70 | va_start(args, __format); 71 | _log(t, __format, args); 72 | va_end(args); 73 | } 74 | 75 | void __wrap_log_d(const char *__restrict __format, ...) { 76 | va_list args; 77 | va_start(args, __format); 78 | _log(DEBUG, __format, args); 79 | va_end(args); 80 | } 81 | 82 | void __wrap_log_i(const char *__restrict __format, ...) { 83 | va_list args; 84 | va_start(args, __format); 85 | _log(INFO, __format, args); 86 | va_end(args); 87 | } 88 | 89 | void __wrap_log_w(const char *__restrict __format, ...) { 90 | va_list args; 91 | va_start(args, __format); 92 | _log(WARNING, __format, args); 93 | va_end(args); 94 | } 95 | 96 | void __wrap_log_e(const char *__restrict __format, ...) { 97 | va_list args; 98 | va_start(args, __format); 99 | _log(ERROR, __format, args); 100 | va_end(args); 101 | } 102 | 103 | void __wrap_log_e_errno(const char *__restrict __format, ...) { 104 | va_list args; 105 | va_start(args, __format); 106 | _log(ERROR, __format, args); 107 | va_end(args); 108 | } 109 | 110 | void __wrap_log_f(const char *__restrict __format, ...) { 111 | va_list args; 112 | va_start(args, __format); 113 | _log(FATAL, __format, args); 114 | va_end(args); 115 | } 116 | 117 | void __wrap_log_f_errno(const char *__restrict __format, ...) { 118 | va_list args; 119 | va_start(args, __format); 120 | _log(FATAL, __format, args); 121 | va_end(args); 122 | } 123 | 124 | --------------------------------------------------------------------------------