├── .gitignore ├── LICENSE ├── README.md ├── config.json.example ├── protocol └── wlr-layer-shell-unstable-v1.xml └── src └── main.zig /.gitignore: -------------------------------------------------------------------------------- 1 | *-protocol.h 2 | .vscode 3 | zig-cache 4 | zig-out 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 David Reinharth 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # byway 2 | 3 | byway is a [Wayland](https://wayland.freedesktop.org/) compositor, 4 | inspired by [cwm](http://cvsweb.openbsd.org/cgi-bin/cvsweb/xenocara/app/cwm/), 5 | based on [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots), 6 | and written in [Zig](https://ziglang.org/). It aims to be concise both 7 | visually and in its implementation. Ease of maintenance is also a goal; Zig, wlroots, 8 | and Wayland are all moving quickly, and the intent is for byway to keep pace. 9 | byway began life as a fork of 10 | [TinyWL](https://gitlab.freedesktop.org/wlroots/wlroots/-/tree/master/tinywl), 11 | and borrows significant code from [sway](https://github.com/swaywm/sway/). 12 | 13 | byway is in the early stages of development, and bugs, including crashes, should be expected. 14 | However, byway is mostly feature-complete, and development is intended to consist of bug fixes, 15 | and adding support for Wayland protocols as needed. Additional features will be considered 16 | based on how much value they provide, and whether they can be implemented without significantly expanding 17 | the size or complexity of the codebase. 18 | 19 | ## Installation 20 | 21 | First ensure dependencies are installed, and then run `zig build` with any desired 22 | options, e.g. 23 | 24 | ``` 25 | zig build install -Drelease-safe -p ~/.local 26 | ``` 27 | 28 | ### Dependencies 29 | 30 | - zig (0.10) 31 | - wlroots (0.15) 32 | - wayland 33 | - wayland-protocols 34 | - xkbcommon 35 | - xcb 36 | - libinput 37 | - pixman 38 | - xwayland 39 | 40 | ## Configuration 41 | 42 | Behavior can be customized via `$HOME/.config/byway/config.json`. 43 | See [config.json.example](./config.json.example) for a sample configuration. 44 | 45 | - `tap_to_click`: `true` or `false` 46 | - `natural_scrolling`: `true` or `false` 47 | - `background_color`: array of values between 0 and 1 corresponding to RGBA 48 | - `border_color`: array of values between 0 and 1 corresponding to RGBA 49 | - `focused_color`: array of values between 0 and 1 corresponding to RGBA 50 | - `grabbed_color`: array of values between 0 and 1 corresponding to RGBA 51 | - `active_border_width`: pixels 52 | - `hotkeys`: array of `{modifiers, key, action, arg}`; See below 53 | - `mouse_move_modifiers`: modifier keys to move a toplevel view with the mouse 54 | - `mouse_move_button`: button to move a toplevel view with the mouse 55 | - `mouse_grow_modifiers`: modifier keys to grow or shrink a toplevel view with the mouse 56 | - `mouse_grow_button`: button to grow or shrink a toplevel view with the mouse 57 | - `autostart`: array of commands to run on startup 58 | - `move_pixels`: pixels 59 | - `grow_pixels`: pixels 60 | - `damage_tracking`: one of `"minimal"` (redraws all outputs, but only on updates), 61 | `"partial"` (redraws whole surfaces on updates), and `"full"` (only redraws damaged areas), 62 | 63 | Hotkey Actions: 64 | 65 | | Action | Description | `arg` value | 66 | | ------ | ----------- | ----------- | 67 | | `"command"` | Executes the command as a child process | command to execute | 68 | | `"toplevel_to_front"` | Raises the toplevel view with keyboard focus to the top of the stack | `""` | 69 | | `"toplevel_to_back"` | Lowers the toplevel view with keyboard focus to the bottom of the stack | `""` | 70 | | `"cycle_groups"` | Raises and focuses a window of the next application in the stack | `"1"` to cycle forward, '"-1" to cycle backward | 71 | | `"cycle_toplevels"` | Raises and focuses the next window of the currently-focused application | `"1"` to cycle forward, '"-1" to cycle backward | 72 | | `"move_toplevel"` | Moves the toplevel view with keyboard focus in the specified direction | One of `"up"`, `"down"`, `"left"`, `"right"` | 73 | | `"grow_toplevel"` | Grows or shrinks the toplevel view with keyboard focus by moving the bottom or rightmost edge | `"up"`, `"down"`, `"left"`, `"right"` | 74 | | `"close_toplevel"` | Closes the toplevel view with keyboard focus | `""` | 75 | | `"toggle_fullscreen"` | Toggles fullscreen for the toplevel view with keyboard focus | `""` | 76 | | `"toggle_spread_view"` | Toggles mode to display all toplevel views in a grid for selection | `""` | 77 | | `"toggle_hide_toplevels"` | Toggles hiding all toplevel views | `""` | 78 | | `"switch_to_workspace"` | Changes the currently visible workspace | character corresponding to workspace number, `"0"` to `"9"` | 79 | | `"toplevel_to_workspace"` | Moves the toplevel view with keyboard focus to the specified workspace | character corresponding to workspace number, `"0"` to `"9"` | 80 | | `"quit"` | Quits byway | `""` | 81 | | `"chvt"` | Changes to the specified virtual terminal | character corresponding to VT, e.g. `"1"` for tty1 | 82 | | `"reload_config"` | Reloads the configuration from `$HOME/.config/byway/config.json` | `""` | 83 | 84 | ## Contributing 85 | 86 | Issues are welcome as are bug fix PRs. New functionality will be considered on a case-by-case basis. 87 | -------------------------------------------------------------------------------- /config.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "tap_to_click": true, 3 | "natural_scrolling": true, 4 | "background_color": [0.3, 0.3, 0.3, 1.0], 5 | "border_color": [0.5, 0.5, 0.5, 1.0], 6 | "focused_color": [0.28, 0.78, 1.0, 1.0], 7 | "grabbed_color": [1.0, 0.6, 0.7, 1.0], 8 | "active_border_width": 3, 9 | "autostart": ["kanshi", "waybar", "swayidle -w timeout 600 'swaylock -c 000000'"], 10 | "move_pixels": 20, 11 | "grow_pixels": 10, 12 | "damage_tracking": "partial", 13 | "mouse_move_modifiers": ["logo"], 14 | "mouse_move_button": "left", 15 | "mouse_grow_modifiers": ["logo", "shift"], 16 | "mouse_grow_button": "left", 17 | "hotkeys": [ 18 | { 19 | "modifiers": ["logo", "shift"], 20 | "key": "X", 21 | "action": "close_toplevel", 22 | "arg": "" 23 | }, 24 | { 25 | "modifiers": ["logo"], 26 | "key": "d", 27 | "action": "toggle_hide_toplevels", 28 | "arg": "" 29 | }, 30 | { 31 | "modifiers": ["logo"], 32 | "key": "Escape", 33 | "action": "toggle_spread_view", 34 | "arg": "" 35 | }, 36 | { 37 | "modifiers": ["ctrl", "shift", "alt"], 38 | "key": "R", 39 | "action": "reload_config", 40 | "arg": "" 41 | }, 42 | { 43 | "modifiers": ["logo", "shift"], 44 | "key": "Up", 45 | "action": "grow_toplevel", 46 | "arg": "up" 47 | }, 48 | { 49 | "modifiers": ["logo", "shift"], 50 | "key": "Down", 51 | "action": "grow_toplevel", 52 | "arg": "down" 53 | }, 54 | { 55 | "modifiers": ["logo", "shift"], 56 | "key": "Left", 57 | "action": "grow_toplevel", 58 | "arg": "left" 59 | }, 60 | { 61 | "modifiers": ["logo", "shift"], 62 | "key": "Right", 63 | "action": "grow_toplevel", 64 | "arg": "right" 65 | }, 66 | { 67 | "modifiers": ["logo", "ctrl"], 68 | "key": "Up", 69 | "action": "move_toplevel", 70 | "arg": "up" 71 | }, 72 | { 73 | "modifiers": ["logo", "ctrl"], 74 | "key": "Down", 75 | "action": "move_toplevel", 76 | "arg": "down" 77 | }, 78 | { 79 | "modifiers": ["logo", "ctrl"], 80 | "key": "Left", 81 | "action": "move_toplevel", 82 | "arg": "left" 83 | }, 84 | { 85 | "modifiers": ["logo", "ctrl"], 86 | "key": "Right", 87 | "action": "move_toplevel", 88 | "arg": "right" 89 | }, 90 | { 91 | "modifiers": ["logo"], 92 | "key": "space", 93 | "action": "command", 94 | "arg": "bemenu-run" 95 | }, 96 | { 97 | "modifiers": ["logo"], 98 | "key": "t", 99 | "action": "command", 100 | "arg": "alacritty" 101 | }, 102 | { 103 | "modifiers": ["logo"], 104 | "key": "Up", 105 | "action": "toplevel_to_front", 106 | "arg": "" 107 | }, 108 | { 109 | "modifiers": ["logo"], 110 | "key": "Down", 111 | "action": "toplevel_to_back", 112 | "arg": "" 113 | }, 114 | { 115 | "modifiers": ["alt"], 116 | "key": "Tab", 117 | "action": "cycle_groups", 118 | "arg": "1" 119 | }, 120 | { 121 | "modifiers": ["alt", "shift"], 122 | "key": "ISO_Left_Tab", 123 | "action": "cycle_groups", 124 | "arg": "-1" 125 | }, 126 | { 127 | "modifiers": ["alt"], 128 | "key": "grave", 129 | "action": "cycle_toplevels", 130 | "arg": "1" 131 | }, 132 | { 133 | "modifiers": ["alt", "shift"], 134 | "key": "asciitilde", 135 | "action": "cycle_toplevels", 136 | "arg": "-1" 137 | }, 138 | { 139 | "modifiers": ["logo"], 140 | "key": "e", 141 | "action": "command", 142 | "arg": "kate" 143 | }, 144 | { 145 | "modifiers": ["logo"], 146 | "key": "w", 147 | "action": "command", 148 | "arg": "firefox" 149 | }, 150 | { 151 | "modifiers": ["logo"], 152 | "key": "c", 153 | "action": "command", 154 | "arg": "code" 155 | }, 156 | { 157 | "modifiers": ["alt", "shift"], 158 | "key": "dollar", 159 | "action": "command", 160 | "arg": "grim -g \"$(slurp)\"" 161 | }, 162 | { 163 | "modifiers": ["alt", "shift"], 164 | "key": "F", 165 | "action": "command", 166 | "arg": "killall -SIGUSR1 waybar" 167 | }, 168 | { 169 | "modifiers": ["logo", "shift"], 170 | "key": "Z", 171 | "action": "command", 172 | "arg": "makoctl dismiss" 173 | }, 174 | { 175 | "modifiers": ["logo", "shift"], 176 | "key": "X", 177 | "action": "command", 178 | "arg": "makoctl dismiss -a" 179 | }, 180 | { 181 | "modifiers": ["logo", "shift"], 182 | "key": "A", 183 | "action": "command", 184 | "arg": "makoctl invoke" 185 | }, 186 | { 187 | "modifiers": ["logo", "ctrl"], 188 | "key": "s", 189 | "action": "command", 190 | "arg": "swaylock -f -c 000000 && systemctl suspend" 191 | }, 192 | { 193 | "modifiers": ["logo", "shift"], 194 | "key": "L", 195 | "action": "command", 196 | "arg": "swaylock -c 000000" 197 | }, 198 | { 199 | "modifiers": [], 200 | "key": "XF86AudioRaiseVolume", 201 | "action": "command", 202 | "arg": "amixer -M set Master 2%+" 203 | }, 204 | { 205 | "modifiers": [], 206 | "key": "XF86AudioLowerVolume", 207 | "action": "command", 208 | "arg": "amixer -M set Master 2%-" 209 | }, 210 | { 211 | "modifiers": [], 212 | "key": "XF86AudioMute", 213 | "action": "command", 214 | "arg": "amixer -M set Master toggle" 215 | }, 216 | { 217 | "modifiers": [], 218 | "key": "XF86MonBrightnessUp", 219 | "action": "command", 220 | "arg": "brightnessctl s 5%+" 221 | }, 222 | { 223 | "modifiers": [], 224 | "key": "XF86MonBrightnessDown", 225 | "action": "command", 226 | "arg": "brightnessctl s 5%-" 227 | }, 228 | { 229 | "modifiers": ["logo"], 230 | "key": "f", 231 | "action": "toggle_fullscreen", 232 | "arg": "" 233 | }, 234 | { 235 | "modifiers": ["logo"], 236 | "key": "F1", 237 | "action": "switch_to_workspace", 238 | "arg": "1" 239 | }, 240 | { 241 | "modifiers": ["logo"], 242 | "key": "F2", 243 | "action": "switch_to_workspace", 244 | "arg": "2" 245 | }, 246 | { 247 | "modifiers": ["logo"], 248 | "key": "F3", 249 | "action": "switch_to_workspace", 250 | "arg": "3" 251 | }, 252 | { 253 | "modifiers": ["logo"], 254 | "key": "F4", 255 | "action": "switch_to_workspace", 256 | "arg": "4" 257 | }, 258 | { 259 | "modifiers": ["logo", "alt"], 260 | "key": "F1", 261 | "action": "toplevel_to_workspace", 262 | "arg": "1" 263 | }, 264 | { 265 | "modifiers": ["logo", "alt"], 266 | "key": "F2", 267 | "action": "toplevel_to_workspace", 268 | "arg": "2" 269 | }, 270 | { 271 | "modifiers": ["logo", "alt"], 272 | "key": "F3", 273 | "action": "toplevel_to_workspace", 274 | "arg": "3" 275 | }, 276 | { 277 | "modifiers": ["logo", "alt"], 278 | "key": "F4", 279 | "action": "toplevel_to_workspace", 280 | "arg": "4" 281 | }, 282 | { 283 | "modifiers": ["ctrl", "alt", "shift"], 284 | "key": "Q", 285 | "action": "quit", 286 | "arg": "" 287 | }, 288 | { 289 | "modifiers": ["ctrl", "alt"], 290 | "key": "BackSpace", 291 | "action": "quit", 292 | "arg": "" 293 | }, 294 | { 295 | "modifiers": ["ctrl", "alt"], 296 | "key": "XF86Switch_VT_1", 297 | "action": "chvt", 298 | "arg": "1" 299 | }, 300 | { 301 | "modifiers": ["ctrl", "alt"], 302 | "key": "XF86Switch_VT_2", 303 | "action": "chvt", 304 | "arg": "2" 305 | }, 306 | { 307 | "modifiers": ["ctrl", "alt"], 308 | "key": "XF86Switch_VT_3", 309 | "action": "chvt", 310 | "arg": "3" 311 | }, 312 | { 313 | "modifiers": ["ctrl", "alt"], 314 | "key": "XF86Switch_VT_4", 315 | "action": "chvt", 316 | "arg": "4" 317 | }, 318 | { 319 | "modifiers": ["ctrl", "alt"], 320 | "key": "XF86Switch_VT_5", 321 | "action": "chvt", 322 | "arg": "5" 323 | } 324 | ] 325 | } 326 | -------------------------------------------------------------------------------- /protocol/wlr-layer-shell-unstable-v1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright © 2017 Drew DeVault 5 | 6 | Permission to use, copy, modify, distribute, and sell this 7 | software and its documentation for any purpose is hereby granted 8 | without fee, provided that the above copyright notice appear in 9 | all copies and that both that copyright notice and this permission 10 | notice appear in supporting documentation, and that the name of 11 | the copyright holders not be used in advertising or publicity 12 | pertaining to distribution of the software without specific, 13 | written prior permission. The copyright holders make no 14 | representations about the suitability of this software for any 15 | purpose. It is provided "as is" without express or implied 16 | warranty. 17 | 18 | THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS 19 | SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 20 | FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 22 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 23 | AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 24 | ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 25 | THIS SOFTWARE. 26 | 27 | 28 | 29 | 30 | Clients can use this interface to assign the surface_layer role to 31 | wl_surfaces. Such surfaces are assigned to a "layer" of the output and 32 | rendered with a defined z-depth respective to each other. They may also be 33 | anchored to the edges and corners of a screen and specify input handling 34 | semantics. This interface should be suitable for the implementation of 35 | many desktop shell components, and a broad number of other applications 36 | that interact with the desktop. 37 | 38 | 39 | 40 | 41 | Create a layer surface for an existing surface. This assigns the role of 42 | layer_surface, or raises a protocol error if another role is already 43 | assigned. 44 | 45 | Creating a layer surface from a wl_surface which has a buffer attached 46 | or committed is a client error, and any attempts by a client to attach 47 | or manipulate a buffer prior to the first layer_surface.configure call 48 | must also be treated as errors. 49 | 50 | After creating a layer_surface object and setting it up, the client 51 | must perform an initial commit without any buffer attached. 52 | The compositor will reply with a layer_surface.configure event. 53 | The client must acknowledge it and is then allowed to attach a buffer 54 | to map the surface. 55 | 56 | You may pass NULL for output to allow the compositor to decide which 57 | output to use. Generally this will be the one that the user most 58 | recently interacted with. 59 | 60 | Clients can specify a namespace that defines the purpose of the layer 61 | surface. 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | These values indicate which layers a surface can be rendered in. They 79 | are ordered by z depth, bottom-most first. Traditional shell surfaces 80 | will typically be rendered between the bottom and top layers. 81 | Fullscreen shell surfaces are typically rendered at the top layer. 82 | Multiple surfaces can share a single layer, and ordering within a 83 | single layer is undefined. 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | This request indicates that the client will not use the layer_shell 97 | object any more. Objects that have been created through this instance 98 | are not affected. 99 | 100 | 101 | 102 | 103 | 104 | 105 | An interface that may be implemented by a wl_surface, for surfaces that 106 | are designed to be rendered as a layer of a stacked desktop-like 107 | environment. 108 | 109 | Layer surface state (layer, size, anchor, exclusive zone, 110 | margin, interactivity) is double-buffered, and will be applied at the 111 | time wl_surface.commit of the corresponding wl_surface is called. 112 | 113 | Attaching a null buffer to a layer surface unmaps it. 114 | 115 | Unmapping a layer_surface means that the surface cannot be shown by the 116 | compositor until it is explicitly mapped again. The layer_surface 117 | returns to the state it had right after layer_shell.get_layer_surface. 118 | The client can re-map the surface by performing a commit without any 119 | buffer attached, waiting for a configure event and handling it as usual. 120 | 121 | 122 | 123 | 124 | Sets the size of the surface in surface-local coordinates. The 125 | compositor will display the surface centered with respect to its 126 | anchors. 127 | 128 | If you pass 0 for either value, the compositor will assign it and 129 | inform you of the assignment in the configure event. You must set your 130 | anchor to opposite edges in the dimensions you omit; not doing so is a 131 | protocol error. Both values are 0 by default. 132 | 133 | Size is double-buffered, see wl_surface.commit. 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | Requests that the compositor anchor the surface to the specified edges 142 | and corners. If two orthogonal edges are specified (e.g. 'top' and 143 | 'left'), then the anchor point will be the intersection of the edges 144 | (e.g. the top left corner of the output); otherwise the anchor point 145 | will be centered on that edge, or in the center if none is specified. 146 | 147 | Anchor is double-buffered, see wl_surface.commit. 148 | 149 | 150 | 151 | 152 | 153 | 154 | Requests that the compositor avoids occluding an area with other 155 | surfaces. The compositor's use of this information is 156 | implementation-dependent - do not assume that this region will not 157 | actually be occluded. 158 | 159 | A positive value is only meaningful if the surface is anchored to one 160 | edge or an edge and both perpendicular edges. If the surface is not 161 | anchored, anchored to only two perpendicular edges (a corner), anchored 162 | to only two parallel edges or anchored to all edges, a positive value 163 | will be treated the same as zero. 164 | 165 | A positive zone is the distance from the edge in surface-local 166 | coordinates to consider exclusive. 167 | 168 | Surfaces that do not wish to have an exclusive zone may instead specify 169 | how they should interact with surfaces that do. If set to zero, the 170 | surface indicates that it would like to be moved to avoid occluding 171 | surfaces with a positive exclusive zone. If set to -1, the surface 172 | indicates that it would not like to be moved to accommodate for other 173 | surfaces, and the compositor should extend it all the way to the edges 174 | it is anchored to. 175 | 176 | For example, a panel might set its exclusive zone to 10, so that 177 | maximized shell surfaces are not shown on top of it. A notification 178 | might set its exclusive zone to 0, so that it is moved to avoid 179 | occluding the panel, but shell surfaces are shown underneath it. A 180 | wallpaper or lock screen might set their exclusive zone to -1, so that 181 | they stretch below or over the panel. 182 | 183 | The default value is 0. 184 | 185 | Exclusive zone is double-buffered, see wl_surface.commit. 186 | 187 | 188 | 189 | 190 | 191 | 192 | Requests that the surface be placed some distance away from the anchor 193 | point on the output, in surface-local coordinates. Setting this value 194 | for edges you are not anchored to has no effect. 195 | 196 | The exclusive zone includes the margin. 197 | 198 | Margin is double-buffered, see wl_surface.commit. 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | Types of keyboard interaction possible for layer shell surfaces. The 209 | rationale for this is twofold: (1) some applications are not interested 210 | in keyboard events and not allowing them to be focused can improve the 211 | desktop experience; (2) some applications will want to take exclusive 212 | keyboard focus. 213 | 214 | 215 | 216 | 217 | This value indicates that this surface is not interested in keyboard 218 | events and the compositor should never assign it the keyboard focus. 219 | 220 | This is the default value, set for newly created layer shell surfaces. 221 | 222 | This is useful for e.g. desktop widgets that display information or 223 | only have interaction with non-keyboard input devices. 224 | 225 | 226 | 227 | 228 | Request exclusive keyboard focus if this surface is above the shell surface layer. 229 | 230 | For the top and overlay layers, the seat will always give 231 | exclusive keyboard focus to the top-most layer which has keyboard 232 | interactivity set to exclusive. If this layer contains multiple 233 | surfaces with keyboard interactivity set to exclusive, the compositor 234 | determines the one receiving keyboard events in an implementation- 235 | defined manner. In this case, no guarantee is made when this surface 236 | will receive keyboard focus (if ever). 237 | 238 | For the bottom and background layers, the compositor is allowed to use 239 | normal focus semantics. 240 | 241 | This setting is mainly intended for applications that need to ensure 242 | they receive all keyboard events, such as a lock screen or a password 243 | prompt. 244 | 245 | 246 | 247 | 248 | This requests the compositor to allow this surface to be focused and 249 | unfocused by the user in an implementation-defined manner. The user 250 | should be able to unfocus this surface even regardless of the layer 251 | it is on. 252 | 253 | Typically, the compositor will want to use its normal mechanism to 254 | manage keyboard focus between layer shell surfaces with this setting 255 | and regular toplevels on the desktop layer (e.g. click to focus). 256 | Nevertheless, it is possible for a compositor to require a special 257 | interaction to focus or unfocus layer shell surfaces (e.g. requiring 258 | a click even if focus follows the mouse normally, or providing a 259 | keybinding to switch focus between layers). 260 | 261 | This setting is mainly intended for desktop shell components (e.g. 262 | panels) that allow keyboard interaction. Using this option can allow 263 | implementing a desktop shell that can be fully usable without the 264 | mouse. 265 | 266 | 267 | 268 | 269 | 270 | 271 | Set how keyboard events are delivered to this surface. By default, 272 | layer shell surfaces do not receive keyboard events; this request can 273 | be used to change this. 274 | 275 | This setting is inherited by child surfaces set by the get_popup 276 | request. 277 | 278 | Layer surfaces receive pointer, touch, and tablet events normally. If 279 | you do not want to receive them, set the input region on your surface 280 | to an empty region. 281 | 282 | Keyboard interactivity is double-buffered, see wl_surface.commit. 283 | 284 | 285 | 286 | 287 | 288 | 289 | This assigns an xdg_popup's parent to this layer_surface. This popup 290 | should have been created via xdg_surface::get_popup with the parent set 291 | to NULL, and this request must be invoked before committing the popup's 292 | initial state. 293 | 294 | See the documentation of xdg_popup for more details about what an 295 | xdg_popup is and how it is used. 296 | 297 | 298 | 299 | 300 | 301 | 302 | When a configure event is received, if a client commits the 303 | surface in response to the configure event, then the client 304 | must make an ack_configure request sometime before the commit 305 | request, passing along the serial of the configure event. 306 | 307 | If the client receives multiple configure events before it 308 | can respond to one, it only has to ack the last configure event. 309 | 310 | A client is not required to commit immediately after sending 311 | an ack_configure request - it may even ack_configure several times 312 | before its next surface commit. 313 | 314 | A client may send multiple ack_configure requests before committing, but 315 | only the last request sent before a commit indicates which configure 316 | event the client really is responding to. 317 | 318 | 319 | 320 | 321 | 322 | 323 | This request destroys the layer surface. 324 | 325 | 326 | 327 | 328 | 329 | The configure event asks the client to resize its surface. 330 | 331 | Clients should arrange their surface for the new states, and then send 332 | an ack_configure request with the serial sent in this configure event at 333 | some point before committing the new surface. 334 | 335 | The client is free to dismiss all but the last configure event it 336 | received. 337 | 338 | The width and height arguments specify the size of the window in 339 | surface-local coordinates. 340 | 341 | The size is a hint, in the sense that the client is free to ignore it if 342 | it doesn't resize, pick a smaller size (to satisfy aspect ratio or 343 | resize in steps of NxM pixels). If the client picks a smaller size and 344 | is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the 345 | surface will be centered on this axis. 346 | 347 | If the width or height arguments are zero, it means the client should 348 | decide its own window dimension. 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | The closed event is sent by the compositor when the surface will no 358 | longer be shown. The output may have been destroyed or the user may 359 | have asked for it to be removed. Further changes to the surface will be 360 | ignored. The client should destroy the resource after receiving this 361 | event, and create a new surface if they so choose. 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | Change the layer that the surface is rendered on. 384 | 385 | Layer is double-buffered, see wl_surface.commit. 386 | 387 | 388 | 389 | 390 | 391 | 392 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const wlr = @cImport({ 3 | @cDefine("_POSIX_C_SOURCE", "200112L"); 4 | @cDefine("WLR_USE_UNSTABLE", {}); 5 | @cInclude("linux/input-event-codes.h"); 6 | @cInclude("unistd.h"); 7 | @cInclude("wayland-server-core.h"); 8 | @cInclude("wlr/backend.h"); 9 | @cInclude("wlr/backend/libinput.h"); 10 | @cInclude("wlr/render/allocator.h"); 11 | @cInclude("wlr/render/wlr_renderer.h"); 12 | @cInclude("wlr/types/wlr_cursor.h"); 13 | @cInclude("wlr/types/wlr_compositor.h"); 14 | @cInclude("wlr/types/wlr_data_control_v1.h"); 15 | @cInclude("wlr/types/wlr_data_device.h"); 16 | @cInclude("wlr/types/wlr_export_dmabuf_v1.h"); 17 | @cInclude("wlr/types/wlr_gamma_control_v1.h"); 18 | @cInclude("wlr/types/wlr_idle.h"); 19 | @cInclude("wlr/types/wlr_input_device.h"); 20 | @cInclude("wlr/types/wlr_input_inhibitor.h"); 21 | @cInclude("wlr/types/wlr_keyboard.h"); 22 | @cInclude("wlr/types/wlr_layer_shell_v1.h"); 23 | @cInclude("wlr/types/wlr_matrix.h"); 24 | @cInclude("wlr/types/wlr_output.h"); 25 | @cInclude("wlr/types/wlr_output_damage.h"); 26 | @cInclude("wlr/types/wlr_output_layout.h"); 27 | @cInclude("wlr/types/wlr_output_management_v1.h"); 28 | @cInclude("wlr/types/wlr_pointer.h"); 29 | @cInclude("wlr/types/wlr_presentation_time.h"); 30 | @cInclude("wlr/types/wlr_primary_selection.h"); 31 | @cInclude("wlr/types/wlr_primary_selection_v1.h"); 32 | @cInclude("wlr/types/wlr_seat.h"); 33 | @cInclude("wlr/types/wlr_server_decoration.h"); 34 | @cInclude("wlr/types/wlr_screencopy_v1.h"); 35 | @cInclude("wlr/types/wlr_viewporter.h"); 36 | @cInclude("wlr/types/wlr_virtual_keyboard_v1.h"); 37 | @cInclude("wlr/types/wlr_virtual_pointer_v1.h"); 38 | @cInclude("wlr/types/wlr_xcursor_manager.h"); 39 | @cInclude("wlr/types/wlr_xdg_shell.h"); 40 | @cInclude("wlr/types/wlr_xdg_decoration_v1.h"); 41 | @cInclude("wlr/types/wlr_xdg_output_v1.h"); 42 | @cInclude("wlr/util/log.h"); 43 | @cInclude("wlr/util/region.h"); 44 | @cInclude("wlr/xwayland.h"); 45 | @cInclude("X11/Xlib.h"); 46 | @cInclude("xkbcommon/xkbcommon.h"); 47 | }); 48 | 49 | pub fn main() !void { 50 | std.log.info("Starting Byway", .{}); 51 | 52 | var server: Server = undefined; 53 | try server.init(); 54 | defer server.deinit(); 55 | try server.start(); 56 | 57 | std.log.info("Exiting Byway", .{}); 58 | } 59 | 60 | const Surface = struct { 61 | fn create(server: *Server, typed_surface: anytype, ancestor: ?*Surface) !?*Surface { 62 | if (@TypeOf(typed_surface) == *wlr.wlr_xdg_surface and 63 | typed_surface.role == wlr.WLR_XDG_SURFACE_ROLE_POPUP and 64 | ancestor == null) 65 | { 66 | return null; 67 | } 68 | 69 | var surface = try alloc.create(Surface); 70 | try surface.init(typed_surface, server, ancestor); 71 | 72 | return surface; 73 | } 74 | 75 | fn init( 76 | self: *Surface, 77 | typed_surface: anytype, 78 | server: *Server, 79 | ancestor: ?*Surface, 80 | ) !void { 81 | self.server = server; 82 | self.wlr_surface = null; 83 | self.mapped = false; 84 | self.list = null; 85 | self.node.data = self; 86 | self.is_requested_fullscreen = false; 87 | self.has_border = false; 88 | self.ancestor = ancestor; 89 | self.output_box = .{ .x = 0, .y = 0, .width = 0, .height = 0 }; 90 | self.workspace = 0; 91 | self.is_actual_fullscreen = false; 92 | self.borders = .{ 93 | .{ .x = 0, .y = 0, .width = 0, .height = 0 }, 94 | .{ .x = 0, .y = 0, .width = 0, .height = 0 }, 95 | .{ .x = 0, .y = 0, .width = 0, .height = 0 }, 96 | .{ .x = 0, .y = 0, .width = 0, .height = 0 }, 97 | }; 98 | 99 | Signal.connect(void, self, "map", Surface.onMap, &typed_surface.events.map); 100 | Signal.connect(void, self, "unmap", Surface.onUnmap, &typed_surface.events.unmap); 101 | Signal.connect(void, self, "destroy", Surface.onDestroy, &typed_surface.events.destroy); 102 | 103 | switch (@TypeOf(typed_surface)) { 104 | *wlr.wlr_xdg_surface => { 105 | self.typed_surface = SurfaceType{ .xdg = typed_surface }; 106 | Signal.connect( 107 | *wlr.wlr_xdg_popup, 108 | self, 109 | "popup", 110 | Surface.onNewPopup, 111 | &typed_surface.events.new_popup, 112 | ); 113 | 114 | switch (typed_surface.role) { 115 | wlr.WLR_XDG_SURFACE_ROLE_TOPLEVEL => { 116 | wlr.wlr_xdg_surface_ping(typed_surface); 117 | self.list = &server.toplevels; 118 | self.has_border = true; 119 | 120 | Signal.connect( 121 | void, 122 | self, 123 | "request_fullscreen", 124 | Surface.onRequestFullscreen, 125 | &@ptrCast( 126 | *wlr.wlr_xdg_toplevel, 127 | @field(typed_surface, wlr_xdg_surface_union).toplevel, 128 | ).events.request_fullscreen, 129 | ); 130 | }, 131 | else => {}, 132 | } 133 | }, 134 | *wlr.wlr_xwayland_surface => { 135 | self.typed_surface = SurfaceType{ .xwayland = typed_surface }; 136 | if (typed_surface.override_redirect) { 137 | self.list = &server.unmanaged_toplevels; 138 | } else { 139 | self.has_border = true; 140 | self.list = &server.toplevels; 141 | } 142 | Signal.connect( 143 | void, 144 | self, 145 | "request_fullscreen", 146 | Surface.onRequestFullscreen, 147 | &typed_surface.events.request_fullscreen, 148 | ); 149 | Signal.connect( 150 | void, 151 | self, 152 | "activate", 153 | Surface.onActivate, 154 | &typed_surface.events.request_activate, 155 | ); 156 | Signal.connect( 157 | *wlr.wlr_xwayland_surface_configure_event, 158 | self, 159 | "configure", 160 | Surface.onConfigure, 161 | &typed_surface.events.request_configure, 162 | ); 163 | }, 164 | *wlr.wlr_layer_surface_v1 => { 165 | self.typed_surface = SurfaceType{ .layer = typed_surface }; 166 | Signal.connect( 167 | *wlr.wlr_xdg_popup, 168 | self, 169 | "popup", 170 | Surface.onNewPopup, 171 | &typed_surface.events.new_popup, 172 | ); 173 | if (server.outputAtCursor()) |output| { 174 | if (typed_surface.output == null) { 175 | typed_surface.output = output.wlr_output; 176 | } 177 | } 178 | if (Output.fromWlrOutput(typed_surface.output)) |output| { 179 | self.layer = typed_surface.pending.layer; 180 | output.layers[self.layer].prepend(&self.node); 181 | output.arrangeLayers(); 182 | } 183 | }, 184 | *wlr.wlr_subsurface => { 185 | self.typed_surface = SurfaceType{ .subsurface = typed_surface }; 186 | }, 187 | *wlr.wlr_drag_icon => { 188 | self.typed_surface = SurfaceType{ .drag_icon = typed_surface }; 189 | }, 190 | else => { 191 | std.log.err("unknown surface {s} {d}", .{ @TypeOf(typed_surface), typed_surface }); 192 | }, 193 | } 194 | } 195 | 196 | fn onCommit(self: *Surface, _: void) !void { 197 | switch (self.typed_surface) { 198 | .xdg => |xdg| { 199 | if (self.pending_serial > 0 and xdg.current.configure_serial >= self.pending_serial) { 200 | self.pending_serial = 0; 201 | self.server.damageAllOutputs(); 202 | } 203 | }, 204 | .layer => |layer_surface| { 205 | if (Output.fromWlrOutput(layer_surface.output)) |output| { 206 | if (layer_surface.current.committed != 0 or 207 | self.mapped != layer_surface.mapped) 208 | { 209 | self.mapped = layer_surface.mapped; 210 | if (self.layer != layer_surface.current.layer) { 211 | output.layers[self.layer].remove(&self.node); 212 | output.layers[layer_surface.current.layer].prepend( 213 | &self.node, 214 | ); 215 | self.layer = layer_surface.current.layer; 216 | } 217 | output.arrangeLayers(); 218 | } 219 | } 220 | }, 221 | else => {}, 222 | } 223 | 224 | self.updateBorders(); 225 | self.damage(if (self.server.config.damage_tracking == .full) false else true); 226 | } 227 | 228 | fn shouldFocus(self: *Surface) bool { 229 | return switch (self.typed_surface) { 230 | .xwayland => |xwayland| !xwayland.override_redirect or 231 | wlr.wlr_xwayland_or_surface_wants_focus(xwayland), 232 | .layer => self.layer != wlr.ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND and 233 | self.layer != wlr.ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, 234 | else => true, 235 | }; 236 | } 237 | 238 | fn onMap(self: *Surface, _: void) !void { 239 | self.mapped = true; 240 | self.initWlrSurface(); 241 | 242 | if (self.has_border) self.place(); 243 | switch (self.typed_surface) { 244 | .xwayland => |xwayland| { 245 | self.output_box.x = xwayland.x; 246 | self.output_box.y = xwayland.y; 247 | }, 248 | else => {}, 249 | } 250 | if (self.list) |list| list.prepend(&self.node); 251 | self.setKeyboardFocus(); 252 | self.updateOutputs(); 253 | self.server.damageAllOutputs(); 254 | } 255 | 256 | fn onUnmap(self: *Surface, _: void) !void { 257 | if (!self.mapped) return; 258 | self.mapped = false; 259 | wlr.wl_list_remove(&self.commit.link); 260 | switch (self.typed_surface) { 261 | .xdg, .subsurface => { 262 | wlr.wl_list_remove(&self.subsurface.link); 263 | }, 264 | else => {}, 265 | } 266 | self.server.damageAllOutputs(); 267 | 268 | self.wlr_surface = null; 269 | 270 | if (self.list) |list| { 271 | list.remove(&self.node); 272 | } 273 | if (self.server.grabbed_toplevel == self) { 274 | self.server.cursor_mode = .passthrough; 275 | self.server.grabbed_toplevel = null; 276 | } 277 | self.server.processCursorMotion(0); 278 | } 279 | 280 | fn onDestroy(self: *Surface, _: void) !void { 281 | wlr.wl_list_remove(&self.map.link); 282 | wlr.wl_list_remove(&self.unmap.link); 283 | if (self.mapped) { 284 | self.server.damageAllOutputs(); 285 | } 286 | wlr.wl_list_remove(&self.destroy.link); 287 | 288 | if (self.list) |_| { 289 | wlr.wl_list_remove(&self.request_fullscreen.link); 290 | switch (self.typed_surface) { 291 | .xwayland => |xwayland| { 292 | if (!xwayland.override_redirect) { 293 | wlr.wl_list_remove(&self.activate.link); 294 | wlr.wl_list_remove(&self.configure.link); 295 | } 296 | }, 297 | else => {}, 298 | } 299 | } 300 | 301 | switch (self.typed_surface) { 302 | .layer => |layer_surface| { 303 | if (Output.fromWlrOutput(layer_surface.output)) |output| { 304 | output.arrangeLayers(); 305 | output.layers[self.layer].remove(&self.node); 306 | layer_surface.output = null; 307 | } 308 | }, 309 | else => {}, 310 | } 311 | 312 | alloc.destroy(self); 313 | } 314 | 315 | fn onActivate(self: *Surface, _: void) !void { 316 | switch (self.typed_surface) { 317 | .xwayland => |xwayland| { 318 | wlr.wlr_xwayland_surface_activate(xwayland, true); 319 | }, 320 | else => { 321 | std.log.err("unexpected activate", .{}); 322 | }, 323 | } 324 | self.server.damageAllOutputs(); 325 | } 326 | 327 | fn onConfigure( 328 | self: *Surface, 329 | event: *wlr.wlr_xwayland_surface_configure_event, 330 | ) !void { 331 | switch (self.typed_surface) { 332 | .xwayland => |xwayland| { 333 | self.updateBorders(); 334 | wlr.wlr_xwayland_surface_configure( 335 | xwayland, 336 | event.x, 337 | event.y, 338 | event.width, 339 | event.height, 340 | ); 341 | }, 342 | else => { 343 | std.log.err("unexpected configure", .{}); 344 | }, 345 | } 346 | self.server.damageAllOutputs(); 347 | } 348 | 349 | fn onNewPopup(self: *Surface, wlr_popup: *wlr.wlr_xdg_popup) !void { 350 | _ = try Surface.create( 351 | self.server, 352 | @ptrCast(*wlr.wlr_xdg_surface, wlr_popup.base), 353 | if (self.has_border) self else if (self.ancestor) |ancestor| ancestor else self, 354 | ); 355 | } 356 | 357 | fn onNewSubsurface(self: *Surface, wlr_subsurface: *wlr.wlr_subsurface) !void { 358 | _ = try Surface.create(self.server, wlr_subsurface, if (self.has_border) self else self.ancestor); 359 | } 360 | 361 | fn onRequestFullscreen(self: *Surface, _: void) !void { 362 | self.is_requested_fullscreen = !self.is_requested_fullscreen; 363 | 364 | switch (self.typed_surface) { 365 | .xdg => |xdg| { 366 | _ = wlr.wlr_xdg_toplevel_set_fullscreen(xdg, self.is_requested_fullscreen); 367 | }, 368 | .xwayland => |xwayland| { 369 | wlr.wlr_xwayland_surface_set_fullscreen(xwayland, self.is_requested_fullscreen); 370 | }, 371 | else => {}, 372 | } 373 | } 374 | 375 | fn initWlrSurface(self: *Surface) void { 376 | if (self.wlr_surface != null) return; 377 | 378 | self.wlr_surface = switch (self.typed_surface) { 379 | .xdg => |xdg| xdg.surface, 380 | .xwayland => |xwayland| xwayland.surface, 381 | .layer => |layer_surface| layer_surface.surface, 382 | .subsurface => |subsurface| subsurface.surface, 383 | .drag_icon => |drag_icon| drag_icon.surface, 384 | }; 385 | 386 | if (self.wlr_surface) |wlr_surface| { 387 | wlr_surface.data = self; 388 | Signal.connect( 389 | *wlr.wlr_subsurface, 390 | self, 391 | "subsurface", 392 | Surface.onNewSubsurface, 393 | &wlr_surface.events.new_subsurface, 394 | ); 395 | Signal.connect( 396 | void, 397 | self, 398 | "commit", 399 | Surface.onCommit, 400 | &wlr_surface.events.commit, 401 | ); 402 | 403 | for ([2]*wlr.wl_list{ 404 | &wlr_surface.current.subsurfaces_below, 405 | &wlr_surface.current.subsurfaces_above, 406 | }) |subsurfaces| { 407 | var iter: *wlr.wl_list = subsurfaces.next; 408 | while (iter != subsurfaces) : (iter = iter.next) { 409 | self.onNewSubsurface(@fieldParentPtr( 410 | wlr.wlr_subsurface, 411 | "current", 412 | @fieldParentPtr(wlr.wlr_subsurface_parent_state, "link", iter), 413 | )) catch unreachable; 414 | } 415 | } 416 | } 417 | } 418 | 419 | fn unconstrainPopup(self: *Surface) void { 420 | switch (self.typed_surface) { 421 | .xdg => |xdg| { 422 | if (xdg.role == wlr.WLR_XDG_SURFACE_ROLE_POPUP) { 423 | if (self.getCenterOutput()) |output| { 424 | var box: wlr.wlr_box = undefined; 425 | output.getBox(&box); 426 | wlr.wlr_xdg_popup_unconstrain_from_box( 427 | @ptrCast(*wlr.wlr_xdg_popup, @field(xdg, wlr_xdg_surface_union).popup), 428 | &box, 429 | ); 430 | } 431 | } 432 | }, 433 | else => {}, 434 | } 435 | } 436 | fn toFront(self: *Surface) void { 437 | switch (self.typed_surface) { 438 | .xwayland => |xwayland| { 439 | wlr.wlr_xwayland_surface_restack(xwayland, null, wlr.XCB_STACK_MODE_ABOVE); 440 | }, 441 | else => {}, 442 | } 443 | } 444 | 445 | fn toBack(self: *Surface) void { 446 | switch (self.typed_surface) { 447 | .xwayland => |xwayland| { 448 | wlr.wlr_xwayland_surface_restack(xwayland, null, wlr.XCB_STACK_MODE_BELOW); 449 | }, 450 | else => {}, 451 | } 452 | } 453 | 454 | fn setKeyboardFocus(self: *Surface) void { 455 | if (self.wlr_surface) |wlr_surface| { 456 | if (self.shouldFocus()) self.server.setKeyboardFocus(wlr_surface); 457 | } 458 | } 459 | 460 | fn setGeometry(self: *Surface, box: wlr.wlr_box) void { 461 | self.output_box = box; 462 | 463 | switch (self.typed_surface) { 464 | .xdg => |xdg| { 465 | var xdg_box: wlr.wlr_box = undefined; 466 | wlr.wlr_xdg_surface_get_geometry(xdg, &xdg_box); 467 | self.output_box.x -= xdg_box.x; 468 | self.output_box.y -= xdg_box.y; 469 | self.pending_serial = wlr.wlr_xdg_toplevel_set_size( 470 | xdg, 471 | @intCast(u32, box.width), 472 | @intCast(u32, box.height), 473 | ); 474 | }, 475 | .xwayland => |xwayland| { 476 | if (self.getCenterOutput()) |output| { 477 | wlr.wlr_xwayland_surface_configure( 478 | xwayland, 479 | @intCast(i16, output.total_box.x + self.output_box.x), 480 | @intCast(i16, output.total_box.y + self.output_box.y), 481 | @intCast(u16, box.width), 482 | @intCast(u16, box.height), 483 | ); 484 | } 485 | }, 486 | else => {}, 487 | } 488 | 489 | self.updateOutputs(); 490 | self.updateBorders(); 491 | self.server.damageAllOutputs(); 492 | } 493 | 494 | fn getGeometry(self: *Surface, box: *wlr.wlr_box) void { 495 | box.x = 0; 496 | box.y = 0; 497 | switch (self.typed_surface) { 498 | .xdg => |xdg| { 499 | wlr.wlr_xdg_surface_get_geometry(xdg, box); 500 | }, 501 | .xwayland => |xwayland| { 502 | box.width = xwayland.width; 503 | box.height = xwayland.height; 504 | }, 505 | .layer => { 506 | box.width = self.output_box.width; 507 | box.height = self.output_box.height; 508 | }, 509 | else => {}, 510 | } 511 | box.x += self.output_box.x; 512 | box.y += self.output_box.y; 513 | } 514 | 515 | fn isXdgPopup(wlr_surface: *wlr.wlr_surface) bool { 516 | return std.mem.eql(u8, "xdg_popup", std.mem.span( 517 | @ptrCast(*const wlr.wlr_surface_role, wlr_surface.role).name, 518 | )); 519 | } 520 | 521 | fn adjustBoundsFromPopup(xdg_popup: *wlr.wlr_xdg_popup, parent_box: *wlr.wlr_box) void { 522 | parent_box.x += xdg_popup.geometry.x; 523 | parent_box.y += xdg_popup.geometry.y; 524 | 525 | if (@ptrCast(?*wlr.wlr_surface, xdg_popup.parent)) |parent| { 526 | if (Surface.isXdgPopup(parent)) { 527 | adjustBoundsFromPopup( 528 | @field(@ptrCast( 529 | *wlr.wlr_xdg_surface, 530 | wlr.wlr_xdg_surface_from_wlr_surface(parent), 531 | ), wlr_xdg_surface_union).popup, 532 | parent_box, 533 | ); 534 | } 535 | } 536 | } 537 | 538 | fn getParentBox(self: *Surface, box: *wlr.wlr_box) void { 539 | if (self.ancestor) |ancestor| { 540 | ancestor.getGeometry(box); 541 | } else { 542 | self.getGeometry(box); 543 | } 544 | 545 | switch (self.typed_surface) { 546 | .xdg => |xdg| { 547 | if (xdg.role == wlr.WLR_XDG_SURFACE_ROLE_POPUP) { 548 | adjustBoundsFromPopup( 549 | @field(xdg, wlr_xdg_surface_union).popup, 550 | box, 551 | ); 552 | } 553 | }, 554 | .subsurface => |subsurface| { 555 | var parent: *wlr.wlr_surface = subsurface.parent; 556 | if (Surface.isXdgPopup(parent)) { 557 | adjustBoundsFromPopup( 558 | @field(@ptrCast( 559 | *wlr.wlr_xdg_surface, 560 | wlr.wlr_xdg_surface_from_wlr_surface(parent), 561 | ), wlr_xdg_surface_union).popup, 562 | box, 563 | ); 564 | } 565 | }, 566 | else => {}, 567 | } 568 | } 569 | 570 | fn getLayoutBox(self: *Surface, box: *wlr.wlr_box) void { 571 | if (self.wlr_surface) |wlr_surface| { 572 | var parent_box: wlr.wlr_box = undefined; 573 | self.getParentBox(&parent_box); 574 | box.width = wlr_surface.current.width; 575 | box.height = wlr_surface.current.height; 576 | box.x = parent_box.x + wlr_surface.sx; 577 | box.y = parent_box.y + wlr_surface.sy; 578 | } 579 | } 580 | 581 | const UpdateOutput = struct { 582 | wlr_output: *wlr.wlr_output, 583 | enter: bool, 584 | }; 585 | 586 | fn updateOutputIter( 587 | surf: [*c]wlr.wlr_surface, 588 | _: i32, 589 | _: i32, 590 | data: ?*anyopaque, 591 | ) callconv(.C) void { 592 | const uo = @ptrCast(*UpdateOutput, @alignCast(@alignOf(*UpdateOutput), data)); 593 | const wlr_surface = @ptrCast(*wlr.wlr_surface, surf); 594 | 595 | if (uo.enter) { 596 | wlr.wlr_surface_send_enter(wlr_surface, uo.wlr_output); 597 | if (Surface.fromWlrSurface(wlr_surface)) |s| s.unconstrainPopup(); 598 | } else { 599 | wlr.wlr_surface_send_leave(wlr_surface, uo.wlr_output); 600 | } 601 | } 602 | 603 | fn updateOutputs(self: *Surface) void { 604 | var box: wlr.wlr_box = undefined; 605 | self.getLayoutBox(&box); 606 | var iter = self.server.outputs.first; 607 | while (iter) |node| : (iter = node.next) { 608 | var output_box: wlr.wlr_box = undefined; 609 | node.data.getBox(&output_box); 610 | var intersection: wlr.wlr_box = undefined; 611 | var uo = UpdateOutput{ 612 | .wlr_output = node.data.wlr_output, 613 | .enter = wlr.wlr_box_intersection(&intersection, &output_box, &box), 614 | }; 615 | self.forEachSurface(updateOutputIter, &uo, false); 616 | self.forEachSurface(updateOutputIter, &uo, true); 617 | } 618 | } 619 | 620 | fn damage(self: *Surface, whole: bool) void { 621 | if (self.server.config.damage_tracking == .minimal) { 622 | self.server.damageAllOutputs(); 623 | return; 624 | } 625 | 626 | var box: wlr.wlr_box = undefined; 627 | self.getLayoutBox(&box); 628 | var iter = self.server.outputs.first; 629 | while (iter) |node| : (iter = node.next) { 630 | const layout = node.data.getLayout(); 631 | var intersection: wlr.wlr_box = undefined; 632 | var output_box: wlr.wlr_box = undefined; 633 | node.data.getBox(&output_box); 634 | 635 | if (wlr.wlr_box_intersection(&intersection, &output_box, &box)) { 636 | var ddata = DamageData{ 637 | .output = node.data, 638 | .whole = whole, 639 | .box = wlr.wlr_box{ 640 | .width = box.width, 641 | .height = box.height, 642 | .x = box.x - layout.x, 643 | .y = box.y - layout.y, 644 | }, 645 | }; 646 | 647 | self.forEachSurface(damageIter, &ddata, false); 648 | } 649 | } 650 | } 651 | 652 | const DamageData = struct { 653 | output: *Output, 654 | box: wlr.wlr_box, 655 | whole: bool, 656 | }; 657 | 658 | fn damageIter( 659 | surf: [*c]wlr.wlr_surface, 660 | sx: i32, 661 | sy: i32, 662 | data: ?*anyopaque, 663 | ) callconv(.C) void { 664 | const ddata = @ptrCast(*DamageData, @alignCast(@alignOf(*DamageData), data)); 665 | const wlr_surface = @ptrCast(*wlr.wlr_surface, surf); 666 | var box = ddata.box; 667 | box.x += sx; 668 | box.y += sy; 669 | scaleBox(&box, ddata.output.wlr_output.scale); 670 | 671 | if (wlr.pixman_region32_not_empty(&wlr_surface.buffer_damage) != 0) { 672 | var pixman_damage: wlr.pixman_region32_t = undefined; 673 | wlr.pixman_region32_init(&pixman_damage); 674 | wlr.wlr_surface_get_effective_damage(wlr_surface, &pixman_damage); 675 | wlr.wlr_region_scale(&pixman_damage, &pixman_damage, ddata.output.wlr_output.scale); 676 | if (@floatToInt(i32, @ceil(ddata.output.wlr_output.scale)) > wlr_surface.current.scale) { 677 | wlr.wlr_region_expand( 678 | &pixman_damage, 679 | &pixman_damage, 680 | @floatToInt(i32, @ceil(ddata.output.wlr_output.scale)) - wlr_surface.current.scale, 681 | ); 682 | } 683 | wlr.pixman_region32_translate(&pixman_damage, box.x, box.y); 684 | wlr.wlr_output_damage_add(ddata.output.damage, &pixman_damage); 685 | wlr.pixman_region32_fini(&pixman_damage); 686 | } 687 | 688 | if (ddata.whole) { 689 | wlr.wlr_output_damage_add_box(ddata.output.damage, &box); 690 | } 691 | 692 | if (wlr.wl_list_empty(&wlr_surface.current.frame_callback_list) == 0) { 693 | wlr.wlr_output_schedule_frame(ddata.output.wlr_output); 694 | } 695 | } 696 | fn forEachSurface( 697 | self: *Surface, 698 | cb: fn ([*c]wlr.wlr_surface, i32, i32, ?*anyopaque) callconv(.C) void, 699 | data: *anyopaque, 700 | popups: bool, 701 | ) void { 702 | switch (self.typed_surface) { 703 | .xdg => |xdg| { 704 | if (!popups) { 705 | wlr.wlr_xdg_surface_for_each_surface(xdg, cb, data); 706 | } else { 707 | wlr.wlr_xdg_surface_for_each_popup_surface(xdg, cb, data); 708 | } 709 | }, 710 | .layer => |layer_surface| { 711 | if (!popups) { 712 | wlr.wlr_layer_surface_v1_for_each_surface(layer_surface, cb, data); 713 | } else { 714 | wlr.wlr_layer_surface_v1_for_each_popup_surface(layer_surface, cb, data); 715 | } 716 | }, 717 | else => { 718 | wlr.wlr_surface_for_each_surface(self.wlr_surface, cb, data); 719 | }, 720 | } 721 | } 722 | 723 | fn surfaceAt(self: *Surface, x: f64, y: f64, sx: *f64, sy: *f64) ?*wlr.wlr_surface { 724 | const vx = x - @intToFloat(f64, self.output_box.x); 725 | const vy = y - @intToFloat(f64, self.output_box.y); 726 | 727 | switch (self.typed_surface) { 728 | .xdg => |xdg| { 729 | return wlr.wlr_xdg_surface_surface_at(xdg, vx, vy, sx, sy); 730 | }, 731 | .xwayland => |xwayland| { 732 | if (wlr.wlr_box_contains_point(&wlr.wlr_box{ 733 | .x = self.output_box.x, 734 | .y = self.output_box.y, 735 | .width = xwayland.width, 736 | .height = xwayland.height, 737 | }, x, y)) { 738 | return wlr.wlr_surface_surface_at(self.wlr_surface, vx, vy, sx, sy); 739 | } 740 | }, 741 | .layer => |layer_surface| { 742 | if (layer_surface.mapped) { 743 | return wlr.wlr_layer_surface_v1_surface_at(layer_surface, vx, vy, sx, sy); 744 | } 745 | }, 746 | else => {}, 747 | } 748 | 749 | return null; 750 | } 751 | 752 | fn updateBorders(self: *Surface) void { 753 | if (!self.has_border) return; 754 | 755 | var box: wlr.wlr_box = undefined; 756 | self.getGeometry(&box); 757 | box.x -= self.output_box.x; 758 | box.y -= self.output_box.y; 759 | var border_left: i32 = box.x - self.server.config.active_border_width; 760 | var border_top: i32 = box.y - self.server.config.active_border_width; 761 | self.borders = .{ 762 | .{ // top 763 | .x = border_left, 764 | .y = border_top, 765 | .width = box.width + 2 * self.server.config.active_border_width, 766 | .height = self.server.config.active_border_width, 767 | }, 768 | .{ // left 769 | .x = border_left, 770 | .y = border_top + self.server.config.active_border_width, 771 | .width = self.server.config.active_border_width, 772 | .height = box.height, 773 | }, 774 | .{ // right 775 | .x = border_left + box.width + self.server.config.active_border_width, 776 | .y = border_top + self.server.config.active_border_width, 777 | .width = self.server.config.active_border_width, 778 | .height = box.height, 779 | }, 780 | .{ // bottom 781 | .x = border_left, 782 | .y = border_top + box.height + self.server.config.active_border_width, 783 | .width = box.width + 2 * self.server.config.active_border_width, 784 | .height = self.server.config.active_border_width, 785 | }, 786 | }; 787 | self.damageBorders(); 788 | } 789 | 790 | fn damageBorders(self: *Surface) void { 791 | if (self.has_border) { 792 | var iter = self.server.outputs.first; 793 | var parent_box: wlr.wlr_box = undefined; 794 | self.getParentBox(&parent_box); 795 | while (iter) |node| : (iter = node.next) { 796 | const layout = @ptrCast( 797 | *wlr.wlr_output_layout_output, 798 | wlr.wlr_output_layout_get( 799 | self.server.wlr_output_layout, 800 | node.data.wlr_output, 801 | ), 802 | ); 803 | 804 | for (self.borders) |border| { 805 | var box = border; 806 | box.x -= layout.x - parent_box.x; 807 | box.y -= layout.y - parent_box.y; 808 | scaleBox(&box, node.data.wlr_output.scale); 809 | wlr.wlr_output_damage_add_box(node.data.damage, &box); 810 | } 811 | } 812 | } 813 | } 814 | 815 | fn getAppId(self: *Surface) [*:0]const u8 { 816 | return switch (self.typed_surface) { 817 | .xdg => |xdg| @ptrCast( 818 | *wlr.wlr_xdg_toplevel, 819 | @field(xdg, wlr_xdg_surface_union).toplevel, 820 | ).app_id, 821 | .xwayland => |xwayland| xwayland.class, 822 | else => "", 823 | }; 824 | } 825 | 826 | fn getCenterOutput(self: *Surface) ?*Output { 827 | return self.server.outputAt( 828 | @intToFloat(f64, self.output_box.x + @divFloor(self.output_box.width, 2)), 829 | @intToFloat(f64, self.output_box.y + @divFloor(self.output_box.height, 2)), 830 | ); 831 | } 832 | 833 | fn fromWlrSurface(wlr_surface: ?*wlr.wlr_surface) ?*Surface { 834 | if (wlr_surface) |surface| { 835 | return @ptrCast(?*Surface, @alignCast(@alignOf(?*Surface), surface.data)); 836 | } 837 | 838 | return null; 839 | } 840 | 841 | fn getToplevel(self: *Surface) ?*Surface { 842 | if (self.has_border) { 843 | return self; 844 | } else if (self.ancestor) |ancestor| { 845 | return ancestor.getToplevel(); 846 | } else { 847 | return null; 848 | } 849 | } 850 | 851 | fn move( 852 | self: *Surface, 853 | dir: Config.Direction, 854 | pixels: u32, 855 | comptime abscissa: []const u8, 856 | comptime ordinate: []const u8, 857 | ) void { 858 | if (self.getCenterOutput()) |output| { 859 | var box: wlr.wlr_box = undefined; 860 | self.getGeometry(&box); 861 | const factor = @floatToInt( 862 | i32, 863 | @intToFloat(f64, pixels) / output.wlr_output.scale, 864 | ); 865 | const delta_abscissa: i32 = switch (dir) { 866 | .left => -1, 867 | .right => 1, 868 | else => 0, 869 | }; 870 | const delta_ordinate: i32 = switch (dir) { 871 | .up => -1, 872 | .down => 1, 873 | else => 0, 874 | }; 875 | @field(box, abscissa) += factor * delta_abscissa; 876 | @field(box, ordinate) += factor * delta_ordinate; 877 | self.setGeometry(box); 878 | } 879 | } 880 | 881 | fn toggleFullscreen(self: *Surface) void { 882 | if (self.is_actual_fullscreen) { 883 | self.is_actual_fullscreen = false; 884 | self.setGeometry(self.pre_fullscreen_position); 885 | } else { 886 | if (self.getCenterOutput()) |output| { 887 | self.is_actual_fullscreen = true; 888 | self.getGeometry(&self.pre_fullscreen_position); 889 | self.setGeometry(output.total_box); 890 | } 891 | } 892 | } 893 | 894 | fn close(self: *Surface) void { 895 | switch (self.typed_surface) { 896 | .xdg => |xdg| { 897 | wlr.wlr_xdg_toplevel_send_close(xdg); 898 | }, 899 | .xwayland => |xwayland| { 900 | wlr.wlr_xwayland_surface_close(xwayland); 901 | }, 902 | else => {} 903 | } 904 | } 905 | 906 | fn place(self: *Surface) void { 907 | if (self.server.outputAtCursor()) |output| { 908 | self.workspace = output.active_workspace; 909 | } 910 | 911 | var parent_box = wlr.wlr_box{ .x = 0, .y = 0, .width = 0, .height = 0 }; 912 | var box: wlr.wlr_box = undefined; 913 | self.getGeometry(&box); 914 | 915 | switch (self.typed_surface) { 916 | .xdg => |xdg| { 917 | if (xdg.role == wlr.WLR_XDG_SURFACE_ROLE_TOPLEVEL) { 918 | if (@ptrCast(?*wlr.wlr_xdg_surface, @ptrCast( 919 | *wlr.wlr_xdg_toplevel, 920 | @field(xdg, wlr_xdg_surface_union).toplevel, 921 | ).parent)) |parent_xdg| { 922 | if (Surface.fromWlrSurface(parent_xdg.surface)) |parent| { 923 | parent.getGeometry(&parent_box); 924 | } 925 | } 926 | } 927 | }, 928 | else => {}, 929 | } 930 | 931 | if (parent_box.width == 0) { 932 | if (self.server.outputAtCursor()) |output| { 933 | output.getBox(&parent_box); 934 | } 935 | } 936 | 937 | if (parent_box.width == 0) { 938 | if (self.server.outputs.first) |node| { 939 | node.data.getBox(&parent_box); 940 | } 941 | } 942 | 943 | box.x = parent_box.x + @divFloor(parent_box.width, 2) - @divFloor(box.width, 2); 944 | box.y = parent_box.y + @divFloor(parent_box.height, 2) - @divFloor(box.height, 2); 945 | self.setGeometry(box); 946 | } 947 | 948 | fn initDamageRender( 949 | box: wlr.wlr_box, 950 | region: *wlr.pixman_region32_t, 951 | output_damage: *wlr.pixman_region32_t, 952 | ) ?[]wlr.pixman_box32_t { 953 | wlr.pixman_region32_init(region); 954 | _ = wlr.pixman_region32_union_rect( 955 | region, 956 | region, 957 | box.x, 958 | box.y, 959 | @intCast(u32, box.width), 960 | @intCast(u32, box.height), 961 | ); 962 | _ = wlr.pixman_region32_intersect(region, region, output_damage); 963 | if (wlr.pixman_region32_not_empty(region) != 0) { 964 | var num_rects: i32 = undefined; 965 | var rects: [*c]wlr.pixman_box32_t = wlr.pixman_region32_rectangles(region, &num_rects); 966 | return rects[0..@intCast(usize, num_rects)]; 967 | } 968 | 969 | return null; 970 | } 971 | 972 | fn scaleLength(length: i32, offset: i32, scale: f64) i32 { 973 | return @floatToInt(i32, @round(@intToFloat(f64, offset + length) * scale) - 974 | @round(@intToFloat(f64, offset) * scale)); 975 | } 976 | 977 | fn scaleBox(box: *wlr.wlr_box, scale: f64) void { 978 | box.x = @floatToInt(i32, @round(@intToFloat(f64, box.x) * scale)); 979 | box.y = @floatToInt(i32, @round(@intToFloat(f64, box.y) * scale)); 980 | box.width = scaleLength(box.width, box.x, scale); 981 | box.height = scaleLength(box.height, box.x, scale); 982 | } 983 | 984 | fn renderFirstFullscreen(surfaces: std.TailQueue(*Surface), rdata: *RenderData) bool { 985 | var iter = surfaces.last; 986 | return while (iter) |node| : (iter = node.prev) { 987 | if (node.data.is_actual_fullscreen and node.data.isVisibleOn(rdata.output, false)) { 988 | node.data.triggerRender(rdata, false); 989 | break true; 990 | } 991 | } else false; 992 | } 993 | 994 | fn renderList( 995 | surfaces: std.TailQueue(*Surface), 996 | rdata: *RenderData, 997 | popups: bool, 998 | ) void { 999 | rdata.spread.set(surfaces, rdata.output); 1000 | var iter = surfaces.last; 1001 | rdata.spread.index = 0; 1002 | while (iter) |node| : (iter = node.prev) { 1003 | if (!node.data.isVisibleOn(rdata.output, false)) continue; 1004 | node.data.triggerRender(rdata, popups); 1005 | if (rdata.output.spread_view) rdata.spread.index += 1; 1006 | } 1007 | } 1008 | 1009 | fn isVisible(self: *Surface, any_workspace: bool) bool { 1010 | var iter = self.server.outputs.first; 1011 | return while (iter) |node| : (iter = node.next) { 1012 | if (self.isVisibleOn(node.data, any_workspace)) break true; 1013 | } else false; 1014 | } 1015 | 1016 | fn isVisibleOn(self: *Surface, output: *Output, any_workspace: bool) bool { 1017 | if (!any_workspace and self.workspace != 0 and self.workspace != output.active_workspace) return false; 1018 | var box: wlr.wlr_box = undefined; 1019 | self.getGeometry(&box); 1020 | return wlr.wlr_output_layout_intersects( 1021 | output.server.wlr_output_layout, 1022 | output.wlr_output, 1023 | &box, 1024 | ); 1025 | } 1026 | 1027 | fn triggerRender(self: *Surface, rdata: *RenderData, popups: bool) void { 1028 | switch (self.typed_surface) { 1029 | .xdg, .xwayland => { 1030 | if (rdata.output.spread_view) { 1031 | const col = @intCast(i32, @rem(rdata.spread.index, rdata.spread.cols)); 1032 | const row = @intCast(i32, @divFloor(rdata.spread.index, rdata.spread.cols)); 1033 | rdata.x = col * rdata.spread.width + rdata.spread.margin_x + rdata.spread.layout.x; 1034 | rdata.y = row * rdata.spread.height + rdata.spread.margin_y + rdata.spread.layout.y; 1035 | rdata.spread.scale = @minimum( 1036 | @intToFloat(f32, rdata.spread.width - 2 * rdata.spread.margin_x) / @intToFloat(f32, self.output_box.width), 1037 | @intToFloat(f32, rdata.spread.height - 2 * rdata.spread.margin_y) / @intToFloat(f32, self.output_box.height), 1038 | ); 1039 | } else { 1040 | rdata.x = self.output_box.x; 1041 | rdata.y = self.output_box.y; 1042 | } 1043 | }, 1044 | .drag_icon => |drag_icon| { 1045 | if (!drag_icon.mapped or rdata.output != self.server.outputAtCursor()) return; 1046 | 1047 | rdata.x = @floatToInt(i32, self.server.cursor.x); 1048 | rdata.y = @floatToInt(i32, self.server.cursor.y); 1049 | }, 1050 | else => { 1051 | rdata.x = self.output_box.x; 1052 | rdata.y = self.output_box.y; 1053 | }, 1054 | } 1055 | self.forEachSurface(renderIter, rdata, popups); 1056 | } 1057 | 1058 | fn renderIter( 1059 | surf: [*c]wlr.wlr_surface, 1060 | sx: i32, 1061 | sy: i32, 1062 | data: ?*anyopaque, 1063 | ) callconv(.C) void { 1064 | if (Surface.fromWlrSurface(@ptrCast(*wlr.wlr_surface, surf))) |surface| { 1065 | surface.render(@ptrCast(*RenderData, @alignCast(@alignOf(*RenderData), data)), sx, sy); 1066 | } else { 1067 | std.log.err("Could not find surface to render: {s}", .{@ptrCast(*const wlr.wlr_surface_role, @ptrCast(*wlr.wlr_surface, surf).role).name}); 1068 | } 1069 | } 1070 | 1071 | fn render(self: *Surface, rdata: *RenderData, sx: i32, sy: i32) void { 1072 | if (self.wlr_surface) |wlr_surface| { 1073 | if (wlr.wlr_surface_get_texture(wlr_surface)) |texture| { 1074 | var oxf: f64 = 0; 1075 | var oyf: f64 = 0; 1076 | wlr.wlr_output_layout_output_coords( 1077 | rdata.output.server.wlr_output_layout, 1078 | rdata.output.wlr_output, 1079 | &oxf, 1080 | &oyf, 1081 | ); 1082 | 1083 | oxf += @intToFloat(f64, rdata.x + sx); 1084 | oyf += @intToFloat(f64, rdata.y + sy); 1085 | const ox = @floatToInt(i32, oxf); 1086 | const oy = @floatToInt(i32, oyf); 1087 | 1088 | var region: wlr.pixman_region32_t = undefined; 1089 | 1090 | if (self.has_border and !rdata.output.spread_view) { 1091 | const color = if (self == rdata.output.server.grabbed_toplevel) 1092 | &rdata.output.server.config.grabbed_color 1093 | else if (wlr_surface == rdata.output.server.seat.keyboard_state.focused_surface) 1094 | &rdata.output.server.config.focused_color 1095 | else 1096 | &rdata.output.server.config.border_color; 1097 | for (self.borders) |border| { 1098 | var b = border; 1099 | b.x += ox; 1100 | b.y += oy; 1101 | scaleBox(&b, rdata.output.wlr_output.scale); 1102 | if (initDamageRender(b, ®ion, rdata.damage)) |rects| { 1103 | for (rects) |rect| { 1104 | scissor(rdata.output.wlr_output, rect); 1105 | wlr.wlr_render_rect( 1106 | rdata.output.wlr_output.renderer, 1107 | &b, 1108 | color, 1109 | &rdata.output.wlr_output.transform_matrix, 1110 | ); 1111 | } 1112 | } 1113 | wlr.pixman_region32_fini(®ion); 1114 | } 1115 | } 1116 | 1117 | var box = wlr.wlr_box{ 1118 | .x = ox, 1119 | .y = oy, 1120 | .width = @floatToInt(i32, @intToFloat(f32, wlr_surface.current.width) * rdata.spread.scale), 1121 | .height = @floatToInt(i32, @intToFloat(f32, wlr_surface.current.height) * rdata.spread.scale), 1122 | }; 1123 | scaleBox(&box, rdata.output.wlr_output.scale); 1124 | var matrix: [9]f32 = undefined; 1125 | const transform = wlr.wlr_output_transform_invert(wlr_surface.current.transform); 1126 | wlr.wlr_matrix_project_box(&matrix, &box, transform, 0, &rdata.output.wlr_output.transform_matrix); 1127 | 1128 | if (initDamageRender(box, ®ion, rdata.damage)) |rects| { 1129 | for (rects) |rect| { 1130 | scissor(rdata.output.wlr_output, rect); 1131 | _ = wlr.wlr_render_texture_with_matrix(rdata.output.wlr_output.renderer, texture, &matrix, 1); 1132 | } 1133 | } 1134 | wlr.pixman_region32_fini(®ion); 1135 | wlr.wlr_surface_send_frame_done(wlr_surface, &rdata.when); 1136 | wlr.wlr_presentation_surface_sampled_on_output( 1137 | rdata.output.server.presentation, 1138 | wlr_surface, 1139 | rdata.output.wlr_output, 1140 | ); 1141 | } 1142 | } 1143 | } 1144 | 1145 | fn setActivated(self: *Surface, activated: bool) bool { 1146 | switch (self.typed_surface) { 1147 | .xdg => |xdg| { 1148 | if (xdg.role == wlr.WLR_XDG_SURFACE_ROLE_TOPLEVEL) { 1149 | _ = wlr.wlr_xdg_toplevel_set_activated(xdg, activated); 1150 | return true; 1151 | } 1152 | }, 1153 | .xwayland => |xwayland| { 1154 | wlr.wlr_xwayland_surface_activate(xwayland, activated); 1155 | if (activated) { 1156 | wlr.wlr_xwayland_surface_restack(xwayland, null, wlr.XCB_STACK_MODE_ABOVE); 1157 | } 1158 | return true; 1159 | }, 1160 | .layer => return true, 1161 | else => {}, 1162 | } 1163 | return false; 1164 | } 1165 | 1166 | const SurfaceType = enum { xdg, xwayland, layer, subsurface, drag_icon }; 1167 | 1168 | node: std.TailQueue(*Surface).Node, 1169 | list: ?*std.TailQueue(*Surface), 1170 | server: *Server, 1171 | ancestor: ?*Surface, 1172 | typed_surface: union(SurfaceType) { 1173 | xdg: *wlr.wlr_xdg_surface, 1174 | xwayland: *wlr.wlr_xwayland_surface, 1175 | layer: *wlr.wlr_layer_surface_v1, 1176 | subsurface: *wlr.wlr_subsurface, 1177 | drag_icon: *wlr.wlr_drag_icon, 1178 | }, 1179 | wlr_surface: ?*wlr.wlr_surface, 1180 | mapped: bool, 1181 | layer: wlr.zwlr_layer_shell_v1_layer, 1182 | output_box: wlr.wlr_box, 1183 | activate: wlr.wl_listener, 1184 | configure: wlr.wl_listener, 1185 | popup: wlr.wl_listener, 1186 | subsurface: wlr.wl_listener, 1187 | map: wlr.wl_listener, 1188 | unmap: wlr.wl_listener, 1189 | commit: wlr.wl_listener, 1190 | destroy: wlr.wl_listener, 1191 | request_fullscreen: wlr.wl_listener, 1192 | is_requested_fullscreen: bool, 1193 | has_border: bool, 1194 | borders: [4]wlr.wlr_box, 1195 | is_actual_fullscreen: bool, 1196 | pre_fullscreen_position: wlr.wlr_box, 1197 | workspace: u32, 1198 | pending_serial: u32, 1199 | }; 1200 | 1201 | const Keyboard = struct { 1202 | fn create(server: *Server, device: *wlr.wlr_input_device) !?*Keyboard { 1203 | var keyboard = try alloc.create(Keyboard); 1204 | keyboard.init(server, device); 1205 | 1206 | return keyboard; 1207 | } 1208 | fn init(self: *Keyboard, server: *Server, device: *wlr.wlr_input_device) void { 1209 | self.node.data = self; 1210 | self.server = server; 1211 | self.device = device; 1212 | self.wlr_keyboard = @field(device, wlr_input_device_union).keyboard; 1213 | self.captured_modifiers = 0; 1214 | 1215 | self.server.keyboards.append(&self.node); 1216 | var context = wlr.xkb_context_new(wlr.XKB_CONTEXT_NO_FLAGS); 1217 | var keymap = wlr.xkb_keymap_new_from_names( 1218 | context, 1219 | null, 1220 | wlr.XKB_KEYMAP_COMPILE_NO_FLAGS, 1221 | ); 1222 | 1223 | _ = wlr.wlr_keyboard_set_keymap(self.wlr_keyboard, keymap); 1224 | wlr.xkb_keymap_unref(keymap); 1225 | wlr.xkb_context_unref(context); 1226 | wlr.wlr_keyboard_set_repeat_info(self.wlr_keyboard, 25, 600); 1227 | 1228 | Signal.connect( 1229 | void, 1230 | self, 1231 | "modifiers", 1232 | Keyboard.onModifiers, 1233 | &self.wlr_keyboard.events.modifiers, 1234 | ); 1235 | Signal.connect( 1236 | *wlr.wlr_event_keyboard_key, 1237 | self, 1238 | "key", 1239 | Keyboard.onKey, 1240 | &self.wlr_keyboard.events.key, 1241 | ); 1242 | 1243 | wlr.wlr_seat_set_keyboard(server.seat, device); 1244 | } 1245 | 1246 | fn onModifiers(self: *Keyboard, _: void) !void { 1247 | wlr.wlr_seat_set_keyboard(self.server.seat, self.device); 1248 | wlr.wlr_seat_keyboard_notify_modifiers(self.server.seat, &self.wlr_keyboard.modifiers); 1249 | 1250 | if (self.wlr_keyboard.modifiers.depressed == 0 and self.captured_modifiers != 0) { 1251 | self.captured_modifiers = 0; 1252 | self.server.reportHotkeyModifierState(false); 1253 | } 1254 | } 1255 | 1256 | fn onKey(self: *Keyboard, event: *wlr.wlr_event_keyboard_key) !void { 1257 | wlr.wlr_idle_notify_activity(self.server.idle, self.server.seat); 1258 | 1259 | var handled = false; 1260 | const keycode: u32 = event.keycode + 8; 1261 | var syms: [*c]wlr.xkb_keysym_t = undefined; 1262 | const nsyms = wlr.xkb_state_key_get_syms(self.wlr_keyboard.xkb_state, keycode, &syms); 1263 | if (nsyms > 0) { 1264 | const modifiers: u32 = wlr.wlr_keyboard_get_modifiers(self.wlr_keyboard); 1265 | if (event.state == wlr.WL_KEYBOARD_KEY_STATE_PRESSED) { 1266 | if (self.server.outputAtCursor()) |output| { 1267 | if (output.spread_view and modifiers == 0 and syms[0] == wlr.XKB_KEY_Escape) { 1268 | output.toggleSpreadView(); 1269 | return; 1270 | } 1271 | } 1272 | if (self.server.input_inhibit_mgr.active_inhibitor == null) { 1273 | for (syms[0..@intCast(usize, nsyms)]) |symbol| { 1274 | handled = self.handleKeybinding(modifiers, symbol) or handled; 1275 | if (handled) break; 1276 | } 1277 | } 1278 | } 1279 | } 1280 | if (!handled) { 1281 | wlr.wlr_seat_set_keyboard(self.server.seat, self.device); 1282 | wlr.wlr_seat_keyboard_notify_key( 1283 | self.server.seat, 1284 | event.time_msec, 1285 | event.keycode, 1286 | event.state, 1287 | ); 1288 | } 1289 | } 1290 | 1291 | fn handleKeybinding(self: *Keyboard, modifiers: u32, symbol: wlr.xkb_keysym_t) bool { 1292 | for (self.server.config.hotkeys) |hotkey| { 1293 | if (hotkey.modifiers == modifiers and hotkey.key == symbol) { 1294 | if (modifiers != 0) { 1295 | self.captured_modifiers = modifiers; 1296 | self.server.reportHotkeyModifierState(true); 1297 | } 1298 | 1299 | hotkey.cb(self.server, hotkey.arg); 1300 | return true; 1301 | } 1302 | } 1303 | 1304 | return false; 1305 | } 1306 | 1307 | node: std.TailQueue(*Keyboard).Node, 1308 | server: *Server, 1309 | device: *wlr.wlr_input_device, 1310 | wlr_keyboard: *wlr.wlr_keyboard, 1311 | modifiers: wlr.wl_listener, 1312 | key: wlr.wl_listener, 1313 | captured_modifiers: u32, 1314 | }; 1315 | 1316 | const SpreadParams = struct { 1317 | fn set(self: *SpreadParams, surfaces: std.TailQueue(*Surface), output: *Output) void { 1318 | self.scale = 1; 1319 | if (!output.spread_view) return; 1320 | var visible_count: f32 = 0; 1321 | var iter = surfaces.last; 1322 | while (iter) |node| : (iter = node.prev) { 1323 | if (node.data.isVisibleOn(output, false)) visible_count += 1; 1324 | } 1325 | if (visible_count == 0) return; 1326 | self.layout = output.getLayout(); 1327 | self.rows = @floatToInt(u32, @sqrt(visible_count)); 1328 | self.cols = @floatToInt(u32, @ceil(visible_count / @intToFloat(f64, self.rows))); 1329 | var output_box: wlr.wlr_box = undefined; 1330 | output.getBox(&output_box); 1331 | const margin_ratio = 20; 1332 | self.margin_x = @divFloor(output_box.width, margin_ratio); 1333 | self.margin_y = @divFloor(output_box.height, margin_ratio); 1334 | self.width = @divFloor(output_box.width, @intCast(i32, self.cols)); 1335 | self.height = @divFloor(output_box.height, @intCast(i32, self.rows)); 1336 | } 1337 | 1338 | cols: u32, 1339 | rows: u32, 1340 | index: u32, 1341 | scale: f32, 1342 | layout: *wlr.wlr_output_layout_output, 1343 | margin_x: i32, 1344 | margin_y: i32, 1345 | width: i32, 1346 | height: i32, 1347 | }; 1348 | const RenderData = struct { 1349 | output: *Output, 1350 | x: i32 = -1, 1351 | y: i32 = -1, 1352 | when: wlr.timespec, 1353 | damage: *wlr.pixman_region32_t, 1354 | spread: SpreadParams = undefined, 1355 | }; 1356 | 1357 | fn scissor(wlr_output: *wlr.wlr_output, rect: wlr.pixman_box32_t) void { 1358 | var box = wlr.wlr_box{ 1359 | .x = rect.x1, 1360 | .y = rect.y1, 1361 | .width = rect.x2 - rect.x1, 1362 | .height = rect.y2 - rect.y1, 1363 | }; 1364 | 1365 | var ow: i32 = undefined; 1366 | var oh: i32 = undefined; 1367 | wlr.wlr_output_transformed_resolution(wlr_output, &ow, &oh); 1368 | var transform = wlr.wlr_output_transform_invert(wlr_output.transform); 1369 | wlr.wlr_box_transform(&box, &box, transform, ow, oh); 1370 | wlr.wlr_renderer_scissor(wlr_output.renderer, &box); 1371 | } 1372 | 1373 | const Output = struct { 1374 | fn create(server: *Server, wlr_output: *wlr.wlr_output) !?*Output { 1375 | var output = try alloc.create(Output); 1376 | output.init(server, wlr_output); 1377 | 1378 | return output; 1379 | } 1380 | fn init(self: *Output, server: *Server, wlr_output: *wlr.wlr_output) void { 1381 | _ = wlr.wlr_output_init_render(wlr_output, server.wlr_allocator, server.wlr_renderer); 1382 | if (wlr.wl_list_empty(&wlr_output.modes) == 0) { 1383 | const mode = wlr.wlr_output_preferred_mode(wlr_output); 1384 | wlr.wlr_output_set_mode(wlr_output, mode); 1385 | wlr.wlr_output_enable_adaptive_sync(wlr_output, true); 1386 | wlr.wlr_output_enable(wlr_output, true); 1387 | if (!wlr.wlr_output_commit(wlr_output)) { 1388 | alloc.destroy(self); 1389 | return; 1390 | } 1391 | } 1392 | 1393 | self.node.data = self; 1394 | self.active_workspace = 1; 1395 | self.wlr_output = wlr_output; 1396 | self.server = server; 1397 | self.wlr_output.data = self; 1398 | self.damage = wlr.wlr_output_damage_create(wlr_output); 1399 | for (LAYERS_TOP_TO_BOTTOM) |layerIndex| { 1400 | self.layers[layerIndex] = std.TailQueue(*Surface){}; 1401 | } 1402 | self.server.outputs.append(&self.node); 1403 | self.spread_view = false; 1404 | wlr.wlr_output_layout_add_auto(self.server.wlr_output_layout, self.wlr_output); 1405 | 1406 | Signal.connect( 1407 | void, 1408 | self, 1409 | "frame", 1410 | Output.onFrame, 1411 | &self.damage.events.frame, 1412 | ); 1413 | Signal.connect( 1414 | void, 1415 | self, 1416 | "destroy", 1417 | Output.onDestroy, 1418 | &wlr_output.events.destroy, 1419 | ); 1420 | } 1421 | 1422 | fn fromWlrOutput(wlr_output: ?*wlr.wlr_output) ?*Output { 1423 | if (wlr_output) |output| { 1424 | return @ptrCast(?*Output, @alignCast(@alignOf(?*Output), output.data)); 1425 | } 1426 | 1427 | return null; 1428 | } 1429 | 1430 | fn damageAll(self: *Output) void { 1431 | wlr.wlr_output_damage_add_whole(self.damage); 1432 | } 1433 | 1434 | fn onFrame(self: *Output, _: void) !void { 1435 | var now: wlr.timespec = undefined; 1436 | _ = wlr.clock_gettime(wlr.CLOCK_MONOTONIC, &now); 1437 | 1438 | var damage: wlr.pixman_region32_t = undefined; 1439 | wlr.pixman_region32_init(&damage); 1440 | var needs_frame: bool = false; 1441 | 1442 | if (!wlr.wlr_output_damage_attach_render(self.damage, &needs_frame, &damage)) { 1443 | return; 1444 | } 1445 | 1446 | if (needs_frame) { 1447 | wlr.wlr_renderer_begin( 1448 | self.wlr_output.renderer, 1449 | @intCast(u32, self.wlr_output.width), 1450 | @intCast(u32, self.wlr_output.height), 1451 | ); 1452 | 1453 | if (wlr.pixman_region32_not_empty(&damage) != 0) { 1454 | var rdata: RenderData = .{ .output = self, .damage = &damage, .when = now }; 1455 | rdata.spread.scale = 1; 1456 | 1457 | var nrects: i32 = undefined; 1458 | for (wlr.pixman_region32_rectangles(&damage, &nrects)[0..@intCast(usize, nrects)]) |rect| { 1459 | scissor(self.wlr_output, rect); 1460 | wlr.wlr_renderer_clear(self.wlr_output.renderer, &self.server.config.background_color); 1461 | } 1462 | 1463 | if (!Surface.renderFirstFullscreen(self.server.toplevels, &rdata)) { 1464 | Surface.renderList(self.layers[wlr.ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], &rdata, false); 1465 | Surface.renderList(self.layers[wlr.ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], &rdata, false); 1466 | if (self.server.show_toplevels) Surface.renderList(self.server.toplevels, &rdata, false); 1467 | if (self.server.show_toplevels) Surface.renderList(self.server.unmanaged_toplevels, &rdata, false); 1468 | Surface.renderList(self.layers[wlr.ZWLR_LAYER_SHELL_V1_LAYER_TOP], &rdata, false); 1469 | Surface.renderList(self.layers[wlr.ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], &rdata, false); 1470 | } 1471 | if (self.server.grabbed_toplevel) |grabbed| grabbed.triggerRender(&rdata, false); 1472 | if (self.server.drag_icon) |drag_icon| drag_icon.triggerRender(&rdata, false); 1473 | Surface.renderList(self.layers[wlr.ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], &rdata, true); 1474 | Surface.renderList(self.layers[wlr.ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], &rdata, true); 1475 | Surface.renderList(self.layers[wlr.ZWLR_LAYER_SHELL_V1_LAYER_TOP], &rdata, true); 1476 | Surface.renderList(self.layers[wlr.ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], &rdata, true); 1477 | } 1478 | wlr.wlr_renderer_scissor(self.wlr_output.renderer, null); 1479 | wlr.wlr_output_render_software_cursors(self.wlr_output, null); 1480 | wlr.wlr_renderer_end(self.wlr_output.renderer); 1481 | wlr.wlr_output_set_damage(self.wlr_output, &self.damage.current); 1482 | _ = wlr.wlr_output_commit(self.wlr_output); 1483 | } else { 1484 | wlr.wlr_output_rollback(self.wlr_output); 1485 | } 1486 | 1487 | wlr.pixman_region32_fini(&damage); 1488 | } 1489 | 1490 | fn onDestroy(self: *Output, _: void) !void { 1491 | wlr.wl_list_remove(&self.frame.link); 1492 | wlr.wl_list_remove(&self.destroy.link); 1493 | self.server.outputs.remove(&self.node); 1494 | wlr.wlr_output_layout_remove(self.server.wlr_output_layout, self.wlr_output); 1495 | alloc.destroy(self); 1496 | } 1497 | 1498 | fn onLayoutChanged(self: *Output, config: *wlr.wlr_output_configuration_v1) void { 1499 | var head: *wlr.wlr_output_configuration_head_v1 = wlr.wlr_output_configuration_head_v1_create(config, self.wlr_output); 1500 | self.total_box = wlr.wlr_output_layout_get_box( 1501 | self.server.wlr_output_layout, 1502 | self.wlr_output, 1503 | ).*; 1504 | self.usable_box = self.total_box; 1505 | head.state.enabled = self.wlr_output.enabled; 1506 | head.state.mode = self.wlr_output.current_mode; 1507 | head.state.x = self.total_box.x; 1508 | head.state.y = self.total_box.y; 1509 | self.arrangeLayers(); 1510 | self.server.ensureToplevelsVisible(); 1511 | } 1512 | 1513 | fn arrangeLayer( 1514 | self: *Output, 1515 | layer_surfaces: std.TailQueue(*Surface), 1516 | usable_area: *wlr.wlr_box, 1517 | exclusive: bool, 1518 | ) void { 1519 | const both_horiz: u32 = LAYER_ANCHOR_LEFT | LAYER_ANCHOR_RIGHT; 1520 | const both_vert: u32 = LAYER_ANCHOR_TOP | LAYER_ANCHOR_BOTTOM; 1521 | 1522 | var iter = layer_surfaces.first; 1523 | while (iter) |node| : (iter = node.next) { 1524 | var surface = node.data; 1525 | var state: *wlr.wlr_layer_surface_v1_state = &surface.typed_surface.layer.current; 1526 | if (exclusive != (state.exclusive_zone > 0)) { 1527 | continue; 1528 | } 1529 | 1530 | var new_x: u32 = undefined; 1531 | var new_y: u32 = undefined; 1532 | var new_width = @intCast(i32, state.desired_width); 1533 | var new_height = @intCast(i32, state.desired_height); 1534 | var bounds: wlr.wlr_box = if (state.exclusive_zone == -1) self.total_box else usable_area.*; 1535 | 1536 | // Horizontal axis 1537 | if (((state.anchor & both_horiz) != 0) and new_width == 0) { 1538 | new_x = @intCast(u32, bounds.x); 1539 | new_width = bounds.width; 1540 | } else if ((state.anchor & LAYER_ANCHOR_LEFT) != 0) { 1541 | new_x = @intCast(u32, bounds.x); 1542 | } else if ((state.anchor & LAYER_ANCHOR_RIGHT) != 0) { 1543 | new_x = @intCast(u32, bounds.x + bounds.width - new_width); 1544 | } else { 1545 | new_x = @intCast(u32, bounds.x + @divFloor(bounds.width, 2) - @divFloor(new_width, 2)); 1546 | } 1547 | // Vertical axis 1548 | if (((state.anchor & both_vert) != 0) and new_height == 0) { 1549 | new_y = @intCast(u32, bounds.y); 1550 | new_height = bounds.height; 1551 | } else if ((state.anchor & LAYER_ANCHOR_TOP) != 0) { 1552 | new_y = @intCast(u32, bounds.y); 1553 | } else if ((state.anchor & LAYER_ANCHOR_BOTTOM) != 0) { 1554 | new_y = @intCast(u32, bounds.y + bounds.height - new_height); 1555 | } else { 1556 | new_y = @intCast(u32, bounds.y + @divFloor(bounds.height, 2) - @divFloor(new_height, 2)); 1557 | } 1558 | // Margin 1559 | if ((state.anchor & both_horiz) == both_horiz) { 1560 | new_x += state.margin.left; 1561 | new_width -= @intCast(i32, state.margin.left + state.margin.right); 1562 | } else if ((state.anchor & LAYER_ANCHOR_LEFT) != 0) { 1563 | new_x += state.margin.left; 1564 | } else if ((state.anchor & LAYER_ANCHOR_RIGHT) != 0) { 1565 | new_x -= state.margin.right; 1566 | } 1567 | if ((state.anchor & both_vert) == both_vert) { 1568 | new_y += state.margin.top; 1569 | new_height -= @intCast(i32, state.margin.top + state.margin.bottom); 1570 | } else if ((state.anchor & LAYER_ANCHOR_TOP) != 0) { 1571 | new_y += state.margin.top; 1572 | } else if ((state.anchor & LAYER_ANCHOR_BOTTOM) != 0) { 1573 | new_y -= state.margin.bottom; 1574 | } 1575 | if (new_width < 0 or new_height < 0) { 1576 | wlr.wlr_layer_surface_v1_destroy(surface.typed_surface.layer); 1577 | continue; 1578 | } 1579 | 1580 | surface.output_box = wlr.wlr_box{ 1581 | .x = @intCast(i32, new_x), 1582 | .y = @intCast(i32, new_y), 1583 | .width = @intCast(i32, new_width), 1584 | .height = @intCast(i32, new_height), 1585 | }; 1586 | 1587 | if (state.exclusive_zone > 0) 1588 | handleLayerExclusives( 1589 | usable_area, 1590 | state.anchor, 1591 | state.exclusive_zone, 1592 | @intCast(i32, state.margin.top), 1593 | @intCast(i32, state.margin.right), 1594 | @intCast(i32, state.margin.bottom), 1595 | @intCast(i32, state.margin.left), 1596 | ); 1597 | _ = wlr.wlr_layer_surface_v1_configure( 1598 | surface.typed_surface.layer, 1599 | @intCast(u32, new_width), 1600 | @intCast(u32, new_height), 1601 | ); 1602 | } 1603 | } 1604 | 1605 | fn arrangeLayers(self: *Output) void { 1606 | var updated_usable_box = self.total_box; 1607 | 1608 | for (LAYERS_TOP_TO_BOTTOM) |layer| { 1609 | self.arrangeLayer(self.layers[layer], &updated_usable_box, true); 1610 | } 1611 | 1612 | if (!std.meta.eql(updated_usable_box, self.usable_box)) { 1613 | self.usable_box = updated_usable_box; 1614 | // TODO: move toplevels to avoid? 1615 | } 1616 | 1617 | for (LAYERS_TOP_TO_BOTTOM) |layer| { 1618 | self.arrangeLayer(self.layers[layer], &updated_usable_box, false); 1619 | } 1620 | self.damageAll(); 1621 | } 1622 | 1623 | fn layerSurfaceAt( 1624 | self: *Output, 1625 | layer: wlr.zwlr_layer_shell_v1_layer, 1626 | x: f64, 1627 | y: f64, 1628 | sx: *f64, 1629 | sy: *f64, 1630 | ) ?*wlr.wlr_surface { 1631 | var iter = self.layers[layer].last; 1632 | return while (iter) |node| : (iter = node.prev) { 1633 | if (node.data.surfaceAt(x, y, sx, sy)) |wlr_surface| { 1634 | break wlr_surface; 1635 | } 1636 | } else null; 1637 | } 1638 | 1639 | fn handleLayerExclusives( 1640 | usable_area: *wlr.wlr_box, 1641 | anchor: u32, 1642 | exclusive: i32, 1643 | margin_top: i32, 1644 | margin_right: i32, 1645 | margin_bottom: i32, 1646 | margin_left: i32, 1647 | ) void { 1648 | const Edge = struct { 1649 | singular_anchor: u32, 1650 | anchor_triplet: u32, 1651 | positive_axis: ?*i32, 1652 | negative_axis: ?*i32, 1653 | margin: i32, 1654 | }; 1655 | 1656 | const edges: [4]Edge = .{ 1657 | .{ // Top 1658 | .singular_anchor = LAYER_ANCHOR_TOP, 1659 | .anchor_triplet = LAYER_ANCHOR_LEFT | LAYER_ANCHOR_RIGHT | LAYER_ANCHOR_TOP, 1660 | .positive_axis = &usable_area.y, 1661 | .negative_axis = &usable_area.height, 1662 | .margin = margin_top, 1663 | }, 1664 | .{ // Bottom 1665 | .singular_anchor = LAYER_ANCHOR_BOTTOM, 1666 | .anchor_triplet = LAYER_ANCHOR_LEFT | LAYER_ANCHOR_RIGHT | LAYER_ANCHOR_BOTTOM, 1667 | .positive_axis = null, 1668 | .negative_axis = &usable_area.height, 1669 | .margin = margin_bottom, 1670 | }, 1671 | .{ // Left 1672 | .singular_anchor = LAYER_ANCHOR_LEFT, 1673 | .anchor_triplet = LAYER_ANCHOR_LEFT | LAYER_ANCHOR_TOP | LAYER_ANCHOR_BOTTOM, 1674 | .positive_axis = &usable_area.x, 1675 | .negative_axis = &usable_area.width, 1676 | .margin = margin_left, 1677 | }, 1678 | .{ // Right 1679 | .singular_anchor = LAYER_ANCHOR_RIGHT, 1680 | .anchor_triplet = LAYER_ANCHOR_RIGHT | LAYER_ANCHOR_TOP | LAYER_ANCHOR_BOTTOM, 1681 | .positive_axis = null, 1682 | .negative_axis = &usable_area.width, 1683 | .margin = margin_right, 1684 | }, 1685 | }; 1686 | 1687 | for (edges) |*edge| { 1688 | if ((anchor == edge.singular_anchor or anchor == edge.anchor_triplet) and 1689 | exclusive + @intCast(i32, edge.margin) > 0) 1690 | { 1691 | if (edge.positive_axis) |*positive_axis| { 1692 | positive_axis.*.* += exclusive + @intCast(i32, edge.margin); 1693 | } 1694 | if (edge.negative_axis) |*negative_axis| { 1695 | negative_axis.*.* -= exclusive + @intCast(i32, edge.margin); 1696 | } 1697 | break; 1698 | } 1699 | } 1700 | } 1701 | 1702 | fn getLayout(self: *Output) *wlr.wlr_output_layout_output { 1703 | return @ptrCast( 1704 | *wlr.wlr_output_layout_output, 1705 | wlr.wlr_output_layout_get(self.server.wlr_output_layout, self.wlr_output), 1706 | ); 1707 | } 1708 | 1709 | fn getBox(self: *Output, box: *wlr.wlr_box) void { 1710 | const layout = self.getLayout(); 1711 | box.x = layout.x; 1712 | box.y = layout.y; 1713 | wlr.wlr_output_effective_resolution( 1714 | self.wlr_output, 1715 | &box.width, 1716 | &box.height, 1717 | ); 1718 | } 1719 | 1720 | fn toggleSpreadView(self: *Output) void { 1721 | self.spread_view = !self.spread_view; 1722 | self.damageAll(); 1723 | } 1724 | 1725 | const LAYERS_TOP_TO_BOTTOM: [LAYER_COUNT]usize = .{ 1726 | wlr.ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, 1727 | wlr.ZWLR_LAYER_SHELL_V1_LAYER_TOP, 1728 | wlr.ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, 1729 | wlr.ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, 1730 | }; 1731 | const LAYER_COUNT = 4; 1732 | 1733 | const LAYER_ANCHOR_TOP = wlr.ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; 1734 | const LAYER_ANCHOR_LEFT = wlr.ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT; 1735 | const LAYER_ANCHOR_BOTTOM = wlr.ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; 1736 | const LAYER_ANCHOR_RIGHT = wlr.ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; 1737 | 1738 | node: std.TailQueue(*Output).Node, 1739 | server: *Server, 1740 | wlr_output: *wlr.wlr_output, 1741 | frame: wlr.wl_listener, 1742 | destroy: wlr.wl_listener, 1743 | total_box: wlr.wlr_box, 1744 | usable_box: wlr.wlr_box, 1745 | layers: [LAYER_COUNT]std.TailQueue(*Surface), 1746 | active_workspace: u32, 1747 | damage: *wlr.wlr_output_damage, 1748 | spread_view: bool, 1749 | }; 1750 | 1751 | const Server = struct { 1752 | fn init(self: *Server) !void { 1753 | self.config.init(); 1754 | self.drag_icon = null; 1755 | self.grabbed_toplevel = null; 1756 | self.modifier_pressed = false; 1757 | self.show_toplevels = true; 1758 | wlr.wlr_log_init(wlr.WLR_DEBUG, null); 1759 | self.wl_display = wlr.wl_display_create() orelse 1760 | return error.CannotCreateDisplay; 1761 | self.wlr_backend = wlr.wlr_backend_autocreate(self.wl_display) orelse 1762 | return error.CannotCreateBackend; 1763 | self.wlr_renderer = wlr.wlr_renderer_autocreate(self.wlr_backend) orelse 1764 | return error.CannotGetRenderer; 1765 | _ = wlr.wlr_renderer_init_wl_display(self.wlr_renderer, self.wl_display); 1766 | self.wlr_allocator = wlr.wlr_allocator_autocreate(self.wlr_backend, self.wlr_renderer); 1767 | var compositor: *wlr.wlr_compositor = wlr.wlr_compositor_create( 1768 | self.wl_display, 1769 | self.wlr_renderer, 1770 | ) orelse 1771 | return error.CannotCreateCompositor; 1772 | 1773 | _ = wlr.wlr_data_device_manager_create(self.wl_display); 1774 | _ = wlr.wlr_primary_selection_v1_device_manager_create(self.wl_display); 1775 | _ = wlr.wlr_export_dmabuf_manager_v1_create(self.wl_display); 1776 | _ = wlr.wlr_screencopy_manager_v1_create(self.wl_display); 1777 | _ = wlr.wlr_data_control_manager_v1_create(self.wl_display); 1778 | _ = wlr.wlr_gamma_control_manager_v1_create(self.wl_display); 1779 | _ = wlr.wlr_viewporter_create(self.wl_display); 1780 | self.wlr_output_layout = wlr.wlr_output_layout_create() orelse 1781 | return error.CannotCreateOutputLayout; 1782 | self.outputs = std.TailQueue(*Output){}; 1783 | Signal.connect( 1784 | void, 1785 | self, 1786 | "output_changed", 1787 | Server.onOutputLayoutChanged, 1788 | &self.wlr_output_layout.events.change, 1789 | ); 1790 | _ = wlr.wlr_xdg_output_manager_v1_create(self.wl_display, self.wlr_output_layout); 1791 | Signal.connect( 1792 | *wlr.wlr_output, 1793 | self, 1794 | "new_output", 1795 | Server.onNewOutput, 1796 | &self.wlr_backend.events.new_output, 1797 | ); 1798 | self.toplevels = std.TailQueue(*Surface){}; 1799 | self.unmanaged_toplevels = std.TailQueue(*Surface){}; 1800 | self.xdg_shell = wlr.wlr_xdg_shell_create(self.wl_display); 1801 | Signal.connect( 1802 | *wlr.wlr_xdg_surface, 1803 | self, 1804 | "new_xdg_surface", 1805 | Server.onNewXdgSurface, 1806 | &self.xdg_shell.events.new_surface, 1807 | ); 1808 | self.layer_shell = wlr.wlr_layer_shell_v1_create(self.wl_display); 1809 | Signal.connect( 1810 | *wlr.wlr_layer_surface_v1, 1811 | self, 1812 | "new_layer_surface", 1813 | Server.onNewLayerSurface, 1814 | &self.layer_shell.events.new_surface, 1815 | ); 1816 | self.cursor_mode = .passthrough; 1817 | self.cursor = wlr.wlr_cursor_create(); 1818 | wlr.wlr_cursor_attach_output_layout(self.cursor, self.wlr_output_layout); 1819 | self.cursor_mgr = wlr.wlr_xcursor_manager_create(null, 24); 1820 | _ = wlr.wlr_xcursor_manager_load(self.cursor_mgr, 1); 1821 | Signal.connect( 1822 | *wlr.wlr_event_pointer_motion, 1823 | self, 1824 | "cursor_motion", 1825 | Server.onCursorMotion, 1826 | &self.cursor.events.motion, 1827 | ); 1828 | Signal.connect( 1829 | *wlr.wlr_event_pointer_motion_absolute, 1830 | self, 1831 | "cursor_motion_absolute", 1832 | Server.onCursorMotionAbsolute, 1833 | &self.cursor.events.motion_absolute, 1834 | ); 1835 | Signal.connect( 1836 | *wlr.wlr_event_pointer_button, 1837 | self, 1838 | "cursor_button", 1839 | Server.onCursorButton, 1840 | &self.cursor.events.button, 1841 | ); 1842 | Signal.connect( 1843 | *wlr.wlr_event_pointer_axis, 1844 | self, 1845 | "cursor_axis", 1846 | Server.onCursorAxis, 1847 | &self.cursor.events.axis, 1848 | ); 1849 | Signal.connect( 1850 | void, 1851 | self, 1852 | "cursor_frame", 1853 | Server.onCursorFrame, 1854 | &self.cursor.events.frame, 1855 | ); 1856 | self.keyboards = std.TailQueue(*Keyboard){}; 1857 | Signal.connect( 1858 | *wlr.wlr_input_device, 1859 | self, 1860 | "new_input", 1861 | Server.onNewInputDevice, 1862 | &self.wlr_backend.events.new_input, 1863 | ); 1864 | self.wlr_virtual_keyboard_mgr = wlr.wlr_virtual_keyboard_manager_v1_create(self.wl_display); 1865 | Signal.connect( 1866 | *wlr.wlr_virtual_keyboard_v1, 1867 | self, 1868 | "virtual_keyboard", 1869 | Server.onNewVirtualKeyboard, 1870 | &self.wlr_virtual_keyboard_mgr.events.new_virtual_keyboard, 1871 | ); 1872 | self.wlr_virtual_pointer_mgr = wlr.wlr_virtual_pointer_manager_v1_create(self.wl_display); 1873 | Signal.connect( 1874 | *wlr.wlr_virtual_pointer_v1_new_pointer_event, 1875 | self, 1876 | "virtual_pointer", 1877 | Server.onNewVirtualPointer, 1878 | &self.wlr_virtual_pointer_mgr.events.new_virtual_pointer, 1879 | ); 1880 | self.seat = wlr.wlr_seat_create(self.wl_display, "seat0"); 1881 | Signal.connect( 1882 | *wlr.wlr_seat_pointer_request_set_cursor_event, 1883 | self, 1884 | "request_cursor", 1885 | Server.onRequestSetCursor, 1886 | &self.seat.events.request_set_cursor, 1887 | ); 1888 | Signal.connect( 1889 | *wlr.wlr_seat_request_set_selection_event, 1890 | self, 1891 | "request_set_selection", 1892 | Server.onRequestSetSelection, 1893 | &self.seat.events.request_set_selection, 1894 | ); 1895 | Signal.connect( 1896 | *wlr.wlr_seat_request_set_primary_selection_event, 1897 | self, 1898 | "request_set_primary_selection", 1899 | Server.onRequestSetPrimarySelection, 1900 | &self.seat.events.request_set_primary_selection, 1901 | ); 1902 | Signal.connect( 1903 | *wlr.wlr_seat_request_start_drag_event, 1904 | self, 1905 | "request_start_drag", 1906 | Server.onRequestStartDrag, 1907 | &self.seat.events.request_start_drag, 1908 | ); 1909 | Signal.connect( 1910 | *wlr.wlr_drag, 1911 | self, 1912 | "start_drag", 1913 | Server.onStartDrag, 1914 | &self.seat.events.start_drag, 1915 | ); 1916 | wlr.wlr_server_decoration_manager_set_default_mode( 1917 | wlr.wlr_server_decoration_manager_create(self.wl_display), 1918 | wlr.WLR_SERVER_DECORATION_MANAGER_MODE_SERVER, 1919 | ); 1920 | _ = wlr.wlr_xdg_decoration_manager_v1_create(self.wl_display); 1921 | self.input_inhibit_mgr = wlr.wlr_input_inhibit_manager_create(self.wl_display); 1922 | self.idle = wlr.wlr_idle_create(self.wl_display); 1923 | self.output_manager = wlr.wlr_output_manager_v1_create(self.wl_display); 1924 | Signal.connect( 1925 | *wlr.wlr_output_configuration_v1, 1926 | self, 1927 | "output_manager_apply", 1928 | Server.onOutputManagerApply, 1929 | &self.output_manager.events.apply, 1930 | ); 1931 | Signal.connect( 1932 | *wlr.wlr_output_configuration_v1, 1933 | self, 1934 | "output_manager_test", 1935 | Server.onOutputManagerTest, 1936 | &self.output_manager.events.@"test", 1937 | ); 1938 | self.presentation = wlr.wlr_presentation_create(self.wl_display, self.wlr_backend); 1939 | self.xwayland = wlr.wlr_xwayland_create(self.wl_display, compositor, true); 1940 | if (self.xwayland) |xwayland| { 1941 | Signal.connect( 1942 | void, 1943 | self, 1944 | "xwayland_ready", 1945 | Server.onXwaylandReady, 1946 | &xwayland.events.ready, 1947 | ); 1948 | Signal.connect( 1949 | *wlr.wlr_xwayland_surface, 1950 | self, 1951 | "new_xwayland_surface", 1952 | Server.onNewXwaylandSurface, 1953 | &xwayland.events.new_surface, 1954 | ); 1955 | if (wlr.setenv("DISPLAY", xwayland.display_name, 1) == -1) { 1956 | std.log.err("Failed to set DISPLAY env var for xwayland", .{}); 1957 | } 1958 | } else { 1959 | std.log.err("Failed to setup XWayland X server, continuing without it", .{}); 1960 | } 1961 | } 1962 | 1963 | fn deinit(self: *Server) void { 1964 | std.log.info("Shutting down Byway", .{}); 1965 | 1966 | if (self.xwayland) |xwayland| { 1967 | wlr.wlr_xwayland_destroy(xwayland); 1968 | } 1969 | wlr.wl_display_destroy_clients(self.wl_display); 1970 | wlr.wl_display_destroy(self.wl_display); 1971 | wlr.wlr_xcursor_manager_destroy(self.cursor_mgr); 1972 | wlr.wlr_cursor_destroy(self.cursor); 1973 | wlr.wlr_output_layout_destroy(self.wlr_output_layout); 1974 | } 1975 | 1976 | fn start(self: *Server) !void { 1977 | const socket = wlr.wl_display_add_socket_auto(self.wl_display) orelse return error.CannotAddSocket; 1978 | 1979 | if (!wlr.wlr_backend_start(self.wlr_backend)) { 1980 | return error.CannotStartBackend; 1981 | } 1982 | 1983 | if (wlr.setenv("WAYLAND_DISPLAY", socket, 1) == -1) { 1984 | return error.CannotSetWaylandDisplayVar; 1985 | } 1986 | 1987 | std.log.info("Running Byway on WAYLAND_DISPLAY={s}", .{socket}); 1988 | for (self.config.autostart.items) |cmd| { 1989 | std.log.info("cmd {s}", .{cmd}); 1990 | self.actionCmd(cmd); 1991 | } 1992 | 1993 | wlr.wl_display_run(self.wl_display); 1994 | } 1995 | 1996 | fn onOutputLayoutChanged(self: *Server, _: void) !void { 1997 | var config = wlr.wlr_output_configuration_v1_create(); 1998 | 1999 | var iter = self.outputs.first; 2000 | while (iter) |node| : (iter = node.next) { 2001 | node.data.onLayoutChanged(config); 2002 | } 2003 | 2004 | wlr.wlr_output_manager_v1_set_configuration(self.output_manager, config); 2005 | } 2006 | 2007 | fn onNewOutput(self: *Server, wlr_output: *wlr.wlr_output) !void { 2008 | _ = try Output.create(self, wlr_output); 2009 | } 2010 | 2011 | fn onNewXdgSurface(self: *Server, xdg_surface: *wlr.wlr_xdg_surface) !void { 2012 | _ = try Surface.create(self, xdg_surface, null); 2013 | } 2014 | 2015 | fn onNewXwaylandSurface(self: *Server, xwayland_surface: *wlr.wlr_xwayland_surface) !void { 2016 | _ = try Surface.create(self, xwayland_surface, null); 2017 | } 2018 | 2019 | fn onNewLayerSurface(self: *Server, wlr_layer_surface: *wlr.wlr_layer_surface_v1) !void { 2020 | _ = try Surface.create(self, wlr_layer_surface, null); 2021 | } 2022 | 2023 | fn onCursorMotion(self: *Server, event: *wlr.wlr_event_pointer_motion) !void { 2024 | wlr.wlr_cursor_move(self.cursor, event.device, event.delta_x, event.delta_y); 2025 | self.processCursorMotion(event.time_msec); 2026 | } 2027 | 2028 | fn onCursorMotionAbsolute(self: *Server, event: *wlr.wlr_event_pointer_motion_absolute) !void { 2029 | wlr.wlr_cursor_warp_absolute(self.cursor, event.device, event.x, event.y); 2030 | self.processCursorMotion(event.time_msec); 2031 | } 2032 | 2033 | fn onCursorButton(self: *Server, event: *wlr.wlr_event_pointer_button) !void { 2034 | wlr.wlr_idle_notify_activity(self.idle, self.seat); 2035 | 2036 | if (self.outputAtCursor()) |output| { 2037 | if (output.spread_view) { 2038 | var spread: SpreadParams = undefined; 2039 | spread.set(self.toplevels, output); 2040 | if (spread.cols == 0) return; 2041 | const layout = output.getLayout(); 2042 | const col = @divTrunc(@floatToInt(i32, self.cursor.x) - layout.x, spread.width); 2043 | const row = @divTrunc(@floatToInt(i32, self.cursor.y) - layout.y, spread.height); 2044 | var index = row * @intCast(i32, spread.cols) + col; 2045 | var iter = self.toplevels.last; 2046 | while (iter) |node| : ({ 2047 | iter = node.prev; 2048 | index -= 1; 2049 | }) { 2050 | if (index == 0) { 2051 | self.toplevelToFront(node.data); 2052 | break; 2053 | } 2054 | } 2055 | output.toggleSpreadView(); 2056 | return; 2057 | } 2058 | } 2059 | self.processCursorMotion(event.time_msec); 2060 | 2061 | var handled = false; 2062 | 2063 | if (event.state == wlr.WLR_BUTTON_RELEASED) { 2064 | if (self.cursor_mode != .passthrough) { 2065 | self.cursor_mode = .passthrough; 2066 | handled = true; 2067 | self.grabbed_toplevel = null; 2068 | } 2069 | } else if (wlr.wlr_seat_get_keyboard(self.seat)) |keyboard| { 2070 | const modifiers = wlr.wlr_keyboard_get_modifiers(keyboard); 2071 | if (modifiers == self.config.mouse_move_modifiers and 2072 | event.button == self.config.mouse_move_button) 2073 | { 2074 | self.actionMove(); 2075 | handled = true; 2076 | } else if (modifiers == self.config.mouse_grow_modifiers and 2077 | event.button == self.config.mouse_grow_button) 2078 | { 2079 | self.actionResize(); 2080 | handled = true; 2081 | } 2082 | } 2083 | 2084 | if (!handled) { 2085 | _ = wlr.wlr_seat_pointer_notify_button( 2086 | self.seat, 2087 | event.time_msec, 2088 | event.button, 2089 | event.state, 2090 | ); 2091 | } 2092 | } 2093 | 2094 | fn onCursorAxis(self: *Server, event: *wlr.wlr_event_pointer_axis) !void { 2095 | wlr.wlr_idle_notify_activity(self.idle, self.seat); 2096 | self.processCursorMotion(event.time_msec); 2097 | wlr.wlr_seat_pointer_notify_axis( 2098 | self.seat, 2099 | event.time_msec, 2100 | event.orientation, 2101 | event.delta, 2102 | event.delta_discrete, 2103 | event.source, 2104 | ); 2105 | } 2106 | 2107 | fn onCursorFrame(self: *Server, _: void) !void { 2108 | wlr.wlr_seat_pointer_notify_frame(self.seat); 2109 | } 2110 | 2111 | fn setKeyboardFocus(self: *Server, wlr_surface: *wlr.wlr_surface) void { 2112 | if (Surface.fromWlrSurface(wlr_surface)) |next_surface| { 2113 | if (!next_surface.shouldFocus()) { 2114 | return; 2115 | } 2116 | 2117 | if (self.seat.keyboard_state.focused_surface) |prev_wlr_surface| { 2118 | if (prev_wlr_surface == wlr_surface) { 2119 | return; 2120 | } 2121 | 2122 | if (Surface.fromWlrSurface(prev_wlr_surface)) |prev_surface| { 2123 | _ = prev_surface.setActivated(false); 2124 | if (prev_surface != next_surface) { 2125 | prev_surface.damageBorders(); 2126 | } 2127 | } 2128 | } 2129 | 2130 | if (next_surface.setActivated(true)) { 2131 | if (@ptrCast(?*wlr.wlr_keyboard, wlr.wlr_seat_get_keyboard(self.seat))) |wlr_keyboard| { 2132 | wlr.wlr_seat_keyboard_notify_enter( 2133 | self.seat, 2134 | wlr_surface, 2135 | &wlr_keyboard.keycodes, 2136 | wlr_keyboard.num_keycodes, 2137 | &wlr_keyboard.modifiers, 2138 | ); 2139 | } 2140 | } 2141 | 2142 | next_surface.damageBorders(); 2143 | } 2144 | } 2145 | 2146 | fn onNewInputDevice(self: *Server, device: *wlr.wlr_input_device) !void { 2147 | switch (device.type) { 2148 | wlr.WLR_INPUT_DEVICE_KEYBOARD => { 2149 | _ = try Keyboard.create(self, device); 2150 | }, 2151 | wlr.WLR_INPUT_DEVICE_POINTER => { 2152 | if (wlr.wlr_input_device_is_libinput(device)) { 2153 | var libinput_device = wlr.wlr_libinput_get_device_handle(device); 2154 | 2155 | if (self.config.tap_to_click and wlr.libinput_device_config_tap_get_finger_count(libinput_device) != 0) { 2156 | _ = wlr.libinput_device_config_tap_set_enabled( 2157 | libinput_device, 2158 | wlr.LIBINPUT_CONFIG_TAP_ENABLED, 2159 | ); 2160 | } 2161 | if (wlr.libinput_device_config_scroll_has_natural_scroll(libinput_device) != 0) { 2162 | _ = wlr.libinput_device_config_scroll_set_natural_scroll_enabled( 2163 | libinput_device, 2164 | if (self.config.natural_scrolling) 1 else 0, 2165 | ); 2166 | } 2167 | } 2168 | wlr.wlr_cursor_attach_input_device(self.cursor, device); 2169 | }, 2170 | else => {}, 2171 | } 2172 | 2173 | var capabilities: u32 = wlr.WL_SEAT_CAPABILITY_POINTER; 2174 | if (self.keyboards.first != null) capabilities |= wlr.WL_SEAT_CAPABILITY_KEYBOARD; 2175 | wlr.wlr_seat_set_capabilities(self.seat, capabilities); 2176 | } 2177 | 2178 | fn onNewVirtualKeyboard(self: *Server, event: *wlr.wlr_virtual_keyboard_v1) !void { 2179 | try self.onNewInputDevice(&event.input_device); 2180 | } 2181 | 2182 | fn onNewVirtualPointer( 2183 | self: *Server, 2184 | event: *wlr.wlr_virtual_pointer_v1_new_pointer_event, 2185 | ) !void { 2186 | try self.onNewInputDevice( 2187 | &@ptrCast(*wlr.wlr_virtual_pointer_v1, event.new_pointer).input_device, 2188 | ); 2189 | } 2190 | 2191 | fn onRequestSetCursor( 2192 | self: *Server, 2193 | event: *wlr.wlr_seat_pointer_request_set_cursor_event, 2194 | ) !void { 2195 | if (self.seat.pointer_state.focused_client == event.seat_client) { 2196 | wlr.wlr_cursor_set_surface( 2197 | self.cursor, 2198 | event.surface, 2199 | event.hotspot_x, 2200 | event.hotspot_y, 2201 | ); 2202 | } 2203 | } 2204 | 2205 | fn onRequestSetSelection( 2206 | self: *Server, 2207 | event: *wlr.wlr_seat_request_set_selection_event, 2208 | ) !void { 2209 | wlr.wlr_seat_set_selection(self.seat, event.source, event.serial); 2210 | } 2211 | 2212 | fn onRequestSetPrimarySelection( 2213 | self: *Server, 2214 | event: *wlr.wlr_seat_request_set_primary_selection_event, 2215 | ) !void { 2216 | wlr.wlr_seat_set_primary_selection(self.seat, event.source, event.serial); 2217 | } 2218 | 2219 | fn onRequestStartDrag(self: *Server, event: *wlr.wlr_seat_request_start_drag_event) !void { 2220 | if (wlr.wlr_seat_validate_pointer_grab_serial(self.seat, event.origin, event.serial)) { 2221 | wlr.wlr_seat_start_pointer_drag(self.seat, event.drag, event.serial); 2222 | } else { 2223 | wlr.wlr_data_source_destroy(@ptrCast(*wlr.wlr_drag, event.drag).source); 2224 | } 2225 | } 2226 | 2227 | fn onStartDrag(self: *Server, wlr_drag: *wlr.wlr_drag) !void { 2228 | self.drag_icon = try Surface.create(self, @ptrCast(*wlr.wlr_drag_icon, wlr_drag.icon), null); 2229 | Signal.connect( 2230 | void, 2231 | self, 2232 | "destroy_drag", 2233 | Server.onDestroyDrag, 2234 | &wlr_drag.events.destroy, 2235 | ); 2236 | } 2237 | 2238 | fn onDestroyDrag(self: *Server, _: void) !void { 2239 | self.drag_icon = null; 2240 | } 2241 | 2242 | fn onOutputManagerApply(self: *Server, config: *wlr.wlr_output_configuration_v1) !void { 2243 | self.outputManagerApply(config, false); 2244 | } 2245 | 2246 | fn onOutputManagerTest(self: *Server, config: *wlr.wlr_output_configuration_v1) !void { 2247 | self.outputManagerApply(config, true); 2248 | } 2249 | 2250 | fn outputManagerApply( 2251 | self: *Server, 2252 | config: *wlr.wlr_output_configuration_v1, 2253 | is_test: bool, 2254 | ) void { 2255 | var test_passed = true; 2256 | 2257 | var iter: *wlr.wl_list = config.heads.next; 2258 | while (iter != &config.heads) : (iter = iter.next) { 2259 | const head = @fieldParentPtr(wlr.wlr_output_configuration_head_v1, "link", iter); 2260 | wlr.wlr_output_enable(head.state.output, head.state.enabled); 2261 | if (head.state.enabled) { 2262 | if (head.state.mode) |mode| { 2263 | wlr.wlr_output_set_mode(head.state.output, mode); 2264 | } else { 2265 | wlr.wlr_output_set_custom_mode( 2266 | head.state.output, 2267 | head.state.custom_mode.width, 2268 | head.state.custom_mode.height, 2269 | head.state.custom_mode.refresh, 2270 | ); 2271 | } 2272 | wlr.wlr_output_layout_move( 2273 | self.wlr_output_layout, 2274 | head.state.output, 2275 | head.state.x, 2276 | head.state.y, 2277 | ); 2278 | wlr.wlr_output_set_transform(head.state.output, head.state.transform); 2279 | wlr.wlr_output_set_scale(head.state.output, head.state.scale); 2280 | } 2281 | 2282 | test_passed = wlr.wlr_output_test(head.state.output); 2283 | if (!test_passed) { 2284 | break; 2285 | } 2286 | } 2287 | if (test_passed) { 2288 | iter = config.heads.next; 2289 | while (iter != &config.heads) : (iter = iter.next) { 2290 | const head = @fieldParentPtr(wlr.wlr_output_configuration_head_v1, "link", iter); 2291 | if (is_test) { 2292 | wlr.wlr_output_rollback(head.state.output); 2293 | } else { 2294 | _ = wlr.wlr_output_commit(head.state.output); 2295 | } 2296 | } 2297 | wlr.wlr_output_configuration_v1_send_succeeded(config); 2298 | } else { 2299 | wlr.wlr_output_configuration_v1_send_failed(config); 2300 | } 2301 | wlr.wlr_output_configuration_v1_destroy(config); 2302 | self.configureCursor(); 2303 | } 2304 | 2305 | fn onXwaylandReady(self: *Server, _: void) !void { 2306 | if (self.xwayland) |xwayland| { 2307 | var xc = wlr.xcb_connect(xwayland.display_name, null); 2308 | const xc_err = wlr.xcb_connection_has_error(xc); 2309 | if (xc_err != 0) { 2310 | std.log.err("xcb_connect failed with code {d}", .{xc_err}); 2311 | return; 2312 | } 2313 | 2314 | wlr.wlr_xwayland_set_seat(xwayland, self.seat); 2315 | 2316 | if (wlr.wlr_xcursor_manager_get_xcursor(self.cursor_mgr, "left_ptr", 1)) |xcursor| { 2317 | const img = @ptrCast( 2318 | *wlr.wlr_xcursor_image, 2319 | @ptrCast(*wlr.wlr_xcursor, xcursor).images[0], 2320 | ); 2321 | wlr.wlr_xwayland_set_cursor( 2322 | xwayland, 2323 | img.buffer, 2324 | img.width * 4, 2325 | img.width, 2326 | img.height, 2327 | @intCast(i32, img.hotspot_x), 2328 | @intCast(i32, img.hotspot_y), 2329 | ); 2330 | } 2331 | wlr.xcb_disconnect(xc); 2332 | } 2333 | } 2334 | 2335 | fn processCursorMove(self: *Server) void { 2336 | if (self.grabbed_toplevel) |toplevel| { 2337 | var box: wlr.wlr_box = undefined; 2338 | toplevel.getGeometry(&box); 2339 | box.x = @floatToInt(i32, self.cursor.x) - self.grab_x; 2340 | box.y = @floatToInt(i32, self.cursor.y) - self.grab_y; 2341 | toplevel.setGeometry(box); 2342 | } 2343 | } 2344 | 2345 | fn processCursorResize(self: *Server) void { 2346 | if (self.grabbed_toplevel) |toplevel| { 2347 | var border_x = @floatToInt(i32, self.cursor.x) - self.grab_x; 2348 | var border_y = @floatToInt(i32, self.cursor.y) - self.grab_y; 2349 | var new_position: wlr.wlr_box = undefined; 2350 | new_position.x = self.grab_geobox.x; 2351 | var new_right = self.grab_geobox.x + self.grab_geobox.width; 2352 | new_position.y = self.grab_geobox.y; 2353 | var new_bottom = self.grab_geobox.y + self.grab_geobox.height; 2354 | 2355 | if (self.resize_edges & wlr.WLR_EDGE_TOP != 0) { 2356 | new_position.y = border_y; 2357 | if (new_position.y >= new_bottom) { 2358 | new_position.y = new_bottom - 1; 2359 | } 2360 | } else if (self.resize_edges & wlr.WLR_EDGE_BOTTOM != 0) { 2361 | new_bottom = border_y; 2362 | if (new_bottom <= new_position.y) { 2363 | new_bottom = new_position.y + 1; 2364 | } 2365 | } 2366 | if (self.resize_edges & wlr.WLR_EDGE_LEFT != 0) { 2367 | new_position.x = border_x; 2368 | if (new_position.x >= new_right) { 2369 | new_position.x = new_right - 1; 2370 | } 2371 | } else if (self.resize_edges & wlr.WLR_EDGE_RIGHT != 0) { 2372 | new_right = border_x; 2373 | if (new_right <= new_position.x) { 2374 | new_right = new_position.x + 1; 2375 | } 2376 | } 2377 | 2378 | new_position.width = new_right - new_position.x; 2379 | new_position.height = new_bottom - new_position.y; 2380 | toplevel.setGeometry(new_position); 2381 | } 2382 | } 2383 | 2384 | fn outputAt(self: *Server, x: f64, y: f64) ?*Output { 2385 | return Output.fromWlrOutput(wlr.wlr_output_layout_output_at(self.wlr_output_layout, x, y)); 2386 | } 2387 | 2388 | fn outputAtCursor(self: *Server) ?*Output { 2389 | return self.outputAt(self.cursor.x, self.cursor.y); 2390 | } 2391 | 2392 | fn processCursorMotion(self: *Server, time: u32) void { 2393 | wlr.wlr_idle_notify_activity(self.idle, self.seat); 2394 | 2395 | if (self.cursor_mode == .move) { 2396 | self.processCursorMove(); 2397 | return; 2398 | } else if (self.cursor_mode == .resize) { 2399 | self.processCursorResize(); 2400 | return; 2401 | } 2402 | 2403 | if (self.outputAtCursor()) |output| { 2404 | if (output.spread_view) return; 2405 | 2406 | var sx: f64 = undefined; 2407 | var sy: f64 = undefined; 2408 | var wlr_surface: ?*wlr.wlr_surface = null; 2409 | 2410 | wlr_surface = output.layerSurfaceAt( 2411 | wlr.ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, 2412 | self.cursor.x, 2413 | self.cursor.y, 2414 | &sx, 2415 | &sy, 2416 | ); 2417 | 2418 | if (wlr_surface == null) { 2419 | wlr_surface = output.layerSurfaceAt( 2420 | wlr.ZWLR_LAYER_SHELL_V1_LAYER_TOP, 2421 | self.cursor.x, 2422 | self.cursor.y, 2423 | &sx, 2424 | &sy, 2425 | ); 2426 | } 2427 | 2428 | if (wlr_surface == null) { 2429 | wlr_surface = self.toplevelAt(self.cursor.x, self.cursor.y, &sx, &sy); 2430 | } 2431 | 2432 | if (wlr_surface == null) { 2433 | wlr_surface = output.layerSurfaceAt( 2434 | wlr.ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, 2435 | self.cursor.x, 2436 | self.cursor.y, 2437 | &sx, 2438 | &sy, 2439 | ); 2440 | } 2441 | 2442 | if (wlr_surface == null) { 2443 | wlr_surface = output.layerSurfaceAt( 2444 | wlr.ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, 2445 | self.cursor.x, 2446 | self.cursor.y, 2447 | &sx, 2448 | &sy, 2449 | ); 2450 | } 2451 | 2452 | if (wlr_surface) |surface_under_cursor| { 2453 | self.setKeyboardFocus(surface_under_cursor); 2454 | wlr.wlr_seat_pointer_notify_enter(self.seat, surface_under_cursor, sx, sy); 2455 | wlr.wlr_seat_pointer_notify_motion(self.seat, time, sx, sy); 2456 | } else { 2457 | wlr.wlr_seat_pointer_clear_focus(self.seat); 2458 | wlr.wlr_xcursor_manager_set_cursor_image(self.cursor_mgr, "left_ptr", self.cursor); 2459 | var focused_surface = Surface.fromWlrSurface(self.seat.keyboard_state.focused_surface); 2460 | if (focused_surface == null or !focused_surface.?.mapped) { 2461 | var iter = self.toplevels.first; 2462 | while (iter) |node| : (iter = node.next) { 2463 | if (node.data.workspace == output.active_workspace) { 2464 | node.data.setKeyboardFocus(); 2465 | break; 2466 | } 2467 | } 2468 | } 2469 | } 2470 | } 2471 | } 2472 | 2473 | fn toplevelAt( 2474 | self: *Server, 2475 | x: f64, 2476 | y: f64, 2477 | sx: *f64, 2478 | sy: *f64, 2479 | ) ?*wlr.wlr_surface { 2480 | if (self.outputAt(x, y)) |output| { 2481 | for ([2]std.TailQueue(*Surface){ 2482 | self.unmanaged_toplevels, 2483 | self.toplevels, 2484 | }) |list| { 2485 | var iter = list.first; 2486 | while (iter) |node| : (iter = node.next) { 2487 | var toplevel = node.data; 2488 | if (toplevel.workspace != output.active_workspace) { 2489 | continue; 2490 | } 2491 | if (toplevel.surfaceAt(x, y, sx, sy)) |wlr_surface| { 2492 | return wlr_surface; 2493 | } 2494 | } 2495 | } 2496 | } 2497 | return null; 2498 | } 2499 | 2500 | fn actionMove(self: *Server) void { 2501 | self.grabToplevelForResizeMove(.move); 2502 | } 2503 | 2504 | fn actionResize(self: *Server) void { 2505 | self.grabToplevelForResizeMove(.resize); 2506 | 2507 | if (self.grabbed_toplevel) |toplevel| { 2508 | toplevel.getGeometry(&self.grab_geobox); 2509 | 2510 | self.resize_edges = 0; 2511 | 2512 | var cursor_x = @floatToInt(i32, self.cursor.x); 2513 | var cursor_y = @floatToInt(i32, self.cursor.y); 2514 | if (cursor_x < self.grab_geobox.x + @divFloor(self.grab_geobox.width, 2)) { 2515 | self.resize_edges |= wlr.WLR_EDGE_LEFT; 2516 | } else { 2517 | self.resize_edges |= wlr.WLR_EDGE_RIGHT; 2518 | } 2519 | if (cursor_y < self.grab_geobox.y + @divFloor(self.grab_geobox.height, 2)) { 2520 | self.resize_edges |= wlr.WLR_EDGE_TOP; 2521 | } else { 2522 | self.resize_edges |= wlr.WLR_EDGE_BOTTOM; 2523 | } 2524 | 2525 | var border_x = self.grab_geobox.x + (if ((self.resize_edges & wlr.WLR_EDGE_RIGHT) == 0) 0 else self.grab_geobox.width); 2526 | var border_y = self.grab_geobox.y + (if ((self.resize_edges & wlr.WLR_EDGE_BOTTOM) == 0) 0 else self.grab_geobox.height); 2527 | self.grab_x = cursor_x - border_x; 2528 | self.grab_y = cursor_y - border_y; 2529 | } 2530 | } 2531 | 2532 | fn actionCmd(_: *Server, cmd: []const u8) void { 2533 | const childProc = std.ChildProcess.init(&.{ "/bin/sh", "-c", cmd }, alloc) catch |err| { 2534 | std.log.err("command failed: {s} {s}", .{ cmd, err }); 2535 | return; 2536 | }; 2537 | childProc.spawn() catch |err| { 2538 | std.log.err("command failed: {s} {s}", .{ cmd, err }); 2539 | return; 2540 | }; 2541 | } 2542 | 2543 | fn grabToplevelForResizeMove(self: *Server, cursor_mode: CursorMode) void { 2544 | if (self.getFocusedToplevel()) |toplevel| { 2545 | if (!toplevel.is_actual_fullscreen) { 2546 | var box: wlr.wlr_box = undefined; 2547 | toplevel.getGeometry(&box); 2548 | self.grabbed_toplevel = toplevel; 2549 | self.cursor_mode = cursor_mode; 2550 | self.grab_x = @floatToInt(i32, self.cursor.x) - box.x; 2551 | self.grab_y = @floatToInt(i32, self.cursor.y) - box.y; 2552 | } 2553 | } 2554 | } 2555 | 2556 | fn getFocusedToplevel(self: *Server) ?*Surface { 2557 | if (Surface.fromWlrSurface(self.seat.keyboard_state.focused_surface)) |surface| { 2558 | return surface.getToplevel(); 2559 | } 2560 | return null; 2561 | } 2562 | 2563 | fn toplevelToFront(self: *Server, surface: *Surface) void { 2564 | surface.setKeyboardFocus(); 2565 | self.toplevels.remove(&surface.node); 2566 | self.toplevels.prepend(&surface.node); 2567 | surface.toFront(); 2568 | self.damageAllOutputs(); 2569 | } 2570 | 2571 | fn actionToplevelToFront(self: *Server, _: []const u8) void { 2572 | if (self.getFocusedToplevel()) |toplevel| { 2573 | self.toplevelToFront(toplevel); 2574 | } 2575 | } 2576 | 2577 | fn actionToplevelToBack(self: *Server, _: []const u8) void { 2578 | if (self.getFocusedToplevel()) |toplevel| { 2579 | self.toplevels.remove(&toplevel.node); 2580 | self.toplevels.append(&toplevel.node); 2581 | toplevel.toBack(); 2582 | self.damageAllOutputs(); 2583 | } 2584 | } 2585 | 2586 | fn actionToplevelToWorkspace(self: *Server, arg: []const u8) void { 2587 | if (self.getFocusedToplevel()) |toplevel| { 2588 | const workspace = std.fmt.parseUnsigned(u32, arg, 10) catch return; 2589 | toplevel.workspace = workspace; 2590 | self.processCursorMotion(0); 2591 | self.damageAllOutputs(); 2592 | } 2593 | } 2594 | 2595 | fn actionSwitchToWorkspace(self: *Server, arg: []const u8) void { 2596 | if (self.outputAtCursor()) |output| { 2597 | const workspace = std.fmt.parseUnsigned(u32, arg, 10) catch return; 2598 | output.active_workspace = workspace; 2599 | self.processCursorMotion(0); 2600 | output.damageAll(); 2601 | } 2602 | } 2603 | 2604 | fn actionToggleSpreadView(self: *Server, _: []const u8) void { 2605 | if (self.outputAtCursor()) |output| output.toggleSpreadView(); 2606 | } 2607 | 2608 | fn actionToggleHideToplevels(self: *Server, _: []const u8) void { 2609 | self.show_toplevels = !self.show_toplevels; 2610 | self.damageAllOutputs(); 2611 | } 2612 | 2613 | fn actionQuit(self: *Server, _: []const u8) void { 2614 | wlr.wl_display_terminate(self.wl_display); 2615 | } 2616 | 2617 | fn actionChvt(self: *Server, arg: []const u8) void { 2618 | const vt = std.fmt.parseUnsigned(u32, arg, 10) catch return; 2619 | _ = wlr.wlr_session_change_vt(wlr.wlr_backend_get_session(self.wlr_backend), vt); 2620 | } 2621 | 2622 | fn ensureToplevelsVisible(self: *Server) void { 2623 | var iter = self.toplevels.last; 2624 | while (iter) |node| : (iter = node.prev) { 2625 | if (!node.data.isVisible(true)) node.data.place(); 2626 | } 2627 | } 2628 | fn nextIterCirc( 2629 | list: std.TailQueue(*Surface), 2630 | node: *std.TailQueue(*Surface).Node, 2631 | forward: bool, 2632 | ) ?*std.TailQueue(*Surface).Node { 2633 | var iter = if (forward) node.next else node.prev; 2634 | 2635 | if (iter) |i| return i; 2636 | 2637 | return if (forward) list.first else list.last; 2638 | } 2639 | 2640 | fn grabNextToplevel(self: *Server, forward: bool, app_id_comp: bool) void { 2641 | if (if (self.grabbed_toplevel != null) 2642 | self.grabbed_toplevel 2643 | else 2644 | self.getFocusedToplevel()) |start_toplevel| 2645 | { 2646 | var start_node = &start_toplevel.node; 2647 | var iter = Server.nextIterCirc(self.toplevels, start_node, forward); 2648 | while (iter) |node| : (iter = Server.nextIterCirc(self.toplevels, node, forward)) { 2649 | if (node == start_node) { 2650 | break; 2651 | } 2652 | 2653 | if (!node.data.isVisible(false)) continue; 2654 | 2655 | if (std.mem.eql( 2656 | u8, 2657 | std.mem.span(node.data.getAppId()), 2658 | std.mem.span(start_node.data.getAppId()), 2659 | ) != app_id_comp) { 2660 | continue; 2661 | } 2662 | 2663 | self.grabbed_toplevel = node.data; 2664 | self.damageAllOutputs(); 2665 | break; 2666 | } 2667 | } 2668 | } 2669 | 2670 | fn actionCycleToplevels(self: *Server, arg: []const u8) void { 2671 | var forward = std.fmt.parseInt(i32, arg, 10) catch return; 2672 | self.grabNextToplevel(forward > 0, true); 2673 | } 2674 | 2675 | fn actionCycleGroups(self: *Server, arg: []const u8) void { 2676 | var forward = std.fmt.parseInt(i32, arg, 10) catch return; 2677 | self.grabNextToplevel(forward > 0, false); 2678 | } 2679 | 2680 | fn reportHotkeyModifierState(self: *Server, pressed: bool) void { 2681 | self.modifier_pressed = pressed; 2682 | if (!pressed and self.cursor_mode == .passthrough) { 2683 | if (self.grabbed_toplevel) |grabbed_toplevel| { 2684 | self.grabbed_toplevel = null; 2685 | self.toplevelToFront(grabbed_toplevel); 2686 | } 2687 | } 2688 | } 2689 | 2690 | fn keyboardAdjustToplevel( 2691 | self: *Server, 2692 | arg: []const u8, 2693 | comptime abscissa: []const u8, 2694 | comptime ordinate: []const u8, 2695 | ) void { 2696 | if (self.getFocusedToplevel()) |toplevel| { 2697 | if (std.meta.stringToEnum(Config.Direction, arg)) |dir| { 2698 | toplevel.move(dir, self.config.move_pixels, abscissa, ordinate); 2699 | } 2700 | } 2701 | } 2702 | fn actionMoveKeyboard(self: *Server, arg: []const u8) void { 2703 | self.keyboardAdjustToplevel(arg, "x", "y"); 2704 | } 2705 | 2706 | fn actionGrowKeyboard(self: *Server, arg: []const u8) void { 2707 | self.keyboardAdjustToplevel(arg, "width", "height"); 2708 | } 2709 | 2710 | fn actionToggleFullscreen(self: *Server, _: []const u8) void { 2711 | if (self.getFocusedToplevel()) |toplevel| { 2712 | self.toplevelToFront(toplevel); 2713 | toplevel.toggleFullscreen(); 2714 | } 2715 | } 2716 | 2717 | fn actionCloseToplevel(self: *Server, _: []const u8) void { 2718 | if (self.getFocusedToplevel()) |toplevel| { 2719 | toplevel.close(); 2720 | } 2721 | } 2722 | 2723 | fn actionReloadConfig(self: *Server, _: []const u8) void { 2724 | self.config.reload(); 2725 | } 2726 | 2727 | fn damageAllOutputs(self: *Server) void { 2728 | var iter = self.outputs.first; 2729 | while (iter) |node| : (iter = node.next) { 2730 | node.data.damageAll(); 2731 | } 2732 | } 2733 | 2734 | fn configureCursor(self: *Server) void { 2735 | var iter = self.outputs.first; 2736 | while (iter) |node| : (iter = node.next) { 2737 | _ = wlr.wlr_xcursor_manager_load(self.cursor_mgr, node.data.wlr_output.scale); 2738 | } 2739 | } 2740 | 2741 | config: Config, 2742 | wl_display: *wlr.wl_display, 2743 | wlr_backend: *wlr.wlr_backend, 2744 | wlr_renderer: *wlr.wlr_renderer, 2745 | wlr_allocator: *wlr.wlr_allocator, 2746 | 2747 | toplevels: std.TailQueue(*Surface), 2748 | unmanaged_toplevels: std.TailQueue(*Surface), 2749 | show_toplevels: bool, 2750 | 2751 | wlr_output_layout: *wlr.wlr_output_layout, 2752 | outputs: std.TailQueue(*Output), 2753 | output_changed: wlr.wl_listener, 2754 | new_output: wlr.wl_listener, 2755 | output_manager: *wlr.wlr_output_manager_v1, 2756 | output_manager_test: wlr.wl_listener, 2757 | output_manager_apply: wlr.wl_listener, 2758 | 2759 | xdg_shell: *wlr.wlr_xdg_shell, 2760 | new_xdg_surface: wlr.wl_listener, 2761 | layer_shell: *wlr.wlr_layer_shell_v1, 2762 | new_layer_surface: wlr.wl_listener, 2763 | 2764 | cursor: *wlr.wlr_cursor, 2765 | cursor_mgr: *wlr.wlr_xcursor_manager, 2766 | cursor_motion: wlr.wl_listener, 2767 | cursor_motion_absolute: wlr.wl_listener, 2768 | cursor_button: wlr.wl_listener, 2769 | cursor_axis: wlr.wl_listener, 2770 | cursor_frame: wlr.wl_listener, 2771 | 2772 | seat: *wlr.wlr_seat, 2773 | new_input: wlr.wl_listener, 2774 | request_cursor: wlr.wl_listener, 2775 | request_set_selection: wlr.wl_listener, 2776 | request_set_primary_selection: wlr.wl_listener, 2777 | request_start_drag: wlr.wl_listener, 2778 | start_drag: wlr.wl_listener, 2779 | destroy_drag: wlr.wl_listener, 2780 | drag_icon: ?*Surface, 2781 | 2782 | keyboards: std.TailQueue(*Keyboard), 2783 | wlr_virtual_keyboard_mgr: *wlr.wlr_virtual_keyboard_manager_v1, 2784 | virtual_keyboard: wlr.wl_listener, 2785 | wlr_virtual_pointer_mgr: *wlr.wlr_virtual_pointer_manager_v1, 2786 | virtual_pointer: wlr.wl_listener, 2787 | cursor_mode: CursorMode, 2788 | grabbed_toplevel: ?*Surface, 2789 | grab_geobox: wlr.wlr_box, 2790 | grab_x: i32, 2791 | grab_y: i32, 2792 | resize_edges: u32, 2793 | modifier_pressed: bool, 2794 | 2795 | input_inhibit_mgr: *wlr.wlr_input_inhibit_manager, 2796 | idle: *wlr.wlr_idle, 2797 | 2798 | presentation: *wlr.wlr_presentation, 2799 | new_xwayland_surface: wlr.wl_listener, 2800 | xwayland_ready: wlr.wl_listener, 2801 | xwayland: ?*wlr.wlr_xwayland, 2802 | 2803 | const CursorMode = enum { 2804 | passthrough, 2805 | move, 2806 | resize, 2807 | }; 2808 | }; 2809 | 2810 | const Config = struct { 2811 | const Action = enum { 2812 | command, 2813 | toplevel_to_front, 2814 | toplevel_to_back, 2815 | cycle_groups, 2816 | cycle_toplevels, 2817 | move_toplevel, 2818 | grow_toplevel, 2819 | close_toplevel, 2820 | toggle_fullscreen, 2821 | toggle_spread_view, 2822 | toggle_hide_toplevels, 2823 | switch_to_workspace, 2824 | toplevel_to_workspace, 2825 | quit, 2826 | chvt, 2827 | reload_config, 2828 | }; 2829 | 2830 | const Direction = enum { 2831 | up, 2832 | down, 2833 | left, 2834 | right, 2835 | }; 2836 | 2837 | const DamageTrackingLevel = enum { 2838 | minimal, 2839 | partial, 2840 | full, 2841 | }; 2842 | 2843 | const KeyModifier = enum(u32) { 2844 | shift = wlr.WLR_MODIFIER_SHIFT, 2845 | caps = wlr.WLR_MODIFIER_CAPS, 2846 | ctrl = wlr.WLR_MODIFIER_CTRL, 2847 | alt = wlr.WLR_MODIFIER_ALT, 2848 | mod2 = wlr.WLR_MODIFIER_MOD2, 2849 | mod3 = wlr.WLR_MODIFIER_MOD3, 2850 | logo = wlr.WLR_MODIFIER_LOGO, 2851 | mod5 = wlr.WLR_MODIFIER_MOD5, 2852 | }; 2853 | 2854 | const MouseButton = enum(u32) { 2855 | left = wlr.BTN_LEFT, 2856 | right = wlr.BTN_RIGHT, 2857 | middle = wlr.BTN_MIDDLE, 2858 | }; 2859 | 2860 | const Unparsed = struct { 2861 | tap_to_click: ?bool = null, 2862 | natural_scrolling: ?bool = null, 2863 | background_color: ?[4]f32 = null, 2864 | border_color: ?[4]f32 = null, 2865 | focused_color: ?[4]f32 = null, 2866 | grabbed_color: ?[4]f32 = null, 2867 | active_border_width: ?i32 = null, 2868 | hotkeys: ?[]struct { 2869 | modifiers: []KeyModifier, 2870 | key: []u8, 2871 | action: Action, 2872 | arg: []u8, 2873 | } = null, 2874 | mouse_move_modifiers: ?[]KeyModifier = null, 2875 | mouse_move_button: ?MouseButton = null, 2876 | mouse_grow_modifiers: ?[]KeyModifier = null, 2877 | mouse_grow_button: ?MouseButton = null, 2878 | autostart: ?[][]u8 = null, 2879 | move_pixels: ?u32 = null, 2880 | grow_pixels: ?u32 = null, 2881 | damage_tracking: ?DamageTrackingLevel = null, 2882 | }; 2883 | 2884 | fn loadDefault(self: *Config) void { 2885 | const default = 2886 | \\ { 2887 | \\ "tap_to_click": true, 2888 | \\ "natural_scrolling": true, 2889 | \\ "background_color": [0.3, 0.3, 0.3, 1.0], 2890 | \\ "border_color": [0.5, 0.5, 0.5, 1.0], 2891 | \\ "focused_color": [0.28, 0.78, 1.0, 1.0], 2892 | \\ "grabbed_color": [1.0, 0.6, 0.7, 1.0], 2893 | \\ "active_border_width": 3, 2894 | \\ "autostart": [], 2895 | \\ "move_pixels": 10, 2896 | \\ "grow_pixels": 10, 2897 | \\ "damage_tracking": "minimal", 2898 | \\ "mouse_move_modifiers": ["logo"], 2899 | \\ "mouse_move_button": "left", 2900 | \\ "mouse_grow_modifiers": ["logo", "shift"], 2901 | \\ "mouse_grow_button": "left", 2902 | \\ "hotkeys": [ 2903 | \\ { 2904 | \\ "modifiers": ["logo"], 2905 | \\ "key": "t", 2906 | \\ "action": "command", 2907 | \\ "arg": "$TERM" 2908 | \\ }, 2909 | \\ { 2910 | \\ "modifiers": ["ctrl", "alt"], 2911 | \\ "key": "BackSpace", 2912 | \\ "action": "quit", 2913 | \\ "arg": "" 2914 | \\ } 2915 | \\ ] 2916 | \\ } 2917 | ; 2918 | load(self, default); 2919 | } 2920 | 2921 | fn loadFromFile(self: *Config) void { 2922 | var path: ?[]const u8 = std.os.getenv("XDG_CONFIG_HOME"); 2923 | if (path == null) { 2924 | path = std.mem.concat( 2925 | alloc, 2926 | u8, 2927 | &[2][]const u8{ std.os.getenv("HOME").?, "/.config" }, 2928 | ) catch |err| { 2929 | std.log.err("Could not read config: {s}", .{err}); 2930 | return; 2931 | }; 2932 | } 2933 | 2934 | if (path) |cfgdir| { 2935 | defer alloc.free(cfgdir); 2936 | const cfgpath = std.mem.concat(alloc, u8, &.{ cfgdir, "/byway" }) catch |err| { 2937 | std.log.err("Could not read config: {s}", .{err}); 2938 | return; 2939 | }; 2940 | defer alloc.free(cfgpath); 2941 | var byway_config_dir = std.fs.cwd().openDir(cfgpath, .{}) catch |err| { 2942 | std.log.err("Could not read config: {s}", .{err}); 2943 | return; 2944 | }; 2945 | defer byway_config_dir.close(); 2946 | const contents = byway_config_dir.readFileAlloc( 2947 | alloc, 2948 | "config.json", 2949 | 256000, 2950 | ) catch |err| { 2951 | std.log.err("Could not read config: {s}", .{err}); 2952 | return; 2953 | }; 2954 | defer alloc.free(contents); 2955 | 2956 | load(self, contents); 2957 | } 2958 | } 2959 | 2960 | fn load(self: *Config, update: []const u8) void { 2961 | var stream = std.json.TokenStream.init(update); 2962 | @setEvalBranchQuota(10000); 2963 | const parsed = std.json.parse(Unparsed, &stream, .{ 2964 | .ignore_unknown_fields = true, 2965 | .allocator = alloc, 2966 | }) catch |err| { 2967 | std.log.err("Could not parse config: {s}", .{err}); 2968 | return; 2969 | }; 2970 | defer std.json.parseFree(Unparsed, parsed, .{ 2971 | .allocator = alloc, 2972 | }); 2973 | 2974 | if (parsed.mouse_grow_button) |mgb| self.mouse_grow_button = @enumToInt(mgb); 2975 | if (parsed.mouse_grow_modifiers) |mgm| { 2976 | self.mouse_grow_modifiers = 0; 2977 | for (mgm) |mod| { 2978 | self.mouse_grow_modifiers |= @enumToInt(mod); 2979 | } 2980 | } 2981 | if (parsed.mouse_move_button) |mmb| self.mouse_move_button = @enumToInt(mmb); 2982 | if (parsed.mouse_move_modifiers) |mmm| { 2983 | self.mouse_move_modifiers = 0; 2984 | for (mmm) |mod| { 2985 | self.mouse_move_modifiers |= @enumToInt(mod); 2986 | } 2987 | } 2988 | 2989 | if (parsed.tap_to_click) |val| self.tap_to_click = val; 2990 | if (parsed.natural_scrolling) |val| self.natural_scrolling = val; 2991 | if (parsed.background_color) |val| self.background_color = val; 2992 | if (parsed.border_color) |val| self.border_color = val; 2993 | if (parsed.focused_color) |val| self.focused_color = val; 2994 | if (parsed.grabbed_color) |val| self.grabbed_color = val; 2995 | if (parsed.active_border_width) |val| self.active_border_width = val; 2996 | if (parsed.move_pixels) |val| self.move_pixels = val; 2997 | if (parsed.grow_pixels) |val| self.grow_pixels = val; 2998 | if (parsed.damage_tracking) |val| self.damage_tracking = val; 2999 | 3000 | if (parsed.autostart) |autostart| { 3001 | self.autostart.clearAndFree(); 3002 | for (autostart) |cmdcfg| { 3003 | var cmd = alloc.alloc(u8, cmdcfg.len) catch return; 3004 | std.mem.copy(u8, cmd, cmdcfg); 3005 | self.autostart.append(cmd) catch return; 3006 | } 3007 | } 3008 | if (parsed.hotkeys) |hotkeys| { 3009 | self.hotkeys = alloc.alloc(Hotkey, hotkeys.len) catch unreachable; 3010 | for (hotkeys) |hotkeyConfig, idx| { 3011 | self.hotkeys[idx].arg = alloc.alloc(u8, hotkeyConfig.arg.len) catch return; 3012 | std.mem.copy(u8, self.hotkeys[idx].arg, hotkeyConfig.arg); 3013 | 3014 | self.hotkeys[idx].modifiers = 0; 3015 | for (hotkeyConfig.modifiers) |mod| { 3016 | self.hotkeys[idx].modifiers |= @enumToInt(mod); 3017 | } 3018 | var configKey = std.cstr.addNullByte(alloc, hotkeyConfig.key) catch return; 3019 | defer alloc.free(configKey); 3020 | self.hotkeys[idx].key = wlr.xkb_keysym_from_name(configKey, wlr.XKB_KEYSYM_NO_FLAGS); 3021 | self.hotkeys[idx].cb = switch (hotkeyConfig.action) { 3022 | .command => Server.actionCmd, 3023 | .toplevel_to_front => Server.actionToplevelToFront, 3024 | .toplevel_to_back => Server.actionToplevelToBack, 3025 | .cycle_groups => Server.actionCycleGroups, 3026 | .cycle_toplevels => Server.actionCycleToplevels, 3027 | .move_toplevel => Server.actionMoveKeyboard, 3028 | .grow_toplevel => Server.actionGrowKeyboard, 3029 | .toggle_fullscreen => Server.actionToggleFullscreen, 3030 | .close_toplevel => Server.actionCloseToplevel, 3031 | .switch_to_workspace => Server.actionSwitchToWorkspace, 3032 | .toplevel_to_workspace => Server.actionToplevelToWorkspace, 3033 | .toggle_spread_view => Server.actionToggleSpreadView, 3034 | .toggle_hide_toplevels => Server.actionToggleHideToplevels, 3035 | .quit => Server.actionQuit, 3036 | .chvt => Server.actionChvt, 3037 | .reload_config => Server.actionReloadConfig, 3038 | }; 3039 | } 3040 | } 3041 | } 3042 | 3043 | const Hotkey = struct { 3044 | modifiers: u32, 3045 | key: u32, 3046 | cb: fn (*Server, []const u8) void, 3047 | arg: []u8, 3048 | }; 3049 | 3050 | tap_to_click: bool, 3051 | natural_scrolling: bool, 3052 | background_color: [4]f32, 3053 | border_color: [4]f32, 3054 | focused_color: [4]f32, 3055 | grabbed_color: [4]f32, 3056 | active_border_width: i32, 3057 | hotkeys: []Hotkey, 3058 | mouse_move_modifiers: u32, 3059 | mouse_move_button: u32, 3060 | mouse_grow_modifiers: u32, 3061 | mouse_grow_button: u32, 3062 | autostart: std.ArrayList([]u8), 3063 | move_pixels: u32, 3064 | grow_pixels: u32, 3065 | damage_tracking: DamageTrackingLevel, 3066 | 3067 | fn init(self: *Config) void { 3068 | self.autostart = std.ArrayList([]u8).init(alloc); 3069 | self.loadDefault(); 3070 | self.reload(); 3071 | } 3072 | 3073 | fn reload(self: *Config) void { 3074 | self.loadFromFile(); 3075 | } 3076 | }; 3077 | 3078 | const Signal = struct { 3079 | fn connect( 3080 | comptime PayloadType: type, 3081 | container: anytype, 3082 | comptime listenerField: []const u8, 3083 | comptime cb: fn (container: @TypeOf(container), data: PayloadType) anyerror!void, 3084 | signal: *wlr.wl_signal, 3085 | ) void { 3086 | var listener = &@field(container, listenerField); 3087 | listener.notify = Listener(PayloadType, @TypeOf(container.*), listenerField, cb).onSignal; 3088 | wlr.wl_signal_add(signal, listener); 3089 | } 3090 | 3091 | fn Listener( 3092 | comptime PayloadType: type, 3093 | comptime ContainerType: type, 3094 | comptime listenerField: []const u8, 3095 | comptime cb: fn (container: *ContainerType, data: PayloadType) anyerror!void, 3096 | ) type { 3097 | return struct { 3098 | fn onSignal(cbListener: [*c]wlr.wl_listener, data: ?*anyopaque) callconv(.C) void { 3099 | cb( 3100 | @fieldParentPtr(ContainerType, listenerField, cbListener), 3101 | if (PayloadType == void) {} else @ptrCast( 3102 | PayloadType, 3103 | @alignCast(@alignOf(PayloadType), data), 3104 | ), 3105 | ) catch |err| { 3106 | std.log.err("Error from callback {d}", .{err}); 3107 | }; 3108 | } 3109 | }; 3110 | } 3111 | }; 3112 | 3113 | const wlr_xdg_surface_union = @typeInfo(wlr.wlr_xdg_surface).Struct.fields[5].name; 3114 | const wlr_input_device_union = @typeInfo(wlr.wlr_input_device).Struct.fields[8].name; 3115 | 3116 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 3117 | var alloc = gpa.allocator(); 3118 | --------------------------------------------------------------------------------