diff --git a/book/AGENTS.md b/book/AGENTS.md index 820b25be..85e88261 100644 --- a/book/AGENTS.md +++ b/book/AGENTS.md @@ -1,299 +1,135 @@ # Jay Book -- Agent Instructions -This file provides context for AI agents working on the Jay user documentation -book (mdbook). Read this before making changes. +User-facing [mdbook](https://rust-lang.github.io/mdBook/) documentation for +the [Jay Wayland compositor](https://github.com/mahkoh/jay). Target audience +is end users, not developers. Goal is feature discoverability. -## What is this? +## Quick reference -An [mdbook](https://rust-lang.github.io/mdBook/) providing comprehensive -user-facing documentation for the [Jay Wayland compositor](https://github.com/mahkoh/jay). -The target audience is end users, not developers. The goal is feature -discoverability: every capability Jay offers should be documented here in a -way that users can find and understand. +### Book files (`book/src/`) -## Current status +The table of contents is `SUMMARY.md`. Key chapter-to-topic mapping: -The book is feature-complete. All 29 chapters cover every known Jay feature: -a features overview, installation (including AUR packages), running, the -control center, all configuration sections, tiling, workspaces, floating -windows, mouse interactions, input modes, window/client rules, screen sharing, -HDR & color management, the full CLI reference, and troubleshooting. +| File | Covers | +|------|--------| +| `configuration/shortcuts.md` | Shortcuts, actions (simple + parameterized), marks, named actions, virtual outputs, actions in window rules | +| `configuration/outputs.md` | Monitor config, VRR, tearing, scaling, transforms | +| `configuration/inputs.md` | Input devices, per-device settings | +| `configuration/misc.md` | Color management, libei, floating defaults, ui-drag | +| `window-rules.md` | Window/client rules, privileges, capabilities | +| `control-center.md` | All 11 control center panes | +| `cli.md` | All CLI subcommands, JSON output | +| `input-modes.md` | Modal keybinding system (push/pop/latch/clear) | +| `floating.md` | Floating windows, window management mode | +| `hdr.md` | HDR & color management walkthrough | -Three rounds of source-code verification have been completed and all -identified issues have been fixed. The book should be accurate against the -codebase as of the latest review. +### Source-of-truth files (from repo root) -A subsequent style and formatting pass has been completed: - -- Converted ~45 two-column tables to mdbook definition lists across 16 files. -- Lowercased all keyboard shortcut modifiers to match TOML parser expectations. -- Reformatted long inline TOML tables into multiline format with trailing - commas and 4-space indentation. -- Created a dedicated Features chapter (`features.md`) consolidating content - from the old `docs/features.md` and the introduction; removed the duplicated - feature list from `introduction.md`. -- Added a comprehensive "Granting Privileges" section to `window-rules.md` and - privilege troubleshooting to `troubleshooting.md`. -- Clarified `jay unlock` usage in `configuration/idle.md` and `cli.md`. -- Added AUR package (`jay`, `jay-git`) mentions to `installation.md`. -- Added a dedicated HDR & Color Management chapter (`hdr.md`) with - end-to-end walkthrough, cross-referenced from `features.md`, `outputs.md`, - and `misc.md`. -- Integrated remaining information from `docs/` and `README.md` that was - missing from the book: `JAY_NO_REALTIME` env var and `config.so`/SCHED_RR - interaction in `installation.md`, rule loop client-kill consequences and - Xwayland override-redirect auto-focus exception in `window-rules.md`, - license (GPLv3) and community Discord link in `introduction.md`, ArchWiki - XKB link in `keymaps.md`. - -A third review pass fixed: - -- `window-rules.md`: exec action example used a plain string with spaces - (`exec = "notify-send 'Firefox connected'"`) which would be treated as a - single program name; changed to array form - (`exec = ["notify-send", "Firefox connected"]`). -- `window-rules.md`: default capabilities paragraph incorrectly said rule - capabilities are "added on top" of defaults; corrected to say they - **replace** defaults entirely. -- `troubleshooting.md`: said Jay "requires at least one working renderer to - start"; corrected to say that without one, no GPU can be initialized and - nothing will be displayed (Jay itself still starts). -- `outputs.md`: VRR variant3 description said "describes its content as" - instead of the correct protocol term "describes its content type as". - -A fourth update documented the new `--json` and `--all-json-fields` global -CLI flags, which enable machine-readable JSONL output from all query/status -subcommands. A new "JSON Output" section was added to `cli.md` listing all -supported commands, the JSONL format, field omission behavior, and `jq` -usage examples. - -**Future work might include:** - -- Keeping the book in sync as Jay adds new features or changes behavior. -- Adding screenshots or diagrams (especially for the control center and tiling). -- Reviewing after spec.yaml changes (the spec is the canonical source for - config options). - -## Directory layout - -``` -book/ - AGENTS.md -- this file - book.toml -- mdbook configuration - src/ - SUMMARY.md -- table of contents (defines chapter ordering) - introduction.md - features.md -- feature overview, protocol table, CLI help output - installation.md -- deps, AUR, crates.io, git builds, CAP_SYS_NICE - running.md -- includes control center intro and default keybindings - control-center.md -- detailed tour of all 11 panes - configuration/ - index.md -- config overview, jay config init, file semantics, auto-reload - keymaps.md - shortcuts.md - outputs.md - inputs.md - gpu.md - idle.md - theme.md - environment.md - status-bar.md - startup.md - xwayland.md - misc.md -- color management, libei, floating, ui-drag, etc. - tiling.md - workspaces.md - floating.md - mouse.md - input-modes.md - window-rules.md -- includes "Granting Privileges" section - cli.md - screen-sharing.md - hdr.md -- HDR & color management walkthrough - troubleshooting.md -- includes privilege troubleshooting -``` - -## Source of truth - -The authoritative source for what Jay supports is the source code itself. Key -reference files: - -- `toml-spec/spec/spec.yaml` -- Full TOML config specification. Every config - key, action, match criterion, and type is defined here. The auto-generated - `toml-spec/spec/spec.generated.md` is derived from this file. -- `toml-config/src/default-config.toml` -- The built-in default configuration. - Contains default keybindings, startup actions (mako, wl-tray-bridge), etc. -- `src/cli/*.rs` -- CLI subcommands and their arguments (clap definitions). - Jay has 24 CLI subcommands with nesting up to 4 levels deep. -- `src/control_center/*.rs` -- Control center pane implementations. Each pane - has its own file (cc_outputs.rs, cc_gpus.rs, cc_input.rs, cc_clients.rs, - cc_window.rs, cc_idle.rs, cc_compositor.rs, cc_look_and_feel.rs, - cc_xwayland.rs, cc_color_management.rs, cc_virtual_outputs.rs). When - documenting control center fields, always verify against these source files - for correct ordering, labels, and conditional visibility. -- `toml-config/src/config/parsers/color.rs` -- Color parser. Accepts 3, 4, 6, - or 8 hex digits (`#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`). Note: spec.yaml - says `#rrggbba` (7 digits) which is a typo -- the actual format is 8 digits. -- `toml-config/src/config/parsers/exec.rs` -- Exec parser. Accepts string, - array, or table. Understanding these three forms is important for writing - correct TOML examples. -- `src/config/handler.rs` -- Contains `update_capabilities`, which shows that - client rule capabilities **replace** defaults (accumulator starts from zero). -- The `docs/` directory has been removed. All of its content has been - integrated into the book. The README now points to the hosted book at - `https://mahkoh.github.io/jay/book`. +| File | What it tells you | +|------|-------------------| +| `toml-spec/spec/spec.yaml` | **Canonical** TOML config spec: every key, action, match criterion, type | +| `toml-config/src/default-config.toml` | Built-in default config (keybindings, startup actions) | +| `toml-config/src/config/parsers/action.rs` | Action parser — see which `type` strings are accepted | +| `toml-config/src/lib.rs` | Action dispatch — `window_or_seat!` macro shows which actions work in window rules | +| `src/config/handler.rs` | Config handler; `update_capabilities` shows capability replacement semantics | +| `src/cli/*.rs` | CLI subcommands (clap definitions) | +| `src/control_center/cc_*.rs` | Control center pane implementations (verify field names/ordering here) | +| `toml-config/src/config/parsers/exec.rs` | Exec parser (string, array, or table forms) | ### Known spec.yaml bugs -- `px-per-wheel-scroll` is listed as `kind: boolean` but the parser - (`toml-config/src/config/parsers/input.rs`) uses `fltorint` (a number). - The documentation correctly describes it as a numeric value. +- `px-per-wheel-scroll`: listed as `kind: boolean` but parser uses `fltorint` + (a number). Book correctly documents it as numeric. -## Critical facts to get right +## Critical facts -1. **Config replacement semantics.** Once `~/.config/jay/config.toml` exists, - the entire built-in default configuration is replaced -- not merged. Even an - empty config file means no shortcuts, no startup actions, nothing. Users must - run `jay config init` to get a config pre-populated with the defaults. +1. **Config replacement.** `~/.config/jay/config.toml` replaces the entire + built-in default — not merged. Empty file = no shortcuts, nothing. + Users must run `jay config init`. -2. **Config reload.** By default, Jay does not automatically reload - config.toml. Reload must be triggered manually via the `alt-shift-r` - shortcut (default) or the `reload-config-toml` action. However, setting - `auto-reload = true` in config.toml enables inotify-based file watching - with a 400 ms debounce (parsed in - `toml-config/src/config/parsers/config.rs:209`, acted on in - `toml-config/src/lib.rs:1348-1358`). +2. **Config reload.** Manual by default (`alt-shift-r` / `reload-config-toml`). + `auto-reload = true` enables inotify watching with 400 ms debounce. -3. **Renderer requirement.** Jay requires at least one working renderer - (OpenGL via libEGL+libGLESv2, or Vulkan via libvulkan + a GPU driver). - Without one, no GPU can be initialized and nothing will be displayed. (Jay - itself still starts -- the metal backend initializes logind, udev, and - libinput successfully -- but no DRM devices can be set up, so the user sees - a black screen.) These libraries are loaded at runtime, not linked at build - time. Vulkan is the recommended renderer; OpenGL is legacy and receives no - new features. +3. **Actions are composable.** Anywhere an action is accepted, an array works. + Named actions (`$name`) add reuse. -4. **Actions are composable.** Anywhere an action is accepted, an array of - actions can be used instead. Named actions (`$name`) add another layer of - reuse. +4. **Exec action gotcha.** Plain string = program name only (no arg splitting). + `exec = "notify-send 'hello'"` is wrong — use array form. -5. **Match systems.** Outputs, connectors, DRM devices, inputs, clients, and - windows each have their own match system. All support the same logical - combinators: AND (multiple fields in one table), OR (array of matchers), - `not`, `all`, `any`, `exactly`. +5. **Capability replacement.** When any client rule matches, its capabilities + **replace** defaults entirely (not additive). Verify against + `handler.rs:update_capabilities`. -6. **Input modes.** Jay supports an input mode stack (`push-mode`, - `latch-mode`, `pop-mode`, `clear-modes`). Each mode can define its own - shortcuts and inherit from a parent mode. This is how vim-style modal - keybindings are implemented. Note: there are NO `resize-left/right/up/down` - actions -- resizing is mouse-only. Use `move-*` actions in examples. +6. **Window rule actions.** Actions using `window_or_seat!` in `lib.rs` work + in both shortcuts (focused window) and window rules (matched window). + The list in shortcuts.md "Actions in window rules" must stay in sync. -7. **Window/client rules are reactive.** Rules are re-evaluated whenever the - matching criteria change (e.g. a window's title changes). The `latch` action - fires when a previously-matching rule stops matching. +7. **Window/client rules are reactive.** Re-evaluated when criteria change. + `latch` fires when a rule stops matching. -8. **Control center.** Opened with `alt-c` (default) or `jay control-center`. - It is an egui-based GUI with 11 panes: Clients, Color Management, - Compositor, GPUs, Idle, Input, Look and Feel, Outputs (with visual - arrangement editor), Virtual Outputs, Window Search, and Xwayland. +8. **VRR vs tearing subtlety.** VRR variant1/2 use "fullscreen"; tearing + variant3 says "a single application is displayed" (not necessarily + fullscreen). Always check spec.yaml wording. -9. **VRR and tearing.** Both have per-output overrides and multiple mode - variants (never, always, variant1/2/3). Important distinction: VRR - variant1/2 use "fullscreen" as the criterion. Tearing variant3 says "a - single application is displayed" (not necessarily fullscreen) -- check the - spec.yaml wording carefully. +## Style rules -10. **Input device type flags.** The full list is: `is-keyboard`, `is-pointer`, - `is-touch`, `is-tablet-tool`, `is-tablet-pad`, `is-gesture`, `is-switch`. - Note that `is-switch` exists in the parser but is missing from spec.yaml. +- **Audience:** users, not developers. Explain what, not how. +- **Code fences:** ` ```shell ` for commands (prefix `~$`), ` ```toml ` for config. +- **Admonitions:** `> [!NOTE]`, `> [!TIP]`, `> [!WARNING]`, `> [!IMPORTANT]`. +- **Key combos:** backticks, lowercase modifiers: `alt-shift-c`, `ctrl-alt-F2`, + `alt-Return`. Never `Alt+C`. +- **Definition lists** for two-column term/description. Tables only for 3+ data columns. +- **TOML formatting:** multiline with trailing commas, 4-space indent. +- **Examples:** practical, not abstract. Link to + [spec.generated.md](https://github.com/mahkoh/jay/blob/master/toml-spec/spec/spec.generated.md) + for exhaustive listings. +- **Control center docs:** verify field names, ordering, and conditional + visibility against `cc_*.rs` source files. Labels must match exactly. -11. **Features chapter is the single source for the feature list.** The - introduction links to `features.md` but does not duplicate any feature - bullets. All feature descriptions, the protocol support table, and CLI help - output live in `features.md` only. +## Common tasks -12. **Privilege separation.** Jay supports granting elevated Wayland protocol - access via three methods: `privileged = true` in window rules with - `jay run-privileged`, connection tags with client rules, and client match - rules by PID/sandboxing. This is documented in the "Granting Privileges" - section of `window-rules.md`. **Important:** when any client rule matches, - its capabilities **replace** the defaults entirely (they are not added on - top). Multiple matching rules are unioned together, but the defaults - (`layer-shell | drm-lease` for unsandboxed, `drm-lease` for sandboxed) - are only used when no rule matches at all. +### Documenting a new action -13. **Exec action formats.** The `exec` field in exec actions accepts three - forms: a plain string (program name only, no arguments), an array of - strings (first element is the program, rest are arguments), or a table - with `prog`/`shell`, `args`, `env`, `privileged`, and `tag` fields. A - common mistake is using a plain string with spaces like - `exec = "notify-send 'hello'"` -- this treats the entire string as the - program name. Use the array form instead: - `exec = ["notify-send", "hello"]`. +1. Read `git diff` for the commit introducing the action. Key files: + - `toml-spec/spec/spec.yaml` — spec entry (description, fields, examples) + - `toml-config/src/config/parsers/action.rs` — parser (field names, types, defaults) + - `toml-config/src/lib.rs` — dispatch (check if `window_or_seat!` is used) + - `jay-config/src/input.rs` and/or `jay-config/src/window.rs` — Rust API -## Style guidelines +2. Edit `book/src/configuration/shortcuts.md`: + - **Simple actions** (no fields): add to the appropriate list in the + "Simple actions" section. + - **Parameterized actions** (has fields): add a new `###` subsection before + "Other parameterized actions". Include definition list for fields and + practical TOML examples. + - **Also parameterized but minor:** just add a `- name -- description` + bullet to the "Other parameterized actions" list. -- Write for users, not developers. Explain what things do, not how they are - implemented. -- Use ` ```shell ` for shell commands, prefixed with `~$` and `sudo` where - appropriate. -- Use ` ```toml ` for TOML configuration examples. -- Use **mdbook admonitions** (`> [!NOTE]`, `> [!TIP]`, `> [!WARNING]`, - `> [!IMPORTANT]`) instead of `> **Note:**` blockquote patterns. mdbook - 0.5.2+ is installed and supports this syntax. -- **Keyboard shortcuts** must be enclosed in backticks and use **lowercase - modifiers** -- this matches the format the TOML parser expects. Keysym names - retain their original case. Examples: `alt-shift-c`, `ctrl-alt-F2`, - `alt-Return`. Never write `Alt-Shift-c` or `Alt+C`. -- **Tables vs definition lists.** Use mdbook definition lists (`Term\n: - Description`) for two-column term-to-description mappings. Keep true - grid-like tables only when they have 3+ meaningful data columns (e.g., - package names per distro, the protocol support table with version numbers). -- **TOML formatting.** Break long inline TOML tables over multiple lines with - trailing commas (TOML 1.1 syntax). Use 4-space indentation inside TOML code - blocks, never 2-space. -- Keep examples practical -- show real use cases, not abstract syntax. -- Link to the auto-generated spec (`spec.generated.md`) for exhaustive field - listings. The book should teach concepts and workflows; the spec is the - complete reference. -- Each chapter should be self-contained enough to be useful on its own, but - cross-reference related chapters where helpful. -- Avoid duplicating large tables of every possible value. Summarize, give - examples, and link to the spec for the full list. -- When documenting the control center, always verify field names, ordering, - and conditional visibility against the `src/control_center/cc_*.rs` source - files. UI labels must match exactly (case-sensitive). +3. If `window_or_seat!` is used in `lib.rs`, add the action name to the + "Actions in window rules" list at the bottom of `shortcuts.md`. -## Verification methodology +### Documenting a new config field -When making changes, verify against source code. The most error-prone areas -are: +1. Read `toml-spec/spec/spec.yaml` for the field definition. +2. Identify which book chapter covers that config section (see table above). +3. Add the field with a definition-list entry or example, matching the + existing style of that chapter. -1. **Control center pane documentation** -- Field names, ordering, and - conditional visibility must match the `show_*` functions in the cc_*.rs - files. Fields are often conditional (only shown for certain device types or - when certain values are set). +### Documenting a new CLI subcommand -2. **VRR/tearing mode descriptions** -- The exact semantics differ subtly - between modes and between VRR vs tearing. Always check spec.yaml wording. +1. Read `src/cli/*.rs` for clap definitions. +2. Edit `book/src/cli.md`. Follow the existing pattern for subcommand docs. +3. If the subcommand has `--json` support, mention it in the "JSON Output" section. -3. **Exec action examples** -- A plain string is the program name only (no - argument splitting). Use arrays or tables when arguments are needed. +### Documenting a control center change -4. **Capability/privilege semantics** -- Capabilities from matching rules - replace defaults; they do not add to them. Verify against - `src/config/handler.rs:update_capabilities` if in doubt. +1. Read the relevant `src/control_center/cc_*.rs` file. +2. Edit `book/src/control-center.md`. Match field names, ordering, and + conditional visibility exactly. -## Building the book +## Building ```shell -~$ cd book -~$ mdbook build # outputs to book/book/ -~$ mdbook serve # local preview at http://localhost:3000 -``` - -Requires [mdbook](https://github.com/rust-lang/mdBook) to be installed: - -```shell -~$ cargo install mdbook +~$ cd book && mdbook build # outputs to book/book/ +~$ cd book && mdbook serve # preview at http://localhost:3000 ``` diff --git a/book/src/configuration/shortcuts.md b/book/src/configuration/shortcuts.md index dba99b56..ecc0b64d 100644 --- a/book/src/configuration/shortcuts.md +++ b/book/src/configuration/shortcuts.md @@ -196,6 +196,46 @@ logo-ctrl-shift-Left = { } ``` +### Resizing windows + +The `resize` action resizes the focused window by adjusting its edges. +Each of the four optional fields (`dx1`, `dy1`, `dx2`, `dy2`) specifies a +pixel offset for one edge of the window. Fields default to `0` when omitted. + +`dx1` +: Change at the left edge (negative = grow left, positive = shrink left) + +`dy1` +: Change at the top edge (negative = grow up, positive = shrink down) + +`dx2` +: Change at the right edge (positive = grow right, negative = shrink right) + +`dy2` +: Change at the bottom edge (positive = grow down, negative = shrink down) + +Growing the window by 10 pixels on the right: + +```toml +[shortcuts] +alt-Right = { type = "resize", dx2 = 10 } +``` + +Shrinking the window by 10 pixels on the right: + +```toml +[shortcuts] +alt-Left = { type = "resize", dx2 = -10 } +``` + +Moving the window 10 pixels to the right without changing its size (both +left and right edges shift by the same amount): + +```toml +[shortcuts] +alt-shift-Right = { type = "resize", dx1 = 10, dx2 = 10 } +``` + ### Other parameterized actions - `set-keymap` -- change the active keymap @@ -499,7 +539,7 @@ affected actions are: `move-left`, `move-down`, `move-up`, `move-right`, `split-horizontal`, `split-vertical`, `toggle-split`, `tile-horizontal`, `tile-vertical`, `show-single`, `show-all`, `toggle-fullscreen`, `enter-fullscreen`, `exit-fullscreen`, `close`, `toggle-floating`, `float`, -`tile`, `toggle-float-pinned`, `pin-float`, and `unpin-float`. +`tile`, `toggle-float-pinned`, `pin-float`, `unpin-float`, and `resize`. Similarly, `kill-client` applies to the matched window's client in a window rule, or to the matched client in a client rule. diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index 61dfbf2f..ab344838 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -826,6 +826,16 @@ impl ConfigClient { self.send(&ClientMessage::WindowClose { window }); } + pub fn resize_window(&self, window: Window, dx1: i32, dy1: i32, dx2: i32, dy2: i32) { + self.send(&ClientMessage::WindowResize { + window, + dx1, + dy1, + dx2, + dy2, + }); + } + pub fn focus_seat_parent(&self, seat: Seat) { self.send(&ClientMessage::FocusSeatParent { seat }); } diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index 618332f6..8db94669 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -864,6 +864,13 @@ pub enum ClientMessage<'a> { CleanLogsOlderThan { time: SystemTime, }, + WindowResize { + window: Window, + dx1: i32, + dy1: i32, + dx2: i32, + dy2: i32, + }, } #[derive(Serialize, Deserialize, Debug)] diff --git a/jay-config/src/input.rs b/jay-config/src/input.rs index 87e2ec7e..dd6ecfeb 100644 --- a/jay-config/src/input.rs +++ b/jay-config/src/input.rs @@ -650,6 +650,11 @@ impl Seat { pub fn warp_mouse_to_focus(self) { get!().seat_warp_mouse_to_focus(self) } + + /// Resizes the focused window. + pub fn resize(self, dx1: i32, dy1: i32, dx2: i32, dy2: i32) { + self.window().resize(dx1, dy1, dx2, dy2); + } } /// A focus-follows-mouse mode. diff --git a/jay-config/src/window.rs b/jay-config/src/window.rs index 610e8580..662cda44 100644 --- a/jay-config/src/window.rs +++ b/jay-config/src/window.rs @@ -236,6 +236,11 @@ impl Window { pub fn toggle_float_pinned(self) { self.set_float_pinned(!self.float_pinned()); } + + /// Resizes the window. + pub fn resize(self, dx1: i32, dy1: i32, dx2: i32, dy2: i32) { + get!().resize_window(self, dx1, dy1, dx2, dy2); + } } /// A window matcher. diff --git a/src/config/handler.rs b/src/config/handler.rs index 668176da..1c694f1e 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -2623,6 +2623,18 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_window_resize( + &self, + window: Window, + dx1: i32, + dy1: i32, + dx2: i32, + dy2: i32, + ) -> Result<(), CphError> { + self.get_window(window)?.tl_resize(dx1, dy1, dx2, dy2); + Ok(()) + } + fn handle_window_exists(&self, window: Window) { self.respond(Response::WindowExists { exists: self.get_window(window).is_ok(), @@ -3381,6 +3393,15 @@ impl ConfigProxyHandler { ClientMessage::CreateVirtualOutput { name } => self.handle_create_virtual_output(name), ClientMessage::RemoveVirtualOutput { name } => self.handle_remove_virtual_output(name), ClientMessage::CleanLogsOlderThan { time } => self.handle_clean_logs_older_than(time), + ClientMessage::WindowResize { + window, + dx1, + dy1, + dx2, + dy2, + } => self + .handle_window_resize(window, dx1, dy1, dx2, dy2) + .wrn("window_resize")?, } Ok(()) } diff --git a/src/tree/toplevel.rs b/src/tree/toplevel.rs index e2de765b..4f7d77e1 100644 --- a/src/tree/toplevel.rs +++ b/src/tree/toplevel.rs @@ -73,6 +73,7 @@ pub trait ToplevelNode: ToplevelNodeBase { fn tl_set_float(&self, float: Option<&Rc>); fn tl_mark_ancestor_fullscreen(&self, fullscreen: bool); fn tl_mark_fullscreen(&self, fullscreen: bool); + fn tl_resize(&self, dx1: i32, dy1: i32, dx2: i32, dy2: i32); } impl ToplevelNode for T { @@ -250,6 +251,18 @@ impl ToplevelNode for T { self.tl_mark_ancestor_fullscreen(fullscreen); self.tl_mark_fullscreen_ext(); } + + fn tl_resize(&self, dx1: i32, dy1: i32, dx2: i32, dy2: i32) { + let Some(parent) = self.tl_data().parent.get() else { + return; + }; + let pos = self.node_absolute_position(); + let x1 = (dx1 != 0).then_some(pos.x1().saturating_add(dx1)); + let y1 = (dy1 != 0).then_some(pos.y1().saturating_add(dy1)); + let x2 = (dx2 != 0).then_some(pos.x2().saturating_add(dx2)); + let y2 = (dy2 != 0).then_some(pos.y2().saturating_add(dy2)); + parent.cnode_resize_child(self, x1, y1, x2, y2); + } } pub trait ToplevelNodeBase: Node { diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs index 6dabbed5..b30d225e 100644 --- a/toml-config/src/config.rs +++ b/toml-config/src/config.rs @@ -190,6 +190,12 @@ pub enum Action { RemoveVirtualOutput { name: String, }, + Resize { + dx1: i32, + dy1: i32, + dx2: i32, + dy2: i32, + }, } #[derive(Debug, Clone, Default)] diff --git a/toml-config/src/config/parsers/action.rs b/toml-config/src/config/parsers/action.rs index e34a48c4..b7710317 100644 --- a/toml-config/src/config/parsers/action.rs +++ b/toml-config/src/config/parsers/action.rs @@ -3,7 +3,7 @@ use { config::{ Action, SimpleCommand, context::Context, - extractor::{Extractor, ExtractorError, arr, bol, n32, opt, str, val}, + extractor::{Extractor, ExtractorError, arr, bol, n32, opt, s32, str, val}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, parsers::{ StringParser, StringParserError, @@ -495,6 +495,21 @@ impl ActionParser<'_> { name: name.value.to_string(), }) } + + fn parse_resize(&mut self, ext: &mut Extractor<'_>) -> ParseResult { + let (dx1, dy1, dx2, dy2) = ext.extract(( + opt(s32("dx1")), + opt(s32("dy1")), + opt(s32("dx2")), + opt(s32("dy2")), + ))?; + Ok(Action::Resize { + dx1: dx1.despan().unwrap_or(0), + dy1: dy1.despan().unwrap_or(0), + dx2: dx2.despan().unwrap_or(0), + dy2: dy2.despan().unwrap_or(0), + }) + } } impl Parser for ActionParser<'_> { @@ -556,6 +571,7 @@ impl Parser for ActionParser<'_> { "latch-mode" => self.parse_latch_mode(&mut ext), "create-virtual-output" => self.parse_create_virtual_output(&mut ext), "remove-virtual-output" => self.parse_remove_virtual_output(&mut ext), + "resize" => self.parse_resize(&mut ext), v => { ext.ignore_unused(); return Err(ActionParserError::UnknownType(v.to_string()).spanned(ty.span)); diff --git a/toml-config/src/lib.rs b/toml-config/src/lib.rs index 11a3e2f8..17d120a1 100644 --- a/toml-config/src/lib.rs +++ b/toml-config/src/lib.rs @@ -482,6 +482,9 @@ impl Action { } Action::CreateVirtualOutput { name } => b.new(move || create_virtual_output(&name)), Action::RemoveVirtualOutput { name } => b.new(move || remove_virtual_output(&name)), + Action::Resize { dx1, dy1, dx2, dy2 } => { + window_or_seat!(s, s.resize(dx1, dy1, dx2, dy2)) + } } } } diff --git a/toml-spec/spec/spec.generated.json b/toml-spec/spec/spec.generated.json index 61226d56..cc5dbf93 100644 --- a/toml-spec/spec/spec.generated.json +++ b/toml-spec/spec/spec.generated.json @@ -608,6 +608,34 @@ "type", "name" ] + }, + { + "description": "Resizes the focused window.\n\n- Example (Growing the window by 2 pixels on the left):\n\n ```toml\n [shortcuts]\n alt-x = { type = \"resize\", dx1 = -2 }\n ```\n\n- Example (Moving the window 2 pixels to the left):\n\n ```toml\n [shortcuts]\n alt-x = { type = \"resize\", dx1 = -2, dx2 = -2 }\n ```\n", + "type": "object", + "properties": { + "type": { + "const": "resize" + }, + "dx1": { + "type": "integer", + "description": "The change at the left edge of the window." + }, + "dy1": { + "type": "integer", + "description": "The change at the top edge of the window." + }, + "dx2": { + "type": "integer", + "description": "The change at the right edge of the window." + }, + "dy2": { + "type": "integer", + "description": "The change at the bottom edge of the window." + } + }, + "required": [ + "type" + ] } ] } diff --git a/toml-spec/spec/spec.generated.md b/toml-spec/spec/spec.generated.md index 160fe941..e9038628 100644 --- a/toml-spec/spec/spec.generated.md +++ b/toml-spec/spec/spec.generated.md @@ -889,6 +889,58 @@ This table is a tagged union. The variant is determined by the `type` field. It The value of this field should be a string. +- `resize`: + + Resizes the focused window. + + - Example (Growing the window by 2 pixels on the left): + + ```toml + [shortcuts] + alt-x = { type = "resize", dx1 = -2 } + ``` + + - Example (Moving the window 2 pixels to the left): + + ```toml + [shortcuts] + alt-x = { type = "resize", dx1 = -2, dx2 = -2 } + ``` + + The table has the following fields: + + - `dx1` (optional): + + The change at the left edge of the window. + + The value of this field should be a number. + + The numbers should be integers. + + - `dy1` (optional): + + The change at the top edge of the window. + + The value of this field should be a number. + + The numbers should be integers. + + - `dx2` (optional): + + The change at the right edge of the window. + + The value of this field should be a number. + + The numbers should be integers. + + - `dy2` (optional): + + The change at the bottom edge of the window. + + The value of this field should be a number. + + The numbers should be integers. + ### `BarPosition` diff --git a/toml-spec/spec/spec.yaml b/toml-spec/spec/spec.yaml index b31b9a4d..f4b6739a 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -851,6 +851,44 @@ Action: description: The name of the output. required: true kind: string + resize: + description: | + Resizes the focused window. + + - Example (Growing the window by 2 pixels on the left): + + ```toml + [shortcuts] + alt-x = { type = "resize", dx1 = -2 } + ``` + + - Example (Moving the window 2 pixels to the left): + + ```toml + [shortcuts] + alt-x = { type = "resize", dx1 = -2, dx2 = -2 } + ``` + fields: + dx1: + description: The change at the left edge of the window. + required: false + kind: number + integer_only: true + dy1: + description: The change at the top edge of the window. + required: false + kind: number + integer_only: true + dx2: + description: The change at the right edge of the window. + required: false + kind: number + integer_only: true + dy2: + description: The change at the bottom edge of the window. + required: false + kind: number + integer_only: true Exec: