├── .gitattributes ├── .github └── workflows │ └── main.yml ├── .gitignore ├── LICENSE ├── README.md ├── config ├── config.toml └── viv_config.h ├── include ├── meson.build ├── viv_background.h ├── viv_bar.h ├── viv_cli.h ├── viv_config_support.h ├── viv_config_types.h ├── viv_cursor.h ├── viv_damage.h ├── viv_debug_support.h ├── viv_input.h ├── viv_ipc.h ├── viv_layer_view.h ├── viv_layout.h ├── viv_mappable_functions.h ├── viv_output.h ├── viv_render.h ├── viv_seat.h ├── viv_server.h ├── viv_toml_config.h ├── viv_types.h ├── viv_view.h ├── viv_wl_list_utils.h ├── viv_wlr_surface_tree.h ├── viv_workspace.h ├── viv_xdg_popup.h ├── viv_xdg_shell.h ├── viv_xwayland_shell.h └── viv_xwayland_types.h ├── media ├── layout_counter_illustrations.png ├── layout_split_dist_illustrations.png ├── layout_type_illustrations.png └── readme_screenshot.png ├── meson.build ├── meson_options.txt ├── protocols ├── meson.build └── xml │ ├── idle.xml │ ├── wlr-layer-shell-unstable-v1.xml │ ├── wlr-output-power-management-unstable-v1.xml │ └── wlr-screencopy-unstable-v1.xml ├── src ├── meson.build ├── viv_background.c ├── viv_bar.c ├── viv_cli.c ├── viv_cursor.c ├── viv_damage.c ├── viv_input.c ├── viv_ipc.c ├── viv_layer_view.c ├── viv_layout.c ├── viv_mappable_functions.c ├── viv_output.c ├── viv_render.c ├── viv_seat.c ├── viv_server.c ├── viv_toml_config.c ├── viv_view.c ├── viv_wl_list_utils.c ├── viv_wlr_surface_tree.c ├── viv_workspace.c ├── viv_xdg_popup.c ├── viv_xdg_shell.c ├── viv_xwayland_shell.c └── vivarium.c ├── subprojects ├── fff.wrap ├── packagefiles │ ├── fff │ │ └── meson.build │ └── tomlc99 │ │ └── meson.build ├── tomlc99.wrap ├── unity.wrap └── wlroots.wrap └── tests ├── meson.build ├── mock_layouts.h ├── mock_mappable_functions.h ├── test_config.c └── test_layouts.c /.gitattributes: -------------------------------------------------------------------------------- 1 | *.h linguist-language=C -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build and run Vivarium 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | schedule: 9 | - cron: '30 3 * * *' 10 | 11 | workflow_dispatch: 12 | 13 | jobs: 14 | build: 15 | name: Build and run Vivarium 16 | runs-on: ubuntu-22.04 17 | 18 | steps: 19 | 20 | - name: Set up Python 3.9 21 | uses: actions/setup-python@v2.2.1 22 | with: 23 | python-version: 3.9 24 | 25 | - name: Install dependencies 26 | run: | 27 | sudo apt-get update 28 | sudo apt-get install libwayland-dev libwayland-server0 wayland-protocols libxkbcommon-dev libwayland-egl1 libegl-dev libdrm-dev libgles-dev libgbm-dev libinput-dev libudev-dev libpixman-1-dev libpixman-1-0 libxcb-composite0-dev xcb libxcb-render0-dev libxcb-xfixes0-dev xwayland waybar swaybg graphviz libxcb-icccm4 libxcb-ewmh-dev libxcb-ewmh2 libxcb-icccm4-dev libxcb-res0 libxcb-res0-dev libpciaccess0 libpciaccess-dev hwdata 29 | python -m pip install --upgrade pip 30 | python -m pip install ninja 31 | python -m pip install meson 32 | sudo python -m pip install ninja 33 | 34 | - name: Install wayland-server 35 | run: | 36 | # The ubuntu-22.04 Wayland Server version is too low so we must install wayland ourselves 37 | git clone --depth 1 --branch 1.21.0 https://gitlab.freedesktop.org/wayland/wayland.git 38 | cd wayland 39 | meson setup build -Ddocumentation=false 40 | sudo ninja -C build install 41 | cd .. 42 | 43 | - name: Install libdrm 44 | run: | 45 | # The ubuntu-22.04 libdrm version is too low so we must install it ourselves 46 | git clone --depth 1 --branch libdrm-2.4.115 https://gitlab.freedesktop.org/mesa/drm.git 47 | cd drm 48 | meson setup build 49 | sudo ninja -C build install 50 | cd .. 51 | 52 | - name: Install wayland-protocols 53 | run: | 54 | # The ubuntu-22.04 wayland-protocols version is too low so we must install it ourselves 55 | git clone --depth 1 --branch 1.27 https://gitlab.freedesktop.org/wayland/wayland-protocols.git 56 | cd wayland-protocols 57 | meson setup build 58 | sudo ninja -C build install 59 | cd .. 60 | 61 | - name: Checkout vivarium 62 | uses: actions/checkout@v2 63 | 64 | - name: Prepare subprojects that wlroots depends on 65 | run: | 66 | git clone https://git.sr.ht/~kennylevinsen/seatd subprojects/seatd 67 | 68 | - name: Prepare build dir 69 | run: | 70 | meson setup build 71 | 72 | - name: Build vivarium, debug=false, xwayland=disabled 73 | run: | 74 | meson setup --reconfigure build -Dxwayland=disabled -Ddebug=false 75 | ninja -C build 76 | 77 | - name: Build vivarium, debug=false, xwayland=enabled 78 | run: | 79 | meson setup --reconfigure build -Dxwayland=enabled -Ddebug=false 80 | ninja -C build 81 | 82 | - name: Build vivarium, debug=true xwayland=enabled 83 | run: | 84 | meson setup --reconfigure build -Dxwayland=enabled -Ddebug=true 85 | ninja -C build 86 | 87 | - name: Build vivarium, debug=true xwayland=enabled headless-test=true 88 | run: | 89 | meson setup --reconfigure build -Dxwayland=enabled -Ddebug=true -Dheadless-test=true 90 | ninja -C build 91 | 92 | - name: Run vivarium (headless) and check for clean exit 93 | run: | 94 | mkdir -p $XDG_RUNTIME_DIR 95 | ./build/src/vivarium 96 | env: 97 | XDG_RUNTIME_DIR: /tmp/xdg_runtime_dir 98 | 99 | - name: Run vivarium (headless) with config.toml and check for clean exit 100 | run: | 101 | mkdir -p $XDG_RUNTIME_DIR 102 | mkdir -p ~/.config/vivarium 103 | cp config/config.toml ~/.config/vivarium/ 104 | ./build/src/vivarium 105 | env: 106 | XDG_RUNTIME_DIR: /tmp/xdg_runtime_dir 107 | 108 | - name: Run tests 109 | run: | 110 | meson test -C build --print-errorlogs 111 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vivarium 2 | .clangd 3 | .cache 4 | obj 5 | build 6 | build_* 7 | compile_commands.json 8 | subprojects/* 9 | !subprojects/packagefiles 10 | !subprojects/*.wrap 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vivarium 2 | 3 | ![Build Status](https://github.com/inclement/vivarium/workflows/Build%20and%20run%20Vivarium/badge.svg) 4 | 5 | IRC chat: [#vivarium](https://web.libera.chat/?channels=#vivarium) on [irc.libera.chat:6697](ircs://irc.libera.chat:6697) 6 | 7 | A dynamic tiling [Wayland](https://wayland.freedesktop.org/) compositor using [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots), with desktop semantics inspired by [xmonad](https://xmonad.org/). 8 | 9 |

10 | Vivarium screenshot showing several tiled windows 11 |

12 | 13 | Core features include: 14 | 15 | * Automatic/dynamic tiling with your choice of layouts. 16 | * Per-output workspaces: display the workspace you want, where you want. 17 | * Floating windows on demand. 18 | * (optional) XWayland support. 19 | * Layer shell support, compatible with tools like [Waybar](https://github.com/Alexays/Waybar), [bemenu](https://github.com/Cloudef/bemenu) and [swaybg](https://github.com/swaywm/swaybg). 20 | * Damage tracking. 21 | 22 | Vivarium is unstable and unfinished...but usable! 23 | 24 | ## Tiling model 25 | 26 | Vivarium lets you define any number of workspaces, each with some number of tiling layouts that you can switch between at runtime. New windows are automatically tiled according to the layout, or can be made floating to be placed anywhere with any size you like. The order of windows within the layout is adjustable at runtime. 27 | 28 | You will probably want to set up a small number of layouts, updating their parameters according to your needs. For instance, if you find you need too many terminals to fit in a single stack next to a browser window then you might switch the layout to one that places windows in multiple columns. Or if you want to focus on the browser, you might switch to a fullscreen layout that displays only the active window. 29 | 30 | Example layouts include (left to right): split, fullscreen, central column, and recursive split: 31 | 32 |

33 | Illustrated Vivarium layouts 34 |

35 | 36 | Most layouts have a main panel displaying the largest window, and a secondary space for the other windows. 37 | 38 | Layouts have a "fill fraction" parameter, adjustable at runtime via hotkeys, which controls the size of the main panel: 39 | 40 |

41 | Illustrated Vivarium layouts with different fill fraction 42 |

43 | 44 | Layouts also have an integer main panel "count", adjustable at runtime via hotkeys, which controls how many windows are stacked in the main panel. It can be zero so that all windows occupy the secondary space: 45 | 46 |

47 | Illustrated Vivarium layouts with different main panel counts 48 |

49 | 50 | 51 | Other per-layout options include whether window borders are displayed, and whether the layout leaves space for programs like the desktop bar or draws windows over their normally-excluded region. 52 | 53 | 54 | ## Build instructions 55 | 56 | Get install dependencies. You need: 57 | 58 | * meson 59 | * wlroots 60 | * wayland 61 | * wayland-protocols 62 | * xcb 63 | 64 | Specific package dependencies for Ubuntu 20.04 can be found in [the Github CI file](.github/workflows/main.yml). 65 | 66 | Get Vivarium: 67 | 68 | git clone git@github.com:inclement/vivarium.git 69 | cd vivarium 70 | 71 | Build Vivarium: 72 | 73 | meson build 74 | ninja -C build 75 | 76 | Run Vivarium: 77 | 78 | ./build/src/vivarium 79 | 80 | Install Vivarium: 81 | 82 | sudo ninja -C build install 83 | 84 | Vivarium expects to be run from a TTY, but also supports embedding in an X session or existing Wayland session out of the box. Running the binary will Do The Right Thing. 85 | 86 | ## Configuration 87 | 88 | Vivarium comes with a default config that you can override by creating your own `config.toml`. You can also adjust the default config at compile time using `viv_config.h`, which is necessary if you want to inject your own code for e.g. custom layouts or adding keypress actions that aren't already provided by Vivarium. 89 | 90 | ### config.toml 91 | 92 | **Use this if:** you've installed Vivarium and want to override its defaults with your own config. 93 | 94 | Copy the default config so that Vivarium will find it. 95 | 96 | mkdir -p $HOME/.config/vivarium 97 | cp config/config.toml $HOME/.config/vivarium 98 | 99 | The default config is extensively documented and includes all the Vivarium default bindings. See the documentation inside the file to see what other options you can set. 100 | 101 | If you installed Vivarium globally, the default config should instead be found in `/etc/vivarium/config.toml` or `/usr/local/etc/vivarium/config.toml`. 102 | 103 | ### viv_config.h 104 | 105 | **Use this if:** you want to adjust Vivarium's compiled-in default config or write your own C code for layouts or keybinds. 106 | 107 | Vivarium automatically uses the configuration struct defined in `viv_config.h`. Edit that file before compiling to update the configuration. 108 | 109 | If you'd like to maintain multiple different configs, copy the config directory to somewhere else and tell Vivarium to use the appropriate version at compile time: 110 | 111 | cp -r config myconfig 112 | meson build_myconfig -Dconfig-dir=myconfig 113 | ninja -C build_myconfig 114 | 115 | ### Bar support 116 | 117 | Vivarium can automatically start a bar program such as [Waybar](https://github.com/Alexays/Waybar). Only Waybar is currently tested, and only very basic IPC is currently possible, but this is enough to display the current workspace status. 118 | 119 | See `viv_config.h` or the `config.toml` for instructions on starting the bar program. 120 | 121 | ## FAQ (or not-so-FAQ) 122 | 123 | > What does "desktop semantics inspired by xmonad" mean? 124 | 125 | The core tiling experience provides something similar to xmonad's defaults: new windows are added to the current workspace and tiled automatically within a current layout. Each workspace may independently switch between different layouts. Each output (usually equivalent to each monitor) displays a single workspace, and each may be switched independently. Windows may be made floating and moved/resized smoothly, but this is generally the exception rather than the rule. 126 | 127 | Vivarium makes no attempt to rigorously mimic xmonad or to replicate its internal design philosophy. Not least, Vivarium is written in C and is not (for now) so directly and transparently extensible. 128 | 129 | > Why do some windows display title bars with maximize/minimize/close buttons that don't do anything? 130 | > Can I turn that off? 131 | 132 | Vivarium attempts to tell windows not to draw their own decorations and this works for most applications, but the protocols for doing so are not yet standard or universally supported so some windows still draw their own. For now there's probably nothing you can do about it, but this is likely to improve in the future. 133 | 134 | It's also possible that there are bugs in Vivarium's window decoration configuration, bug reports welcome if so. 135 | 136 | > What's the status of Vivarium's damage tracking support? 137 | 138 | Vivarium supports damage tracking, i.e. drawing only regions of the screen that have actually changed, but this is semi-experimental. 139 | 140 | Damage tracking currently defaults to `"frame"`, which draws every frame in which something has changed anywhere on the screen. This is a dramatic improvement on rerendering all your unchanging windows every frame, but even better would be to have full damage tracking that redraws only the small parts of the frame that have actually changed. 141 | 142 | To test out full damage tracking, change the config value to `"full"`. This is tested and is thought to work if every monitor has the same scale, but there may be bugs in more complex output configurations. Full damage tracking will become the default as soon as these issues are resolved. 143 | 144 | If at any point you find Vivarium fails to render something (e.g. jerky frames, missing menu popups), try setting the config to `"none"`: if this makes it work then you've found a damage tracking issue. Either way, issue reports are [gratefully received](https://github.com/inclement/vivarium/issues). 145 | 146 | > Why TOML for configuration? How can I configure dynamic behaviour like my own layouts? 147 | 148 | TOML is especially simple and easy to read, and also easy to write and parse. It's intended to support the most common use case where the configuration is something you set up once, then leave for a long time with only occasional tweaks like changing your layouts or adjusting keybinds. The simple TOML syntax makes it easy to tweak minor options even a long time after first writing it, without remembering (for instance) the more complicated syntax of a programming language you don't otherwise use much. (Yes, this is something I found inconvenient about xmonad - I haven't written haskell for years!). 149 | 150 | This has the disadvantage that dynamic configuration is not possible using the config.toml: for instance, you can't bind arbitrary functions to keypresses, only the predefined actions hardcoded in Vivarium. In these cases you can instead configure Vivarium via C code using the `viv_config.h` header described in the Configuration section, but you will need to recompile Vivarium each time you update the config. 151 | 152 | In the longer term I would like to explore providing Vivarium as a library so that you can run Vivarium, and inject arbitrary event handlers, from any language with a FFI. However, this is not an immediate goal. 153 | 154 | > Does Vivarium support $PROTOCOL? Will it in the future? 155 | 156 | I'm aiming to support all the core wayland protocols plus all the extra ones being developed under wlroots. However, there is no ETA for specific protocols right now. 157 | 158 | Currently supported protocols (though all may be incomplete or buggy in places, none are battle tested): 159 | 160 | * XDG shell 161 | * XDG output 162 | * XDG decoration 163 | * XWayland 164 | * Layer shell 165 | 166 | > Can you add $FEATURE? 167 | 168 | I'm not sure! At the time of writing Vivarium is a personal project whose design philosophy isn't fully determined. Suggestions and requests are welcome. 169 | 170 | > Something didn't work! What should I do? 171 | 172 | Please check if the issue has [already been reported](https://github.com/inclement/vivarium/issues). 173 | 174 | If not, [report a new issue](https://github.com/inclement/vivarium/issues/new). 175 | -------------------------------------------------------------------------------- /config/config.toml: -------------------------------------------------------------------------------- 1 | 2 | [global-config] 3 | # Options: alt, ctrl, logo, mod2, mod3 4 | # This key can be referenced in custom keybindings as `meta` 5 | meta-key = "alt" 6 | 7 | focus-follows-mouse = true 8 | 9 | # One workspace with each name is generated automatically. Each workspace receives layouts 10 | # as defined in the [[layout]] list below. You can configure keybindings to switch to 11 | # workspaces by name, or to move windows between them. 12 | workspace-names = ["main", "2", "3", "4", "5", "6", "7", "8", "9"] 13 | 14 | # Autogenerate keybinds to switch to workspaces, specifically: 15 | # - Bind meta-$N to switch to workspace number N, for each number key $N, 16 | # - Bind meta-shift-$N to move the active window to workspace number N, for each number key $N 17 | # You can also/alternatively create these bindings via custom [[keybind]]s below. 18 | # Set this to false to disable automatic keybind generation and leave the keybinds all to you. 19 | bind-workspaces-to-numbers = true 20 | 21 | # Cursor button used to drag windows around (also making them floating) when meta is held 22 | win-move-cursor-button = "left" 23 | 24 | # Cursor button used to resize windows (also making them floating) when meta is held 25 | win-resize-cursor-button = "right" 26 | 27 | # Window border configuration 28 | border-width = 2 # pixels 29 | active-border-colour = [1.0, 0.0, 0.7, 1.0] # rgba 30 | inactive-border-colour = [0.6, 0.6, 0.9, 1.0] # rgba 31 | 32 | # Gap distance between windows 33 | gap-width = 0 # pixels 34 | 35 | # Allow views to enter fullscreen state. If allowed this bypasses the current layout. 36 | allow-fullscreen = true 37 | 38 | # Desktop background colour. Note this is overridden by the [background] if set. 39 | clear-colour = [0.73, 0.73, 0.73, 1.0] 40 | 41 | ### BACKGROUND ### 42 | # The background options are displayed using `swaybg`. Make sure you have this installed 43 | # if you want to use them. 44 | [background] 45 | colour = "#bbbbbb" 46 | image = "/path/to/your/background.png" 47 | mode = "fill" 48 | 49 | ### XKB-CONFIG ### 50 | # These all accept standard xkb syntax and options, e.g. as you would pass to `setxkbmap` 51 | # run from a shell. 52 | [xkb-config] 53 | # model = "pc104" 54 | # layout = "us" 55 | # variant = "basic" 56 | # options = "ctrl:nocaps" 57 | 58 | ### BAR ### 59 | # Options for configuring a bar program. The desktop bar (if any) is often used to display 60 | # information such as current workspaces, system information, and open windows. 61 | [bar] 62 | command = "waybar" # note this is a command to execute: this program must be installed to work 63 | update-signal-number = 1 64 | 65 | ### IPC ### 66 | # Inter-process communication settings. 67 | [ipc] 68 | # The filename to which Vivarium will write workspace state information whenever something changes. 69 | # Programs can watch this file to display the workspace status. 70 | # This is a hacky solution that will be deprecated at some point. 71 | # workspaces-filename = "/path/to/some/file.txt" 72 | 73 | ### LAYOUTS ### 74 | # Configure any number of layouts. Each workspace gets an independent copy of each layout. 75 | # The following options are available for each layout: 76 | # - name : The layout name to display in bar programs and logs, can be anything 77 | # Defaults to matching the `layout` option. 78 | # - layout : The name of the Vivarium layout function to use. 79 | # Run `vivarium -h` to get a list of available layout names. 80 | # - show-borders : If true, draw borders around windows. Defaults to true. 81 | # - ignore-excluded-regions : If true, tile windows across the full workspace regardless of 82 | # space reserved for bars, system tray etc. 83 | # 84 | # Run `vivarium --list-config-options` to get a list of available layouts. 85 | 86 | [[layout]] 87 | name = "Tall" 88 | layout = "split" 89 | 90 | [[layout]] 91 | name = "Fullscreen" 92 | layout = "fullscreen" 93 | show-borders = false 94 | 95 | [[layout]] 96 | name = "Fullscreen No Borders" 97 | layout = "fullscreen" 98 | ignore-excluded-regions = true 99 | show-borders = false 100 | 101 | ### KEYBINDS ### 102 | # Configure any number of keybinds. The following options are available for each one: 103 | # - modifiers : List of strings, not compulsory, defaults to ["meta"] 104 | # - keysym : Keysym to bind to. The keysym is the value of the key as interpreted by your keyboard layout. 105 | # You may not bind to both keysym and keycode in the same binding. 106 | # - keycode : Keycode to bind to. The keycode is the numerical identifier of the hardware key. 107 | # It always has the same value for a given physical key regardless of your keyboard layout. 108 | # - action : Any Vivarium action. 109 | # Run `vivarium -h` to show documentation of all available actions, or see examples below. 110 | # - : Arguments for the `action`, if applicable. 111 | # See action documentation for available arguments. 112 | # 113 | # To find the keysym or keycode of a given key, use tools like `wev` or `xev`. 114 | # 115 | # Run `vivarium --list-config-options` to get a list of available keybind actions 116 | 117 | [[keybind]] 118 | keysym = "Q" 119 | action = "terminate" 120 | 121 | [[keybind]] 122 | keysym = "T" 123 | action = "do_exec" 124 | executable = "alacritty" 125 | 126 | [[keybind]] 127 | modifiers = ["meta", "shift"] 128 | keysym = "Return" 129 | action = "do_exec" 130 | executable = "alacritty" 131 | 132 | [[keybind]] 133 | keysym = "l" 134 | action = "increment_divide" 135 | increment = 0.05 136 | 137 | [[keybind]] 138 | keysym = "h" 139 | action = "increment_divide" 140 | increment = -0.05 141 | 142 | [[keybind]] 143 | keysym = "comma" 144 | action = "increment_counter" 145 | increment = 1 146 | 147 | [[keybind]] 148 | keysym = "period" 149 | action = "increment_counter" 150 | increment = -1 151 | 152 | [[keybind]] 153 | keysym = "j" 154 | action = "next_window" 155 | 156 | [[keybind]] 157 | keysym = "k" 158 | action = "prev_window" 159 | 160 | [[keybind]] 161 | keysym = "J" 162 | action = "shift_active_window_down" 163 | 164 | [[keybind]] 165 | keysym = "K" 166 | action = "shift_active_window_up" 167 | 168 | [[keybind]] 169 | keysym = "t" 170 | action = "tile_window" 171 | 172 | [[keybind]] 173 | keysym = "e" 174 | action = "right_output" 175 | 176 | [[keybind]] 177 | keysym = "w" 178 | action = "left_output" 179 | 180 | [[keybind]] 181 | keysym = "space" 182 | action = "next_layout" 183 | 184 | [[keybind]] 185 | keysym = "E" 186 | action = "shift_active_window_to_right_output" 187 | 188 | [[keybind]] 189 | keysym = "W" 190 | action = "shift_active_window_to_left_output" 191 | 192 | [[keybind]] 193 | keysym = "C" 194 | action = "close_window" 195 | 196 | [[keybind]] 197 | keysym = "Return" 198 | action = "make_window_main" 199 | 200 | [[keybind]] 201 | keysym = "R" 202 | action = "reload_config" 203 | 204 | [[keybind]] 205 | keysym = "o" 206 | action = "do_exec" 207 | executable = "bemenu-run" 208 | 209 | [[keybind]] 210 | modifiers = [] 211 | keysym = "XF86AudioMute" 212 | action = "do_shell" 213 | command = "pactl set-sink-mute 0 toggle" 214 | 215 | [[keybind]] 216 | modifiers = [] 217 | keysym = "XF86AudioLowerVolume" 218 | action = "do_shell" 219 | command = "amixer -q sset Master 3%-" 220 | 221 | [[keybind]] 222 | modifiers = [] 223 | keysym = "XF86AudioRaiseVolume" 224 | action = "do_shell" 225 | command = "amixer -q sset Master 3%+" 226 | 227 | ### LIBINPUT CONFIG ### 228 | # Create any number of libinput configurations. The following options are available for each libinput config: 229 | # - device-name : A string to match libinput devices that should have your rules applied. 230 | # - scroll-method : One of "no-scroll", "2-finger", "edge" or "scroll-button". 231 | # - scroll-button : An integer representing the libinput button index to use for scrolling. 232 | # Has no effect unless scroll-method="scroll-button". 233 | # - middle-emulation : If true, emulate middle click events when clicking left and right simultaneously. 234 | # Defaults to false. 235 | # - left-handed : If true, switch left and right buttons. Defaults to false. 236 | # - natural-scroll : If true, switch the scrolling direction from default style (scroll down => go down) 237 | # to so-called "natural" style (scroll down => go up). Defaults to false. 238 | # - disable-while-typing : If true, disables the device while typing. Defaults to false. 239 | # - click-method : Method for determining which button is being clicked. 240 | # One of "none", "areas" or "fingers". Defaults to "none" 241 | # - tap-to-click : If true, enable tap to click on touchpads and similar devices. Defaults to true. 242 | # - tap-button-map : Map from number of fingers (1,2,3) to clicked button when using tap-to-click. 243 | # One of "left-right-middle" or "left-middle-right", other options not supported. 244 | 245 | [[libinput-config]] 246 | # Example libinput-config for a popular trackball: 247 | device-name = "Logitech USB Trackball" 248 | scroll-method = "scroll-button" 249 | scroll-button = 8 250 | middle-emulation = true 251 | left-handed = false 252 | natural-scroll = false 253 | disable-while-typing = false 254 | click-method = "none" 255 | tap-to-click = true 256 | tap-button-map = "left-right-middle" 257 | 258 | 259 | ### DEBUG ### 260 | # Vivarium debug options included for completion. You will want to leave these with their 261 | # default values under normal operation. 262 | [debug] 263 | # Draw a graphical indicator of what shell (XDG, XWayland, Layer) is providing each window surface. 264 | mark-views-by-shell = false 265 | 266 | # Draw a graphical indicator of what shell (i.e. normally what monitor) is currently active. 267 | mark-active-output = false 268 | 269 | # Damage tracking: "none" to draw every frame, "frame" to draw only damaged frames, "full" 270 | # to draw only damaged regions of damaged frames 271 | damage-tracking-mode = "frame" 272 | 273 | # Draw the background red before drawing damaged regions, so only damaged regions are rendered 274 | mark-undamaged-regions = false 275 | 276 | # Draw a small square that cycles through red/green/blue each time a frame is drawn 277 | mark-frame-draws = false 278 | -------------------------------------------------------------------------------- /include/meson.build: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inclement/vivarium/1ffa4368cc87f96955f5358b5896156753b59a4f/include/meson.build -------------------------------------------------------------------------------- /include/viv_background.h: -------------------------------------------------------------------------------- 1 | #ifndef VIV_BACKGROUND_H 2 | #define VIV_BACKGROUND_H 3 | 4 | /// Fork and run swaybg with the given options 5 | void viv_parse_and_run_background_config(char *colour, char *image, char *mode); 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /include/viv_bar.h: -------------------------------------------------------------------------------- 1 | #ifndef VIV_BAR_H 2 | #define VIV_BAR_H 3 | 4 | #include 5 | #include 6 | 7 | /// Fork and run swaybg with the given options 8 | pid_t viv_parse_and_run_bar_config(char *bar_command, uint32_t update_signal_number); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /include/viv_cli.h: -------------------------------------------------------------------------------- 1 | #ifndef VIV_CLI_H 2 | #define VIV_CLI_H 3 | 4 | #include "viv_types.h" 5 | 6 | struct viv_args { 7 | char *config_filen; 8 | }; 9 | 10 | struct viv_args viv_cli_parse_args(int argc, char *argv[]); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /include/viv_config_support.h: -------------------------------------------------------------------------------- 1 | #ifndef VIV_CONFIG_SUPPORT_H 2 | #define VIV_CONFIG_SUPPORT_H 3 | 4 | #define NULL_KEY 0 5 | #define TERMINATE_KEYBINDS_LIST() { .key = NULL_KEY } 6 | #define TERMINATE_LAYOUTS_LIST() { .name = "" } 7 | #define TERMINATE_LIBINPUT_CONFIG_LIST() { .device_name = "" } 8 | 9 | #define MAX_NUM_KEYBINDS 10000 10 | #define MAX_NUM_LIBINPUT_CONFIGS 10000 11 | #define MAX_WORKSPACE_NAME_LENGTH 80 12 | #define MAX_NUM_WORKSPACES 50 13 | #define MAX_NUM_LAYOUTS 50 14 | 15 | #include 16 | 17 | #include 18 | 19 | #define NO_MODIFIERS (0u) 20 | 21 | #define KEYBIND_MAPPABLE(MODIFIERS, KEY, BINDING, ...) \ 22 | { \ 23 | .type = VIV_KEYBIND_TYPE_KEYSYM, \ 24 | .key = XKB_KEY_ ## KEY, \ 25 | .modifiers = (MODIFIERS), \ 26 | .binding = &viv_mappable_ ## BINDING, \ 27 | .payload = { .BINDING = { ._empty = 0u, __VA_ARGS__ } } \ 28 | } 29 | 30 | #define KEYBIND_USER_FUNCTION(MODIFIERS, KEY, BINDING) \ 31 | { \ 32 | .type = VIV_KEYBIND_TYPE_KEYSYM, \ 33 | .key = XKB_KEY_ ## KEY, \ 34 | .modifiers = (MODIFIERS), \ 35 | .binding = &viv_mappable_user_function, \ 36 | .payload = { .user_function = { .function = BINDING } } \ 37 | } 38 | 39 | #define EXIT_WITH_MESSAGE(MESSAGE) \ 40 | wlr_log(WLR_ERROR, "%s", MESSAGE); \ 41 | exit(EXIT_FAILURE); 42 | 43 | #define EXIT_WITH_FORMATTED_MESSAGE(MESSAGE, ...) \ 44 | wlr_log(WLR_ERROR, MESSAGE, __VA_ARGS__); \ 45 | exit(EXIT_FAILURE); 46 | 47 | #define CHECK_ALLOCATION(POINTER) \ 48 | if (!POINTER) { \ 49 | EXIT_WITH_MESSAGE("ALLOCATION FAILURE: " #POINTER); \ 50 | } 51 | 52 | #define ASSERT(EXPR) \ 53 | if (!(EXPR)) { \ 54 | EXIT_WITH_MESSAGE("Assert failed: " #EXPR); \ 55 | } 56 | 57 | #define UNREACHABLE() \ 58 | EXIT_WITH_MESSAGE("UNREACHABLE"); 59 | 60 | #define UNUSED(SYMBOL) \ 61 | (void)(SYMBOL); 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /include/viv_config_types.h: -------------------------------------------------------------------------------- 1 | #ifndef VIV_CONFIG_TYPES_H 2 | #define VIV_CONFIG_TYPES_H 3 | 4 | #include 5 | #include 6 | 7 | #include "viv_types.h" 8 | #include "viv_mappable_functions.h" 9 | 10 | enum viv_keybind_type { 11 | VIV_KEYBIND_TYPE_KEYSYM, 12 | VIV_KEYBIND_TYPE_KEYCODE, 13 | }; 14 | 15 | struct viv_keybind { 16 | enum viv_keybind_type type; 17 | union { 18 | xkb_keysym_t key; 19 | uint32_t keycode; 20 | }; 21 | uint32_t modifiers; 22 | void (*binding)(struct viv_workspace *workspace, union viv_mappable_payload payload); 23 | union viv_mappable_payload payload; 24 | }; 25 | 26 | struct viv_libinput_config { 27 | char *device_name; 28 | enum libinput_config_scroll_method scroll_method; 29 | uint32_t scroll_button; 30 | enum libinput_config_middle_emulation_state middle_emulation; 31 | int left_handed; 32 | int natural_scroll; 33 | enum libinput_config_dwt_state disable_while_typing; 34 | enum libinput_config_click_method click_method; 35 | enum libinput_config_tap_state tap_to_click; 36 | enum libinput_config_tap_button_map tap_button_map; 37 | enum libinput_config_accel_profile accel_profile; 38 | double accel_speed; 39 | }; 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /include/viv_cursor.h: -------------------------------------------------------------------------------- 1 | #ifndef VIV_CURSOR_H 2 | #define VIV_CURSOR_H 3 | 4 | #include "viv_types.h" 5 | 6 | void viv_cursor_process_cursor_motion(struct viv_seat *seat, uint32_t time); 7 | 8 | /// Give pointer focus to whatever window is currently beneath the cursor (if any) 9 | void viv_cursor_reset_focus(struct viv_server *server, uint32_t time); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /include/viv_damage.h: -------------------------------------------------------------------------------- 1 | #ifndef VIV_DAMAGE_H 2 | #define VIV_DAMAGE_H 3 | 4 | #include "viv_types.h" 5 | 6 | /// Damage the given surface, which is placed at the given layout coords, on every output 7 | void viv_damage_surface(struct viv_server *server, struct wlr_surface *surface, int lx, int ly); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /include/viv_debug_support.h: -------------------------------------------------------------------------------- 1 | 2 | #ifdef DEBUG 3 | #define DEBUG_ASSERT_EQUAL(EXPR1, EXPR2) \ 4 | if ((EXPR1) != (EXPR2)) { \ 5 | wlr_log(WLR_ERROR, "ERROR IN DEBUG ASSERT: (" #EXPR1 ") != (" #EXPR2 ")"); \ 6 | } 7 | #define DEBUG_ASSERT(EXPR) \ 8 | if (!(EXPR)) { \ 9 | wlr_log(WLR_ERROR, "DEBUG ASSERT FAILURE: failed ASSERT(" #EXPR ")"); \ 10 | } 11 | #else 12 | #define DEBUG_ASSERT_EQUAL(EXPR1, EXPR2) 13 | #define DEBUG_ASSERT(EXPR) 14 | #endif 15 | -------------------------------------------------------------------------------- /include/viv_input.h: -------------------------------------------------------------------------------- 1 | #ifndef VIV_INPUT_H 2 | #define VIV_INPUT_H 3 | 4 | #include 5 | 6 | #include "viv_config_types.h" 7 | 8 | /// Configure the input according to the config instructions (if any) 9 | void viv_input_configure(struct wlr_input_device *device, struct viv_libinput_config *libinput_configs); 10 | 11 | 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /include/viv_ipc.h: -------------------------------------------------------------------------------- 1 | #ifndef VIV_IPC_H 2 | #define VIV_IPC_H 3 | 4 | #include "viv_types.h" 5 | 6 | /// Log compositor state in the routine way as configured 7 | void viv_routine_log_state(struct viv_server *server); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /include/viv_layer_view.h: -------------------------------------------------------------------------------- 1 | #ifndef VIV_LAYER_VIEW_H 2 | #define VIV_LAYER_VIEW_H 3 | 4 | #include "viv_types.h" 5 | #include 6 | #include "wlr-layer-shell-unstable-v1-protocol.h" 7 | 8 | enum viv_layer_mask { 9 | VIV_LAYER_MASK_BACKGROUND = 1 << ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, 10 | VIV_LAYER_MASK_BOTTOM = 1 << ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, 11 | VIV_LAYER_MASK_TOP = 1 << ZWLR_LAYER_SHELL_V1_LAYER_TOP, 12 | VIV_LAYER_MASK_OVERLAY = 1 << ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, 13 | }; 14 | 15 | #define VIV_ANY_LAYER (VIV_LAYER_MASK_BACKGROUND | VIV_LAYER_MASK_BOTTOM | VIV_LAYER_MASK_TOP | VIV_LAYER_MASK_OVERLAY) 16 | 17 | /// Initialise a new layer view struct 18 | void viv_layer_view_init(struct viv_layer_view *view, struct viv_server *server, struct wlr_layer_surface_v1 *layer_surface); 19 | 20 | /// Arrange the layer views on the given output according to their properties, and set 21 | /// excluded regions appropriately. 22 | void viv_layers_arrange(struct viv_output *output); 23 | 24 | /// Test if any surfaces of the given layer view are at the given layout coordinates, including 25 | /// nested surfaces (e.g. popup windows, tooltips). If so, return the surface data. 26 | bool viv_layer_view_is_at(struct viv_layer_view *layer_view, double lx, double ly, struct wlr_surface **surface, double *sx, double *sy); 27 | 28 | /// Test if the given layer view is in any of the queried layers 29 | bool viv_layer_view_layer_in(struct viv_layer_view *layer_view, uint32_t layers); 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /include/viv_layout.h: -------------------------------------------------------------------------------- 1 | #ifndef VIV_LAYOUT_H 2 | #define VIV_LAYOUT_H 3 | 4 | #include 5 | 6 | #include "viv_types.h" 7 | 8 | 9 | #define MACRO_FOR_EACH_LAYOUT(MACRO) \ 10 | MACRO(split, "Main panel on the left, other windows stacked on the right", \ 11 | " |--------------|---------|\n" \ 12 | " | | 2 |\n" \ 13 | " | |---------|\n" \ 14 | " | | 3 |\n" \ 15 | " | MAIN |---------|\n" \ 16 | " | | 4 |\n" \ 17 | " | |---------|\n" \ 18 | " | | 5 |\n" \ 19 | " |--------------|---------|" \ 20 | ); \ 21 | MACRO(fullscreen, "All windows fullscreen, only active window visible", \ 22 | " |-------------------------|\n" \ 23 | " | |\n" \ 24 | " | |\n" \ 25 | " | |\n" \ 26 | " | MAIN |\n" \ 27 | " | |\n" \ 28 | " | |\n" \ 29 | " | |\n" \ 30 | " |-------------------------|\n" \ 31 | ); \ 32 | MACRO(fibonacci_spiral, "Each window takes up a certain fraction of the remaining space",\ 33 | " |---------------|---------|\n" \ 34 | " | | |\n" \ 35 | " | | 2 |\n" \ 36 | " | | |\n" \ 37 | " | MAIN |----|----|\n" \ 38 | " | | | 4 |\n" \ 39 | " | | 3 |----|\n" \ 40 | " | | | 5 |\n" \ 41 | " |---------------|----|----|" \ 42 | ); \ 43 | MACRO(central_column, "Main panel in the centre, other windows stacked on both sides", \ 44 | " |-------|---------|-------|\n" \ 45 | " | | | |\n" \ 46 | " | 2 | | 4 |\n" \ 47 | " | | | |\n" \ 48 | " |-------| MAIN |-------|\n" \ 49 | " | | | |\n" \ 50 | " | 3 | | 5 |\n" \ 51 | " | | | |\n" \ 52 | " |-------|---------|-------|\n" \ 53 | ); \ 54 | MACRO(columns, "Windows placed in columns, all with equal width", \ 55 | " |----|----|----|----|-----|\n" \ 56 | " | | | | | |\n" \ 57 | " | | | | | |\n" \ 58 | " | | | | | |\n" \ 59 | " |MAIN| 2 | 3 | 4 | 5 |\n" \ 60 | " | | | | | |\n" \ 61 | " | | | | | |\n" \ 62 | " | | | | | |\n" \ 63 | " |----|----|----|----|-----|\n" \ 64 | ); 65 | 66 | #define GENERATE_DECLARATION(LAYOUT_NAME, _1, _2) void viv_layout_do_ ## LAYOUT_NAME(struct wl_array *views, float float_param, uint32_t counter_param, uint32_t width, uint32_t height); 67 | 68 | MACRO_FOR_EACH_LAYOUT(GENERATE_DECLARATION); 69 | 70 | #undef GENERATE_DECLARATION 71 | 72 | void viv_layout_apply(struct viv_workspace *workspace, uint32_t width, uint32_t height); 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /include/viv_mappable_functions.h: -------------------------------------------------------------------------------- 1 | #ifndef VIV_MAPPABLE_FUNCTIONS_H 2 | #define VIV_MAPPABLE_FUNCTIONS_H 3 | 4 | #include "viv_types.h" 5 | 6 | // Forward-declare in order to be able to declare the individual union members alongside the 7 | // functions they relate to. This is easier to read than declaring the payload structs separately 8 | // from their functions. 9 | union viv_mappable_payload; 10 | 11 | #define GENERATE_PAYLOAD_STRUCT(FUNCTION_NAME, ...) \ 12 | struct viv_mappable_payload_ ## FUNCTION_NAME { uint8_t _empty; __VA_ARGS__ }; 13 | #define GENERATE_DECLARATION(FUNCTION_NAME, DOC, ...) \ 14 | void viv_mappable_ ## FUNCTION_NAME(struct viv_workspace *workspace, union viv_mappable_payload payload); \ 15 | GENERATE_PAYLOAD_STRUCT(FUNCTION_NAME, __VA_ARGS__) 16 | 17 | #define GENERATE_UNION_ENTRY(FUNCTION_NAME, DOC, ...) struct viv_mappable_payload_ ## FUNCTION_NAME FUNCTION_NAME ; 18 | 19 | // Mappable functions have a specific form to make them easy for a user to bind to keys, but this 20 | // also means we need to generate both a declaration and a union parameter to pass to their generic 21 | // keyword argument mechanism. This macro defines the table of mappable functions. 22 | #define MACRO_FOR_EACH_MAPPABLE(MACRO) \ 23 | MACRO(do_exec, "Run an executable. Args: executable (string)", char executable[100]; char *args[100];) \ 24 | MACRO(do_shell, "Run a shell command. Args: command (string)", char command[100];) \ 25 | MACRO(increment_divide, "Increment the layout float parameter. Args: increment (float)", float increment; ) \ 26 | MACRO(increment_counter, "Increment the layout counter parameter. Args: increment (int)", uint32_t increment; ) \ 27 | MACRO(terminate, "Terminate vivarum") \ 28 | MACRO(next_window, "Move focus to next window") \ 29 | MACRO(prev_window, "Move focus to previous window") \ 30 | MACRO(shift_active_window_down, "Swap active window with next in the layout") \ 31 | MACRO(shift_active_window_up, "Swap active window with previous in the layout") \ 32 | MACRO(tile_window, "Make window tiling (does nothing for non-floating windows)") \ 33 | MACRO(float_window, "Make window floating (does nothing for already-floating windows)") \ 34 | MACRO(toggle_floating, "Switch window between tiling and floating") \ 35 | MACRO(next_layout, "Switch workspace to next layout") \ 36 | MACRO(prev_layout, "Switch workspace to previous layout") \ 37 | MACRO(right_output, "Switch focus to output to the right of current") \ 38 | MACRO(left_output, "Switch focus to output to the left of current") \ 39 | MACRO(switch_to_workspace, "Switch to workspace with given name. Args: workspace_name (string)", char workspace_name[100];) \ 40 | MACRO(shift_active_window_to_workspace, "Move active window to given workspace. Args: workspace_name (string)", char workspace_name[100];) \ 41 | MACRO(shift_active_window_to_right_output, "Move active window to output to the right of current") \ 42 | MACRO(shift_active_window_to_left_output, "Move active window to output to the left of current") \ 43 | MACRO(remove_fullscreen, "Unfullscreen the fullscreen view in the current workspace") \ 44 | MACRO(close_window, "Close active window") \ 45 | MACRO(make_window_main, "Move active window to first position in current layout") \ 46 | MACRO(reload_config, "Reload the config.toml (warning: may have weird results, restart vivarium if possible)") \ 47 | MACRO(user_function, "Run a C function. Args: function (pointer (*function)(struct viv_workspace *workspace))", void (*function)(struct viv_workspace *workspace);) \ 48 | MACRO(debug_damage_all, "Mark all outputs as damaged to force a full redraw") \ 49 | MACRO(debug_swap_buffers, "Swap buffers") \ 50 | MACRO(debug_toggle_show_undamaged_regions, "Debug option to draw undamaged regions as red") \ 51 | MACRO(debug_toggle_mark_frame_draws, "Debug option to mark frame draw events with a colour-cycling square") \ 52 | MACRO(debug_next_damage_tracking_mode, "Switch to the next damage tracking mode (cycling back to the start if necessary)") \ 53 | 54 | // Declare each mappable function and generate a payload struct to pass as its argument 55 | MACRO_FOR_EACH_MAPPABLE(GENERATE_DECLARATION) 56 | 57 | union viv_mappable_payload { 58 | // Pack all the possible payload structs into a union type 59 | MACRO_FOR_EACH_MAPPABLE(GENERATE_UNION_ENTRY) 60 | }; 61 | 62 | #undef GENERATE_DECLARATION 63 | #undef GENERATE_UNION_ENTRY 64 | 65 | # endif 66 | -------------------------------------------------------------------------------- /include/viv_output.h: -------------------------------------------------------------------------------- 1 | #ifndef VIV_OUTPUT_H 2 | #define VIV_OUTPUT_H 3 | 4 | #include 5 | 6 | #include "viv_types.h" 7 | 8 | struct viv_output *viv_output_at(struct viv_server *server, double lx, double ly); 9 | 10 | void viv_output_make_active(struct viv_output *output); 11 | 12 | struct viv_output *viv_output_of_wlr_output(struct viv_server *server, struct wlr_output *wlr_output); 13 | 14 | struct viv_output *viv_output_next_in_direction(struct viv_output *output, enum wlr_direction direction); 15 | 16 | /// Display the given workspace on the given output. If the given workspace was previously 17 | /// displayed on another output, swap with that one. 18 | void viv_output_display_workspace(struct viv_output *output, struct viv_workspace *workspace); 19 | 20 | /// Initialise a new viv_output 21 | void viv_output_init(struct viv_output *output, struct viv_server *server, struct wlr_output *wlr_output); 22 | 23 | /// Apply the layout function of the current workspace, but only if the output or current 24 | /// workspace need layouting 25 | void viv_output_do_layout_if_necessary(struct viv_output *output); 26 | 27 | /// Mark the whole output as damaged 28 | void viv_output_damage(struct viv_output *output); 29 | 30 | /// Damage the given box, expected to be unscaled and in output-layout coordinates 31 | void viv_output_damage_layout_coords_box(struct viv_output *output, struct wlr_box *box); 32 | 33 | /// Damage the given region, expected to be unscaled an in output-layout coordinates 34 | void viv_output_damage_layout_coords_region(struct viv_output *output, pixman_region32_t *damage); 35 | 36 | void viv_output_layout_coords_box_to_output_coords(struct viv_output *output, struct wlr_box *geo_box); 37 | 38 | /// Mark that whatever workspace is active will need its layout function applying 39 | void viv_output_mark_for_relayout(struct viv_output *output); 40 | #endif 41 | -------------------------------------------------------------------------------- /include/viv_render.h: -------------------------------------------------------------------------------- 1 | #ifndef VIV_RENDER_H 2 | #define VIV_RENDER_H 3 | 4 | #include "viv_types.h" 5 | 6 | void viv_render_view(struct wlr_renderer *renderer, struct viv_view *view, struct viv_output *output, pixman_region32_t *damage); 7 | 8 | void viv_render_layer_view(struct wlr_renderer *renderer, struct viv_layer_view *layer_view, struct viv_output *output); 9 | 10 | /// Render all surfaces on the given output, in appropriate order 11 | void viv_render_output(struct wlr_renderer *renderer, struct viv_output *output); 12 | #endif 13 | -------------------------------------------------------------------------------- /include/viv_seat.h: -------------------------------------------------------------------------------- 1 | #ifndef VIV_SEAT_H 2 | #define VIV_SEAT_H 3 | 4 | #include "viv_types.h" 5 | 6 | struct viv_seat *viv_seat_create(struct viv_server *server, char *name); 7 | 8 | void viv_seat_begin_interactive(struct viv_seat *seat, struct viv_view *view, enum viv_cursor_mode mode, uint32_t edges); 9 | 10 | /// Give keyboard focus to the given layer_view 11 | void viv_seat_focus_layer_view(struct viv_seat *seat, struct viv_layer_view *view); 12 | 13 | /// Give keyboard focus to the given view 14 | void viv_seat_focus_view(struct viv_seat *seat, struct viv_view *view); 15 | 16 | /// Clear the focus 17 | void viv_seat_clear_focus(struct viv_seat *seat); 18 | 19 | /// Create a new viv_keyboard in the given seat 20 | void viv_seat_create_new_keyboard(struct viv_seat *seat, struct wlr_input_device *device); 21 | 22 | /// Set the given client as the only one that may be focused, or if NULL unset the exclusive client 23 | void viv_seat_set_exclusive_client(struct viv_seat *seat, struct wl_client *client); 24 | 25 | /// True if there is no exclusive client or the surface is owned by the exclusive client, else false 26 | bool viv_seat_input_allowed(struct viv_seat *seat, struct wlr_surface *surface); 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /include/viv_server.h: -------------------------------------------------------------------------------- 1 | #ifndef VIV_SERVER_H 2 | #define VIV_SERVER_H 3 | 4 | #include "viv_types.h" 5 | 6 | /// Fully initialise the server, including loading config, setting up all Vivarium state 7 | /// (workspaces, layouts, keybinds...), and initialising wayland/wlroots state ready to 8 | /// run. 9 | void viv_server_init(struct viv_server *server); 10 | 11 | /// Deinitialise server-held state where necessary for a clean exit, including XWayland 12 | /// shell if necessary and wayland display 13 | void viv_server_deinit(struct viv_server *server); 14 | 15 | struct viv_view *viv_server_view_at( 16 | struct viv_server *server, double lx, double ly, 17 | struct wlr_surface **surface, double *sx, double *sy); 18 | 19 | struct viv_layer_view *viv_server_layer_view_at( 20 | struct viv_server *server, double lx, double ly, 21 | struct wlr_surface **surface, double *sx, double *sy, 22 | uint32_t layers); 23 | 24 | 25 | struct viv_workspace *viv_server_retrieve_workspace_by_name(struct viv_server *server, char *name); 26 | 27 | /// Check through all data held by the server to check for consistency, e.g. all 28 | /// view->workspace is equal to the workspace of their workspace_link. This is intended 29 | /// for debugging only, failures just result in logged errors. 30 | void viv_check_data_consistency(struct viv_server *server); 31 | 32 | /// Reload the TOML config file 33 | void viv_server_reload_config(struct viv_server *server); 34 | 35 | /// Clear the server grab state, i.e. set cursor mode to passthrough and invalidate the 36 | /// grabbed view pointer 37 | void viv_server_clear_grab_state(struct viv_server *server); 38 | 39 | /// Look up a key press in the configured keybindings, and run the bound function if found 40 | bool viv_server_handle_keybinding(struct viv_server *server, uint32_t keycode, xkb_keysym_t sym, uint32_t modifiers); 41 | 42 | /// Get the default seat against which all inputs are registered by default. 43 | struct viv_seat *viv_server_get_default_seat(struct viv_server *server); 44 | 45 | /// Clear the given view from being grabbed by any seat 46 | void viv_server_clear_view_from_grab_state(struct viv_server *server, struct viv_view *view); 47 | 48 | /// True if the given view is currently grabbed by any seat, else false 49 | bool viv_server_any_seat_grabs(struct viv_server *server, struct viv_view *view); 50 | 51 | void viv_server_update_idle_inhibitor_state(struct viv_server *server); 52 | #endif 53 | -------------------------------------------------------------------------------- /include/viv_toml_config.h: -------------------------------------------------------------------------------- 1 | #ifndef VIV_TOML_CONFIG_H 2 | #define VIV_TOML_CONFIG_H 3 | 4 | #include 5 | 6 | #include "viv_types.h" 7 | 8 | void viv_toml_config_load(char *path, struct viv_config *config, bool user_path); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /include/viv_types.h: -------------------------------------------------------------------------------- 1 | #ifndef VIV_TYPES_H 2 | #define VIV_TYPES_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "viv_config_support.h" 14 | 15 | #ifdef XWAYLAND 16 | #include 17 | #include "viv_xwayland_types.h" 18 | #endif 19 | 20 | enum viv_damage_tracking_mode { 21 | VIV_DAMAGE_TRACKING_NONE, // every frame is fully re-rendered 22 | VIV_DAMAGE_TRACKING_FRAME, // any damage triggers a full frame render 23 | VIV_DAMAGE_TRACKING_FULL, // only damaged regions are rendered 24 | VIV_DAMAGE_TRACKING_MAX, 25 | }; 26 | 27 | enum viv_cursor_mode { 28 | VIV_CURSOR_PASSTHROUGH, /// Pass through cursor data to views 29 | VIV_CURSOR_MOVE, /// A view is being moved 30 | VIV_CURSOR_RESIZE, /// A view is being resized 31 | }; 32 | 33 | enum cursor_buttons { 34 | VIV_LEFT_BUTTON = 272, 35 | VIV_RIGHT_BUTTON = 273, 36 | VIV_MIDDLE_BUTTON = 274, 37 | }; 38 | 39 | struct viv_output; // Forward declare for use by viv_server 40 | struct viv_view; 41 | 42 | struct viv_server { 43 | char *user_provided_config_filen; 44 | struct viv_config *config; 45 | 46 | struct wl_display *wl_display; 47 | struct wlr_backend *backend; 48 | struct wlr_renderer *renderer; 49 | struct wlr_allocator *allocator; 50 | struct wlr_compositor *compositor; 51 | 52 | struct wlr_xdg_shell *xdg_shell; 53 | struct wl_listener new_xdg_surface; 54 | 55 | #ifdef XWAYLAND 56 | struct wlr_xwayland *xwayland_shell; 57 | xcb_atom_t window_type_atoms[WINDOW_TYPE_ATOM_MAX]; 58 | struct wl_listener new_xwayland_surface; 59 | struct wl_listener xwayland_ready; 60 | #endif 61 | 62 | struct wlr_layer_shell_v1 *layer_shell; 63 | struct wl_listener new_layer_surface; 64 | 65 | struct wlr_xcursor_manager *cursor_mgr; 66 | 67 | struct viv_seat *default_seat; 68 | struct wl_list seats; // server_link 69 | 70 | struct wlr_idle *idle; 71 | 72 | struct wlr_idle_inhibit_manager_v1 *idle_inhibit_manager; 73 | struct wl_listener new_idle_inhibitor; 74 | struct wl_listener destroy_idle_inhibitor; 75 | 76 | struct wlr_gamma_control_manager_v1 *gamma_control_manager; 77 | 78 | struct wl_listener new_input; 79 | 80 | struct wlr_xdg_output_manager_v1 *xdg_output_manager; 81 | 82 | struct wlr_server_decoration_manager *decoration_manager; 83 | 84 | struct wlr_xdg_decoration_manager_v1 *xdg_decoration_manager; 85 | struct wl_listener xdg_decoration_new_toplevel_decoration; 86 | 87 | struct wlr_input_inhibit_manager *input_inhibit_manager; 88 | struct wl_listener input_inhibit_activate; 89 | struct wl_listener input_inhibit_deactivate; 90 | 91 | struct wlr_output_layout *output_layout; 92 | struct viv_output *active_output; 93 | struct wl_list outputs; 94 | struct wl_listener new_output; 95 | 96 | struct wlr_output_power_manager_v1 *output_power_manager; 97 | struct wl_listener output_power_manager_set_mode; 98 | 99 | struct wl_list workspaces; 100 | 101 | pid_t bar_pid; 102 | 103 | /// State relating to changes that should be logged 104 | struct { 105 | struct viv_output *last_active_output; 106 | struct viv_workspace *last_active_workspace; 107 | } log_state; 108 | 109 | /// Unmapped views are not kept within the workspace view lists, 110 | /// in order to keep things simple when iterating through them 111 | struct wl_list unmapped_views; 112 | }; 113 | 114 | struct viv_keybindings { 115 | struct wl_list bindings; 116 | }; 117 | 118 | 119 | struct viv_workspace; 120 | 121 | struct viv_output { 122 | struct wl_list link; 123 | struct viv_server *server; 124 | struct wlr_output *wlr_output; 125 | 126 | struct wl_listener frame; 127 | struct wl_listener damage_event; 128 | struct wl_listener present; 129 | struct wl_listener enable; 130 | struct wl_listener mode; 131 | struct wl_listener destroy; 132 | 133 | struct wlr_output_damage *damage; 134 | 135 | bool needs_layout; 136 | struct viv_workspace *current_workspace; 137 | 138 | uint32_t frame_draw_count; // only used by debug options 139 | 140 | struct wl_list layer_views; 141 | struct { 142 | uint32_t left; 143 | uint32_t right; 144 | uint32_t top; 145 | uint32_t bottom; 146 | } excluded_margin; 147 | }; 148 | 149 | struct viv_layout { 150 | char name[100]; 151 | void (*layout_function)(struct wl_array *views, float float_param, uint32_t counter_param, uint32_t width, uint32_t height); /// Function that applies the layout 152 | 153 | float parameter; /// A float between 0-1 which the user may configure at runtime 154 | uint32_t counter; /// User-configurable uint which the user may configure at runtime, effectively unbounded 155 | 156 | bool no_borders; /// If true, don't draw borders around windows (including the active window) 157 | bool ignore_excluded_regions; /// If true, ignore regions marked excluded by other apps such as taskbars 158 | 159 | struct wl_list workspace_link; 160 | }; 161 | 162 | struct viv_layer_view { 163 | struct wlr_layer_surface_v1 *layer_surface; 164 | struct viv_server *server; 165 | struct viv_output *output; 166 | 167 | struct viv_surface_tree_node *surface_tree; 168 | 169 | struct wl_listener map; 170 | struct wl_listener unmap; 171 | struct wl_listener destroy; 172 | struct wl_listener new_popup; 173 | struct wl_listener surface_commit; 174 | bool mapped; 175 | 176 | struct wl_list output_link; 177 | 178 | int x, y; 179 | }; 180 | 181 | enum viv_view_type { 182 | VIV_VIEW_TYPE_UNKNOWN, 183 | VIV_VIEW_TYPE_XDG_SHELL, 184 | #ifdef XWAYLAND 185 | VIV_VIEW_TYPE_XWAYLAND, 186 | #endif 187 | }; 188 | 189 | struct viv_view_implementation { 190 | void (*set_size)(struct viv_view *view, uint32_t width, uint32_t height); 191 | void (*set_pos)(struct viv_view *view, uint32_t x, uint32_t y); 192 | void (*get_geometry)(struct viv_view *view, struct wlr_box *geo_box); 193 | void (*set_tiled)(struct viv_view *view, uint32_t edges); 194 | void (*get_string_identifier)(struct viv_view *view, char *output, size_t max_len); 195 | struct wlr_surface *(*get_toplevel_surface)(struct viv_view *view); 196 | void (*close)(struct viv_view *view); 197 | bool (*is_at)(struct viv_view *view, double lx, double ly, struct wlr_surface **surface, double *sx, double *sy); 198 | bool (*oversized)(struct viv_view *view); 199 | void (*inform_unrequested_fullscreen_change)(struct viv_view *view); 200 | void (*grow_and_center_fullscreen)(struct viv_view *view); 201 | }; 202 | 203 | struct viv_xdg_popup { 204 | struct wlr_xdg_popup *wlr_popup; 205 | struct viv_xdg_popup *parent_popup; 206 | struct viv_server *server; 207 | 208 | struct viv_surface_tree_node *surface_tree; 209 | 210 | int *lx; // pointer to x of parent view/layer-view in layout coords 211 | int *ly; // pointer to y of parent view/layer-view in layout coords 212 | 213 | struct wl_listener surface_commit; 214 | struct wl_listener surface_map; 215 | struct wl_listener surface_unmap; 216 | struct wl_listener destroy; 217 | struct wl_listener new_popup; 218 | }; 219 | 220 | struct viv_view { 221 | enum viv_view_type type; 222 | 223 | struct viv_view_implementation *implementation; 224 | 225 | struct wl_list workspace_link; 226 | 227 | struct viv_server *server; 228 | struct viv_workspace *workspace; 229 | 230 | struct viv_surface_tree_node *surface_tree; 231 | 232 | union { 233 | struct wlr_xdg_surface *xdg_surface; 234 | struct wlr_xwayland_surface *xwayland_surface; 235 | }; 236 | 237 | // XDG view bindings 238 | struct wl_listener map; 239 | struct wl_listener unmap; 240 | struct wl_listener destroy; 241 | struct wl_listener request_move; 242 | struct wl_listener request_resize; 243 | struct wl_listener request_maximize; 244 | struct wl_listener request_minimize; 245 | struct wl_listener set_title; 246 | struct wl_listener request_fullscreen; 247 | #ifdef XWAYLAND 248 | struct wl_listener request_configure; 249 | #endif 250 | 251 | bool mapped; 252 | int x, y; 253 | 254 | // Surface bindings 255 | struct wl_listener surface_commit; 256 | struct wl_listener new_xdg_popup; 257 | 258 | // Target positions where the layout is trying to place the view. These boxes describe 259 | // the geometry that the application should output, they do not include any space for 260 | // borders or gaps. That padding should be accounted for by whoever sets the target 261 | // box. 262 | struct wlr_box target_box; 263 | struct wlr_box target_box_before_fullscreen; 264 | 265 | bool is_floating; 266 | float floating_width, floating_height; /// width and height to be used if the view becomes floating 267 | 268 | bool is_static; /// true for e.g. X11 right click menus, signals that no borders should be drawn 269 | /// and resizing/moving is not allowed 270 | }; 271 | 272 | struct viv_workspace { 273 | char name[100]; 274 | struct wl_list layouts; /// List of layouts available in this workspace 275 | struct viv_layout *active_layout; 276 | 277 | bool needs_layout; // true if the layout function needs applying, e.g. in response to a new view 278 | bool was_laid_out; // true if the workspace was laid out at the end of the last frame, else false 279 | 280 | struct viv_server *server; 281 | struct viv_output *output; 282 | 283 | struct wl_list views; /// Ordered list of views associated with this workspace 284 | struct viv_view *active_view; /// The view that currently has focus within the workspace 285 | struct viv_view *fullscreen_view; /// The view currently in the fullscreen state, i.e. drawn 286 | /// on top of everything else regardless of the current layout 287 | 288 | struct wl_list server_link; 289 | }; 290 | 291 | struct viv_keyboard { 292 | struct wl_list link; 293 | struct viv_seat *seat; 294 | struct wlr_input_device *device; 295 | 296 | struct wl_listener destroy; 297 | struct wl_listener modifiers; 298 | struct wl_listener key; 299 | }; 300 | 301 | struct viv_config { 302 | enum wlr_keyboard_modifier global_meta_key; 303 | 304 | bool focus_follows_mouse; 305 | enum cursor_buttons win_move_cursor_button; 306 | enum cursor_buttons win_resize_cursor_button; 307 | 308 | int border_width; 309 | float active_border_colour[4]; 310 | float inactive_border_colour[4]; 311 | float clear_colour[4]; 312 | 313 | int gap_width; 314 | 315 | bool allow_fullscreen; 316 | 317 | char workspaces[MAX_NUM_WORKSPACES][MAX_WORKSPACE_NAME_LENGTH]; 318 | 319 | struct { 320 | char *colour; 321 | char *image; 322 | char *mode; 323 | } background; 324 | 325 | char *ipc_workspaces_filename; 326 | 327 | struct { 328 | char *command; 329 | uint32_t update_signal_number; 330 | } bar; 331 | 332 | struct { 333 | char *rules; 334 | char *model; 335 | char *layout; 336 | char *variant; 337 | char *options; 338 | } xkb_rules; 339 | 340 | struct viv_keybind *keybinds; 341 | 342 | struct viv_layout *layouts; 343 | 344 | struct viv_libinput_config *libinput_configs; 345 | 346 | enum viv_damage_tracking_mode damage_tracking_mode; 347 | 348 | bool debug_mark_views_by_shell; 349 | bool debug_mark_active_output; 350 | bool debug_mark_frame_draws; 351 | bool debug_mark_undamaged_regions; 352 | }; 353 | 354 | struct viv_seat { 355 | struct viv_server *server; 356 | struct wlr_seat *wlr_seat; 357 | 358 | enum viv_cursor_mode cursor_mode; 359 | 360 | struct wl_list keyboards; 361 | 362 | /// State relating to any currently-grabbed view 363 | struct { 364 | struct viv_view *view; /// Currently-grabbed view 365 | double x, y; 366 | struct wlr_box geobox; 367 | uint32_t resize_edges; /// union of ::wlr_edges along which the view is being resized 368 | } grab_state; 369 | 370 | /// Client that has exclusive focus due to input_inhibit protocol - note this client 371 | /// is specifically assumed to come from that protocol and is also used to trigger 372 | /// other protocol behaviours such as ignoring hotkeys 373 | struct wl_client *exclusive_client; 374 | 375 | struct wl_listener request_cursor; 376 | struct wl_listener request_set_selection; 377 | 378 | struct wlr_cursor *cursor; 379 | struct wl_listener cursor_motion; 380 | struct wl_listener cursor_motion_absolute; 381 | struct wl_listener cursor_button; 382 | struct wl_listener cursor_axis; 383 | struct wl_listener cursor_frame; 384 | 385 | struct wl_listener request_start_drag; 386 | struct wl_listener start_drag; 387 | struct wl_listener drag_destroy; 388 | 389 | struct wl_listener request_set_primary_selection; 390 | 391 | struct wl_list server_link; 392 | }; 393 | 394 | 395 | #endif 396 | -------------------------------------------------------------------------------- /include/viv_view.h: -------------------------------------------------------------------------------- 1 | #ifndef VIV_VIEW_H 2 | #define VIV_VIEW_H 3 | 4 | #include 5 | 6 | #include "viv_types.h" 7 | 8 | /// Bring the given view to the front of its workspace view list 9 | void viv_view_bring_to_front(struct viv_view *view); 10 | 11 | /// Clear focus from all views handled by the server; 12 | void viv_view_clear_all_focus(struct viv_server *server); 13 | 14 | /// Make the given view the current focus of keyboard input, and the active 15 | /// view in the current workspace 16 | void viv_view_focus(struct viv_view *view); 17 | 18 | /// Mark the given view as floating, and trigger a workspace layout update if necessary 19 | void viv_view_ensure_floating(struct viv_view *view); 20 | 21 | /// Mark the given view as tiled, and trigger a workspace layout update if necessary 22 | void viv_view_ensure_tiled(struct viv_view *view); 23 | 24 | /// Move the given view to the target workspace. If the workspace is 25 | /// currently visible, preserve its focus, otherwise focus the next 26 | /// view in the current workspace. 27 | void viv_view_shift_to_workspace(struct viv_view *view, struct viv_workspace *workspace); 28 | 29 | /// Get the next view in the current workspace (which may be the same 30 | /// view if it's the only one present) 31 | struct viv_view *viv_view_next_in_workspace(struct viv_view *view); 32 | 33 | /// Get the previous view in the current workspace (which may be the same 34 | /// view if it's the only one present) 35 | struct viv_view *viv_view_prev_in_workspace(struct viv_view *view); 36 | 37 | /// Send a close request to this view 38 | void viv_view_request_close(struct viv_view *view); 39 | 40 | /// Return a string identifying the view type, as reported by the running application 41 | void viv_view_get_string_identifier(struct viv_view *view, char *buffer, size_t len); 42 | 43 | /// True if the surface geometry size exceeds that of the target draw region, else false 44 | bool viv_view_oversized(struct viv_view *view); 45 | 46 | /// Mark the view as damaged on every output 47 | void viv_view_damage(struct viv_view *view); 48 | 49 | /// Set the size of a view 50 | void viv_view_set_size(struct viv_view *view, uint32_t width, uint32_t height); 51 | 52 | /// Set the pos of a view, in global coordinates 53 | void viv_view_set_size(struct viv_view *view, uint32_t width, uint32_t height); 54 | 55 | /// Get the geometry box of a view 56 | void viv_view_get_geometry(struct viv_view *view, struct wlr_box *geo_box); 57 | 58 | /// Perform generic initialisation of a viv_view. Requires that shell-specific 59 | /// configuration has already taken place. 60 | void viv_view_init(struct viv_view *view, struct viv_server *server); 61 | 62 | /// Clear up view state, remove it from its workspace, and free its memory 63 | void viv_view_destroy(struct viv_view *view); 64 | 65 | /// Get the wlr_surface that is the main/toplevel surface for this view 66 | struct wlr_surface *viv_view_get_toplevel_surface(struct viv_view *view); 67 | 68 | /** Test if any surfaces of the given view are at the given layout coordinates, including 69 | nested surfaces (e.g. popup windows, tooltips). If so, return the surface data. 70 | @param view Pointer to the view to test 71 | @param lx Position to test in layout coordinates 72 | @param ly Position to test in layout coordinates 73 | @param surface wlr_surface via which to return if found 74 | @param sx Surface coordinate to return (relative to surface top left corner) 75 | @param sy Surface coordinate to return (relative to surface top left corner) 76 | @returns true if a surface was found, else false 77 | */ 78 | bool viv_view_is_at(struct viv_view *view, double lx, double ly, struct wlr_surface **surface, double *sx, double *sy); 79 | 80 | /// Set the target bounding box for the view, in workspace-local coordinates. The view's 81 | /// actual position and size will be set consistent with this demand in global 82 | /// coordinates, with size reduced to make space for view borders and gaps. 83 | void viv_view_set_target_box(struct viv_view *view, uint32_t x, uint32_t y, uint32_t width, uint32_t height); 84 | 85 | /// Adjusts the target box so that it matches the surface exactly, taking into 86 | /// account borders and gaps 87 | void viv_view_match_target_box_with_surface_geometry(struct viv_view *view); 88 | 89 | /// Ensure that the given view is not active: if it is active, the active view in its 90 | /// workspace will be set to the next view (if present) or otherwise to NULL. This is 91 | /// useful for making sure focus goes somewhere when a view is unmapped. 92 | void viv_view_ensure_not_active_in_workspace(struct viv_view *view); 93 | 94 | /// Sets the view's fullscreen state. Returns true if the operation was succesful, false otherwise 95 | bool viv_view_set_fullscreen(struct viv_view *view, bool fullscreen); 96 | 97 | /// Sets a view to non-fullscreen and informs the client if necessary 98 | void viv_view_force_remove_fullscreen(struct viv_view *view); 99 | 100 | #endif 101 | -------------------------------------------------------------------------------- /include/viv_wl_list_utils.h: -------------------------------------------------------------------------------- 1 | #ifndef VIV_WL_LIST_UTILS_H 2 | #define VIV_WL_LIST_UTILS_H 3 | 4 | #include 5 | 6 | struct wl_list *viv_wl_list_next_ignoring_root(struct wl_list *cur_element, struct wl_list *root_element); 7 | struct wl_list *viv_wl_list_prev_ignoring_root(struct wl_list *cur_element, struct wl_list *root_element); 8 | 9 | /// Switch the given list elements with one another 10 | void viv_wl_list_swap(struct wl_list *elm1, struct wl_list *elm2); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /include/viv_wlr_surface_tree.h: -------------------------------------------------------------------------------- 1 | #ifndef VIV_WLR_SURFACE_TREE_H 2 | #define VIV_WLR_SURFACE_TREE_H 3 | 4 | #include "viv_types.h" 5 | 6 | struct viv_surface_tree_node { 7 | struct viv_server *server; 8 | struct wlr_surface *wlr_surface; 9 | 10 | struct wl_listener new_subsurface; 11 | struct wl_listener commit; 12 | struct wl_listener destroy; 13 | 14 | struct viv_surface_tree_node *parent; 15 | struct viv_wlr_subsurface *subsurface; 16 | 17 | struct wl_list child_subsurfaces; 18 | 19 | void (*apply_global_offset)(void *, int *, int *); 20 | void *global_offset_data; 21 | }; 22 | 23 | struct viv_wlr_subsurface { 24 | struct viv_server *server; 25 | struct wlr_subsurface *wlr_subsurface; 26 | struct viv_surface_tree_node *parent; 27 | struct viv_surface_tree_node *child; 28 | 29 | struct wl_list node_link; 30 | 31 | struct wl_listener map; 32 | struct wl_listener unmap; 33 | struct wl_listener destroy; 34 | }; 35 | 36 | 37 | // Create a surface tree from the input surface. The surface tree will automatically wrap 38 | // all of the subsurfaces (existing or later-created) and handle all surface commit 39 | // events. Commit events will be used to damage every output, with offsets calculated 40 | // including the global offset passed here. 41 | struct viv_surface_tree_node *viv_surface_tree_root_create(struct viv_server *server, struct wlr_surface *surface, void (*apply_global_offset)(void *, int *, int *), void *global_offset_data); 42 | 43 | /// Clean up the node's state (bound events etc.) and free it 44 | void viv_surface_tree_destroy(struct viv_surface_tree_node *node); 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /include/viv_workspace.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef VIV_WORKSPACE_H 3 | #define VIV_WORKSPACE_H 4 | 5 | #include "viv_types.h" 6 | 7 | void viv_workspace_increment_divide(struct viv_workspace *workspace, float increment); 8 | 9 | void viv_workspace_increment_counter(struct viv_workspace *workspace, uint32_t increment); 10 | 11 | void viv_workspace_focus_next_window(struct viv_workspace *workspace); 12 | void viv_workspace_focus_prev_window(struct viv_workspace *workspace); 13 | 14 | void viv_workspace_shift_active_window_down(struct viv_workspace *workspace); 15 | void viv_workspace_shift_active_window_up(struct viv_workspace *workspace); 16 | 17 | void viv_workspace_next_layout(struct viv_workspace *workspace); 18 | void viv_workspace_prev_layout(struct viv_workspace *workspace); 19 | 20 | /// Set the given output's workspace to the given workspace, switching with the previous 21 | /// one if necessary 22 | void viv_workspace_assign_to_output(struct viv_workspace *workspace, struct viv_output *output); 23 | 24 | 25 | /// Returns the first non-floating view in the workspace, or NULL if there is none 26 | struct viv_view *viv_workspace_main_view(struct viv_workspace *workspace); 27 | 28 | 29 | /// Switches the current active window with the main window from the workspace 30 | void viv_workspace_swap_active_and_main(struct viv_workspace *workspace); 31 | 32 | /// Apply the layout function of the workspace, and handle tidying up e.g. pointer focus 33 | void viv_workspace_do_layout(struct viv_workspace *workspace); 34 | 35 | /// Get the number of tiled (i.e. non-floating) views in the workspace 36 | uint32_t viv_workspace_num_tiled_views(struct viv_workspace *workspace); 37 | 38 | /// Add the given view to the current workspace, taking the place of the current active 39 | /// view in the views list (if any). The `view` must not already be in any views list, its 40 | /// link will be reused without checking. 41 | void viv_workspace_add_view(struct viv_workspace *workspace, struct viv_view *view); 42 | 43 | /// Mark all views in the workspace as damaged 44 | void viv_workspace_damage_views(struct viv_workspace *workspace); 45 | 46 | /// Mark that the workspace needs a layout - this works by damaging it 47 | /// then, after the next draw, actually applying the new layout 48 | // TODO: Should we just layout straight away now? 49 | void viv_workspace_mark_for_relayout(struct viv_workspace *workspace); 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /include/viv_xdg_popup.h: -------------------------------------------------------------------------------- 1 | #ifndef VIV_XDG_POPUP_H 2 | #define VIV_XDG_POPUP_H 3 | 4 | #include "viv_types.h" 5 | 6 | void viv_xdg_popup_init(struct viv_xdg_popup *popup, struct wlr_xdg_popup *wlr_popup); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /include/viv_xdg_shell.h: -------------------------------------------------------------------------------- 1 | #ifndef VIV_XDG_SHELL_H 2 | #define VIV_XDG_SHELL_H 3 | 4 | #include "viv_types.h" 5 | 6 | /// init and return a viv_view to represent the given xdg_surface 7 | void viv_xdg_view_init(struct viv_view *view, struct wlr_xdg_surface *xdg_surface); 8 | 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /include/viv_xwayland_shell.h: -------------------------------------------------------------------------------- 1 | #ifndef VIV_XWAYLAND_SHELL_H 2 | #define VIV_XWAYLAND_SHELL_H 3 | 4 | #include "viv_types.h" 5 | 6 | /// Initialise a new view with the given xwayland surface 7 | void viv_xwayland_view_init(struct viv_view *view, struct wlr_xwayland_surface *xwayland_surface); 8 | 9 | // Create an xcb connection and look up the xcb atom uints for all the 10 | // X properties we care about, for access later 11 | void viv_xwayland_lookup_atoms(struct viv_server *server); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /include/viv_xwayland_types.h: -------------------------------------------------------------------------------- 1 | #ifndef VIV_XWAYLAND_TYPES_H 2 | #define VIV_XWAYLAND_TYPES_H 3 | 4 | /// See documentation at https://specifications.freedesktop.org/wm-spec/1.4/ar01s05.html 5 | #define MACRO_FOR_EACH_ATOM_NAME(MACRO) \ 6 | MACRO(_NET_WM_WINDOW_TYPE_DESKTOP) \ 7 | MACRO(_NET_WM_WINDOW_TYPE_DOCK) \ 8 | MACRO(_NET_WM_WINDOW_TYPE_TOOLBAR) \ 9 | MACRO(_NET_WM_WINDOW_TYPE_MENU) \ 10 | MACRO(_NET_WM_WINDOW_TYPE_UTILITY) \ 11 | MACRO(_NET_WM_WINDOW_TYPE_SPLASH) \ 12 | MACRO(_NET_WM_WINDOW_TYPE_DIALOG) \ 13 | MACRO(_NET_WM_WINDOW_TYPE_DROPDOWN_MENU) \ 14 | MACRO(_NET_WM_WINDOW_TYPE_POPUP_MENU) \ 15 | MACRO(_NET_WM_WINDOW_TYPE_TOOLTIP) \ 16 | MACRO(_NET_WM_WINDOW_TYPE_NOTIFICATION) \ 17 | MACRO(_NET_WM_WINDOW_TYPE_COMBO) \ 18 | MACRO(_NET_WM_WINDOW_TYPE_DND) \ 19 | MACRO(_NET_WM_WINDOW_TYPE_NORMAL) \ 20 | 21 | #define AS_SYMBOL(ATOM) ATOM, 22 | enum window_type_atom { 23 | MACRO_FOR_EACH_ATOM_NAME(AS_SYMBOL) 24 | WINDOW_TYPE_ATOM_MAX, 25 | }; 26 | #undef AS_SYMBOL 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /media/layout_counter_illustrations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inclement/vivarium/1ffa4368cc87f96955f5358b5896156753b59a4f/media/layout_counter_illustrations.png -------------------------------------------------------------------------------- /media/layout_split_dist_illustrations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inclement/vivarium/1ffa4368cc87f96955f5358b5896156753b59a4f/media/layout_split_dist_illustrations.png -------------------------------------------------------------------------------- /media/layout_type_illustrations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inclement/vivarium/1ffa4368cc87f96955f5358b5896156753b59a4f/media/layout_type_illustrations.png -------------------------------------------------------------------------------- /media/readme_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inclement/vivarium/1ffa4368cc87f96955f5358b5896156753b59a4f/media/readme_screenshot.png -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'vivarium', 3 | 'c', 4 | version : '0.0.4dev', 5 | default_options : [ 6 | 'c_std=c11', 7 | 'warning_level=2', 8 | 'werror=true', 9 | ], 10 | ) 11 | 12 | add_project_arguments([ 13 | '-DWLR_USE_UNSTABLE', 14 | '-D_POSIX_C_SOURCE=200809', 15 | ], language : 'c') 16 | 17 | if get_option('develop') 18 | add_project_arguments([ 19 | '-DDEBUG', 20 | ], language : 'c') 21 | endif 22 | 23 | if get_option('xwayland').enabled() 24 | add_project_arguments([ 25 | '-DXWAYLAND', 26 | ], language : 'c') 27 | endif 28 | 29 | if get_option('headless-test') 30 | add_project_arguments([ 31 | '-DHEADLESS_TEST', 32 | ], language : 'c') 33 | endif 34 | 35 | cc = meson.get_compiler('c') 36 | 37 | # For wlroots, use the system version if the correct version is available 38 | wlroots_dep = dependency('wlroots', version : ['>=0.15.0', '<0.16.0'], fallback : ['wlroots', 'wlroots'], 39 | default_options : ['examples=false']) 40 | 41 | tomlc99_dep = dependency('toml', fallback : ['tomlc99', 'tomlc99_static_dep']) 42 | 43 | # Unit testing deps 44 | fff_dep = dependency('fff', fallback : ['fff', 'fff_dep']) 45 | unity_dep = dependency('unity', fallback : ['unity', 'unity_dep']) 46 | 47 | wayland_server_dep = dependency('wayland-server') 48 | wayland_client_dep = dependency('wayland-client') 49 | wayland_protocols_dep = dependency('wayland-protocols') 50 | xkbcommon_dep = dependency('xkbcommon') 51 | libinput_dep = dependency('libinput') 52 | xcb_dep = dependency('xcb', required: get_option('xwayland')) 53 | pixman_dep = dependency('pixman-1') 54 | 55 | math_dep = cc.find_library('m') 56 | 57 | includes = [ 58 | include_directories('include'), 59 | include_directories('protocols'), 60 | include_directories(get_option('config-dir')), 61 | ] 62 | 63 | subdir('include') 64 | subdir('protocols') 65 | subdir('src') 66 | subdir('tests') 67 | 68 | # Make sure our default config file is also installed so users can copy it into place. 69 | # We don't actually need the meson config file declaration for this, but it's convenient. 70 | conf_data = configuration_data() 71 | configure_file(input : 'config/config.toml', 72 | output : 'config.toml', 73 | install_dir : join_paths(get_option('sysconfdir'), 'vivarium'), 74 | configuration : conf_data) 75 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('xwayland', type : 'feature', value : 'enabled', description : 'Include XWayland support') 2 | option('develop', type : 'boolean', value : true, description : 'Include debug logging and assertions') 3 | option('config-dir', type : 'string', value : 'config', description : 'Path to your config folder, must contain viv_config.h defining `struct viv_config the_config`') 4 | option('headless-test', type : 'boolean', value : false, description : 'Build Vivarium to immediately set up some headless devices then exit, for testing purposes only') 5 | -------------------------------------------------------------------------------- /protocols/meson.build: -------------------------------------------------------------------------------- 1 | 2 | wayland_protocols_dir = wayland_protocols_dep.get_pkgconfig_variable('pkgdatadir') 3 | local_protocols_dir = 'xml' 4 | 5 | wayland_scanner = find_program('wayland-scanner', native : true) 6 | 7 | 8 | xdg_shell_src = custom_target( 9 | 'xdg_shell_protocol_c', 10 | input : join_paths([wayland_protocols_dir, 'stable/xdg-shell/xdg-shell.xml']), 11 | output : '@BASENAME@-protocol.c', 12 | command : [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'], 13 | ) 14 | 15 | xdg_shell_include = custom_target( 16 | 'xdg_shell_protocol_h', 17 | input : join_paths([wayland_protocols_dir, 'stable/xdg-shell/xdg-shell.xml']), 18 | output : '@BASENAME@-protocol.h', 19 | command : [wayland_scanner, 'server-header', '@INPUT@', '@OUTPUT@'], 20 | ) 21 | 22 | xdg_shell_protocol = static_library( 23 | 'xdg_shell_protocol', 24 | [xdg_shell_src, xdg_shell_include], 25 | ) 26 | 27 | xdg_shell_protocol_dep = declare_dependency( 28 | link_with : xdg_shell_protocol, 29 | sources : xdg_shell_include, 30 | ) 31 | 32 | 33 | layer_shell_src = custom_target( 34 | 'layer_shell_protocol_c', 35 | input : join_paths([local_protocols_dir, 'wlr-layer-shell-unstable-v1.xml']), 36 | output : '@BASENAME@-protocol.c', 37 | command : [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'], 38 | ) 39 | 40 | layer_shell_include = custom_target( 41 | 'layer_shell_protocol_h', 42 | input : join_paths([local_protocols_dir, 'wlr-layer-shell-unstable-v1.xml']), 43 | output : '@BASENAME@-protocol.h', 44 | command : [wayland_scanner, 'server-header', '@INPUT@', '@OUTPUT@'], 45 | ) 46 | 47 | layer_shell_protocol = static_library( 48 | 'layer_shell_protocol', 49 | [layer_shell_src, layer_shell_include], 50 | ) 51 | 52 | layer_shell_protocol_dep = declare_dependency( 53 | link_with : layer_shell_protocol, 54 | sources : layer_shell_include, 55 | ) 56 | 57 | output_power_manager_src = custom_target( 58 | 'output_power_manager_protocol_c', 59 | input : join_paths([local_protocols_dir, 'wlr-output-power-management-unstable-v1.xml']), 60 | output : '@BASENAME@-protocol.c', 61 | command : [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'], 62 | ) 63 | 64 | output_power_manager_include = custom_target( 65 | 'output_power_manager_protocol_h', 66 | input : join_paths([local_protocols_dir, 'wlr-output-power-management-unstable-v1.xml']), 67 | output : '@BASENAME@-protocol.h', 68 | command : [wayland_scanner, 'server-header', '@INPUT@', '@OUTPUT@'], 69 | ) 70 | 71 | output_power_manager_protocol = static_library( 72 | 'output_power_manager_protocol', 73 | [output_power_manager_src, output_power_manager_include], 74 | ) 75 | 76 | output_power_manager_protocol_dep = declare_dependency( 77 | link_with : output_power_manager_protocol, 78 | sources : output_power_manager_include, 79 | ) 80 | 81 | idle_src = custom_target( 82 | 'idle_protocol_c', 83 | input : join_paths([local_protocols_dir, 'idle.xml']), 84 | output : '@BASENAME@-protocol.c', 85 | command : [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'], 86 | ) 87 | 88 | idle_include = custom_target( 89 | 'idle_protocol_h', 90 | input : join_paths([local_protocols_dir, 'idle.xml']), 91 | output : '@BASENAME@-protocol.h', 92 | command : [wayland_scanner, 'server-header', '@INPUT@', '@OUTPUT@'], 93 | ) 94 | 95 | idle_protocol = static_library( 96 | 'idle_protocol', 97 | [idle_src, idle_include], 98 | ) 99 | 100 | idle_protocol_dep = declare_dependency( 101 | link_with : idle_protocol, 102 | sources : idle_include, 103 | ) 104 | 105 | screencopy_src = custom_target( 106 | 'wlr-screencopy-unstable-v1_protocol_c', 107 | input : join_paths([local_protocols_dir, 'wlr-screencopy-unstable-v1.xml']), 108 | output : '@BASENAME@-protocol.c', 109 | command : [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'], 110 | ) 111 | 112 | screencopy_include = custom_target( 113 | 'wlr-screencopy-unstable-v1_protocol_h', 114 | input : join_paths([local_protocols_dir, 'wlr-screencopy-unstable-v1.xml']), 115 | output : '@BASENAME@-protocol.h', 116 | command : [wayland_scanner, 'server-header', '@INPUT@', '@OUTPUT@'], 117 | ) 118 | 119 | screencopy_protocol = static_library( 120 | 'wlr-screencopy-unstable-v1_protocol', 121 | [screencopy_src, screencopy_include], 122 | ) 123 | 124 | screencopy_protocol_dep = declare_dependency( 125 | link_with : screencopy_protocol, 126 | sources : screencopy_include, 127 | ) 128 | -------------------------------------------------------------------------------- /protocols/xml/idle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | . 18 | ]]> 19 | 20 | 21 | This interface allows to monitor user idle time on a given seat. The interface 22 | allows to register timers which trigger after no user activity was registered 23 | on the seat for a given interval. It notifies when user activity resumes. 24 | 25 | This is useful for applications wanting to perform actions when the user is not 26 | interacting with the system, e.g. chat applications setting the user as away, power 27 | management features to dim screen, etc.. 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /protocols/xml/wlr-output-power-management-unstable-v1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright © 2019 Purism SPC 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a 7 | copy of this software and associated documentation files (the "Software"), 8 | to deal in the Software without restriction, including without limitation 9 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | and/or sell copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice (including the next 14 | paragraph) shall be included in all copies or substantial portions of the 15 | Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 20 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | 25 | 26 | 27 | This protocol allows clients to control power management modes 28 | of outputs that are currently part of the compositor space. The 29 | intent is to allow special clients like desktop shells to power 30 | down outputs when the system is idle. 31 | 32 | To modify outputs not currently part of the compositor space see 33 | wlr-output-management. 34 | 35 | Warning! The protocol described in this file is experimental and 36 | backward incompatible changes may be made. Backward compatible changes 37 | may be added together with the corresponding interface version bump. 38 | Backward incompatible changes are done by bumping the version number in 39 | the protocol and interface names and resetting the interface version. 40 | Once the protocol is to be declared stable, the 'z' prefix and the 41 | version number in the protocol and interface names are removed and the 42 | interface version number is reset. 43 | 44 | 45 | 46 | 47 | This interface is a manager that allows creating per-output power 48 | management mode controls. 49 | 50 | 51 | 52 | 53 | Create a output power management mode control that can be used to 54 | adjust the power management mode for a given output. 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | All objects created by the manager will still remain valid, until their 63 | appropriate destroy request has been called. 64 | 65 | 66 | 67 | 68 | 69 | 70 | This object offers requests to set the power management mode of 71 | an output. 72 | 73 | 74 | 75 | 77 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | Set an output's power save mode to the given mode. The mode change 88 | is effective immediately. If the output does not support the given 89 | mode a failed event is sent. 90 | 91 | 92 | 93 | 94 | 95 | 96 | Report the power management mode change of an output. 97 | 98 | The mode event is sent after an output changed its power 99 | management mode. The reason can be a client using set_mode or the 100 | compositor deciding to change an output's mode. 101 | This event is also sent immediately when the object is created 102 | so the client is informed about the current power management mode. 103 | 104 | 106 | 107 | 108 | 109 | 110 | This event indicates that the output power management mode control 111 | is no longer valid. This can happen for a number of reasons, 112 | including: 113 | - The output doesn't support power management 114 | - Another client already has exclusive power management mode control 115 | for this output 116 | - The output disappeared 117 | 118 | Upon receiving this event, the client should destroy this object. 119 | 120 | 121 | 122 | 123 | 124 | Destroys the output power management mode control object. 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /protocols/xml/wlr-screencopy-unstable-v1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright © 2018 Simon Ser 5 | Copyright © 2019 Andri Yngvason 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a 8 | copy of this software and associated documentation files (the "Software"), 9 | to deal in the Software without restriction, including without limitation 10 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | and/or sell copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice (including the next 15 | paragraph) shall be included in all copies or substantial portions of the 16 | Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | 26 | 27 | 28 | This protocol allows clients to ask the compositor to copy part of the 29 | screen content to a client buffer. 30 | 31 | Warning! The protocol described in this file is experimental and 32 | backward incompatible changes may be made. Backward compatible changes 33 | may be added together with the corresponding interface version bump. 34 | Backward incompatible changes are done by bumping the version number in 35 | the protocol and interface names and resetting the interface version. 36 | Once the protocol is to be declared stable, the 'z' prefix and the 37 | version number in the protocol and interface names are removed and the 38 | interface version number is reset. 39 | 40 | 41 | 42 | 43 | This object is a manager which offers requests to start capturing from a 44 | source. 45 | 46 | 47 | 48 | 49 | Capture the next frame of an entire output. 50 | 51 | 52 | 54 | 55 | 56 | 57 | 58 | 59 | Capture the next frame of an output's region. 60 | 61 | The region is given in output logical coordinates, see 62 | xdg_output.logical_size. The region will be clipped to the output's 63 | extents. 64 | 65 | 66 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | All objects created by the manager will still remain valid, until their 78 | appropriate destroy request has been called. 79 | 80 | 81 | 82 | 83 | 84 | 85 | This object represents a single frame. 86 | 87 | When created, a series of buffer events will be sent, each representing a 88 | supported buffer type. The "buffer_done" event is sent afterwards to 89 | indicate that all supported buffer types have been enumerated. The client 90 | will then be able to send a "copy" request. If the capture is successful, 91 | the compositor will send a "flags" followed by a "ready" event. 92 | 93 | For objects version 2 or lower, wl_shm buffers are always supported, ie. 94 | the "buffer" event is guaranteed to be sent. 95 | 96 | If the capture failed, the "failed" event is sent. This can happen anytime 97 | before the "ready" event. 98 | 99 | Once either a "ready" or a "failed" event is received, the client should 100 | destroy the frame. 101 | 102 | 103 | 104 | 105 | Provides information about wl_shm buffer parameters that need to be 106 | used for this frame. This event is sent once after the frame is created 107 | if wl_shm buffers are supported. 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | Copy the frame to the supplied buffer. The buffer must have a the 118 | correct size, see zwlr_screencopy_frame_v1.buffer and 119 | zwlr_screencopy_frame_v1.linux_dmabuf. The buffer needs to have a 120 | supported format. 121 | 122 | If the frame is successfully copied, a "flags" and a "ready" events are 123 | sent. Otherwise, a "failed" event is sent. 124 | 125 | 126 | 127 | 128 | 129 | 131 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | Provides flags about the frame. This event is sent once before the 142 | "ready" event. 143 | 144 | 145 | 146 | 147 | 148 | 149 | Called as soon as the frame is copied, indicating it is available 150 | for reading. This event includes the time at which presentation happened 151 | at. 152 | 153 | The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples, 154 | each component being an unsigned 32-bit value. Whole seconds are in 155 | tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo, 156 | and the additional fractional part in tv_nsec as nanoseconds. Hence, 157 | for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part 158 | may have an arbitrary offset at start. 159 | 160 | After receiving this event, the client should destroy the object. 161 | 162 | 164 | 166 | 168 | 169 | 170 | 171 | 172 | This event indicates that the attempted frame copy has failed. 173 | 174 | After receiving this event, the client should destroy the object. 175 | 176 | 177 | 178 | 179 | 180 | Destroys the frame. This request can be sent at any time by the client. 181 | 182 | 183 | 184 | 185 | 186 | 187 | Same as copy, except it waits until there is damage to copy. 188 | 189 | 190 | 191 | 192 | 193 | 194 | This event is sent right before the ready event when copy_with_damage is 195 | requested. It may be generated multiple times for each copy_with_damage 196 | request. 197 | 198 | The arguments describe a box around an area that has changed since the 199 | last copy request that was derived from the current screencopy manager 200 | instance. 201 | 202 | The union of all regions received between the call to copy_with_damage 203 | and a ready event is the total damage since the prior ready event. 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | Provides information about linux-dmabuf buffer parameters that need to 215 | be used for this frame. This event is sent once after the frame is 216 | created if linux-dmabuf buffers are supported. 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | This event is sent once after all buffer events have been sent. 226 | 227 | The client should proceed to create a buffer of one of the supported 228 | types, and send a "copy" request. 229 | 230 | 231 | 232 | 233 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | viv_sources = [ 2 | 'vivarium.c', 3 | 'viv_background.c', 4 | 'viv_bar.c', 5 | 'viv_cli.c', 6 | 'viv_cursor.c', 7 | 'viv_damage.c', 8 | 'viv_input.c', 9 | 'viv_ipc.c', 10 | 'viv_layout.c', 11 | 'viv_mappable_functions.c', 12 | 'viv_output.c', 13 | 'viv_render.c', 14 | 'viv_seat.c', 15 | 'viv_server.c', 16 | 'viv_layer_view.c', 17 | 'viv_toml_config.c', 18 | 'viv_view.c', 19 | 'viv_wl_list_utils.c', 20 | 'viv_wlr_surface_tree.c', 21 | 'viv_workspace.c', 22 | 'viv_xdg_popup.c', 23 | 'viv_xdg_shell.c', 24 | ] 25 | 26 | if get_option('xwayland').enabled() 27 | viv_sources += [ 28 | 'viv_xwayland_shell.c', 29 | ] 30 | endif 31 | 32 | viv_deps = [ 33 | wlroots_dep, 34 | wayland_server_dep, 35 | xkbcommon_dep, 36 | libinput_dep, 37 | xdg_shell_protocol_dep, 38 | layer_shell_protocol_dep, 39 | output_power_manager_protocol_dep, 40 | idle_protocol_dep, 41 | tomlc99_dep, 42 | xcb_dep, 43 | pixman_dep, 44 | math_dep, 45 | ] 46 | 47 | executable( 48 | 'vivarium', 49 | viv_sources, 50 | include_directories : includes, 51 | dependencies : viv_deps, 52 | install : true, 53 | ) 54 | -------------------------------------------------------------------------------- /src/viv_background.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | static void run_swaybg(char *colour, char *image, char *mode) { 16 | pid_t pid = fork(); 17 | if (pid == 0) { 18 | // TODO: We need to do more shenanigans to fork properly/safely, see e.g. sway's version 19 | 20 | wlr_log(WLR_INFO, "Running swaybg"); 21 | 22 | (void)colour; 23 | char *const cmd[] = { 24 | "swaybg", 25 | "-c", colour, 26 | "-i", image, 27 | "-m", mode, 28 | NULL 29 | }; 30 | execvp(cmd[0], cmd); 31 | _exit(EXIT_SUCCESS); 32 | } 33 | } 34 | 35 | void viv_parse_and_run_background_config(char *colour, char *image, char *mode) { 36 | bool colour_valid = (colour != NULL) && strlen(colour); 37 | bool image_valid = (image != NULL) && strlen(image); 38 | bool mode_valid = (mode != NULL) && strlen(mode); 39 | if (!colour_valid && !image_valid && !mode_valid) { 40 | // Nothing is configured so just don't run swaybg 41 | wlr_log(WLR_INFO, "No background config, skipping"); 42 | return; 43 | } 44 | 45 | run_swaybg(colour, image, mode); 46 | } 47 | -------------------------------------------------------------------------------- /src/viv_bar.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "viv_config_support.h" 11 | 12 | static pid_t run_bar(char *bar_command) { 13 | pid_t pid = fork(); 14 | if (pid == 0) { 15 | // TODO: We need to do more shenanigans to fork properly/safely, see e.g. sway's version 16 | 17 | wlr_log(WLR_INFO, "Running bar %s", bar_command); 18 | 19 | char *const cmd[] = { 20 | bar_command, 21 | NULL 22 | }; 23 | execvp(cmd[0], cmd); 24 | _exit(EXIT_SUCCESS); 25 | } 26 | return pid; 27 | } 28 | 29 | pid_t viv_parse_and_run_bar_config(char *bar_command, uint32_t update_signal_number) { 30 | if (!strlen(bar_command)) { 31 | // Nothing is configured so just don't run swaybg 32 | wlr_log(WLR_INFO, "No bar config, skipping"); 33 | return (pid_t)0; 34 | } 35 | 36 | ASSERT((int)update_signal_number <= (SIGRTMAX - SIGRTMIN)); 37 | 38 | return run_bar(bar_command); 39 | } 40 | -------------------------------------------------------------------------------- /src/viv_cli.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "viv_cli.h" 7 | #include "viv_layout.h" 8 | #include "viv_mappable_functions.h" 9 | #include "viv_types.h" 10 | 11 | #define ALL_OPTIONS_CONSUMED (-1) 12 | #define OPTION_PARSE_FAILURE '?' 13 | 14 | #define LAST_OPTION {0, 0, 0, 0} 15 | 16 | #define MACRO_FOR_EACH_OPTION(MACRO) \ 17 | MACRO("help", help, no_argument, 0, 'h') \ 18 | MACRO("list-config-options", list_config_options, no_argument, 0, 0) \ 19 | MACRO("config", set_config_path, required_argument, 0, 0) \ 20 | 21 | #define GENERATE_OPTION_STRUCT(CLI_NAME, FUNC_NAME, HAS_ARG, FLAG, VAL) \ 22 | {CLI_NAME, HAS_ARG, FLAG, VAL}, 23 | 24 | static bool handle_help(struct viv_args *args) { 25 | UNUSED(args); 26 | printf( 27 | "Usage: vivarium [-h] [--list-config-options] [--config]\n" 28 | "\n" 29 | "-h, --help Show help message and quit\n" 30 | "--list-config-options List available layouts and keybinds\n" 31 | "--config Path to config file to load, overrides normal config\n" 32 | "\n" 33 | ); 34 | 35 | return true; 36 | } 37 | 38 | #define PRINT_LAYOUT_HELP(LAYOUT_NAME, HELP, DIAGRAM) \ 39 | printf(" " #LAYOUT_NAME ": " HELP "\n" DIAGRAM "\n\n\n"); 40 | 41 | #define PRINT_MAPPABLE_HELP(FUNCTION_NAME, DOC, ...) \ 42 | printf(" " #FUNCTION_NAME ": " DOC "\n"); 43 | 44 | static bool handle_list_config_options(struct viv_args *args) { 45 | UNUSED(args); 46 | printf( 47 | "# Layouts\n" 48 | "\n" 49 | "To use a layout, add it to the layout list in your config.toml:\n" 50 | " [[layout]]\n" 51 | " name = \"Split\"\n" 52 | " layout = \"split\" # layout name selected from list below\n" 53 | "\n" 54 | ); 55 | printf("Available layout names:\n\n"); 56 | MACRO_FOR_EACH_LAYOUT(PRINT_LAYOUT_HELP); 57 | 58 | printf( 59 | "# Actions\n" 60 | "\n" 61 | "To use an action in a keybind, add it to the keybind list in your config.toml:\n" 62 | " [[keybind]]\n" 63 | " keysym = \"Q\"\n" 64 | " action = \"terminate\" # action name selected from the list below\n" 65 | "\n" 66 | "Available keybinds:\n" 67 | "\n" 68 | ); 69 | MACRO_FOR_EACH_MAPPABLE(PRINT_MAPPABLE_HELP); 70 | printf("\n"); 71 | 72 | return true; 73 | } 74 | 75 | static bool handle_set_config_path(struct viv_args *args) { 76 | args->config_filen = optarg; 77 | return false; 78 | } 79 | 80 | #define GENERATE_OPTION_HANDLER_LOOKUP(CLI_NAME, FUNC_NAME, HAS_ARG, FLAG, VAL) \ 81 | &handle_ ## FUNC_NAME, 82 | 83 | static bool (*option_handlers[])(struct viv_args *args) = { 84 | MACRO_FOR_EACH_OPTION(GENERATE_OPTION_HANDLER_LOOKUP) 85 | }; 86 | 87 | struct viv_args viv_cli_parse_args(int argc, char *argv[]) { 88 | int option_result; 89 | 90 | struct viv_args parsed_args = { 0 }; 91 | 92 | while (true) { 93 | int option_index = 0; 94 | static struct option long_options[] = { 95 | MACRO_FOR_EACH_OPTION(GENERATE_OPTION_STRUCT) 96 | LAST_OPTION, 97 | }; 98 | option_result = getopt_long(argc, argv, "h", long_options, &option_index); 99 | 100 | if (option_result == ALL_OPTIONS_CONSUMED) { 101 | break; 102 | } 103 | 104 | bool should_exit = false; 105 | switch (option_result) { 106 | case 0: // indicates a long option 107 | should_exit = (*option_handlers[option_index])(&parsed_args); 108 | break; 109 | case 'h': 110 | should_exit = handle_help(&parsed_args); 111 | break; 112 | case OPTION_PARSE_FAILURE: 113 | // In this case getopt_long has already printed an error message so we don't 114 | // need to add anything 115 | should_exit = true; 116 | break; 117 | default: 118 | UNREACHABLE(); 119 | } 120 | 121 | if (should_exit) { 122 | // This option terminates vivarium 123 | exit(0); 124 | } 125 | } 126 | 127 | return parsed_args; 128 | } 129 | -------------------------------------------------------------------------------- /src/viv_cursor.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "viv_cursor.h" 6 | #include "viv_layer_view.h" 7 | #include "viv_output.h" 8 | #include "viv_seat.h" 9 | #include "viv_server.h" 10 | #include "viv_types.h" 11 | #include "viv_view.h" 12 | 13 | static void process_cursor_move_view(struct viv_seat *seat, uint32_t time) { 14 | UNUSED(time); 15 | 16 | struct viv_view *view = seat->grab_state.view; 17 | 18 | int old_x = view->x; 19 | int old_y = view->y; 20 | 21 | // Damage before moving 22 | viv_view_damage(view); 23 | 24 | /* Move the grabbed view to the new position. */ 25 | view->x = seat->cursor->x - seat->grab_state.x; 26 | view->y = seat->cursor->y - seat->grab_state.y; 27 | 28 | view->target_box.x += (view->x - old_x); 29 | view->target_box.y += (view->y - old_y); 30 | 31 | // Move the grabbed view to the new output, if necessary 32 | double cursor_x = seat->cursor->x; 33 | double cursor_y = seat->cursor->y; 34 | struct viv_output *output_at_point = viv_output_at(seat->server, cursor_x, cursor_y); 35 | if (output_at_point != view->workspace->output) { 36 | viv_view_shift_to_workspace(view, output_at_point->current_workspace); 37 | } 38 | 39 | // Damage after moving 40 | viv_view_damage(view); 41 | } 42 | 43 | static void process_cursor_resize_view(struct viv_seat *seat, uint32_t time) { 44 | UNUSED(time); 45 | struct viv_view *view = seat->grab_state.view; 46 | 47 | viv_view_damage(view); 48 | 49 | double border_x = seat->cursor->x - seat->grab_state.x; 50 | double border_y = seat->cursor->y - seat->grab_state.y; 51 | int new_left = seat->grab_state.geobox.x; 52 | int new_right = seat->grab_state.geobox.x + seat->grab_state.geobox.width; 53 | int new_top = seat->grab_state.geobox.y; 54 | int new_bottom = seat->grab_state.geobox.y + seat->grab_state.geobox.height; 55 | 56 | if (seat->grab_state.resize_edges & WLR_EDGE_TOP) { 57 | new_top = border_y; 58 | if (new_top >= new_bottom) { 59 | new_top = new_bottom - 1; 60 | } 61 | } else if (seat->grab_state.resize_edges & WLR_EDGE_BOTTOM) { 62 | new_bottom = border_y; 63 | if (new_bottom <= new_top) { 64 | new_bottom = new_top + 1; 65 | } 66 | } 67 | if (seat->grab_state.resize_edges & WLR_EDGE_LEFT) { 68 | new_left = border_x; 69 | if (new_left >= new_right) { 70 | new_left = new_right - 1; 71 | } 72 | } else if (seat->grab_state.resize_edges & WLR_EDGE_RIGHT) { 73 | new_right = border_x; 74 | if (new_right <= new_left) { 75 | new_right = new_left + 1; 76 | } 77 | } 78 | 79 | view->x = new_left; 80 | view->y = new_top; 81 | 82 | int new_width = new_right - new_left; 83 | int new_height = new_bottom - new_top; 84 | viv_view_set_size(view, new_width, new_height); 85 | 86 | view->target_box.x = new_left; 87 | view->target_box.y = new_top; 88 | view->target_box.width = new_width; 89 | view->target_box.height = new_height; 90 | 91 | viv_view_damage(view); 92 | } 93 | 94 | static bool layer_view_wants_keyboard_focus(struct viv_layer_view *layer_view) { 95 | return (layer_view->layer_surface->current.keyboard_interactive); 96 | } 97 | 98 | /// Find the focusable surface under the pointer (if any) and pass the event data along 99 | static void process_cursor_pass_through_to_surface(struct viv_seat *seat, uint32_t time) { 100 | double sx, sy; 101 | struct wlr_surface *surface = NULL; 102 | struct viv_server *server = seat->server; 103 | 104 | // TODO: This will need to iterate over views in each desktop, with some appropriate ordering 105 | struct viv_layer_view *layer_view; 106 | struct viv_view *view = NULL; 107 | 108 | // Find the uppermost view or layer view under the cursor 109 | if ((layer_view = viv_server_layer_view_at(server, seat->cursor->x, seat->cursor->y, &surface, &sx, &sy, VIV_LAYER_MASK_OVERLAY))) { 110 | } else if (server->active_output && (view = server->active_output->current_workspace->fullscreen_view)) { 111 | // Stop the search if there is a fullscreen view, even if a surface is not found, as we don't want to match views under the borders 112 | viv_view_is_at(view, seat->cursor->x, seat->cursor->y, &surface, &sx, &sy); 113 | } else if ((layer_view = viv_server_layer_view_at(server, seat->cursor->x, seat->cursor->y, &surface, &sx, &sy, VIV_LAYER_MASK_TOP))) { 114 | } else if ((view = viv_server_view_at(server, seat->cursor->x, seat->cursor->y, &surface, &sx, &sy))) { 115 | } else if ((layer_view = viv_server_layer_view_at(server, seat->cursor->x, seat->cursor->y, &surface, &sx, &sy, VIV_LAYER_MASK_BOTTOM))) { 116 | } else if ((layer_view = viv_server_layer_view_at(server, seat->cursor->x, seat->cursor->y, &surface, &sx, &sy, VIV_LAYER_MASK_BACKGROUND))) { 117 | } 118 | 119 | // Act appropriately on whatever view type was found 120 | if (layer_view) { 121 | if (layer_view_wants_keyboard_focus(layer_view)) { 122 | viv_seat_focus_layer_view(seat, layer_view); 123 | server->active_output->current_workspace->active_view = NULL; 124 | } 125 | } else if (view) { 126 | // View under the cursor and not already active => focus it if appropriate 127 | struct viv_view *active_view = NULL; 128 | 129 | struct viv_output *active_output = server->active_output; 130 | if (active_output) { 131 | active_view = active_output->current_workspace->active_view; 132 | } 133 | 134 | if ((view != active_view) && server->config->focus_follows_mouse) { 135 | viv_view_focus(view); 136 | } 137 | } else { 138 | // No focusable surface under the cursor => use the default image 139 | wlr_xcursor_manager_set_cursor_image(server->cursor_mgr, "left_ptr", seat->cursor); 140 | } 141 | 142 | viv_cursor_reset_focus(server, time); 143 | if (surface) { 144 | // Always call both notify-enter and notify-motion, wlroots can handle actually 145 | // sending whichever is appropriate. 146 | // Also note pointer focus is independent of keyboard focus. 147 | wlr_seat_pointer_notify_enter(seat->wlr_seat, surface, sx, sy); 148 | wlr_seat_pointer_notify_motion(seat->wlr_seat, time, sx, sy); 149 | } else { 150 | /* Clear pointer focus so future button events and such are not sent to 151 | * the last client to have the cursor over it. */ 152 | wlr_seat_pointer_clear_focus(seat->wlr_seat); 153 | } 154 | } 155 | 156 | /** Handle new cursor data, i.e. acting on an in-progress move or resize, or otherwise 157 | passing through the event to a view. 158 | */ 159 | void viv_cursor_process_cursor_motion(struct viv_seat *seat, uint32_t time) { 160 | // Always update the current output if necessary 161 | double cursor_x = seat->cursor->x; 162 | double cursor_y = seat->cursor->y; 163 | struct viv_output *output_at_point = viv_output_at(seat->server, cursor_x, cursor_y); 164 | viv_output_make_active(output_at_point); 165 | 166 | // Respond to the specific cursor movement 167 | switch (seat->cursor_mode) { 168 | case VIV_CURSOR_MOVE: 169 | process_cursor_move_view(seat, time); 170 | break; 171 | case VIV_CURSOR_RESIZE: 172 | process_cursor_resize_view(seat, time); 173 | break; 174 | case VIV_CURSOR_PASSTHROUGH: 175 | process_cursor_pass_through_to_surface(seat, time); 176 | break; 177 | } 178 | 179 | } 180 | 181 | /// Check for a layer view or normal view at the cursor pos, in each layer (including the 182 | /// view layer) until one is found, or returns NULL if none is found. 183 | static struct wlr_surface *uppermost_surface_at_cursor(struct viv_server *server, double *sx, double *sy) { 184 | struct viv_seat *seat = viv_server_get_default_seat(server); 185 | double cursor_x = seat->cursor->x; 186 | double cursor_y = seat->cursor->y; 187 | struct wlr_surface *surface = NULL; 188 | if ((viv_server_layer_view_at(server, cursor_x, cursor_y, &surface, sx, sy, VIV_LAYER_MASK_OVERLAY))) { 189 | } else if ((viv_server_layer_view_at(server, cursor_x, cursor_y, &surface, sx, sy, VIV_LAYER_MASK_TOP))) { 190 | } else if ((viv_server_view_at(server, cursor_x, cursor_y, &surface, sx, sy))) { 191 | } else if ((viv_server_layer_view_at(server, cursor_x, cursor_y, &surface, sx, sy, VIV_LAYER_MASK_BOTTOM))) { 192 | } else if ((viv_server_layer_view_at(server, cursor_x, cursor_y, &surface, sx, sy, VIV_LAYER_MASK_BACKGROUND))) { 193 | } 194 | 195 | return surface; 196 | } 197 | 198 | void viv_cursor_reset_focus(struct viv_server *server, uint32_t time) { 199 | struct viv_seat *seat = viv_server_get_default_seat(server); 200 | double sx, sy; 201 | struct wlr_surface *surface = uppermost_surface_at_cursor(server, &sx, &sy); 202 | 203 | if (surface) { 204 | bool focus_changed = seat->wlr_seat->pointer_state.focused_surface != surface; 205 | // Set pointer focus appropriately - note this is distinct from keyboard focus 206 | wlr_seat_pointer_notify_enter(seat->wlr_seat, surface, sx, sy); 207 | if (!focus_changed) { 208 | /* The enter event contains coordinates, so we only need to notify 209 | * on motion if the focus did not change. */ 210 | wlr_seat_pointer_notify_motion(seat->wlr_seat, time, sx, sy); 211 | } 212 | } else { 213 | /* Clear pointer focus so future button events and such are not sent to 214 | * the last client to have the cursor over it. */ 215 | wlr_seat_pointer_clear_focus(seat->wlr_seat); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/viv_damage.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | 6 | #include "viv_output.h" 7 | #include "viv_types.h" 8 | 9 | 10 | /// Apply the damage from this surface to every output 11 | void viv_damage_surface(struct viv_server *server, struct wlr_surface *surface, int lx, int ly) { 12 | pixman_region32_t damage; 13 | pixman_region32_init(&damage); 14 | wlr_surface_get_effective_damage(surface, &damage); 15 | 16 | pixman_region32_translate(&damage, lx, ly); 17 | 18 | struct viv_output *output; 19 | wl_list_for_each(output, &server->outputs, link) { 20 | viv_output_damage_layout_coords_region(output, &damage); 21 | } 22 | 23 | pixman_region32_fini(&damage); 24 | } 25 | -------------------------------------------------------------------------------- /src/viv_input.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "viv_config_types.h" 9 | 10 | static void configure_libinput_device(struct libinput_device *device, struct viv_libinput_config *config) { 11 | libinput_device_config_scroll_set_method(device, config->scroll_method); 12 | libinput_device_config_scroll_set_button(device, config->scroll_button); 13 | libinput_device_config_middle_emulation_set_enabled(device, config->middle_emulation); 14 | libinput_device_config_left_handed_set(device, config->left_handed); 15 | libinput_device_config_scroll_set_natural_scroll_enabled(device, config->natural_scroll); 16 | libinput_device_config_dwt_set_enabled(device, config->disable_while_typing); 17 | libinput_device_config_click_set_method(device, config->click_method); 18 | libinput_device_config_tap_set_enabled(device, config->tap_to_click); 19 | libinput_device_config_tap_set_button_map(device, config->tap_button_map); 20 | libinput_device_config_tap_set_button_map(device, config->tap_button_map); 21 | libinput_device_config_accel_set_profile(device, config->accel_profile); 22 | libinput_device_config_accel_set_speed(device, config->accel_speed); 23 | } 24 | 25 | static void try_configure_libinput_device(struct wlr_input_device *device, struct viv_libinput_config *libinput_configs) { 26 | struct libinput_device *li_device = wlr_libinput_get_device_handle(device); 27 | 28 | for (size_t i = 0; i < MAX_NUM_LIBINPUT_CONFIGS; i++) { 29 | struct viv_libinput_config config = libinput_configs[i]; 30 | if (strlen(config.device_name) == 0) { 31 | break; 32 | } 33 | 34 | if (strstr(device->name, config.device_name) != 0) { 35 | configure_libinput_device(li_device, &config); 36 | } 37 | } 38 | } 39 | 40 | void viv_input_configure(struct wlr_input_device *device, struct viv_libinput_config *libinput_configs) { 41 | if (!libinput_configs) { 42 | // No configs, so do nothing 43 | return; 44 | } 45 | 46 | bool is_libinput = wlr_input_device_is_libinput(device); 47 | 48 | if (is_libinput) { 49 | try_configure_libinput_device(device, libinput_configs); 50 | } else { 51 | wlr_log(WLR_DEBUG, "Cannot configure device \"%s\", it isn't a libinput device", device->name); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/viv_ipc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include "viv_types.h" 8 | 9 | static void write_workspaces_file(char *output_filen, struct wl_list *workspaces, struct viv_output *active_output) { 10 | FILE *handle = fopen(output_filen, "w"); 11 | if (handle == NULL) { 12 | wlr_log(WLR_ERROR, "Error opening file \"%s\", skipping", output_filen); 13 | return; 14 | } 15 | 16 | fprintf(handle, " "); 17 | 18 | struct viv_workspace *workspace; 19 | wl_list_for_each(workspace, workspaces, server_link) { 20 | if (workspace->output == NULL) { 21 | fprintf(handle, "%s ", workspace->name); 22 | } else if (workspace->output == active_output) { 23 | fprintf(handle, "<%s> ", workspace->name); 24 | } else { 25 | fprintf(handle, "[%s] ", workspace->name); 26 | } 27 | } 28 | 29 | fprintf(handle, "\n"); 30 | 31 | fclose(handle); 32 | } 33 | 34 | 35 | void viv_routine_log_state(struct viv_server *server) { 36 | char *output_filen = server->config->ipc_workspaces_filename; 37 | if (output_filen == NULL) { 38 | // No output file configured, so just do nothing 39 | return; 40 | } 41 | 42 | if (!server->active_output) { 43 | // Don't try to log anything, we aren't expecting anything to really change 44 | return; 45 | } 46 | 47 | if ((server->log_state.last_active_output == server->active_output) && 48 | (server->log_state.last_active_workspace == server->active_output->current_workspace)) { 49 | // Nothing loggable has changed 50 | return; 51 | } 52 | 53 | server->log_state.last_active_output = server->active_output; 54 | server->log_state.last_active_workspace = server->active_output->current_workspace; 55 | 56 | write_workspaces_file(output_filen, &server->workspaces, server->active_output); 57 | 58 | // If configured, send a signal to the bar program to let it know it should update 59 | if (server->bar_pid && server->config->bar.update_signal_number) { 60 | kill(server->bar_pid, SIGRTMIN + (int)server->config->bar.update_signal_number); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/viv_layout.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "viv_types.h" 25 | #include "viv_view.h" 26 | 27 | static void viv_wl_array_append_view(struct wl_array *array, struct viv_view *view) { 28 | *(struct viv_view **)wl_array_add(array, sizeof(struct viv_view *)) = view; 29 | } 30 | 31 | static uint32_t viv_wl_array_num_views(struct wl_array *array) { 32 | return array->size / sizeof(struct viv_view *); 33 | } 34 | 35 | static void distribute_views(struct wl_array *views, struct wl_array *main_box, struct wl_array *secondary_box, uint32_t main_box_count) { 36 | struct viv_view **view_ptr; 37 | uint32_t main_count = 0; 38 | uint32_t secondary_count = 0; 39 | wl_array_for_each(view_ptr, views) { 40 | struct viv_view *view = *view_ptr; 41 | if (main_count < main_box_count) { 42 | viv_wl_array_append_view(main_box, view); 43 | main_count++; 44 | } else { 45 | viv_wl_array_append_view(secondary_box, view); 46 | secondary_count++; 47 | } 48 | } 49 | } 50 | 51 | /// Layout the views in the given rectangle, one above the other, with heights as equal as possible. 52 | static void layout_views_in_column(struct wl_array *views, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { 53 | struct viv_view *view; 54 | uint32_t num_views = viv_wl_array_num_views(views); 55 | if (!num_views) { 56 | wlr_log(WLR_ERROR, "Asked to layout views in column, but view count was 0?"); 57 | return; 58 | } 59 | 60 | uint32_t view_height = (uint32_t)((float)height / (float)num_views); 61 | uint32_t spare_pixels = height - num_views * view_height; 62 | uint32_t spare_pixels_used = 0; 63 | 64 | uint32_t cur_y = y; 65 | struct viv_view **view_ptr; 66 | wl_array_for_each(view_ptr, views) { 67 | view = *view_ptr; 68 | uint32_t target_height = view_height; 69 | if (spare_pixels) { 70 | spare_pixels--; 71 | spare_pixels_used++; 72 | target_height++; 73 | } 74 | wlr_log(WLR_INFO, "Setting target box x %d y %d width %d height %d (num views %d)", x, cur_y, width, target_height, num_views); 75 | viv_view_set_target_box(view, x, cur_y, width, target_height); 76 | 77 | cur_y += target_height; 78 | } 79 | } 80 | 81 | /// Layout the views in the given rectangle, each next to the others, with widths as equal as possible. 82 | static void layout_views_in_row(struct wl_array *views, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { 83 | struct viv_view *view; 84 | uint32_t num_views = viv_wl_array_num_views(views); 85 | if (!num_views) { 86 | wlr_log(WLR_ERROR, "Asked to layout views in column, but view count was 0?"); 87 | return; 88 | } 89 | 90 | uint32_t view_width = (uint32_t)((float)width / (float)num_views); 91 | uint32_t spare_pixels = width - num_views * view_width; 92 | uint32_t spare_pixels_used = 0; 93 | 94 | uint32_t cur_x = x; 95 | struct viv_view **view_ptr; 96 | wl_array_for_each(view_ptr, views) { 97 | view = *view_ptr; 98 | uint32_t target_width = view_width; 99 | if (spare_pixels) { 100 | spare_pixels--; 101 | spare_pixels_used++; 102 | target_width++; 103 | } 104 | viv_view_set_target_box(view, cur_x, y, target_width, height); 105 | 106 | cur_x += target_width; 107 | } 108 | } 109 | 110 | void viv_layout_do_columns(struct wl_array *views, float float_param, uint32_t counter_param, uint32_t width, uint32_t height) { 111 | UNUSED(float_param); 112 | UNUSED(counter_param); 113 | layout_views_in_row(views, 0, 0, width, height); 114 | } 115 | 116 | 117 | /** 118 | * |--------------|---------| 119 | * | | | 120 | * | | 2 | 121 | * | | | 122 | * | MAIN |----|----| 123 | * | | | 4 | 124 | * | | 3 |----| 125 | * | | | 5 | 126 | * |--------------|----|----| 127 | */ 128 | void viv_layout_do_fibonacci_spiral(struct wl_array *views, float float_param, uint32_t counter_param, uint32_t width, uint32_t height) { 129 | UNUSED(counter_param); 130 | uint32_t num_views = viv_wl_array_num_views(views); 131 | 132 | uint32_t x = 0; 133 | uint32_t y = 0; 134 | uint32_t available_width = width; 135 | uint32_t available_height = height; 136 | 137 | uint32_t view_index = 0; 138 | struct viv_view **view_ptr; 139 | wl_array_for_each(view_ptr, views) { 140 | struct viv_view *view = *view_ptr; 141 | bool is_last_view = (view_index == num_views - 1); 142 | if (view_index % 2 == 0) { 143 | 144 | uint32_t cur_width = (uint32_t)(float_param * available_width); 145 | if (is_last_view) { 146 | cur_width = available_width; 147 | } 148 | 149 | viv_view_set_target_box(view, x, y, cur_width, available_height); 150 | 151 | x += cur_width; 152 | available_width -= cur_width; 153 | } else { 154 | uint32_t cur_height = (uint32_t)(float_param * available_height); 155 | 156 | if (is_last_view) { 157 | cur_height = available_height; 158 | } 159 | 160 | viv_view_set_target_box(view, x, y, available_width, cur_height); 161 | 162 | y += cur_height; 163 | available_height -= cur_height; 164 | } 165 | 166 | view_index++; 167 | } 168 | } 169 | 170 | /** 171 | * |------|----------|------| 172 | * | | | 2 | 173 | * | 3 | |------| 174 | * | | | 4 | 175 | * |------| MAIN | | 176 | * | | |------| 177 | * | 5 | | 6 | 178 | * | | | | 179 | * |------|----------|------| 180 | */ 181 | void viv_layout_do_central_column(struct wl_array *views, float float_param, uint32_t counter_param, uint32_t width, uint32_t height) { 182 | uint32_t num_views = viv_wl_array_num_views(views); 183 | float split_dist = (num_views == 0) ? 1.0 : float_param; 184 | 185 | uint32_t central_column_width = (uint32_t)(width * split_dist); 186 | if (counter_param == 0) { 187 | central_column_width = 0; 188 | } else if (num_views <= counter_param) { 189 | central_column_width = width; 190 | } 191 | 192 | uint32_t num_non_main_views = (counter_param > num_views) ? 0 : num_views - counter_param; 193 | // Do allocation in this order so that if the number of views is 194 | // odd, the extra one ends up in the right column 195 | uint32_t num_right_views = num_non_main_views / 2; 196 | uint32_t num_left_views = num_non_main_views - num_right_views; 197 | 198 | uint32_t remaining_width = width - central_column_width; 199 | uint32_t left_column_width = remaining_width / 2; 200 | uint32_t right_column_width = remaining_width - left_column_width; 201 | if (num_right_views == 0) { 202 | central_column_width += right_column_width; 203 | right_column_width = 0; 204 | } 205 | 206 | struct wl_array main_box, secondary_box; 207 | wl_array_init(&main_box); 208 | wl_array_init(&secondary_box); 209 | distribute_views(views, &main_box, &secondary_box, counter_param); 210 | 211 | layout_views_in_column(&main_box, left_column_width, 0, central_column_width, height); 212 | 213 | 214 | struct wl_array left_box, right_box; 215 | wl_array_init(&left_box); 216 | wl_array_init(&right_box); 217 | 218 | struct viv_view **view_ptr; 219 | uint32_t col_counter_param = 0; 220 | wl_array_for_each(view_ptr, &secondary_box) { 221 | struct viv_view *view = *view_ptr; 222 | if (col_counter_param >= num_left_views) { 223 | viv_wl_array_append_view(&right_box, view); 224 | } else { 225 | viv_wl_array_append_view(&left_box, view); 226 | } 227 | col_counter_param++; 228 | } 229 | 230 | layout_views_in_column(&left_box, 0, 0, left_column_width, height); 231 | layout_views_in_column(&right_box, left_column_width + central_column_width, 0, 232 | right_column_width, height); 233 | 234 | wl_array_release(&main_box); 235 | wl_array_release(&secondary_box); 236 | wl_array_release(&left_box); 237 | wl_array_release(&right_box); 238 | } 239 | 240 | 241 | /** 242 | * |--------| 243 | * | 2 | 244 | * |---| |---| 245 | * |---| |--------| |---| 246 | * | 5 | MAIN | 3 | 247 | * |---| |---| 248 | * |----------------| 249 | * | 4 | 250 | * |--------| 251 | */ 252 | void viv_layout_do_indented_tabs(struct wl_array *views, float float_param, uint32_t counter_param, uint32_t width, uint32_t height) { 253 | UNUSED(views); 254 | UNUSED(float_param); 255 | UNUSED(counter_param); 256 | UNUSED(width); 257 | UNUSED(height); 258 | } 259 | 260 | /** 261 | * |------------------------| 262 | * | | 263 | * | | 264 | * | | 265 | * | MAIN | 266 | * | | 267 | * | | 268 | * | | 269 | * |------------------------| 270 | */ 271 | void viv_layout_do_fullscreen(struct wl_array *views, float float_param, uint32_t counter_param, uint32_t width, uint32_t height) { 272 | UNUSED(float_param); 273 | UNUSED(counter_param); 274 | struct viv_view **view_ptr; 275 | wl_array_for_each(view_ptr, views) { 276 | struct viv_view *view = *view_ptr; 277 | viv_view_set_target_box(view, 0, 0, width, height); 278 | } 279 | } 280 | 281 | /** 282 | * |--------------|---------| 283 | * | | 2 | 284 | * | |---------| 285 | * | | 3 | 286 | * | MAIN |---------| 287 | * | | 4 | 288 | * | |---------| 289 | * | | 5 | 290 | * |--------------|---------| 291 | */ 292 | void viv_layout_do_split(struct wl_array *views, float float_param, uint32_t counter_param, uint32_t width, uint32_t height) { 293 | uint32_t num_views = viv_wl_array_num_views(views); 294 | float split_dist = (num_views == 0) ? 1.0 : float_param; 295 | 296 | uint32_t split_pixel = (uint32_t)(width * split_dist); 297 | if (counter_param == 0) { 298 | split_pixel = 0; 299 | } else if (num_views <= counter_param) { 300 | split_pixel = width; 301 | } 302 | 303 | struct wl_array main_box, secondary_box; 304 | wl_array_init(&main_box); 305 | wl_array_init(&secondary_box); 306 | distribute_views(views, &main_box, &secondary_box, counter_param); 307 | 308 | layout_views_in_column(&main_box, 0, 0, split_pixel, height); 309 | if (num_views > counter_param) { 310 | layout_views_in_column(&secondary_box, split_pixel, 0, width - split_pixel, height); 311 | } 312 | 313 | wl_array_release(&main_box); 314 | wl_array_release(&secondary_box); 315 | } 316 | 317 | void viv_layout_apply(struct viv_workspace *workspace, uint32_t width, uint32_t height) { 318 | struct wl_array views_array; 319 | wl_array_init(&views_array); 320 | struct viv_view *view; 321 | wl_list_for_each(view, &workspace->views, workspace_link) { 322 | // Pull out only the non-floating views to be laid out 323 | if (view->is_floating || (view->workspace->fullscreen_view == view)) { 324 | continue; 325 | } 326 | viv_wl_array_append_view(&views_array, view); 327 | } 328 | 329 | float float_param = workspace->active_layout->parameter; 330 | uint32_t counter_param = workspace->active_layout->counter; 331 | 332 | workspace->active_layout->layout_function(&views_array, float_param, counter_param, width, height); 333 | } 334 | -------------------------------------------------------------------------------- /src/viv_mappable_functions.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "viv_mappable_functions.h" 13 | 14 | #include "viv_output.h" 15 | #include "viv_server.h" 16 | #include "viv_types.h" 17 | #include "viv_view.h" 18 | #include "viv_workspace.h" 19 | 20 | void viv_mappable_do_exec(struct viv_workspace *workspace, union viv_mappable_payload payload) { 21 | UNUSED(workspace); 22 | wlr_log(WLR_DEBUG, "Mappable do_exec %s", payload.do_exec.executable); 23 | 24 | pid_t p; 25 | if ((p = fork()) == 0) { 26 | setsid(); 27 | freopen("/dev/null", "w", stdout); 28 | freopen("/dev/null", "w", stderr); 29 | execvp(payload.do_exec.executable, payload.do_exec.args); 30 | _exit(EXIT_FAILURE); 31 | } 32 | } 33 | 34 | void viv_mappable_do_shell(struct viv_workspace *workspace, union viv_mappable_payload payload) { 35 | UNUSED(workspace); 36 | wlr_log(WLR_DEBUG, "Mappable do_shell %s", payload.do_shell.command); 37 | 38 | pid_t p; 39 | if ((p = fork()) == 0) { 40 | setsid(); 41 | freopen("/dev/null", "w", stdout); 42 | freopen("/dev/null", "w", stderr); 43 | execl("/bin/sh", "/bin/sh", "-c", payload.do_shell.command, (void *)NULL); 44 | _exit(EXIT_FAILURE); 45 | } 46 | } 47 | 48 | void viv_mappable_increment_divide(struct viv_workspace *workspace, union viv_mappable_payload payload) { 49 | wlr_log(WLR_DEBUG, "Mappable increment divide by %f", payload.increment_divide.increment); 50 | viv_workspace_increment_divide(workspace, payload.increment_divide.increment); 51 | } 52 | 53 | void viv_mappable_increment_counter(struct viv_workspace *workspace, union viv_mappable_payload payload) { 54 | wlr_log(WLR_DEBUG, "Mappable increment counter by %d", payload.increment_counter.increment); 55 | viv_workspace_increment_counter(workspace, payload.increment_counter.increment); 56 | } 57 | 58 | void viv_mappable_terminate(struct viv_workspace *workspace, union viv_mappable_payload payload) { 59 | UNUSED(payload); 60 | wlr_log(WLR_DEBUG, "Mappable terminate"); 61 | wl_display_terminate(workspace->server->wl_display); 62 | } 63 | 64 | void viv_mappable_next_window(struct viv_workspace *workspace, union viv_mappable_payload payload) { 65 | UNUSED(payload); 66 | wlr_log(WLR_DEBUG, "Mappable next_window"); 67 | viv_workspace_focus_next_window(workspace); 68 | } 69 | 70 | void viv_mappable_prev_window(struct viv_workspace *workspace, union viv_mappable_payload payload) { 71 | UNUSED(payload); 72 | wlr_log(WLR_DEBUG, "Mappable prev_window"); 73 | viv_workspace_focus_prev_window(workspace); 74 | } 75 | 76 | void viv_mappable_shift_active_window_down(struct viv_workspace *workspace, union viv_mappable_payload payload) { 77 | UNUSED(payload); 78 | wlr_log(WLR_DEBUG, "Mappable shift_window_down"); 79 | viv_workspace_shift_active_window_down(workspace); 80 | } 81 | 82 | void viv_mappable_shift_active_window_up(struct viv_workspace *workspace, union viv_mappable_payload payload) { 83 | UNUSED(payload); 84 | wlr_log(WLR_DEBUG, "Mappable shift_window_up"); 85 | viv_workspace_shift_active_window_up(workspace); 86 | } 87 | 88 | void viv_mappable_tile_window(struct viv_workspace *workspace, union viv_mappable_payload payload) { 89 | UNUSED(payload); 90 | wlr_log(WLR_DEBUG, "Mappable tile_window"); 91 | 92 | struct viv_view *view = workspace->active_view; 93 | if (view == NULL) { 94 | wlr_log(WLR_DEBUG, "Cannot tile active view, no view is active"); 95 | return; 96 | } 97 | 98 | if (!view->is_floating) { 99 | wlr_log(WLR_DEBUG, "Cannot tile active view, it is not floating"); 100 | return; 101 | } 102 | 103 | viv_view_damage(view); 104 | 105 | bool any_not_floating = false; 106 | struct viv_view *non_floating_view; 107 | wl_list_for_each(non_floating_view, &workspace->views, workspace_link) { 108 | if (!non_floating_view->is_floating) { 109 | any_not_floating = true; 110 | break; 111 | } 112 | } 113 | 114 | viv_view_ensure_tiled(view); 115 | 116 | wl_list_remove(&view->workspace_link); 117 | if (any_not_floating) { 118 | // Insert right before the first non-floating view 119 | wl_list_insert(non_floating_view->workspace_link.prev, &view->workspace_link); 120 | } else { 121 | // Move to the end of the views (as all are floating) 122 | wl_list_insert(workspace->views.prev, &view->workspace_link); 123 | } 124 | 125 | viv_workspace_mark_for_relayout(workspace); 126 | } 127 | 128 | void viv_mappable_float_window(struct viv_workspace *workspace, union viv_mappable_payload payload) { 129 | UNUSED(payload); 130 | wlr_log(WLR_DEBUG, "Mappable float_window"); 131 | 132 | struct viv_view *view = workspace->active_view; 133 | if (view == NULL) { 134 | wlr_log(WLR_DEBUG, "Cannot float active view, no view is active"); 135 | return; 136 | } 137 | 138 | if (view->is_floating) { 139 | wlr_log(WLR_DEBUG, "Cannot float active view, it is already floating"); 140 | return; 141 | } 142 | 143 | if (view->workspace->fullscreen_view == view) { 144 | wlr_log(WLR_DEBUG, "Cannot float active view, it is fullscreen"); 145 | return; 146 | } 147 | 148 | viv_view_damage(view); 149 | 150 | wl_list_remove(&view->workspace_link); 151 | 152 | bool any_not_floating = false; 153 | struct viv_view *non_floating_view; 154 | wl_list_for_each(non_floating_view, &workspace->views, workspace_link) { 155 | if (!non_floating_view->is_floating) { 156 | any_not_floating = true; 157 | break; 158 | } 159 | } 160 | 161 | viv_view_ensure_floating(view); 162 | 163 | if (any_not_floating) { 164 | // Insert right before the first non-floating view 165 | wl_list_insert(non_floating_view->workspace_link.prev, &view->workspace_link); 166 | } else { 167 | // Move to the start of the views (as all are floating) 168 | wl_list_insert(workspace->views.next, &view->workspace_link); 169 | } 170 | 171 | viv_workspace_mark_for_relayout(workspace); 172 | } 173 | 174 | void viv_mappable_toggle_floating(struct viv_workspace *workspace, union viv_mappable_payload payload) { 175 | wlr_log(WLR_DEBUG, "Mappable toggle_floating"); 176 | struct viv_view *view = workspace->active_view; 177 | if (!view) { 178 | wlr_log(WLR_DEBUG, "Cannot toggle floating: no view is active"); 179 | return; 180 | } 181 | 182 | if (view->is_floating) { 183 | viv_mappable_tile_window(workspace, payload); 184 | } else { 185 | viv_mappable_float_window(workspace, payload); 186 | } 187 | } 188 | 189 | void viv_mappable_next_layout(struct viv_workspace *workspace, union viv_mappable_payload payload) { 190 | UNUSED(payload); 191 | wlr_log(WLR_DEBUG, "Mappable next_layout"); 192 | viv_workspace_next_layout(workspace); 193 | } 194 | 195 | void viv_mappable_prev_layout(struct viv_workspace *workspace, union viv_mappable_payload payload) { 196 | UNUSED(payload); 197 | wlr_log(WLR_DEBUG, "Mappable prev_layout"); 198 | viv_workspace_prev_layout(workspace); 199 | } 200 | 201 | void viv_mappable_user_function(struct viv_workspace *workspace, union viv_mappable_payload payload) { 202 | wlr_log(WLR_DEBUG, "Mappable user_function"); 203 | 204 | // Pass through the call to the user-provided function, but without the pointless payload argument 205 | (*payload.user_function.function)(workspace); 206 | } 207 | 208 | void viv_mappable_right_output(struct viv_workspace *workspace, union viv_mappable_payload payload) { 209 | UNUSED(payload); 210 | wlr_log(WLR_DEBUG, "Mappable right_output"); 211 | struct viv_output *cur_output = workspace->output; 212 | 213 | struct viv_output *next_output = viv_output_next_in_direction(cur_output, WLR_DIRECTION_RIGHT); 214 | viv_output_make_active(next_output); 215 | } 216 | 217 | void viv_mappable_left_output(struct viv_workspace *workspace, union viv_mappable_payload payload) { 218 | UNUSED(payload); 219 | wlr_log(WLR_DEBUG, "Mappable left_output"); 220 | struct viv_output *cur_output = workspace->output; 221 | 222 | struct viv_output *next_output = viv_output_next_in_direction(cur_output, WLR_DIRECTION_LEFT); 223 | viv_output_make_active(next_output); 224 | } 225 | 226 | void viv_mappable_shift_active_window_to_right_output(struct viv_workspace *workspace, union viv_mappable_payload payload) { 227 | UNUSED(payload); 228 | wlr_log(WLR_DEBUG, "Mappable shift_active_window_to_right_output"); 229 | struct viv_output *cur_output = workspace->output; 230 | 231 | struct viv_output *next_output = viv_output_next_in_direction(cur_output, WLR_DIRECTION_RIGHT); 232 | struct viv_view *view = workspace->active_view; 233 | 234 | if (!next_output) { 235 | wlr_log(WLR_DEBUG, "Asked to shift to left output but couldn't find one in that direction"); 236 | } else if (!view) { 237 | wlr_log(WLR_DEBUG, "No active view to shift"); 238 | } else { 239 | viv_view_shift_to_workspace(workspace->active_view, next_output->current_workspace); 240 | } 241 | } 242 | 243 | void viv_mappable_shift_active_window_to_left_output(struct viv_workspace *workspace, union viv_mappable_payload payload) { 244 | UNUSED(payload); 245 | wlr_log(WLR_DEBUG, "Mappable shift_active_window_to_left_output"); 246 | struct viv_output *cur_output = workspace->output; 247 | 248 | struct viv_output *next_output = viv_output_next_in_direction(cur_output, WLR_DIRECTION_LEFT); 249 | struct viv_view *view = workspace->active_view; 250 | 251 | if (!next_output) { 252 | wlr_log(WLR_DEBUG, "Asked to shift to left output but couldn't find one in that direction"); 253 | } else if (!view) { 254 | wlr_log(WLR_DEBUG, "No active view to shift"); 255 | } else { 256 | viv_view_shift_to_workspace(workspace->active_view, next_output->current_workspace); 257 | } 258 | } 259 | 260 | void viv_mappable_shift_active_window_to_workspace(struct viv_workspace *workspace, union viv_mappable_payload payload) { 261 | UNUSED(payload); 262 | char *name = payload.shift_active_window_to_workspace.workspace_name; 263 | wlr_log(WLR_DEBUG, "Mappable shift_active_window_to_workspace with name %s", name); 264 | 265 | struct viv_output *cur_output = workspace->output; 266 | struct viv_view *cur_view = workspace->active_view; 267 | 268 | if (!cur_view) { 269 | wlr_log(WLR_DEBUG, "No active view, cannot shift to workspace %s", name); 270 | return; 271 | } 272 | 273 | struct viv_workspace *target_workspace = viv_server_retrieve_workspace_by_name(cur_output->server, name); 274 | ASSERT(target_workspace != NULL); 275 | 276 | viv_view_shift_to_workspace(cur_view, target_workspace); 277 | } 278 | 279 | void viv_mappable_remove_fullscreen(struct viv_workspace *workspace, union viv_mappable_payload payload) { 280 | char *name = payload.shift_active_window_to_workspace.workspace_name; 281 | wlr_log(WLR_DEBUG, "Mappable remove_workspace_fullscreen with name %s", name); 282 | 283 | if (!workspace->fullscreen_view) { 284 | wlr_log(WLR_DEBUG, "No fullscreen view, cannot remove attribute from workspace %s", name); 285 | return; 286 | } 287 | 288 | viv_view_force_remove_fullscreen(workspace->fullscreen_view); 289 | viv_workspace_mark_for_relayout(workspace); 290 | } 291 | 292 | void viv_mappable_switch_to_workspace(struct viv_workspace *workspace, union viv_mappable_payload payload) { 293 | UNUSED(workspace); 294 | 295 | struct viv_server *server = workspace->server; 296 | 297 | char *workspace_name = payload.switch_to_workspace.workspace_name; 298 | struct viv_workspace *target_workspace = viv_server_retrieve_workspace_by_name(server, workspace_name); 299 | 300 | viv_output_display_workspace(workspace->output, target_workspace); 301 | } 302 | 303 | void viv_mappable_close_window(struct viv_workspace *workspace, union viv_mappable_payload payload) { 304 | UNUSED(payload); 305 | struct viv_view *view = workspace->active_view; 306 | if (!view) { 307 | wlr_log(WLR_DEBUG, "Cannot close window, active window is NULL"); 308 | return; 309 | } 310 | viv_view_request_close(view); 311 | } 312 | 313 | void viv_mappable_make_window_main(struct viv_workspace *workspace, union viv_mappable_payload payload) { 314 | UNUSED(payload); 315 | viv_workspace_swap_active_and_main(workspace); 316 | } 317 | 318 | void viv_mappable_reload_config(struct viv_workspace *workspace, union viv_mappable_payload payload) { 319 | UNUSED(payload); 320 | struct viv_server *server = workspace->server; 321 | viv_server_reload_config(server); 322 | } 323 | 324 | void viv_mappable_debug_damage_all(struct viv_workspace *workspace, union viv_mappable_payload payload) { 325 | UNUSED(payload); 326 | struct viv_server *server = workspace->server; 327 | 328 | struct viv_output *output; 329 | wl_list_for_each(output, &server->outputs, link) { 330 | viv_output_damage(output); 331 | } 332 | } 333 | 334 | void viv_mappable_debug_swap_buffers(struct viv_workspace *workspace, union viv_mappable_payload payload) { 335 | UNUSED(payload); 336 | wlr_output_commit(workspace->output->wlr_output); 337 | } 338 | 339 | void viv_mappable_debug_toggle_show_undamaged_regions(struct viv_workspace *workspace, union viv_mappable_payload payload) { 340 | UNUSED(payload); 341 | workspace->server->config->debug_mark_undamaged_regions = !workspace->server->config->debug_mark_undamaged_regions; 342 | struct viv_output *output; 343 | wl_list_for_each(output, &workspace->server->outputs, link) { 344 | viv_output_damage(output); 345 | } 346 | } 347 | 348 | void viv_mappable_debug_toggle_mark_frame_draws(struct viv_workspace *workspace, union viv_mappable_payload payload) { 349 | UNUSED(payload); 350 | workspace->server->config->debug_mark_frame_draws = !workspace->server->config->debug_mark_frame_draws; 351 | } 352 | 353 | void viv_mappable_debug_next_damage_tracking_mode(struct viv_workspace *workspace, union viv_mappable_payload payload) { 354 | UNUSED(payload); 355 | struct viv_config *config = workspace->server->config; 356 | 357 | config->damage_tracking_mode++; 358 | if (config->damage_tracking_mode == VIV_DAMAGE_TRACKING_MAX) { 359 | config->damage_tracking_mode = (enum viv_damage_tracking_mode)0; 360 | } 361 | } 362 | -------------------------------------------------------------------------------- /src/viv_output.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "viv_output.h" 7 | 8 | #include "viv_cursor.h" 9 | #include "viv_ipc.h" 10 | #include "viv_layer_view.h" 11 | #include "viv_render.h" 12 | #include "viv_server.h" 13 | #include "viv_types.h" 14 | #include "viv_view.h" 15 | #include "viv_workspace.h" 16 | 17 | /// Start using the output, i.e. add it to our output layout and draw a workspace on it 18 | static void start_using_output(struct viv_output *output) { 19 | struct viv_server *server = output->server; 20 | wl_list_insert(&server->outputs, &output->link); 21 | 22 | struct viv_workspace *current_workspace; 23 | wl_list_for_each(current_workspace, &server->workspaces, server_link) { 24 | if (current_workspace->output == NULL) { 25 | wlr_log(WLR_INFO, "Assigning new output workspace %s", current_workspace->name); 26 | viv_workspace_assign_to_output(current_workspace, output); 27 | break; 28 | } 29 | } 30 | 31 | wlr_output_layout_add_auto(server->output_layout, output->wlr_output); 32 | } 33 | 34 | /// Remove the output from our output layout, revoke its workspace assignation, and clean 35 | /// any other references to it. 36 | static void stop_using_output(struct viv_output *output) { 37 | struct viv_server *server = output->server; 38 | wl_list_remove(&output->link); 39 | 40 | if (server->active_output == output) { 41 | server->active_output = NULL; 42 | 43 | // Set a new active output if any are available 44 | struct viv_output *new_active_output; 45 | wl_list_for_each(new_active_output, &server->outputs, link) { 46 | viv_output_make_active(new_active_output); 47 | break; 48 | } 49 | } 50 | 51 | if (server->log_state.last_active_output == output) { 52 | server->log_state.last_active_output = NULL; 53 | } 54 | 55 | struct viv_workspace *current_workspace; 56 | wl_list_for_each(current_workspace, &server->workspaces, server_link) { 57 | if (current_workspace->output == output) { 58 | wlr_log(WLR_INFO, "Clearing output for workspace %s", current_workspace->name); 59 | current_workspace->output = NULL; 60 | break; 61 | } 62 | } 63 | output->current_workspace = NULL; 64 | 65 | wlr_output_layout_remove(server->output_layout, output->wlr_output); 66 | 67 | // Clean up layer views last, to ensure that none of the cleanup tries to access 68 | // still-initialised output state 69 | struct viv_layer_view *layer_view; 70 | wl_list_for_each(layer_view, &output->layer_views, output_link){ 71 | layer_view->output = NULL; 72 | wlr_layer_surface_v1_destroy(layer_view->layer_surface); 73 | } 74 | 75 | } 76 | 77 | /// Handle a render frame event: render everything on the output, then do any scheduled relayouts 78 | static void output_frame(struct wl_listener *listener, void *data) { 79 | UNUSED(data); 80 | 81 | // This has been called because a specific output is ready to display a frame, 82 | // retrieve this info 83 | struct viv_output *output = wl_container_of(listener, output, frame); 84 | struct wlr_renderer *renderer = output->server->renderer; 85 | 86 | #ifdef DEBUG 87 | viv_check_data_consistency(output->server); 88 | #endif 89 | 90 | viv_render_output(renderer, output); 91 | 92 | // If the workspace has been been relayout recently, reset the pointer focus just in 93 | // case surfaces have changed size since the last frame 94 | // TODO: There must be a better way to do this 95 | struct viv_workspace *workspace = output->current_workspace; 96 | if (workspace->was_laid_out) { 97 | struct timespec now; 98 | clock_gettime(CLOCK_MONOTONIC, &now); 99 | viv_cursor_reset_focus(workspace->server, (int64_t)now.tv_sec * 1000 + now.tv_nsec / 1000000); 100 | workspace->was_laid_out = false; 101 | } 102 | 103 | // TODO this probably shouldn't be here? For now do layout right after committing a 104 | // frame, to give time for clients to re-draw before the next one. There's probably a 105 | // better way to do this. 106 | viv_output_do_layout_if_necessary(output); 107 | 108 | viv_routine_log_state(output->server); 109 | } 110 | 111 | static void output_damage_event(struct wl_listener *listener, void *data) { 112 | UNUSED(data); 113 | struct viv_output *output = wl_container_of(listener, output, damage_event); 114 | wlr_log(WLR_INFO, "Output \"%s\" event: damage", output->wlr_output->name); 115 | } 116 | 117 | static void output_present(struct wl_listener *listener, void *data) { 118 | UNUSED(listener); 119 | UNUSED(data); 120 | } 121 | 122 | static void output_enable(struct wl_listener *listener, void *data) { 123 | UNUSED(data); 124 | struct viv_output *output = wl_container_of(listener, output, enable); 125 | 126 | wlr_log(WLR_INFO, "Output \"%s\" event: enable became %d", output->wlr_output->name, output->wlr_output->enabled); 127 | } 128 | 129 | static void output_mode(struct wl_listener *listener, void *data) { 130 | UNUSED(data); 131 | struct viv_output *output = wl_container_of(listener, output, mode); 132 | wlr_log(WLR_INFO, "Output \"%s\" event: mode", output->wlr_output->name); 133 | } 134 | 135 | static void output_destroy(struct wl_listener *listener, void *data) { 136 | UNUSED(data); 137 | struct viv_output *output = wl_container_of(listener, output, destroy); 138 | wlr_log(WLR_INFO, "Output \"%s\" event: destroy", output->wlr_output->name); 139 | 140 | stop_using_output(output); 141 | 142 | wl_list_remove(&output->frame.link); 143 | wl_list_remove(&output->damage_event.link); 144 | wl_list_remove(&output->present.link); 145 | wl_list_remove(&output->enable.link); 146 | wl_list_remove(&output->mode.link); 147 | wl_list_remove(&output->destroy.link); 148 | 149 | free(output); 150 | } 151 | 152 | struct viv_output *viv_output_at(struct viv_server *server, double lx, double ly) { 153 | 154 | struct wlr_output *wlr_output_at_point = wlr_output_layout_output_at(server->output_layout, lx, ly); 155 | 156 | struct viv_output *output; 157 | wl_list_for_each(output, &server->outputs, link) { 158 | if (output->wlr_output == wlr_output_at_point) { 159 | return output; 160 | } 161 | } 162 | return NULL; 163 | } 164 | 165 | void viv_output_make_active(struct viv_output *output) { 166 | if (output == NULL) { 167 | // It's acceptable to be asked to make a NULL output active 168 | // TODO: is it really? 169 | return; 170 | } 171 | 172 | if (output->server->active_output == output) { 173 | // Nothing to do 174 | return; 175 | } 176 | 177 | if (output->server->active_output) { 178 | viv_output_damage(output->server->active_output); 179 | } 180 | output->server->active_output = output; 181 | viv_output_damage(output->server->active_output); 182 | 183 | if (output->current_workspace->active_view) { 184 | viv_view_focus(output->current_workspace->active_view); 185 | } 186 | } 187 | 188 | struct viv_output *viv_output_of_wlr_output(struct viv_server *server, struct wlr_output *wlr_output) { 189 | struct viv_output *output = NULL; 190 | wl_list_for_each(output, &server->outputs, link) { 191 | if (output->wlr_output == wlr_output) { 192 | return output; 193 | } 194 | } 195 | return NULL; 196 | } 197 | 198 | 199 | struct viv_output *viv_output_next_in_direction(struct viv_output *output, enum wlr_direction direction) { 200 | struct viv_server *server = output->server; 201 | struct wlr_output *cur_wlr_output = output->wlr_output; 202 | struct wlr_output_layout_output *cur_wlr_output_layout_output = wlr_output_layout_get(server->output_layout, 203 | cur_wlr_output); 204 | 205 | struct wlr_output *new_wlr_output = wlr_output_layout_adjacent_output( 206 | server->output_layout, 207 | direction, 208 | cur_wlr_output, 209 | cur_wlr_output_layout_output->x, 210 | cur_wlr_output_layout_output->y); 211 | 212 | struct viv_output *new_output = viv_output_of_wlr_output(server, new_wlr_output); 213 | 214 | return new_output; 215 | } 216 | 217 | void viv_output_display_workspace(struct viv_output *output, struct viv_workspace *workspace) { 218 | 219 | struct viv_output *other_output = workspace->output; 220 | 221 | if (other_output == output) { 222 | wlr_log(WLR_DEBUG, "Cannot switch to workspace %s, it is already being displayed on this output", 223 | workspace->name); 224 | return; 225 | } 226 | 227 | if (other_output != NULL) { 228 | other_output->current_workspace = output->current_workspace; 229 | other_output->current_workspace->output = other_output; 230 | viv_output_mark_for_relayout(other_output); 231 | } else { 232 | output->current_workspace->output = NULL; 233 | } 234 | 235 | output->current_workspace = workspace; 236 | output->current_workspace->output = output; 237 | viv_output_mark_for_relayout(output); 238 | 239 | if (workspace->active_view) { 240 | viv_view_focus(workspace->active_view); 241 | } else { 242 | viv_view_clear_all_focus(output->server); 243 | } 244 | 245 | struct timespec now; 246 | clock_gettime(CLOCK_MONOTONIC, &now); 247 | viv_cursor_reset_focus(workspace->server, (int64_t)now.tv_sec * 1000 + now.tv_nsec / 1000000); 248 | } 249 | 250 | void viv_output_init(struct viv_output *output, struct viv_server *server, struct wlr_output *wlr_output) { 251 | wl_list_init(&output->layer_views); 252 | 253 | output->wlr_output = wlr_output; 254 | output->server = server; 255 | 256 | output->excluded_margin.top = 0; 257 | output->excluded_margin.bottom = 0; 258 | output->excluded_margin.left = 0; 259 | output->excluded_margin.right = 0; 260 | 261 | output->damage = wlr_output_damage_create(output->wlr_output); 262 | 263 | output->frame.notify = output_frame; 264 | wl_signal_add(&output->damage->events.frame, &output->frame); 265 | 266 | output->damage_event.notify = output_damage_event; 267 | wl_signal_add(&output->wlr_output->events.damage, &output->damage_event); 268 | output->present.notify = output_present; 269 | wl_signal_add(&output->wlr_output->events.present, &output->present); 270 | output->enable.notify = output_enable; 271 | wl_signal_add(&output->wlr_output->events.enable, &output->enable); 272 | output->mode.notify = output_mode; 273 | wl_signal_add(&output->wlr_output->events.mode, &output->mode); 274 | output->destroy.notify = output_destroy; 275 | wl_signal_add(&output->wlr_output->events.destroy, &output->destroy); 276 | 277 | wlr_log(WLR_INFO, "New output width width %d, height %d", wlr_output->width, wlr_output->height); 278 | 279 | start_using_output(output); 280 | } 281 | 282 | void viv_output_do_layout_if_necessary(struct viv_output *output) { 283 | struct viv_workspace *workspace = output->current_workspace; 284 | if (!(output->needs_layout | workspace->needs_layout)) { 285 | return; 286 | } 287 | 288 | viv_layers_arrange(output); 289 | viv_workspace_do_layout(workspace); 290 | } 291 | 292 | void viv_output_damage(struct viv_output *output) { 293 | if (!output) { 294 | wlr_log(WLR_ERROR, "Tried to damage NULL output"); 295 | return; 296 | } 297 | wlr_output_damage_add_whole(output->damage); 298 | } 299 | 300 | void viv_output_damage_layout_coords_box(struct viv_output *output, struct wlr_box *box) { 301 | struct wlr_box scaled_box; 302 | memcpy(&scaled_box, box, sizeof(struct wlr_box)); 303 | 304 | double lx = 0, ly = 0; 305 | wlr_output_layout_output_coords(output->server->output_layout, output->wlr_output, &lx, &ly); 306 | 307 | scaled_box.x += lx; 308 | scaled_box.y += ly; 309 | 310 | float scale = output->wlr_output->scale; 311 | 312 | // TODO: Can we just round these rather than floor/ceil? Need to think through how 313 | // scaling actually works out on different outputs 314 | scaled_box.x = floor(scaled_box.x * scale); 315 | scaled_box.y = floor(scaled_box.y * scale); 316 | scaled_box.width = ceil((scaled_box.x + scaled_box.width) * scale) - floor(scaled_box.x * scale); 317 | scaled_box.height = ceil((scaled_box.y + scaled_box.height) * scale) - floor(scaled_box.y * scale); 318 | 319 | wlr_output_damage_add_box(output->damage, &scaled_box); 320 | } 321 | 322 | void viv_output_damage_layout_coords_region(struct viv_output *output, pixman_region32_t *damage) { 323 | double lx = 0, ly = 0; 324 | wlr_output_layout_output_coords(output->server->output_layout, output->wlr_output, &lx, &ly); 325 | 326 | // Shift to output coords to apply damage 327 | pixman_region32_translate(damage, lx, ly); 328 | 329 | wlr_output_damage_add(output->damage, damage); 330 | 331 | // Shift back to avoid permanently changing the damage 332 | pixman_region32_translate(damage, -lx, -ly); 333 | } 334 | 335 | void viv_output_layout_coords_box_to_output_coords(struct viv_output *output, struct wlr_box *geo_box) { 336 | double lx = 0, ly = 0; 337 | wlr_output_layout_output_coords(output->server->output_layout, output->wlr_output, &lx, &ly); 338 | geo_box->x += lx; 339 | geo_box->y += ly; 340 | } 341 | 342 | void viv_output_mark_for_relayout(struct viv_output *output) { 343 | if (output) { 344 | // The layout will be applied after the next frame 345 | output->needs_layout = true; 346 | viv_output_damage(output); 347 | } else { 348 | wlr_log(WLR_ERROR, "Tried to mark NULL output for relayout"); 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /src/viv_view.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "viv_view.h" 11 | 12 | #include "viv_output.h" 13 | #include "viv_seat.h" 14 | #include "viv_server.h" 15 | #include "viv_types.h" 16 | #include "viv_wl_list_utils.h" 17 | #include "viv_workspace.h" 18 | #include "viv_wlr_surface_tree.h" 19 | 20 | #define VIEW_NAME_LEN 100 21 | 22 | void viv_view_bring_to_front(struct viv_view *view) { 23 | struct wl_list *link = &view->workspace_link; 24 | wl_list_remove(link); 25 | wl_list_insert(&view->workspace->views, link); 26 | } 27 | 28 | void viv_view_clear_all_focus(struct viv_server *server) { 29 | struct viv_seat *seat = viv_server_get_default_seat(server); 30 | viv_seat_clear_focus(seat); 31 | } 32 | 33 | void viv_view_focus(struct viv_view *view) { 34 | if (view == NULL) { 35 | return; 36 | } 37 | struct viv_server *server = view->server; 38 | 39 | // Damage both previous and newly-active surface 40 | // TODO: only damage the borders 41 | if (view->workspace->active_view) { 42 | viv_view_damage(view->workspace->active_view); 43 | } 44 | viv_view_damage(view); 45 | 46 | /* Activate the new surface */ 47 | view->workspace->active_view = view; 48 | if (server->active_output->current_workspace == view->workspace) { 49 | // Prevent focus from leaving current workspace 50 | viv_seat_focus_view(viv_server_get_default_seat(server), view); 51 | } 52 | } 53 | 54 | struct wlr_surface *viv_view_get_toplevel_surface(struct viv_view *view) { 55 | return view->implementation->get_toplevel_surface(view); 56 | } 57 | 58 | void viv_view_ensure_floating(struct viv_view *view) { 59 | if (!view->is_floating) { 60 | // Trigger a relayout only if tiling state is changing 61 | viv_workspace_mark_for_relayout(view->workspace); 62 | } 63 | view->is_floating = true; 64 | 65 | /* // Tell the view it doesn't need to worry about tiling */ 66 | /* wlr_xdg_toplevel_set_tiled(view->xdg_surface, 0u); */ 67 | } 68 | 69 | void viv_view_ensure_tiled(struct viv_view *view) { 70 | if (view->is_floating) { 71 | // Trigger a relayout only if tiling state is changing 72 | viv_workspace_mark_for_relayout(view->workspace); 73 | } 74 | view->is_floating = false; 75 | 76 | uint32_t all_edges = WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT | WLR_EDGE_LEFT; 77 | view->implementation->set_tiled(view, all_edges); 78 | } 79 | 80 | void viv_view_shift_to_workspace(struct viv_view *view, struct viv_workspace *workspace) { 81 | if (view->workspace == workspace) { 82 | wlr_log(WLR_DEBUG, "Asked to shift view to workspace that view was already in, doing nothing"); 83 | return; 84 | } 85 | 86 | char view_name[VIEW_NAME_LEN]; 87 | viv_view_get_string_identifier(view, view_name, VIEW_NAME_LEN); 88 | wlr_log(WLR_DEBUG, "Shifting view %s to workspace with name %s", view_name, workspace->name); 89 | 90 | struct viv_workspace *cur_workspace = view->workspace; 91 | 92 | struct viv_view *next_view = NULL; 93 | if (wl_list_length(&cur_workspace->views) > 1) { 94 | struct wl_list *next_view_link = view->workspace_link.next; 95 | if (next_view_link == &cur_workspace->views) { 96 | next_view_link = next_view_link->next; 97 | } 98 | next_view = wl_container_of(next_view_link, next_view, workspace_link); 99 | } 100 | 101 | wl_list_remove(&view->workspace_link); 102 | wl_list_insert(&workspace->views, &view->workspace_link); 103 | 104 | if (next_view != NULL) { 105 | viv_view_focus(next_view); 106 | } else { 107 | viv_view_clear_all_focus(view->server); 108 | } 109 | 110 | viv_workspace_mark_for_relayout(cur_workspace); 111 | viv_workspace_mark_for_relayout(workspace); 112 | 113 | cur_workspace->active_view = next_view; 114 | if (workspace->active_view == NULL) { 115 | workspace->active_view = view; 116 | } 117 | 118 | if (cur_workspace->fullscreen_view == view) { 119 | if (workspace->fullscreen_view) { 120 | wlr_log(WLR_DEBUG, "Removing fullscreen from %s. New workspace already has a fullscreen view", view_name); 121 | viv_view_force_remove_fullscreen(view); 122 | } else { 123 | workspace->fullscreen_view = view; 124 | workspace->active_view = view; 125 | } 126 | 127 | cur_workspace->fullscreen_view = NULL; 128 | } 129 | 130 | view->workspace = workspace; 131 | } 132 | 133 | struct viv_view *viv_view_next_in_workspace(struct viv_view *view) { 134 | struct viv_workspace *workspace = view->workspace; 135 | 136 | struct viv_view *next_view; 137 | if (workspace->fullscreen_view) { 138 | next_view = workspace->fullscreen_view; 139 | } else if (wl_list_length(&workspace->views) > 1) { 140 | struct wl_list *next_link = viv_wl_list_next_ignoring_root(&view->workspace_link, &workspace->views); 141 | next_view = wl_container_of(next_link, next_view, workspace_link); 142 | } else { 143 | next_view = view; 144 | } 145 | 146 | return next_view; 147 | } 148 | 149 | struct viv_view *viv_view_prev_in_workspace(struct viv_view *view) { 150 | struct viv_workspace *workspace = view->workspace; 151 | 152 | struct viv_view *prev_view; 153 | if (workspace->fullscreen_view) { 154 | prev_view = workspace->fullscreen_view; 155 | } else if (wl_list_length(&workspace->views) > 1) { 156 | struct wl_list *prev_link = viv_wl_list_prev_ignoring_root(&view->workspace_link, &workspace->views); 157 | prev_view = wl_container_of(prev_link, prev_view, workspace_link); 158 | } else { 159 | prev_view = view; 160 | } 161 | 162 | return prev_view; 163 | } 164 | 165 | void viv_view_request_close(struct viv_view *view) { 166 | view->implementation->close(view); 167 | } 168 | 169 | void viv_view_get_string_identifier(struct viv_view *view, char *buffer, size_t len) { 170 | return view->implementation->get_string_identifier(view, buffer, len); 171 | } 172 | 173 | 174 | bool viv_view_oversized(struct viv_view *view) { 175 | return view->implementation->oversized(view); 176 | } 177 | 178 | void viv_view_damage(struct viv_view *view) { 179 | struct viv_output *output; 180 | struct wlr_box geo_box = { 0 }; 181 | 182 | if (view->workspace->fullscreen_view == view) { 183 | wl_list_for_each(output, &view->server->outputs, link) { 184 | viv_output_damage(output); 185 | } 186 | return; 187 | } 188 | 189 | viv_view_get_geometry(view, &geo_box); 190 | 191 | int border_width = view->server->config->border_width; 192 | geo_box.x -= border_width; 193 | geo_box.y -= border_width; 194 | geo_box.width += 2 * border_width; 195 | geo_box.height += 2 * border_width; 196 | 197 | wl_list_for_each(output, &view->server->outputs, link) { 198 | viv_output_damage_layout_coords_box(output, &geo_box); 199 | } 200 | } 201 | 202 | void viv_view_set_size(struct viv_view *view, uint32_t width, uint32_t height) { 203 | ASSERT(view->implementation->set_size != NULL); 204 | view->implementation->set_size(view, width, height); 205 | viv_view_damage(view); 206 | } 207 | 208 | void viv_view_set_pos(struct viv_view *view, uint32_t width, uint32_t height) { 209 | ASSERT(view->implementation->set_pos != NULL); 210 | view->implementation->set_pos(view, width, height); 211 | viv_view_damage(view); 212 | } 213 | 214 | void viv_view_get_geometry(struct viv_view *view, struct wlr_box *geo_box) { 215 | ASSERT(view->implementation->get_geometry != NULL); 216 | view->implementation->get_geometry(view, geo_box); 217 | } 218 | 219 | void viv_view_init(struct viv_view *view, struct viv_server *server) { 220 | // Check that the non-generic parts of the view have been initialised already 221 | ASSERT(view->type != VIV_VIEW_TYPE_UNKNOWN); 222 | 223 | view->server = server; 224 | view->mapped = false; 225 | 226 | // Make sure the view gets added to a workspace 227 | struct viv_output *output = server->active_output; 228 | 229 | if (output) { 230 | view->workspace = output->current_workspace; 231 | } else { 232 | // No output active (=> no outputs available), so just stick in the first workspace 233 | struct viv_workspace *workspace = wl_container_of(&server->workspaces.next, workspace, server_link); 234 | view->workspace = workspace; 235 | } 236 | 237 | viv_view_ensure_tiled(view); 238 | 239 | wl_list_init(&view->workspace_link); 240 | wl_list_insert(&server->unmapped_views, &view->workspace_link); 241 | /* wl_list_insert(&output->current_workspace->views, &view->workspace_link); */ 242 | } 243 | 244 | void viv_view_destroy(struct viv_view *view) { 245 | if (view->workspace->fullscreen_view == view) { 246 | view->workspace->fullscreen_view = NULL; 247 | } 248 | 249 | wl_list_remove(&view->workspace_link); 250 | wlr_log(WLR_INFO, "Destroying view at %p", view); 251 | 252 | if (view->surface_tree) { 253 | viv_surface_tree_destroy(view->surface_tree); 254 | view->surface_tree = NULL; 255 | } 256 | 257 | free(view); 258 | } 259 | 260 | bool viv_view_is_at(struct viv_view *view, double lx, double ly, struct wlr_surface **surface, double *sx, double *sy) { 261 | return view->implementation->is_at(view, lx, ly, surface, sx, sy); 262 | } 263 | 264 | void viv_view_set_target_box(struct viv_view *view, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { 265 | struct viv_workspace *workspace = view->workspace; 266 | struct viv_output *output = workspace->output; 267 | 268 | if (!output) { 269 | // If the view's workspace is not currently being displayed (e.g. it was switched 270 | // away from in between a view init and map), interpret the target box as being on 271 | // the active output. 272 | // TODO: This will be obsolete if the target box abstraction is changed to be output-independent 273 | output = workspace->server->active_output; 274 | } 275 | 276 | struct wlr_output_layout_output *output_layout_output = wlr_output_layout_get(output->server->output_layout, output->wlr_output); 277 | 278 | int gap_width = output->server->config->gap_width; 279 | 280 | int ox = output_layout_output->x; 281 | int oy = output_layout_output->y; 282 | if (!output->current_workspace->active_layout->ignore_excluded_regions && 283 | !view->is_floating && (view->workspace->fullscreen_view != view)) { 284 | ox += output->excluded_margin.left; 285 | oy += output->excluded_margin.top; 286 | } 287 | 288 | x += ox; 289 | y += oy; 290 | 291 | view->target_box.x = x; 292 | view->target_box.y = y; 293 | view->target_box.width = width; 294 | view->target_box.height = height; 295 | 296 | int border_width = output->server->config->border_width; 297 | if (output->current_workspace->active_layout->no_borders || 298 | view->is_static) { 299 | border_width = 0u; 300 | } else if (view->workspace->fullscreen_view == view) { 301 | gap_width = 0u; 302 | border_width = 0u; 303 | } 304 | 305 | width -= 2 * border_width + 2 * gap_width; 306 | height -= 2 * border_width + 2 * gap_width; 307 | 308 | viv_view_set_pos(view, x + border_width + gap_width, y + border_width + gap_width); 309 | viv_view_set_size(view, width, height); 310 | } 311 | 312 | void viv_view_match_target_box_with_surface_geometry(struct viv_view *view) { 313 | struct wlr_box box; 314 | viv_view_get_geometry(view, &box); 315 | 316 | view->target_box.width = box.width; 317 | view->target_box.height = box.height; 318 | } 319 | 320 | void viv_view_ensure_not_active_in_workspace(struct viv_view *view) { 321 | struct viv_workspace *workspace = view->workspace; 322 | 323 | if (workspace->fullscreen_view == view) { 324 | viv_view_force_remove_fullscreen(view); 325 | } 326 | if (view == workspace->active_view) { 327 | struct viv_seat *seat = viv_server_get_default_seat(view->server); 328 | seat->wlr_seat->keyboard_state.focused_surface = NULL; 329 | if (wl_list_length(&workspace->views) > 1) { 330 | viv_workspace_focus_next_window(workspace); 331 | } else { 332 | workspace->active_view = NULL; 333 | } 334 | } 335 | } 336 | 337 | bool viv_view_set_fullscreen(struct viv_view *view, bool fullscreen) { 338 | bool starting_fullscreen = (view->workspace->fullscreen_view == view); 339 | if (starting_fullscreen == fullscreen) { 340 | return true; 341 | } 342 | 343 | char view_name[VIEW_NAME_LEN]; 344 | viv_view_get_string_identifier(view, view_name, VIEW_NAME_LEN); 345 | if (!view->mapped && fullscreen) { 346 | wlr_log(WLR_DEBUG, "View %s requesting fullscreen before being mapped", view_name); 347 | } 348 | if (fullscreen && view->workspace->fullscreen_view) { 349 | wlr_log(WLR_DEBUG, 350 | "Preventing view %s from going fullscreen. Workspace already has a fullscreen view", 351 | view_name); 352 | 353 | return false; 354 | } 355 | 356 | if (fullscreen) { 357 | view->workspace->fullscreen_view = view; 358 | view->target_box_before_fullscreen = view->target_box; 359 | view->implementation->grow_and_center_fullscreen(view); 360 | 361 | } else { 362 | view->workspace->fullscreen_view = NULL; 363 | 364 | if (view->is_floating) { 365 | struct wlr_box *box = &view->target_box_before_fullscreen; 366 | if (box->width && box->height) { 367 | viv_view_set_target_box(view, box->x, box->y, box->width, box->height); 368 | } 369 | } 370 | } 371 | 372 | viv_workspace_mark_for_relayout(view->workspace); 373 | 374 | return true; 375 | } 376 | 377 | void viv_view_force_remove_fullscreen(struct viv_view *view) { 378 | if (view->workspace->fullscreen_view != view) { 379 | return; 380 | } 381 | 382 | viv_view_set_fullscreen(view, false); 383 | view->implementation->inform_unrequested_fullscreen_change(view); 384 | } 385 | -------------------------------------------------------------------------------- /src/viv_wl_list_utils.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include "viv_config_support.h" 5 | 6 | struct wl_list *viv_wl_list_next_ignoring_root(struct wl_list *cur_element, struct wl_list *root_element) { 7 | // This can only be safely called if the list is guaranteed to have another element to find 8 | ASSERT(wl_list_length(root_element) > 1); 9 | 10 | struct wl_list *next_element = cur_element->next; 11 | if (next_element == root_element) { 12 | next_element = next_element->next; 13 | } 14 | 15 | return next_element; 16 | } 17 | 18 | struct wl_list *viv_wl_list_prev_ignoring_root(struct wl_list *cur_element, struct wl_list *root_element) { 19 | // This can only be safely called if the list is guaranteed to have another element to find 20 | ASSERT(wl_list_length(root_element) > 1); 21 | 22 | struct wl_list *prev_element = cur_element->prev; 23 | if (prev_element == root_element) { 24 | prev_element = prev_element->prev; 25 | } 26 | 27 | return prev_element; 28 | } 29 | 30 | void viv_wl_list_swap(struct wl_list *elm1, struct wl_list *elm2) { 31 | struct wl_list *elm1_prev = elm1->prev; 32 | struct wl_list *elm1_next = elm1->next; 33 | 34 | // TODO: This can be done more simply 35 | 36 | elm1->prev = elm2->prev; 37 | elm1->next = elm2->next; 38 | 39 | elm2->prev = elm1_prev; 40 | elm2->next = elm1_next; 41 | 42 | // If the two list items were next to one another then they will have gained circular 43 | // references, so fix these before correcting references of adjacent elements. 44 | if (elm1->next == elm1) { 45 | elm1->next = elm2; 46 | } 47 | if (elm1->prev == elm1) { 48 | elm1->prev = elm2; 49 | } 50 | if (elm2->next == elm2) { 51 | elm2->next = elm1; 52 | } 53 | if (elm2->next == elm2) { 54 | elm2->next = elm1; 55 | } 56 | 57 | elm1->prev->next = elm1; 58 | elm1->next->prev = elm1; 59 | 60 | elm2->prev->next = elm2; 61 | elm2->next->prev = elm2; 62 | } 63 | -------------------------------------------------------------------------------- /src/viv_wlr_surface_tree.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #include "viv_damage.h" 6 | #include "viv_output.h" 7 | #include "viv_types.h" 8 | #include "viv_wlr_surface_tree.h" 9 | 10 | static struct viv_surface_tree_node *viv_surface_tree_create(struct viv_server *server, struct wlr_surface *surface); 11 | static struct viv_surface_tree_node *viv_surface_tree_subsurface_node_create(struct viv_server *server, struct viv_surface_tree_node *parent, 12 | struct viv_wlr_subsurface *subsurface, struct wlr_surface *surface); 13 | static void viv_subsurface_destroy(struct viv_wlr_subsurface *subsurface); 14 | 15 | /// Walks up the surface tree until reaching the root, adding all the surface offsets along the way 16 | static void add_surface_global_offset(struct viv_surface_tree_node *node, int *lx, int *ly) { 17 | *lx += node->wlr_surface->sx; 18 | *ly += node->wlr_surface->sy; 19 | 20 | if (node->apply_global_offset) { 21 | // This is the root node 22 | ASSERT(!node->subsurface); 23 | node->apply_global_offset(node->global_offset_data, lx, ly); 24 | } else { 25 | ASSERT(node->subsurface); 26 | 27 | *lx += node->subsurface->wlr_subsurface->current.x; 28 | *ly += node->subsurface->wlr_subsurface->current.y; 29 | 30 | add_surface_global_offset(node->parent, lx, ly); 31 | } 32 | } 33 | 34 | static void handle_subsurface_map (struct wl_listener *listener, void *data) { 35 | UNUSED(data); 36 | struct viv_wlr_subsurface *subsurface = wl_container_of(listener, subsurface, map); 37 | struct wlr_subsurface *wlr_subsurface = subsurface->wlr_subsurface; 38 | 39 | 40 | subsurface->child = viv_surface_tree_subsurface_node_create(subsurface->server, subsurface->parent, subsurface, wlr_subsurface->surface); 41 | 42 | wlr_log(WLR_INFO, "Mapped subsurface at %p creates node at %p", subsurface, subsurface->child); 43 | } 44 | 45 | static void handle_subsurface_unmap (struct wl_listener *listener, void *data) { 46 | struct viv_wlr_subsurface *subsurface = wl_container_of(listener, subsurface, unmap); 47 | 48 | wlr_log(WLR_INFO, "Unmapped subsurface at %p with child %p", subsurface, subsurface->child); 49 | 50 | if (subsurface->child) { 51 | 52 | struct viv_surface_tree_node *node = subsurface->child; 53 | 54 | int lx = 0; 55 | int ly = 0; 56 | add_surface_global_offset(node, &lx, &ly); 57 | 58 | struct wlr_box surface_extents = { 0 }; 59 | wlr_surface_get_extends(node->wlr_surface, &surface_extents); 60 | surface_extents.x += lx; 61 | surface_extents.y += ly; 62 | 63 | struct viv_output *output; 64 | wl_list_for_each(output, &node->server->outputs, link) { 65 | viv_output_damage_layout_coords_box(output, &surface_extents); 66 | } 67 | 68 | viv_surface_tree_destroy(subsurface->child); 69 | subsurface->child = NULL; 70 | } 71 | 72 | UNUSED(data); 73 | } 74 | 75 | static void handle_subsurface_destroy (struct wl_listener *listener, void *data) { 76 | UNUSED(data); 77 | struct viv_wlr_subsurface *subsurface = wl_container_of(listener, subsurface, destroy); 78 | wl_list_remove(&subsurface->node_link); 79 | wlr_log(WLR_INFO, "Destroyed subsurface at %p", subsurface); 80 | free(subsurface); 81 | } 82 | 83 | static void handle_new_node_subsurface (struct wl_listener *listener, void *data) { 84 | struct viv_surface_tree_node *node = wl_container_of(listener, node, new_subsurface); 85 | 86 | struct wlr_subsurface *wlr_subsurface = data; 87 | 88 | struct viv_wlr_subsurface *subsurface = calloc(1, sizeof(struct viv_wlr_subsurface)); 89 | CHECK_ALLOCATION(subsurface); 90 | 91 | wlr_log(WLR_INFO, "New subsurface at %p", subsurface); 92 | 93 | subsurface->server = node->server; 94 | subsurface->wlr_subsurface = wlr_subsurface; 95 | subsurface->parent = node; 96 | 97 | subsurface->map.notify = handle_subsurface_map; 98 | wl_signal_add(&wlr_subsurface->events.map, &subsurface->map); 99 | 100 | subsurface->unmap.notify = handle_subsurface_unmap; 101 | wl_signal_add(&wlr_subsurface->events.unmap, &subsurface->unmap); 102 | 103 | subsurface->destroy.notify = handle_subsurface_destroy; 104 | wl_signal_add(&wlr_subsurface->events.destroy, &subsurface->destroy); 105 | 106 | struct wlr_subsurface *existing_wlr_subsurface; 107 | wl_list_for_each(existing_wlr_subsurface, &wlr_subsurface->surface->current.subsurfaces_below, current.link) { 108 | handle_new_node_subsurface(&node->new_subsurface, existing_wlr_subsurface); 109 | } 110 | wl_list_for_each(existing_wlr_subsurface, &wlr_subsurface->surface->current.subsurfaces_above, current.link) { 111 | handle_new_node_subsurface(&node->new_subsurface, existing_wlr_subsurface); 112 | } 113 | 114 | wl_list_insert(&node->child_subsurfaces, &subsurface->node_link); 115 | } 116 | 117 | static void handle_commit(struct wl_listener *listener, void *data) { 118 | UNUSED(data); 119 | struct viv_surface_tree_node *node = wl_container_of(listener, node, commit); 120 | struct wlr_surface *surface = node->wlr_surface; 121 | 122 | int lx = 0; 123 | int ly = 0; 124 | add_surface_global_offset(node, &lx, &ly); 125 | viv_damage_surface(node->server, surface, lx, ly); 126 | } 127 | 128 | static void handle_node_destroy(struct wl_listener *listener, void *data) { 129 | UNUSED(data); 130 | struct viv_surface_tree_node *node = wl_container_of(listener, node, destroy); 131 | 132 | wlr_log(WLR_INFO, "Destroy node at %p", node); 133 | 134 | struct viv_wlr_subsurface *subsurface; 135 | wl_list_for_each(subsurface, &node->child_subsurfaces, node_link) { 136 | viv_subsurface_destroy(subsurface); 137 | } 138 | 139 | wl_list_remove(&node->new_subsurface.link); 140 | wl_list_remove(&node->commit.link); 141 | wl_list_remove(&node->destroy.link); 142 | 143 | free(node); 144 | } 145 | 146 | 147 | static struct viv_surface_tree_node *viv_surface_tree_create(struct viv_server *server, struct wlr_surface *surface) { 148 | struct viv_surface_tree_node *node = calloc(1, sizeof(struct viv_surface_tree_node)); 149 | CHECK_ALLOCATION(node); 150 | 151 | wlr_log(WLR_INFO, "New node at %p", node); 152 | 153 | node->server = server; 154 | node->wlr_surface = surface; 155 | 156 | wl_list_init(&node->child_subsurfaces); 157 | 158 | node->new_subsurface.notify = handle_new_node_subsurface; 159 | wl_signal_add(&surface->events.new_subsurface, &node->new_subsurface); 160 | 161 | node->commit.notify = handle_commit; 162 | wl_signal_add(&surface->events.commit, &node->commit); 163 | 164 | node->destroy.notify = handle_node_destroy; 165 | wl_signal_add(&surface->events.destroy, &node->destroy); 166 | 167 | struct wlr_subsurface *wlr_subsurface; 168 | wl_list_for_each(wlr_subsurface, &surface->current.subsurfaces_below, current.link) { 169 | handle_new_node_subsurface(&node->new_subsurface, wlr_subsurface); 170 | } 171 | wl_list_for_each(wlr_subsurface, &surface->current.subsurfaces_above, current.link) { 172 | handle_new_node_subsurface(&node->new_subsurface, wlr_subsurface); 173 | } 174 | 175 | return node; 176 | } 177 | 178 | static struct viv_surface_tree_node *viv_surface_tree_subsurface_node_create(struct viv_server *server, struct viv_surface_tree_node *parent, struct viv_wlr_subsurface *subsurface, struct wlr_surface *surface) { 179 | ASSERT(server); 180 | ASSERT(parent); 181 | ASSERT(subsurface); 182 | ASSERT(surface); 183 | 184 | struct viv_surface_tree_node *node = viv_surface_tree_create(server, surface); 185 | node->parent = parent; 186 | node->subsurface = subsurface; 187 | 188 | return node; 189 | } 190 | 191 | struct viv_surface_tree_node *viv_surface_tree_root_create(struct viv_server *server, struct wlr_surface *surface, void (*apply_global_offset)(void *, int *, int *), void *global_offset_data) { 192 | ASSERT(server); 193 | ASSERT(surface); 194 | ASSERT(apply_global_offset); 195 | 196 | struct viv_surface_tree_node *node = viv_surface_tree_create(server, surface); 197 | node->apply_global_offset = apply_global_offset; 198 | node->global_offset_data = global_offset_data; 199 | 200 | return node; 201 | } 202 | 203 | /// Clean up the subsurface state (unbind events etc.), including for all children, and 204 | /// free it 205 | static void viv_subsurface_destroy(struct viv_wlr_subsurface *subsurface) { 206 | if (subsurface->child) { 207 | viv_surface_tree_destroy(subsurface->child); 208 | subsurface->child = NULL; 209 | } 210 | 211 | wl_list_remove(&subsurface->map.link); 212 | wl_list_remove(&subsurface->unmap.link); 213 | wl_list_remove(&subsurface->destroy.link); 214 | 215 | free(subsurface); 216 | } 217 | 218 | void viv_surface_tree_destroy(struct viv_surface_tree_node *node) { 219 | struct viv_wlr_subsurface *subsurface; 220 | wl_list_for_each(subsurface, &node->child_subsurfaces, node_link) { 221 | viv_subsurface_destroy(subsurface); 222 | } 223 | 224 | wl_list_remove(&node->new_subsurface.link); 225 | wl_list_remove(&node->commit.link); 226 | wl_list_remove(&node->destroy.link); 227 | 228 | free(node); 229 | } 230 | -------------------------------------------------------------------------------- /src/viv_workspace.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "viv_cursor.h" 5 | #include "viv_layout.h" 6 | #include "viv_output.h" 7 | #include "viv_server.h" 8 | #include "viv_types.h" 9 | #include "viv_view.h" 10 | #include "viv_wl_list_utils.h" 11 | #include "viv_workspace.h" 12 | 13 | void viv_workspace_mark_for_relayout(struct viv_workspace *workspace) { 14 | workspace->needs_layout = true; 15 | viv_workspace_damage_views(workspace); // TODO: is this 100% redundant with damaging the output? 16 | if (workspace->output) { 17 | viv_output_damage(workspace->output); 18 | } 19 | } 20 | 21 | void viv_workspace_focus_next_window(struct viv_workspace *workspace) { 22 | struct viv_view *active_view = workspace->active_view; 23 | struct viv_view *next_view = NULL; 24 | if (active_view != NULL) { 25 | next_view = viv_view_next_in_workspace(active_view); 26 | } else if (wl_list_length(&workspace->views) > 0) { 27 | next_view = wl_container_of(workspace->views.next, next_view, workspace_link); 28 | } 29 | 30 | if (next_view) { 31 | viv_view_focus(next_view); 32 | } else { 33 | wlr_log(WLR_DEBUG, "Could not get next window, no active view"); 34 | } 35 | } 36 | 37 | void viv_workspace_focus_prev_window(struct viv_workspace *workspace) { 38 | struct viv_view *active_view = workspace->active_view; 39 | struct viv_view *prev_view = NULL; 40 | if (active_view != NULL) { 41 | prev_view = viv_view_prev_in_workspace(workspace->active_view); 42 | } else if (wl_list_length(&workspace->views) > 0) { 43 | prev_view = wl_container_of(workspace->views.prev, prev_view, workspace_link); 44 | } 45 | 46 | if (prev_view) { 47 | viv_view_focus(prev_view); 48 | } else { 49 | wlr_log(WLR_DEBUG, "Could not get prev window, no active view"); 50 | } 51 | } 52 | 53 | void viv_workspace_shift_active_window_up(struct viv_workspace *workspace) { 54 | struct viv_view *active_view = workspace->active_view; 55 | if (active_view == NULL) { 56 | wlr_log(WLR_DEBUG, "Could not get next window, no active view"); 57 | return; 58 | } 59 | 60 | struct wl_list *prev_link = active_view->workspace_link.prev; 61 | if (prev_link == &workspace->views) { 62 | prev_link = prev_link->prev; 63 | } 64 | 65 | if (prev_link == &workspace->views) { 66 | wlr_log(WLR_ERROR, "Next window couldn't be found\n"); 67 | return; 68 | } 69 | 70 | viv_wl_list_swap(&active_view->workspace_link, prev_link); 71 | 72 | viv_workspace_mark_for_relayout(active_view->workspace); 73 | } 74 | 75 | void viv_workspace_shift_active_window_down(struct viv_workspace *workspace) { 76 | struct viv_view *active_view = workspace->active_view; 77 | if (active_view == NULL) { 78 | wlr_log(WLR_DEBUG, "Could not get next window, no active view"); 79 | return; 80 | } 81 | 82 | struct wl_list *next_link = active_view->workspace_link.next; 83 | if (next_link == &workspace->views) { 84 | next_link = next_link->next; 85 | } 86 | 87 | if (next_link == &workspace->views) { 88 | wlr_log(WLR_ERROR, "Next window couldn't be found\n"); 89 | return; 90 | } 91 | 92 | viv_wl_list_swap(&active_view->workspace_link, next_link); 93 | 94 | viv_workspace_mark_for_relayout(active_view->workspace); 95 | } 96 | 97 | void viv_workspace_increment_divide(struct viv_workspace *workspace, float increment) { 98 | workspace->active_layout->parameter += increment; 99 | if (workspace->active_layout->parameter > 1) { 100 | workspace->active_layout->parameter = 1; 101 | } else if (workspace->active_layout->parameter < 0) { 102 | workspace->active_layout->parameter = 0; 103 | } 104 | 105 | viv_workspace_mark_for_relayout(workspace); 106 | } 107 | 108 | void viv_workspace_increment_counter(struct viv_workspace *workspace, uint32_t increment) { 109 | if (workspace->active_layout->counter >= increment || increment > 0) { 110 | workspace->active_layout->counter += increment; 111 | } 112 | 113 | viv_workspace_mark_for_relayout(workspace); 114 | } 115 | 116 | void viv_workspace_next_layout(struct viv_workspace *workspace) { 117 | struct wl_list *next_layout_link = workspace->active_layout->workspace_link.next; 118 | if (next_layout_link == &workspace->layouts) { 119 | next_layout_link = next_layout_link->next; 120 | } 121 | struct viv_layout *next_layout = wl_container_of(next_layout_link, next_layout, workspace_link); 122 | wlr_log(WLR_DEBUG, "Switching to layout with name %s", next_layout->name); 123 | workspace->active_layout = next_layout; 124 | viv_workspace_mark_for_relayout(workspace); 125 | } 126 | 127 | void viv_workspace_prev_layout(struct viv_workspace *workspace) { 128 | struct wl_list *prev_layout_link = workspace->active_layout->workspace_link.prev; 129 | if (prev_layout_link == &workspace->layouts) { 130 | prev_layout_link = prev_layout_link->prev; 131 | } 132 | struct viv_layout *prev_layout = wl_container_of(prev_layout_link, prev_layout, workspace_link); 133 | wlr_log(WLR_DEBUG, "Switching to layout with name %s", prev_layout->name); 134 | workspace->active_layout = prev_layout; 135 | viv_workspace_mark_for_relayout(workspace); 136 | } 137 | 138 | void viv_workspace_assign_to_output(struct viv_workspace *workspace, struct viv_output *output) { 139 | // TODO add workspace switching between outputs 140 | if (workspace->output != NULL) { 141 | wlr_log(WLR_ERROR, "Changed the output of a workspace that already has an output"); 142 | } 143 | 144 | output->current_workspace = workspace; 145 | workspace->output = output; 146 | } 147 | 148 | struct viv_view *viv_workspace_main_view(struct viv_workspace *workspace) { 149 | struct viv_view *view = NULL; 150 | wl_list_for_each(view, &workspace->views, workspace_link) { 151 | if (!view->is_floating) { 152 | break; 153 | } 154 | } 155 | 156 | return view; 157 | } 158 | 159 | void viv_workspace_swap_active_and_main(struct viv_workspace *workspace) { 160 | struct viv_view *active_view = workspace->active_view; 161 | struct viv_view *main_view = viv_workspace_main_view(workspace); 162 | 163 | if (main_view == NULL || active_view == NULL) { 164 | wlr_log(WLR_ERROR, "Cannot swap active and main views, one or both is NULL"); 165 | return; 166 | } 167 | 168 | viv_wl_list_swap(&active_view->workspace_link, &main_view->workspace_link); 169 | viv_workspace_mark_for_relayout(workspace); 170 | } 171 | 172 | void viv_workspace_damage_views(struct viv_workspace *workspace) { 173 | struct viv_view *view; 174 | wl_list_for_each(view, &workspace->views, workspace_link) { 175 | viv_view_damage(view); 176 | } 177 | } 178 | 179 | void viv_workspace_do_layout(struct viv_workspace *workspace) { 180 | viv_workspace_damage_views(workspace); 181 | 182 | struct viv_output *output = workspace->output; 183 | ASSERT(output); 184 | struct viv_layout *layout = workspace->active_layout; 185 | 186 | int32_t width = output->wlr_output->width; 187 | int32_t height = output->wlr_output->height; 188 | if (!layout->ignore_excluded_regions) { 189 | width -= (output->excluded_margin.left + output->excluded_margin.right); 190 | height -= (output->excluded_margin.top - output->excluded_margin.bottom); 191 | } 192 | 193 | viv_layout_apply(workspace, width, height); 194 | 195 | workspace->needs_layout = false; 196 | workspace->output->needs_layout = false; 197 | 198 | // Reset cursor focus as the view under the cursor may have changed 199 | struct timespec now; 200 | clock_gettime(CLOCK_MONOTONIC, &now); 201 | viv_cursor_reset_focus(workspace->server, (int64_t)now.tv_sec * 1000 + now.tv_nsec / 1000000); 202 | 203 | // Inhibitor visibility may have changed 204 | viv_server_update_idle_inhibitor_state(workspace->server); 205 | 206 | workspace->was_laid_out = true; 207 | 208 | viv_workspace_damage_views(workspace); 209 | if (workspace->output) { 210 | viv_output_damage(workspace->output); 211 | } 212 | } 213 | 214 | uint32_t viv_workspace_num_tiled_views(struct viv_workspace *workspace) { 215 | uint32_t num_views = 0; 216 | struct viv_view *view; 217 | wl_list_for_each(view, &workspace->views, workspace_link) { 218 | if (view->is_floating) { 219 | continue; 220 | } 221 | num_views++; 222 | } 223 | return num_views; 224 | } 225 | 226 | void viv_workspace_add_view(struct viv_workspace *workspace, struct viv_view *view) { 227 | view->workspace = workspace; 228 | 229 | if (view->is_floating) { 230 | wl_list_insert(&workspace->views, &view->workspace_link); 231 | } else if (workspace->active_view != NULL) { 232 | wl_list_insert(workspace->active_view->workspace_link.prev, &view->workspace_link); 233 | } else { 234 | wl_list_insert(&workspace->views, &view->workspace_link); 235 | } 236 | 237 | if (view->workspace->fullscreen_view == view) { 238 | if (workspace->fullscreen_view && (workspace->fullscreen_view != view)) { 239 | wlr_log(WLR_DEBUG, "Tried to add fullscreen view to a workspace that already another one. Adding as non-fullscreen"); 240 | viv_view_force_remove_fullscreen(view); 241 | } else { 242 | workspace->fullscreen_view = view; 243 | } 244 | } 245 | 246 | if (!workspace->fullscreen_view || (workspace->fullscreen_view == view)) { 247 | viv_view_focus(view); 248 | } 249 | 250 | viv_workspace_mark_for_relayout(workspace); 251 | } 252 | -------------------------------------------------------------------------------- /src/viv_xdg_popup.c: -------------------------------------------------------------------------------- 1 | 2 | #include "viv_damage.h" 3 | #include "viv_output.h" 4 | #include "viv_types.h" 5 | 6 | #include "viv_xdg_popup.h" 7 | #include "viv_wlr_surface_tree.h" 8 | 9 | /// Add to x and y the global (i.e. output-layout) coords of the input popup, calculated 10 | /// by walking up the popup tree and adding the geometry of each parent. 11 | static void add_popup_global_coords(void *popup_pointer, int *x, int *y) { 12 | struct viv_xdg_popup *popup = popup_pointer; 13 | 14 | int px = 0; 15 | int py = 0; 16 | 17 | struct viv_xdg_popup *cur_popup = popup; 18 | while (true) { 19 | px += cur_popup->wlr_popup->geometry.x; 20 | py += cur_popup->wlr_popup->geometry.y; 21 | 22 | if (cur_popup->parent_popup != NULL) { 23 | cur_popup = cur_popup->parent_popup; 24 | } else { 25 | break; 26 | } 27 | } 28 | 29 | px += *popup->lx; 30 | py += *popup->ly; 31 | 32 | *x += px; 33 | *y += py; 34 | } 35 | 36 | static void handle_popup_surface_map(struct wl_listener *listener, void *data) { 37 | UNUSED(data); 38 | struct viv_xdg_popup *popup = wl_container_of(listener, popup, surface_map); 39 | wlr_log(WLR_INFO, "Map popup at %p", popup); 40 | 41 | popup->surface_tree = viv_surface_tree_root_create(popup->server, popup->wlr_popup->base->surface, &add_popup_global_coords, popup); 42 | } 43 | 44 | static void handle_popup_surface_unmap(struct wl_listener *listener, void *data) { 45 | UNUSED(data); 46 | struct viv_xdg_popup *popup = wl_container_of(listener, popup, surface_unmap); 47 | 48 | wlr_log(WLR_INFO, "Unmap popup at %p", popup); 49 | 50 | viv_surface_tree_destroy(popup->surface_tree); 51 | popup->surface_tree = NULL; 52 | 53 | int px = 0; 54 | int py = 0; 55 | add_popup_global_coords(popup, &px, &py); 56 | 57 | struct wlr_box geo_box = { 58 | .x = px, 59 | .y = py, 60 | .width = popup->wlr_popup->geometry.width, 61 | .height = popup->wlr_popup->geometry.height, 62 | }; 63 | 64 | struct viv_output *output; 65 | wl_list_for_each(output, &popup->server->outputs, link) { 66 | viv_output_damage_layout_coords_box(output, &geo_box); 67 | } 68 | } 69 | 70 | static void handle_popup_surface_destroy(struct wl_listener *listener, void *data) { 71 | UNUSED(data); 72 | struct viv_xdg_popup *popup = wl_container_of(listener, popup, destroy); 73 | wlr_log(WLR_INFO, "Popup at %p being destroyed", popup); 74 | if (popup->surface_tree) { 75 | viv_surface_tree_destroy(popup->surface_tree); 76 | popup->surface_tree = NULL; 77 | } 78 | free(popup); 79 | } 80 | 81 | static void handle_new_popup(struct wl_listener *listener, void *data) { 82 | struct viv_xdg_popup *popup = wl_container_of(listener, popup, new_popup); 83 | struct wlr_xdg_popup *wlr_popup = data; 84 | 85 | struct viv_xdg_popup *new_popup = calloc(1, sizeof(struct viv_xdg_popup)); 86 | new_popup->server = popup->server; 87 | new_popup->lx = popup->lx; 88 | new_popup->ly = popup->ly; 89 | new_popup->parent_popup = popup; 90 | viv_xdg_popup_init(new_popup, wlr_popup); 91 | } 92 | 93 | /// Set the region in which the popup can be displayed, so that its position is shifted to 94 | /// stay within its output and not be rendered offscreen 95 | static void popup_unconstrain(struct viv_xdg_popup *popup) { 96 | struct viv_output *output = popup->server->active_output; 97 | if (!output) { 98 | wlr_log(WLR_ERROR, "Cannot unconstraint popup, no active output"); 99 | } 100 | 101 | struct wlr_xdg_popup *wlr_popup = popup->wlr_popup; 102 | 103 | double lx = 0, ly = 0; 104 | wlr_output_layout_output_coords(output->server->output_layout, output->wlr_output, &lx, &ly); 105 | 106 | if (!output) { 107 | wlr_log(WLR_ERROR, "Tried to unconstrain a popup that doesn't have an output"); 108 | return; 109 | } 110 | 111 | // The output box is relative to the popup's toplevel parent 112 | struct wlr_box output_box = { 113 | .x = lx - *popup->lx, 114 | .y = ly - *popup->ly, 115 | .width = output->wlr_output->width, 116 | .height = output->wlr_output->height, 117 | }; 118 | 119 | wlr_xdg_popup_unconstrain_from_box(wlr_popup, &output_box); 120 | } 121 | 122 | void viv_xdg_popup_init(struct viv_xdg_popup *popup, struct wlr_xdg_popup *wlr_popup) { 123 | popup->wlr_popup = wlr_popup; 124 | 125 | wlr_log(WLR_INFO, "New popup %p with parent %p", popup, popup->parent_popup); 126 | 127 | 128 | popup->surface_map.notify = handle_popup_surface_map; 129 | wl_signal_add(&wlr_popup->base->events.map, &popup->surface_map); 130 | popup->surface_unmap.notify = handle_popup_surface_unmap; 131 | wl_signal_add(&wlr_popup->base->events.unmap, &popup->surface_unmap); 132 | 133 | popup->destroy.notify = handle_popup_surface_destroy; 134 | wl_signal_add(&wlr_popup->base->surface->events.destroy, &popup->destroy); 135 | 136 | popup->new_popup.notify = handle_new_popup; 137 | wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup); 138 | 139 | popup_unconstrain(popup); 140 | 141 | wlr_log(WLR_INFO, "New wlr_popup surface at %p", wlr_popup->base->surface); 142 | } 143 | -------------------------------------------------------------------------------- /src/vivarium.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #ifdef HEADLESS_TEST 9 | #include 10 | #endif 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "viv_cli.h" 28 | #include "viv_toml_config.h" 29 | #include "viv_types.h" 30 | #include "viv_server.h" 31 | 32 | #ifdef HEADLESS_TEST 33 | /// Setup some headless devices before exiting, just to check we don't segfault or something. 34 | static void headless_test(struct viv_server *server) { 35 | wlr_headless_add_output(server->backend, 1024, 768); 36 | wlr_headless_add_output(server->backend, 1920, 1200); 37 | wlr_headless_add_input_device(server->backend, WLR_INPUT_DEVICE_KEYBOARD); 38 | wlr_headless_add_input_device(server->backend, WLR_INPUT_DEVICE_POINTER); 39 | } 40 | #endif 41 | 42 | int main(int argc, char *argv[]) { 43 | UNUSED(argc); 44 | UNUSED(argv); 45 | 46 | struct viv_args parsed_args = viv_cli_parse_args(argc, argv); 47 | 48 | wlr_log_init(WLR_DEBUG, NULL); 49 | 50 | // Initialise our vivarium server. This sets up all the event bindings so that inputs, 51 | // outputs and window events can be handles. 52 | struct viv_server server = { .config = NULL }; 53 | server.user_provided_config_filen = parsed_args.config_filen; 54 | viv_server_init(&server); 55 | 56 | // Add a Unix socket to the Wayland display. 57 | const char *socket = wl_display_add_socket_auto(server.wl_display); 58 | if (!socket) { 59 | wlr_backend_destroy(server.backend); 60 | return 1; 61 | } 62 | 63 | // Start the wlroots backend, which will manage outputs/inputs, become the DRM master 64 | // etc. 65 | if (!wlr_backend_start(server.backend)) { 66 | wlr_backend_destroy(server.backend); 67 | wl_display_destroy(server.wl_display); 68 | return 1; 69 | } 70 | 71 | // Set the WAYLAND_DISPLAY environment variable, so that clients know how to connect 72 | // to our server 73 | setenv("WAYLAND_DISPLAY", socket, true); 74 | 75 | // Set up env vars to encourage applications to use wayland if possible 76 | // TODO: Should this be necessary? Or is there a better way to do it, for wayland 77 | // using an X11 backend? 78 | setenv("QT_QPA_PLATFORM", "wayland", true); 79 | setenv("MOZ_ENABLE_WAYLAND", "1", true); 80 | 81 | #ifndef HEADLESS_TEST 82 | // Start the wayland eventloop. From here, all compositor activity comes via events it 83 | // sends us. 84 | wlr_log(WLR_INFO, "Running Wayland compositor on WAYLAND_DISPLAY=%s", socket); 85 | wl_display_run(server.wl_display); 86 | #else 87 | // Don't run the compositor, just set up some headless outputs for CI testing 88 | wlr_log(WLR_INFO, "Running headless Wayland compositor on WAYLAND_DISPLAY=%s", socket); 89 | headless_test(&server); 90 | #endif 91 | 92 | viv_server_deinit(&server); 93 | 94 | return 0; 95 | } 96 | -------------------------------------------------------------------------------- /subprojects/fff.wrap: -------------------------------------------------------------------------------- 1 | 2 | [wrap-git] 3 | url = https://github.com/meekrosoft/fff.git 4 | revision = 7e09f07e5b262b1cc826189dc5057379e40ce886 5 | patch_directory = fff -------------------------------------------------------------------------------- /subprojects/packagefiles/fff/meson.build: -------------------------------------------------------------------------------- 1 | 2 | project( 3 | 'fff', 4 | 'c', 5 | default_options : [ 6 | 'c_std=c99', 7 | ] 8 | ) 9 | 10 | cc = meson.get_compiler('c') 11 | 12 | include_dir = include_directories('.') 13 | 14 | fff_dep = declare_dependency( 15 | include_directories: [include_dir], 16 | ) 17 | -------------------------------------------------------------------------------- /subprojects/packagefiles/tomlc99/meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'toml', 3 | 'c', 4 | default_options : [ 5 | 'c_std=c99', 6 | ] 7 | ) 8 | 9 | cc = meson.get_compiler('c') 10 | 11 | include_dir = include_directories('.') 12 | 13 | lib_tomlc99 = library( 14 | meson.project_name(), 15 | ['toml.c'], 16 | include_directories: [include_dir], 17 | ) 18 | 19 | static_tomlc99 = static_library( 20 | meson.project_name(), 21 | ['toml.c', 'toml.h'], 22 | ) 23 | 24 | tomlc99_dep = declare_dependency( 25 | link_with : lib_tomlc99, 26 | include_directories: [include_dir], 27 | ) 28 | 29 | tomlc99_static_dep = declare_dependency( 30 | link_with : static_tomlc99, 31 | include_directories: [include_dir], 32 | ) 33 | -------------------------------------------------------------------------------- /subprojects/tomlc99.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url = https://github.com/cktan/tomlc99 3 | revision = c5d2e37db734fc58f515aaab87d2e037155f6434 4 | patch_directory = tomlc99 -------------------------------------------------------------------------------- /subprojects/unity.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | directory = unity 3 | url = https://github.com/ThrowTheSwitch/Unity.git 4 | revision = 0b899aec14d3a9abb2bf260ac355f0f28630a6a3 -------------------------------------------------------------------------------- /subprojects/wlroots.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | directory = wlroots 3 | url = https://gitlab.freedesktop.org/wlroots/wlroots.git 4 | revision = tags/0.15.1 5 | -------------------------------------------------------------------------------- /tests/meson.build: -------------------------------------------------------------------------------- 1 | test_deps = [ 2 | fff_dep, 3 | unity_dep, 4 | ] 5 | 6 | test_config = executable( 7 | 'test-config', 8 | ['test_config.c', '../src/viv_toml_config.c'], 9 | include_directories : includes + ['./'], 10 | dependencies : viv_deps + test_deps, 11 | ) 12 | 13 | test_layouts = executable( 14 | 'test-layouts', 15 | ['test_layouts.c', '../src/viv_layout.c'], 16 | include_directories : includes + ['./'], 17 | dependencies : viv_deps + test_deps, 18 | ) 19 | 20 | test('Test config', test_config) 21 | test('Test layouts', test_layouts) 22 | -------------------------------------------------------------------------------- /tests/mock_layouts.h: -------------------------------------------------------------------------------- 1 | #ifndef MOCK_LAYOUTS 2 | #define MOCK_LAYOUTS 3 | 4 | #include 5 | 6 | #include "viv_layout.h" 7 | #include "viv_types.h" 8 | 9 | #define MOCK_LAYOUT(LAYOUT_NAME, _1, _2) \ 10 | FAKE_VOID_FUNC(viv_layout_do_ ## LAYOUT_NAME, struct wl_array *, float, uint32_t, uint32_t, uint32_t); 11 | 12 | #define RESET_LAYOUT_MOCK(LAYOUT_NAME, _1, _2) \ 13 | RESET_FAKE(viv_layout_do_ ## LAYOUT_NAME); 14 | 15 | MACRO_FOR_EACH_LAYOUT(MOCK_LAYOUT); 16 | 17 | #define RESET_LAYOUT_FAKES() MACRO_FOR_EACH_LAYOUT(RESET_LAYOUT_MOCK) 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /tests/mock_mappable_functions.h: -------------------------------------------------------------------------------- 1 | #ifndef MOCK_MAPPABLE_FUNCTIONS 2 | #define MOCK_MAPPABLE_FUNCTIONS 3 | 4 | #include 5 | 6 | #include "viv_mappable_functions.h" 7 | #include "viv_types.h" 8 | 9 | #define MOCK_MAPPABLE_FUNCTION(MAPPABLE_NAME, ...) \ 10 | FAKE_VOID_FUNC(viv_mappable_ ## MAPPABLE_NAME, struct viv_workspace *, union viv_mappable_payload); 11 | 12 | #define RESET_MAPPABLE_FUNCTION_MOCK(LAYOUT_NAME, ...) \ 13 | RESET_FAKE(viv_mappable_ ## LAYOUT_NAME); 14 | 15 | MACRO_FOR_EACH_MAPPABLE(MOCK_MAPPABLE_FUNCTION); 16 | 17 | #define RESET_MAPPABLE_FUNCTION_FAKES() MACRO_FOR_EACH_MAPPABLE(RESET_MAPPABLE_FUNCTION_MOCK) 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /tests/test_config.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "viv_config.h" 5 | #include "viv_config_support.h" 6 | #include "viv_toml_config.h" 7 | 8 | DEFINE_FFF_GLOBALS; 9 | #include "mock_layouts.h" 10 | #include "mock_mappable_functions.h" 11 | 12 | // Arbitrary number intended to be higher than anything realistic 13 | #define MAX_LEN_STATIC_LISTS 1000 14 | 15 | FAKE_VOID_FUNC(viv_view_set_target_box, struct viv_view *, uint32_t, uint32_t, uint32_t, uint32_t); 16 | 17 | #define RGBA_NUM_VALUES 4 18 | 19 | static char *default_config_path = "../config/config.toml"; 20 | 21 | void setUp() { 22 | RESET_FAKE(viv_view_set_target_box); 23 | RESET_LAYOUT_FAKES(); 24 | RESET_MAPPABLE_FUNCTION_FAKES(); 25 | 26 | FFF_RESET_HISTORY(); 27 | } 28 | 29 | void tearDown() { 30 | } 31 | 32 | /// The config.toml can be loaded/parsed without crashing 33 | void test_config_load(void) { 34 | struct viv_config config = { 0 }; 35 | viv_toml_config_load(default_config_path, &config, true); 36 | } 37 | 38 | /// The config.toml content matches the compiled-in defaults - this is important so that a 39 | /// user merely copying the config into place doesn't change Vivarium's behaviour. 40 | #define TEST_ASSERT_CONFIG_EQUAL(KEY) TEST_ASSERT_EQUAL_MESSAGE(default_config.KEY, load_config.KEY, "Mismatching key = " #KEY) 41 | #define TEST_ASSERT_CONFIG_EQUAL_STRING(KEY) TEST_ASSERT_EQUAL_STRING_MESSAGE(default_config.KEY, load_config.KEY, "Mismatching key = " #KEY) 42 | #define TEST_ASSERT_CONFIG_EQUAL_FLOAT(KEY) TEST_ASSERT_EQUAL_FLOAT_MESSAGE(default_config.KEY, load_config.KEY, "Mismatching key = " #KEY) 43 | #define TEST_ASSERT_CONFIG_EQUAL_FLOAT_ARRAY(KEY, LEN) TEST_ASSERT_EQUAL_FLOAT_ARRAY_MESSAGE(default_config.KEY, load_config.KEY, LEN, "Mismatching key = " #KEY) 44 | void test_config_toml_static_options_match_defaults(void) { 45 | struct viv_config load_config = { 0 }; 46 | viv_toml_config_load(default_config_path, &load_config, true); 47 | struct viv_config default_config = the_config; 48 | 49 | TEST_ASSERT_CONFIG_EQUAL(global_meta_key); 50 | TEST_ASSERT_CONFIG_EQUAL(focus_follows_mouse); 51 | TEST_ASSERT_CONFIG_EQUAL(win_move_cursor_button); 52 | TEST_ASSERT_CONFIG_EQUAL(win_resize_cursor_button); 53 | 54 | TEST_ASSERT_CONFIG_EQUAL(border_width); 55 | 56 | TEST_ASSERT_CONFIG_EQUAL_FLOAT_ARRAY(active_border_colour, RGBA_NUM_VALUES); 57 | TEST_ASSERT_CONFIG_EQUAL_FLOAT_ARRAY(inactive_border_colour, RGBA_NUM_VALUES); 58 | 59 | TEST_ASSERT_CONFIG_EQUAL(gap_width); 60 | 61 | TEST_ASSERT_CONFIG_EQUAL(allow_fullscreen); 62 | 63 | TEST_ASSERT_CONFIG_EQUAL_FLOAT_ARRAY(clear_colour, RGBA_NUM_VALUES); 64 | 65 | TEST_ASSERT_CONFIG_EQUAL_STRING(background.colour); 66 | TEST_ASSERT_CONFIG_EQUAL_STRING(background.image); 67 | TEST_ASSERT_CONFIG_EQUAL_STRING(background.mode); 68 | 69 | TEST_ASSERT_CONFIG_EQUAL_STRING(xkb_rules.rules); 70 | TEST_ASSERT_CONFIG_EQUAL_STRING(xkb_rules.model); 71 | TEST_ASSERT_CONFIG_EQUAL_STRING(xkb_rules.layout); 72 | TEST_ASSERT_CONFIG_EQUAL_STRING(xkb_rules.variant); 73 | TEST_ASSERT_CONFIG_EQUAL_STRING(xkb_rules.options); 74 | 75 | TEST_ASSERT_CONFIG_EQUAL_STRING(ipc_workspaces_filename); 76 | 77 | TEST_ASSERT_CONFIG_EQUAL_STRING(bar.command); 78 | TEST_ASSERT_CONFIG_EQUAL(bar.update_signal_number); 79 | 80 | TEST_ASSERT_CONFIG_EQUAL(debug_mark_views_by_shell); 81 | TEST_ASSERT_CONFIG_EQUAL(debug_mark_active_output); 82 | TEST_ASSERT_CONFIG_EQUAL(debug_mark_undamaged_regions); 83 | TEST_ASSERT_CONFIG_EQUAL(debug_mark_frame_draws); 84 | 85 | TEST_ASSERT_CONFIG_EQUAL(damage_tracking_mode); 86 | } 87 | 88 | void test_config_toml_workspaces_match_defaults(void) { 89 | struct viv_config load_config = { 0 }; 90 | viv_toml_config_load(default_config_path, &load_config, true); 91 | struct viv_config default_config = the_config; 92 | 93 | for (size_t i = 0; i < MAX_LEN_STATIC_LISTS; i++) { 94 | char *default_name = default_config.workspaces[i]; 95 | char *load_name = load_config.workspaces[i]; 96 | 97 | if (!strlen(default_name)) { 98 | break; 99 | } 100 | 101 | TEST_ASSERT_EQUAL_STRING(default_name, load_name); 102 | 103 | } 104 | } 105 | 106 | void test_config_toml_keybinds_match_defaults(void) { 107 | struct viv_config load_config = { 0 }; 108 | viv_toml_config_load(default_config_path, &load_config, true); 109 | struct viv_config default_config = the_config; 110 | 111 | for (size_t i = 0; i < MAX_LEN_STATIC_LISTS; i++) { 112 | struct viv_keybind default_keybind = default_config.keybinds[i]; 113 | struct viv_keybind load_keybind = load_config.keybinds[i]; 114 | 115 | if (default_keybind.binding == &viv_mappable_user_function) { 116 | // The user function binding marks the end of the default config needing to 117 | // match config.toml 118 | break; 119 | } 120 | 121 | TEST_ASSERT_EQUAL_HEX(default_keybind.key, load_keybind.key); 122 | TEST_ASSERT_EQUAL(default_keybind.modifiers, load_keybind.modifiers); 123 | TEST_ASSERT_EQUAL(default_keybind.binding, load_keybind.binding); 124 | 125 | if (default_keybind.key == NULL_KEY) { 126 | break; 127 | } 128 | } 129 | } 130 | 131 | void test_config_toml_libinput_configs_match_defaults(void) { 132 | struct viv_config load_config = { 0 }; 133 | viv_toml_config_load(default_config_path, &load_config, true); 134 | struct viv_config default_config = the_config; 135 | 136 | for (size_t i = 0; i < MAX_LEN_STATIC_LISTS; i++) { 137 | struct viv_libinput_config default_li_config = default_config.libinput_configs[i]; 138 | struct viv_libinput_config load_li_config = load_config.libinput_configs[i]; 139 | 140 | TEST_ASSERT_EQUAL_STRING(default_li_config.device_name, load_li_config.device_name); 141 | TEST_ASSERT_EQUAL(default_li_config.scroll_method, load_li_config.scroll_method); 142 | TEST_ASSERT_EQUAL(default_li_config.scroll_button, load_li_config.scroll_button); 143 | TEST_ASSERT_EQUAL(default_li_config.middle_emulation, load_li_config.middle_emulation); 144 | TEST_ASSERT_EQUAL(default_li_config.left_handed, load_li_config.left_handed); 145 | TEST_ASSERT_EQUAL(default_li_config.natural_scroll, load_li_config.natural_scroll); 146 | TEST_ASSERT_EQUAL(default_li_config.disable_while_typing, load_li_config.disable_while_typing); 147 | TEST_ASSERT_EQUAL(default_li_config.click_method, load_li_config.click_method); 148 | 149 | if (strlen(default_li_config.device_name) == 0) { 150 | break; 151 | } 152 | } 153 | } 154 | 155 | void test_config_toml_layouts_match_defaults(void) { 156 | struct viv_config load_config = { 0 }; 157 | viv_toml_config_load(default_config_path, &load_config, true); 158 | struct viv_config default_config = the_config; 159 | 160 | for (size_t i = 0; i < MAX_LEN_STATIC_LISTS; i++) { 161 | struct viv_layout default_layout = default_config.layouts[i]; 162 | struct viv_layout load_layout = load_config.layouts[i]; 163 | 164 | if (strcmp(default_layout.name, "User defined layout") == 0) { 165 | break; 166 | } 167 | 168 | TEST_ASSERT_EQUAL_STRING(default_layout.name, load_layout.name); 169 | TEST_ASSERT_EQUAL(default_layout.layout_function, load_layout.layout_function); 170 | TEST_ASSERT_EQUAL(default_layout.parameter, load_layout.parameter); 171 | TEST_ASSERT_EQUAL(default_layout.counter, load_layout.counter); 172 | TEST_ASSERT_EQUAL(default_layout.no_borders, load_layout.no_borders); 173 | TEST_ASSERT_EQUAL(default_layout.ignore_excluded_regions, load_layout.ignore_excluded_regions); 174 | 175 | if (strlen(default_layout.name) == 0) { 176 | break; 177 | } 178 | } 179 | } 180 | 181 | int main(int argc, char *argv[]) { 182 | UNUSED(argc); 183 | UNUSED(argv); 184 | 185 | UNITY_BEGIN(); 186 | RUN_TEST(test_config_load); 187 | RUN_TEST(test_config_toml_static_options_match_defaults); 188 | RUN_TEST(test_config_toml_workspaces_match_defaults); 189 | RUN_TEST(test_config_toml_keybinds_match_defaults); 190 | RUN_TEST(test_config_toml_libinput_configs_match_defaults); 191 | RUN_TEST(test_config_toml_layouts_match_defaults); 192 | return UNITY_END(); 193 | } 194 | -------------------------------------------------------------------------------- /tests/test_layouts.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "viv_layout.h" 6 | #include "viv_config_support.h" 7 | 8 | DEFINE_FFF_GLOBALS; 9 | 10 | FAKE_VOID_FUNC(viv_view_set_target_box, struct viv_view *, uint32_t, uint32_t, uint32_t, uint32_t); 11 | 12 | void setUp() { 13 | RESET_FAKE(viv_view_set_target_box); 14 | FFF_RESET_HISTORY(); 15 | } 16 | 17 | void tearDown() { 18 | } 19 | 20 | static void wl_array_append_view(struct wl_array *array, struct viv_view *view) { 21 | *(struct viv_view **)wl_array_add(array, sizeof(struct viv_view *)) = view; 22 | } 23 | 24 | #define DEFAULT_NUM_VIEWS 5 25 | #define DEFAULT_FLOAT_PARAM 0.66 26 | #define DEFAULT_COUNTER_PARAM 1 27 | #define DEFAULT_WIDTH 1024 28 | #define DEFAULT_HEIGHT 768 29 | #define MACRO_FOR_EACH_TEST_CASE(MACRO, LAYOUT_NAME) \ 30 | MACRO(LAYOUT_NAME, 0, DEFAULT_FLOAT_PARAM, DEFAULT_COUNTER_PARAM, DEFAULT_WIDTH, DEFAULT_HEIGHT) \ 31 | MACRO(LAYOUT_NAME, 1, DEFAULT_FLOAT_PARAM, DEFAULT_COUNTER_PARAM, DEFAULT_WIDTH, DEFAULT_HEIGHT) \ 32 | MACRO(LAYOUT_NAME, 2, DEFAULT_FLOAT_PARAM, DEFAULT_COUNTER_PARAM, DEFAULT_WIDTH, DEFAULT_HEIGHT) \ 33 | MACRO(LAYOUT_NAME, 3, DEFAULT_FLOAT_PARAM, DEFAULT_COUNTER_PARAM, DEFAULT_WIDTH, DEFAULT_HEIGHT) \ 34 | MACRO(LAYOUT_NAME, 4, DEFAULT_FLOAT_PARAM, DEFAULT_COUNTER_PARAM, DEFAULT_WIDTH, DEFAULT_HEIGHT) \ 35 | MACRO(LAYOUT_NAME, 5, DEFAULT_FLOAT_PARAM, DEFAULT_COUNTER_PARAM, DEFAULT_WIDTH, DEFAULT_HEIGHT) \ 36 | MACRO(LAYOUT_NAME, DEFAULT_NUM_VIEWS, 0, DEFAULT_COUNTER_PARAM, DEFAULT_WIDTH, DEFAULT_HEIGHT) \ 37 | MACRO(LAYOUT_NAME, DEFAULT_NUM_VIEWS, 0.5, DEFAULT_COUNTER_PARAM, DEFAULT_WIDTH, DEFAULT_HEIGHT) \ 38 | MACRO(LAYOUT_NAME, DEFAULT_NUM_VIEWS, 1, DEFAULT_COUNTER_PARAM, DEFAULT_WIDTH, DEFAULT_HEIGHT) \ 39 | MACRO(LAYOUT_NAME, DEFAULT_NUM_VIEWS, DEFAULT_FLOAT_PARAM, 0, DEFAULT_WIDTH, DEFAULT_HEIGHT) \ 40 | MACRO(LAYOUT_NAME, DEFAULT_NUM_VIEWS, DEFAULT_FLOAT_PARAM, 1, DEFAULT_WIDTH, DEFAULT_HEIGHT) \ 41 | MACRO(LAYOUT_NAME, DEFAULT_NUM_VIEWS, DEFAULT_FLOAT_PARAM, 2, DEFAULT_WIDTH, DEFAULT_HEIGHT) \ 42 | 43 | /// Call the given layout func with the given parameters, asserting only that it doesn't 44 | /// crash and that it calls viv_view_set_target_box the right number of times 45 | void do_test(void (layout_func)(struct wl_array *views, float float_param, uint32_t counter_param, uint32_t width, uint32_t height), uint32_t num_views, float float_param, uint32_t counter_param, uint32_t width, uint32_t height) { 46 | struct wl_array views; 47 | wl_array_init(&views); 48 | 49 | struct viv_view view; 50 | 51 | for (size_t i = 0; i < num_views; i++) { 52 | wl_array_append_view(&views, &view); 53 | } 54 | layout_func(&views, float_param, counter_param, width, height); 55 | TEST_ASSERT_EQUAL(num_views, viv_view_set_target_box_fake.call_count); 56 | RESET_FAKE(viv_view_set_target_box); 57 | } 58 | 59 | #define DO_TEST(LAYOUT_NAME, NUM_VIEWS, FLOAT_PARAM, COUNTER_PARAM, WIDTH, HEIGHT) \ 60 | do_test(viv_layout_do_ ## LAYOUT_NAME, NUM_VIEWS, FLOAT_PARAM, COUNTER_PARAM, WIDTH, HEIGHT); \ 61 | 62 | #define GENERATE_TEST_CASE_NAME(LAYOUT_NAME) test_layout_ ## LAYOUT_NAME ## _with_various_params 63 | #define GENERATE_TEST_CASE(LAYOUT_NAME, _1, _2) \ 64 | void GENERATE_TEST_CASE_NAME(LAYOUT_NAME)(void) { \ 65 | MACRO_FOR_EACH_TEST_CASE(DO_TEST, LAYOUT_NAME); \ 66 | } \ 67 | 68 | #define GENERATE_TEST_RUN(LAYOUT_NAME, _1, _2) RUN_TEST(GENERATE_TEST_CASE_NAME(LAYOUT_NAME)); 69 | 70 | MACRO_FOR_EACH_LAYOUT(GENERATE_TEST_CASE) 71 | 72 | int main(int argc, char *argv[]) { 73 | UNUSED(argc); 74 | UNUSED(argv); 75 | 76 | UNITY_BEGIN(); 77 | MACRO_FOR_EACH_LAYOUT(GENERATE_TEST_RUN); 78 | return UNITY_END(); 79 | } 80 | --------------------------------------------------------------------------------