diff --git a/.gitmodules b/.gitmodules index 90176cbd..371247f3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "toml-config/toml-test"] - path = toml-config/toml-test +[submodule "crates/toml-config/toml-test"] + path = crates/toml-config/toml-test url = https://github.com/mahkoh/toml-tests.git diff --git a/Cargo.lock b/Cargo.lock index defd9291..9f84c039 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -625,11 +625,21 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jay-algorithms" -version = "0.4.0" +version = "1.12.0" dependencies = [ "smallvec", ] +[[package]] +name = "jay-allocator" +version = "1.12.0" +dependencies = [ + "jay-formats", + "jay-video-types", + "thiserror", + "uapi", +] + [[package]] name = "jay-ash" version = "0.3.0+1.4.344" @@ -639,6 +649,52 @@ dependencies = [ "libloading", ] +[[package]] +name = "jay-async-engine" +version = "1.12.0" +dependencies = [ + "jay-time", + "jay-tracy", + "jay-utils", +] + +[[package]] +name = "jay-bufio" +version = "1.12.0" +dependencies = [ + "jay-io-uring", + "jay-utils", + "thiserror", + "uapi", +] + +[[package]] +name = "jay-bugs" +version = "1.12.0" +dependencies = [ + "ahash", +] + +[[package]] +name = "jay-clientmem" +version = "1.12.0" +dependencies = [ + "jay-cpu-worker", + "jay-gfx-types", + "jay-tracy", + "jay-utils", + "log", + "thiserror", + "uapi", +] + +[[package]] +name = "jay-cmm" +version = "1.12.0" +dependencies = [ + "jay-utils", +] + [[package]] name = "jay-compositor" version = "1.12.0" @@ -664,9 +720,48 @@ dependencies = [ "indexmap", "isnt 0.2.0", "jay-algorithms", + "jay-allocator", "jay-ash", + "jay-async-engine", + "jay-bufio", + "jay-bugs", + "jay-clientmem", + "jay-cmm", "jay-config", + "jay-cpu-worker", + "jay-criteria", + "jay-damage", + "jay-dbus-core", + "jay-drm-feedback", + "jay-edid", + "jay-eventfd-cache", + "jay-formats", + "jay-geometry", + "jay-gfx-types", + "jay-input-types", + "jay-io-uring", + "jay-keyboard", + "jay-layout-animation", + "jay-libinput", + "jay-logger", + "jay-output-schedule", + "jay-output-types", + "jay-pango", + "jay-pr-caps", + "jay-sighand", + "jay-theme", + "jay-time", "jay-toml-config", + "jay-tracy", + "jay-tree-types", + "jay-udmabuf", + "jay-units", + "jay-utils", + "jay-video-types", + "jay-wheel", + "jay-wire-buf", + "jay-wire-types", + "jay-xcon", "kbvm", "libloading", "linearize", @@ -682,13 +777,11 @@ dependencies = [ "regex", "repc", "run-on-drop", - "rustc-demangle", "serde", "serde_json", "smallvec", "thiserror", "tiny-skia", - "tracy-client-sys", "uapi", "walkdir", "with_builtin_macros", @@ -696,10 +789,9 @@ dependencies = [ [[package]] name = "jay-config" -version = "1.10.0" +version = "1.12.0" dependencies = [ "backtrace", - "bincode", "bstr", "error_reporter", "futures-util", @@ -711,24 +803,409 @@ dependencies = [ "uapi", ] +[[package]] +name = "jay-config-schema" +version = "1.12.0" +dependencies = [ + "ahash", + "jay-config", +] + +[[package]] +name = "jay-cpu-worker" +version = "1.12.0" +dependencies = [ + "jay-async-engine", + "jay-geometry", + "jay-io-uring", + "jay-tracy", + "jay-utils", + "jay-wheel", + "log", + "parking_lot", + "thiserror", + "uapi", +] + +[[package]] +name = "jay-criteria" +version = "1.12.0" +dependencies = [ + "ahash", + "jay-utils", + "linearize", + "regex", +] + +[[package]] +name = "jay-damage" +version = "1.12.0" +dependencies = [ + "jay-geometry", + "jay-tree-types", + "jay-units", +] + +[[package]] +name = "jay-dbus-core" +version = "1.12.0" +dependencies = [ + "bstr", + "jay-bufio", + "jay-io-uring", + "jay-utils", + "thiserror", + "uapi", +] + +[[package]] +name = "jay-drm-feedback" +version = "1.12.0" +dependencies = [ + "ahash", + "byteorder", + "jay-video-types", + "thiserror", + "uapi", +] + +[[package]] +name = "jay-edid" +version = "1.12.0" +dependencies = [ + "bstr", + "thiserror", +] + +[[package]] +name = "jay-eventfd-cache" +version = "1.12.0" +dependencies = [ + "jay-async-engine", + "jay-io-uring", + "jay-utils", + "log", + "thiserror", + "uapi", +] + +[[package]] +name = "jay-formats" +version = "1.12.0" +dependencies = [ + "ahash", + "clap", + "jay-ash", + "jay-config", +] + +[[package]] +name = "jay-geometry" +version = "1.12.0" +dependencies = [ + "jay-algorithms", + "smallvec", +] + +[[package]] +name = "jay-gfx-types" +version = "1.12.0" +dependencies = [ + "uapi", +] + +[[package]] +name = "jay-input-types" +version = "1.12.0" +dependencies = [ + "jay-output-types", + "jay-units", + "jay-utils", + "linearize", +] + +[[package]] +name = "jay-io-uring" +version = "1.12.0" +dependencies = [ + "jay-async-engine", + "jay-time", + "jay-utils", + "log", + "run-on-drop", + "thiserror", + "uapi", +] + +[[package]] +name = "jay-keyboard" +version = "1.12.0" +dependencies = [ + "blake3", + "jay-input-types", + "jay-utils", + "kbvm", + "thiserror", + "uapi", +] + +[[package]] +name = "jay-layout-animation" +version = "1.12.0" +dependencies = [ + "jay-geometry", +] + +[[package]] +name = "jay-libinput" +version = "1.12.0" +dependencies = [ + "anyhow", + "bstr", + "cc", + "isnt 0.2.0", + "jay-utils", + "libloading", + "log", + "repc", + "thiserror", + "uapi", +] + +[[package]] +name = "jay-logger" +version = "1.12.0" +dependencies = [ + "backtrace", + "bstr", + "clap", + "dirs", + "humantime", + "jay-config", + "jay-utils", + "linearize", + "log", + "parking_lot", + "thiserror", + "uapi", +] + +[[package]] +name = "jay-output-schedule" +version = "1.12.0" +dependencies = [ + "futures-util", + "jay-async-engine", + "jay-io-uring", + "jay-utils", + "log", + "num-traits", +] + +[[package]] +name = "jay-output-types" +version = "1.12.0" +dependencies = [ + "blake3", + "jay-cmm", + "jay-formats", + "jay-utils", + "linearize", + "uapi", +] + +[[package]] +name = "jay-pango" +version = "1.12.0" +dependencies = [ + "anyhow", + "jay-geometry", + "repc", + "thiserror", + "uapi", +] + +[[package]] +name = "jay-pr-caps" +version = "1.12.0" +dependencies = [ + "jay-utils", + "opera", + "parking_lot", + "uapi", +] + +[[package]] +name = "jay-sighand" +version = "1.12.0" +dependencies = [ + "jay-async-engine", + "jay-io-uring", + "jay-utils", + "log", + "thiserror", + "uapi", +] + +[[package]] +name = "jay-theme" +version = "1.12.0" +dependencies = [ + "jay-cmm", + "jay-config", + "jay-gfx-types", + "jay-utils", + "linearize", + "num-traits", +] + +[[package]] +name = "jay-time" +version = "1.12.0" +dependencies = [ + "uapi", +] + +[[package]] +name = "jay-toml" +version = "1.12.0" +dependencies = [ + "bstr", + "indexmap", + "serde_json", + "thiserror", + "walkdir", +] + [[package]] name = "jay-toml-config" -version = "0.12.0" +version = "1.12.0" dependencies = [ "ahash", "bstr", "error_reporter", "indexmap", "jay-config", + "jay-config-schema", + "jay-toml", "kbvm", "log", "phf", "run-on-drop", - "serde_json", "simplelog", "thiserror", "uapi", - "walkdir", +] + +[[package]] +name = "jay-tracy" +version = "1.12.0" +dependencies = [ + "ahash", + "parking_lot", + "rustc-demangle", + "tracy-client-sys", +] + +[[package]] +name = "jay-tree-types" +version = "1.12.0" +dependencies = [ + "jay-config", + "jay-utils", + "linearize", +] + +[[package]] +name = "jay-udmabuf" +version = "1.12.0" +dependencies = [ + "jay-allocator", + "jay-formats", + "jay-utils", + "jay-video-types", + "log", + "thiserror", + "uapi", +] + +[[package]] +name = "jay-units" +version = "1.12.0" + +[[package]] +name = "jay-utils" +version = "1.12.0" +dependencies = [ + "ahash", + "arrayvec", + "bstr", + "cfg-if", + "isnt 0.2.0", + "jay-config", + "linearize", + "log", + "parking_lot", + "rand 0.10.0", + "serde", + "smallvec", + "thiserror", + "uapi", +] + +[[package]] +name = "jay-video-types" +version = "1.12.0" +dependencies = [ + "arrayvec", + "jay-formats", + "jay-utils", + "uapi", +] + +[[package]] +name = "jay-wheel" +version = "1.12.0" +dependencies = [ + "jay-async-engine", + "jay-io-uring", + "jay-time", + "jay-utils", + "log", + "thiserror", + "uapi", +] + +[[package]] +name = "jay-wire-buf" +version = "1.12.0" +dependencies = [ + "bstr", + "jay-io-uring", + "jay-time", + "jay-units", + "jay-utils", + "jay-wire-types", + "smallvec", + "thiserror", + "uapi", +] + +[[package]] +name = "jay-wire-types" +version = "1.12.0" + +[[package]] +name = "jay-xcon" +version = "1.12.0" +dependencies = [ + "bstr", + "jay-bufio", + "jay-io-uring", + "jay-utils", + "log", + "thiserror", + "uapi", ] [[package]] @@ -1522,7 +1999,7 @@ dependencies = [ [[package]] name = "toml-spec" -version = "0.1.0" +version = "1.12.0" dependencies = [ "anyhow", "error_reporter", @@ -1934,7 +2411,7 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "wire-to-xml" -version = "0.1.0" +version = "1.12.0" dependencies = [ "anyhow", "clap", @@ -2051,7 +2528,7 @@ dependencies = [ [[package]] name = "xml-to-wire" -version = "0.1.0" +version = "1.12.0" dependencies = [ "quick-xml", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index 9a1b38a3..72eb71da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "jay-compositor" -version = "1.12.0" -edition = "2024" +version.workspace = true +edition.workspace = true build = "build/build.rs" -license = "GPL-3.0-only" +license.workspace = true description = "The Jay compositor" repository = "https://github.com/mahkoh/jay" default-run = "jay" @@ -13,7 +13,61 @@ name = "jay" path = "src/main.rs" [workspace] -members = ["jay-config", "toml-config", "algorithms", "toml-spec", "wire-to-xml", "xml-to-wire"] +resolver = "3" +members = [ + "crates/jay-config", + "crates/jay-config-schema", + "crates/geometry", + "crates/layout-animation", + "crates/formats", + "crates/edid", + "crates/units", + "crates/utils", + "crates/criteria", + "crates/cmm", + "crates/time", + "crates/tracy", + "crates/async-engine", + "crates/io-uring", + "crates/bufio", + "crates/dbus-core", + "crates/xcon", + "crates/wire-types", + "crates/wire-buf", + "crates/tree-types", + "crates/eventfd-cache", + "crates/wheel", + "crates/cpu-worker", + "crates/sighand", + "crates/pr-caps", + "crates/bugs", + "crates/logger", + "crates/video-types", + "crates/output-types", + "crates/input-types", + "crates/keyboard", + "crates/gfx-types", + "crates/theme", + "crates/clientmem", + "crates/allocator", + "crates/output-schedule", + "crates/drm-feedback", + "crates/udmabuf", + "crates/damage", + "crates/pango", + "crates/libinput", + "crates/toml-config", + "crates/toml-parser", + "crates/algorithms", + "crates/toml-spec", + "crates/wire-to-xml", + "crates/xml-to-wire", +] + +[workspace.package] +version = "1.12.0" +edition = "2024" +license = "GPL-3.0-only" [profile.release] panic = "abort" @@ -23,9 +77,48 @@ debug = "full" panic = "abort" [dependencies] -jay-config = { version = "1.10.0", path = "jay-config" } -jay-toml-config = { version = "0.12.0", path = "toml-config" } -jay-algorithms = { version = "0.4.0", path = "algorithms" } +jay-config = { path = "crates/jay-config" } +jay-toml-config = { path = "crates/toml-config" } +jay-algorithms = { path = "crates/algorithms" } +jay-geometry = { path = "crates/geometry" } +jay-layout-animation = { path = "crates/layout-animation" } +jay-formats = { path = "crates/formats" } +jay-edid = { path = "crates/edid" } +jay-units = { path = "crates/units" } +jay-utils = { path = "crates/utils" } +jay-criteria = { path = "crates/criteria" } +jay-cmm = { path = "crates/cmm" } +jay-time = { path = "crates/time" } +jay-tracy = { path = "crates/tracy" } +jay-async-engine = { path = "crates/async-engine" } +jay-io-uring = { path = "crates/io-uring" } +jay-bufio = { path = "crates/bufio" } +jay-dbus-core = { path = "crates/dbus-core" } +jay-xcon = { path = "crates/xcon" } +jay-wire-types = { path = "crates/wire-types" } +jay-wire-buf = { path = "crates/wire-buf" } +jay-tree-types = { path = "crates/tree-types" } +jay-eventfd-cache = { path = "crates/eventfd-cache" } +jay-wheel = { path = "crates/wheel" } +jay-cpu-worker = { path = "crates/cpu-worker" } +jay-sighand = { path = "crates/sighand" } +jay-pr-caps = { path = "crates/pr-caps" } +jay-bugs = { path = "crates/bugs" } +jay-logger = { path = "crates/logger" } +jay-video-types = { path = "crates/video-types" } +jay-output-types = { path = "crates/output-types" } +jay-input-types = { path = "crates/input-types" } +jay-keyboard = { path = "crates/keyboard" } +jay-gfx-types = { path = "crates/gfx-types" } +jay-theme = { path = "crates/theme" } +jay-clientmem = { path = "crates/clientmem" } +jay-allocator = { path = "crates/allocator" } +jay-output-schedule = { path = "crates/output-schedule" } +jay-drm-feedback = { path = "crates/drm-feedback" } +jay-udmabuf = { path = "crates/udmabuf" } +jay-damage = { path = "crates/damage" } +jay-pango = { path = "crates/pango" } +jay-libinput = { path = "crates/libinput" } uapi = "0.2.13" thiserror = "2.0.11" @@ -58,8 +151,6 @@ serde = { version = "1.0.196", features = ["derive"] } serde_json = "1.0.128" linearize = { version = "0.1.3", features = ["derive"] } png = "0.18.0" -rustc-demangle = { version = "0.1.24", optional = true } -tracy-client-sys = { version = "0.24.1", features = ["ondemand", "manual-lifetime", "debuginfod", "demangle"], optional = true } kbvm = { version = "0.1.6", features = ["compose"] } tiny-skia = { version = "0.12.0", default-features = false, features = ["std"] } regex = "1.11.1" @@ -90,5 +181,5 @@ opt-level = 3 [features] rc_tracking = [] -it = [] -tracy = ["dep:tracy-client-sys", "dep:rustc-demangle"] +it = ["jay-async-engine/it", "jay-cpu-worker/it"] +tracy = ["jay-tracy/tracy", "jay-async-engine/tracy", "jay-cpu-worker/tracy", "jay-clientmem/tracy"] diff --git a/README.md b/README.md index 14ae7ba4..56a5f577 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![crates.io](https://img.shields.io/crates/v/jay-compositor.svg)](http://crates.io/crates/jay-compositor) Jay is a Wayland compositor for Linux with an i3-like tiling layout, -Vulkan and OpenGL rendering, multi-GPU support, screen sharing, and more. +Vulkan and OpenGL rendering, multi-GPU support, and more. ![screenshot.png](static/screenshot.png) diff --git a/book/AGENTS.md b/book/AGENTS.md index 85e88261..8b5a0051 100644 --- a/book/AGENTS.md +++ b/book/AGENTS.md @@ -27,14 +27,14 @@ The table of contents is `SUMMARY.md`. Key chapter-to-topic mapping: | 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 | +| `crates/toml-spec/spec/spec.yaml` | **Canonical** TOML config spec: every key, action, match criterion, type | +| `crates/toml-config/src/default-config.toml` | Built-in default config (keybindings, startup actions) | +| `crates/toml-config/src/config/parsers/action.rs` | Action parser — see which `type` strings are accepted | +| `crates/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) | +| `crates/toml-config/src/config/parsers/exec.rs` | Exec parser (string, array, or table forms) | ### Known spec.yaml bugs @@ -81,7 +81,7 @@ The table of contents is `SUMMARY.md`. Key chapter-to-topic mapping: - **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) + [spec.generated.md](https://github.com/mahkoh/jay/blob/master/crates/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. @@ -91,10 +91,10 @@ The table of contents is `SUMMARY.md`. Key chapter-to-topic mapping: ### Documenting a new action 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 + - `crates/toml-spec/spec/spec.yaml` — spec entry (description, fields, examples) + - `crates/toml-config/src/config/parsers/action.rs` — parser (field names, types, defaults) + - `crates/toml-config/src/lib.rs` — dispatch (check if `window_or_seat!` is used) + - `crates/jay-config/src/input.rs` and/or `crates/jay-config/src/window.rs` — Rust API 2. Edit `book/src/configuration/shortcuts.md`: - **Simple actions** (no fields): add to the appropriate list in the @@ -110,7 +110,7 @@ The table of contents is `SUMMARY.md`. Key chapter-to-topic mapping: ### Documenting a new config field -1. Read `toml-spec/spec/spec.yaml` for the field definition. +1. Read `crates/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. diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 2bb5904a..29e51133 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -35,7 +35,6 @@ - [Mouse Interactions](mouse.md) - [Input Modes](input-modes.md) - [Window & Client Rules](window-rules.md) -- [Screen Sharing](screen-sharing.md) - [HDR & Color Management](hdr.md) # Reference diff --git a/book/src/cli.md b/book/src/cli.md index c7c222a6..e2b8697f 100644 --- a/book/src/cli.md +++ b/book/src/cli.md @@ -674,18 +674,6 @@ Show color management status: ## Other Commands -### `jay portal` - -Run the Jay desktop portal (provides screen sharing and other XDG desktop -portal interfaces): - -```shell -~$ jay portal -``` - -Normally the portal is started automatically. This command is for running it -manually or debugging. - ### `jay seat-test` Test input events from a seat. Prints all keyboard, pointer, touch, gesture, @@ -697,24 +685,6 @@ tablet, and switch events to stdout: ~$ jay seat-test -a # test all seats simultaneously ``` -### `jay run-privileged` - -Run a program with access to a privileged Wayland socket: - -```shell -~$ jay run-privileged my-program --arg1 -``` - -### `jay run-tagged` - -Run a program with a tagged Wayland connection. All Wayland connections from the -spawned process tree will carry the specified tag, which can be matched in -[client rules](window-rules.md): - -```shell -~$ jay run-tagged my-tag firefox -``` - ### `jay generate-completion` Generate shell completion scripts: diff --git a/book/src/configuration/idle.md b/book/src/configuration/idle.md index 033d1120..d67bfdc9 100644 --- a/book/src/configuration/idle.md +++ b/book/src/configuration/idle.md @@ -62,16 +62,10 @@ on-idle = { type = "exec", exec = { prog = "swaylock", - privileged = true, }, } ``` -> [!IMPORTANT] -> Screen lockers that use the Wayland session lock protocol (like swaylock) -> need `privileged = true` in the exec configuration. This grants the process -> the necessary permissions to lock the session. - You can also combine multiple actions: ```toml @@ -80,7 +74,6 @@ on-idle = [ type = "exec", exec = { prog = "swaylock", - privileged = true, }, }, { type = "exec", exec = ["notify-send", "System locked"] }, @@ -100,7 +93,6 @@ on-idle = { type = "exec", exec = { prog = "swaylock", - privileged = true, }, } ``` diff --git a/book/src/configuration/misc.md b/book/src/configuration/misc.md index 9974d3f7..33f0c8ba 100644 --- a/book/src/configuration/misc.md +++ b/book/src/configuration/misc.md @@ -30,9 +30,8 @@ the color management protocol. ## Libei [libei](https://gitlab.freedesktop.org/libinput/libei) allows applications to -emulate input events. By default, applications can only access libei through -the portal (which prompts the user for permission). Setting `enable-socket` -exposes an unauthenticated socket that any application can use without a prompt. +emulate input events. Setting `enable-socket` exposes an unauthenticated socket +that any application can use. ```toml libei.enable-socket = false # default diff --git a/book/src/configuration/shortcuts.md b/book/src/configuration/shortcuts.md index 6d372be6..b36f4857 100644 --- a/book/src/configuration/shortcuts.md +++ b/book/src/configuration/shortcuts.md @@ -145,7 +145,6 @@ alt-shift-r = "reload-config-toml" (the next pressed key identifies the mark). See [Marks](#marks) below. - `enable-window-management`, `disable-window-management` -- programmatically enable or disable [window management mode](../floating.md#window-management-mode) -- `reload-config-so` -- reload the shared-library configuration (`config.so`) See the [specification](https://github.com/mahkoh/jay/blob/master/toml-spec/spec/spec.generated.md) for the full list of simple actions. @@ -309,7 +308,6 @@ alt-s = { type = "exec", exec = { shell = "grim - | wl-copy", - privileged = true, }, } ``` @@ -328,12 +326,6 @@ Table fields: `env` : Per-process environment variables -`privileged` -: If `true`, grants access to privileged Wayland protocols (default: `false`) - -`tag` -: Tag to apply to all Wayland connections spawned by this process - ### Practical examples Volume control with `pactl`: @@ -362,7 +354,6 @@ Print = { type = "exec", exec = { shell = "grim - | wl-copy", - privileged = true, }, } ``` diff --git a/book/src/configuration/startup.md b/book/src/configuration/startup.md index 9b4c333a..93408650 100644 --- a/book/src/configuration/startup.md +++ b/book/src/configuration/startup.md @@ -63,15 +63,10 @@ on-idle = { type = "exec", exec = { prog = "swaylock", - privileged = true, }, } ``` -> [!NOTE] -> Screen lockers need `privileged = true` to access the privileged Wayland -> protocols required for locking the session. - You can combine idle with a grace period. The idle timeout and grace period are configured separately in the `[idle]` section (see [Idle & Screen Locking](idle.md)): @@ -83,7 +78,6 @@ on-idle = { type = "exec", exec = { prog = "swaylock", - privileged = true, }, } ``` @@ -97,7 +91,6 @@ on-idle = [ type = "exec", exec = { prog = "swaylock", - privileged = true, }, }, ] diff --git a/book/src/configuration/theme.md b/book/src/configuration/theme.md index 69448f5d..efd62d48 100644 --- a/book/src/configuration/theme.md +++ b/book/src/configuration/theme.md @@ -73,7 +73,7 @@ The available color keys in the `[theme]` table are: "Focused-inactive" refers to a window that was most recently focused in its container but whose container is not the active one. The "captured" colors apply -when a window is being recorded (e.g. via screen sharing). +when a window is being captured. ### Example diff --git a/book/src/configuration/xwayland.md b/book/src/configuration/xwayland.md index 8f5817af..2814a3b7 100644 --- a/book/src/configuration/xwayland.md +++ b/book/src/configuration/xwayland.md @@ -87,5 +87,5 @@ Xwayland client itself: ```toml [[clients]] match.is-xwayland = true -# ... grant capabilities, etc. +# ... configure client-specific behavior ``` diff --git a/book/src/features.md b/book/src/features.md index 1e7c69a3..efaefd74 100644 --- a/book/src/features.md +++ b/book/src/features.md @@ -61,10 +61,7 @@ Commands: unlock Unlocks the compositor screenshot Take a screenshot idle Inspect/modify the idle (screensaver) settings - run-privileged Run a privileged program - run-tagged Run a program with a connection tag seat-test Tests the events produced by a seat - portal Run the desktop portal randr Inspect/modify graphics card and connector settings input Inspect/modify input settings xwayland Inspect/modify xwayland settings @@ -101,17 +98,6 @@ runtime. See [GPUs](configuration/gpu.md) for details. -## Screen Sharing - -Jay supports screen sharing via xdg-desktop-portal. Three capture modes are -available: - -- **Window capture** -- share a single window. -- **Output capture** -- share an entire monitor. -- **Workspace capture** -- like output capture, but only one workspace is shown. - -See [Screen Sharing](screen-sharing.md) for setup instructions. - ## Screen Locking Jay can automatically lock your screen and disable outputs after inactivity. @@ -154,20 +140,6 @@ Jay supports running X11 applications seamlessly through Xwayland. See Jay supports clipboard managers via the `zwlr_data_control_manager_v1` and `ext_data_control_manager_v1` protocols. -## Privilege Separation - -Jay splits protocols into unprivileged and privileged protocols. By default, -applications only have access to unprivileged protocols. This means that tools -like screen lockers, status bars, and clipboard managers need to be explicitly -granted access. - -Jay provides several ways to grant privileges, from giving a program full -access to all privileged protocols down to granting individual capabilities to -specific tagged processes. See -[Granting Privileges](window-rules.md#granting-privileges) for a detailed -guide and the [Protocol Support](#protocol-support) section below for the full -list of protocols and their privilege requirements. - ## Push to Talk Jay's shortcut system allows you to execute an action when a key is pressed and @@ -252,7 +224,6 @@ granted access. See | wp_linux_drm_syncobj_manager_v1 | 1 | | | wp_pointer_warp_v1 | 1 | | | wp_presentation | 2 | | -| wp_security_context_manager_v1 | 1 | | | wp_single_pixel_buffer_manager_v1 | 1 | | | wp_tearing_control_manager_v1 | 1 | | | wp_viewporter | 1 | | diff --git a/book/src/installation.md b/book/src/installation.md index e29ec0b2..f331b284 100644 --- a/book/src/installation.md +++ b/book/src/installation.md @@ -63,7 +63,6 @@ For Vulkan, you also need the driver for your GPU: - **Linux 6.7 or later** -- required for explicit sync (needed for Nvidia GPUs). - **Xwayland** -- required for running X11 applications. -- **PipeWire** -- required for screen sharing. - **logind** (part of systemd) -- required when running Jay from a virtual terminal or display manager. ## Building @@ -129,14 +128,7 @@ retains `CAP_SYS_NICE` solely for creating elevated Vulkan queues later. > [!NOTE] > You need to re-run the `setcap` command each time you update the Jay binary. -### SCHED_RR and config.so - -`SCHED_RR` and `config.so` are mutually exclusive: running untrusted code at -real-time priority would be a security risk. Jay enforces this as follows: - -- If `config.so` exists in the config directory, Jay skips the `SCHED_RR` - elevation (elevated Vulkan queues are still created). -- If Jay has already elevated to `SCHED_RR`, it refuses to load `config.so`. +### SCHED_RR You can also skip `SCHED_RR` explicitly by setting `JAY_NO_REALTIME=1`: @@ -144,11 +136,7 @@ You can also skip `SCHED_RR` explicitly by setting `JAY_NO_REALTIME=1`: ~$ JAY_NO_REALTIME=1 jay run ``` -This still allows elevated Vulkan queues and does not affect `config.so` -loading. - -The mutual exclusion can be overridden at compile time by building Jay with -`JAY_ALLOW_REALTIME_CONFIG_SO=1`. +This still allows elevated Vulkan queues. ## Recommended Applications @@ -156,7 +144,6 @@ The following applications work well with Jay: - **[Alacritty](https://alacritty.org/)** -- the default terminal emulator in the built-in configuration. - **[bemenu](https://github.com/Cloudef/bemenu)** -- the default application launcher in the built-in configuration. -- **[xdg-desktop-portal-gtk4](https://github.com/mahkoh/xdg-desktop-portal-gtk4)** -- a file-picker portal with thumbnail support. Used automatically when installed. - **[wl-tray-bridge](https://github.com/mahkoh/wl-tray-bridge)** -- shows D-Bus StatusNotifierItem applications as tray icons. - **[mako](https://github.com/emersion/mako)** -- a notification daemon. Launched automatically by the default configuration. - **[window-to-tray](https://github.com/mahkoh/wl-proxy/tree/master/apps/window-to-tray)** -- run most Wayland applications as tray applications (e.g. `window-to-tray pavucontrol-qt`). diff --git a/book/src/introduction.md b/book/src/introduction.md index 306a5a46..9333dcc6 100644 --- a/book/src/introduction.md +++ b/book/src/introduction.md @@ -22,12 +22,11 @@ Jay is a Wayland compositor for Linux with an i3-inspired tiling layout. It supports Vulkan and OpenGL rendering, multi-GPU setups, fractional scaling, -variable refresh rate (VRR), tearing presentation, HDR, and screen sharing via -xdg-desktop-portal. X11 applications are supported through Xwayland. +variable refresh rate (VRR), tearing presentation, and HDR. X11 applications +are supported through Xwayland. -Jay is configured through a declarative TOML file, with an optional advanced -mode that uses a shared library for programmatic control. A comprehensive -command-line interface makes scripting and automation straightforward. +Jay is configured through a declarative TOML file. A comprehensive command-line +interface makes scripting and automation straightforward. See the [Features](features.md) chapter for a comprehensive overview of what Jay can do, or jump straight to [Installation](installation.md) to get started. diff --git a/book/src/mouse.md b/book/src/mouse.md index bfa92cd5..1b01a054 100644 --- a/book/src/mouse.md +++ b/book/src/mouse.md @@ -84,8 +84,8 @@ This is especially useful for: ## Other -**Toplevel selection.** Some actions (like screen sharing) ask you to select a -window, indicated by a purple overlay. During this selection, right-click a +**Toplevel selection.** Some actions ask you to select a window, indicated by a +purple overlay. During this selection, right-click a tile's title to select the entire container instead of an individual tile. **Canceling interactions.** Press `Escape` to cancel any in-progress mouse diff --git a/book/src/screen-sharing.md b/book/src/screen-sharing.md deleted file mode 100644 index 096c67de..00000000 --- a/book/src/screen-sharing.md +++ /dev/null @@ -1,111 +0,0 @@ -# Screen Sharing - -Jay supports screen sharing via -[xdg-desktop-portal](https://github.com/flatpak/xdg-desktop-portal). Three -capture types are available: - -- **Window capture** -- share a single window. -- **Output capture** -- share an entire monitor. -- **Workspace capture** -- like output capture, but only a single workspace is - shown. - -## Requirements - -[PipeWire](https://pipewire.org/) must be installed and running. Verify with: - -```shell -~$ systemctl --user status pipewire -``` - -## Portal Setup - -Jay implements its own portal backend for the `ScreenCast` and `RemoteDesktop` -interfaces. Two configuration files must be installed so that -`xdg-desktop-portal` knows to use Jay's backend. - -### If the Repository is Checked Out - -```shell -~$ sudo cp etc/jay.portal /usr/share/xdg-desktop-portal/portals/jay.portal -~$ sudo cp etc/jay-portals.conf /usr/share/xdg-desktop-portal/jay-portals.conf -``` - -### If Installed via cargo install - -Create the files manually: - -```shell -~$ sudo tee /usr/share/xdg-desktop-portal/portals/jay.portal > /dev/null << 'EOF' -[portal] -DBusName=org.freedesktop.impl.portal.desktop.jay -Interfaces=org.freedesktop.impl.portal.ScreenCast;org.freedesktop.impl.portal.RemoteDesktop; -EOF -``` - -```shell -~$ sudo tee /usr/share/xdg-desktop-portal/jay-portals.conf > /dev/null << 'EOF' -[preferred] -default=gtk -org.freedesktop.impl.portal.ScreenCast=jay -org.freedesktop.impl.portal.RemoteDesktop=jay -org.freedesktop.impl.portal.Inhibit=none -org.freedesktop.impl.portal.FileChooser=gtk4 -EOF -``` - -### Restart the Portal - -After installing the files, restart the portal service: - -```shell -~$ systemctl --user restart xdg-desktop-portal -``` - -## Configuration - -### workspace-capture - -The top-level `workspace-capture` setting controls whether newly created -workspaces can be captured via workspace capture. The default is `true`: - -```toml -workspace-capture = false -``` - -Set this to `false` if you want to prevent workspace-level capture by default. - -### Capture Indicator Colors - -When a window is being recorded, its title bar color changes to make the -capture visually obvious. You can customize these colors in the `[theme]` -table: - -```toml -[theme] -captured-focused-title-bg-color = "#900000" -captured-unfocused-title-bg-color = "#5f0000" -``` - -- `captured-focused-title-bg-color` -- background color of focused title bars - that are being recorded. -- `captured-unfocused-title-bg-color` -- background color of unfocused title - bars that are being recorded. - -## The jay portal Command - -Jay's portal backend is normally started automatically when a screen-sharing -request comes in via D-Bus activation. If you need to start it manually for -debugging purposes: - -```shell -~$ jay portal -``` - -## Troubleshooting - -If screen sharing does not work: - -1. Verify PipeWire is running: `systemctl --user status pipewire` -2. Verify the portal files are installed in `/usr/share/xdg-desktop-portal/`. -3. Restart the portal: `systemctl --user restart xdg-desktop-portal` -4. Check the Jay log for errors: `jay log` diff --git a/book/src/tiling.md b/book/src/tiling.md index 650cd73a..2ff61d5e 100644 --- a/book/src/tiling.md +++ b/book/src/tiling.md @@ -77,6 +77,20 @@ You can also right-click any title in a container to toggle mono mode. In mono mode, scroll over the title bar to cycle between windows in the container. +## Autotiling + +Autotiling makes newly tiled windows alternate split direction from the focused +tiled window. The first split uses the containing group direction, then later +windows wrap the focused tile in the opposite direction, producing a horizontal, +vertical, horizontal pattern as the layout grows. + +```toml +[shortcuts] +alt-a = "toggle-autotile" +``` + +Manual grouping and split commands still use the direction you request. + ## Fullscreen Press `alt-u` (`toggle-fullscreen`) to make the focused window fill the entire diff --git a/book/src/troubleshooting.md b/book/src/troubleshooting.md index 3f973bb5..a20a33fd 100644 --- a/book/src/troubleshooting.md +++ b/book/src/troubleshooting.md @@ -54,57 +54,6 @@ bindings. > when any config file exists. Always use `jay config init` to start with a > working configuration. -## Application doesn't have access to a protocol - -Jay splits Wayland protocols into unprivileged and privileged. By default, -applications only have access to unprivileged protocols. If a program like a -screen locker, status bar, clipboard manager, or screen-capture tool is not -working, it likely needs access to one or more privileged protocols. - -Common symptoms include: - -- **swaylock** does nothing or fails to lock the screen (needs `session-lock`). -- **waybar** or **i3bar** shows no workspace information (needs - `foreign-toplevel-list`). -- **wl-copy** / **cliphist** cannot access the clipboard (needs - `data-control`). -- **grim** or **slurp** cannot capture the screen (needs `screencopy`). - -**Quick fix -- grant all privileges:** - -The simplest approach is to launch the program with full access to all -privileged protocols. In your config, set `privileged = true` in the exec -action: - -```toml -on-idle = { - type = "exec", - exec = { - prog = "swaylock", - privileged = true, - }, -} -``` - -Or from the command line: - -```shell -~$ jay run-privileged waybar -``` - -**Better fix -- grant only the capabilities needed:** - -Use a client rule to grant specific capabilities: - -```toml -[[clients]] -match.comm = "waybar" -capabilities = ["layer-shell", "foreign-toplevel-list"] -``` - -See [Granting Privileges](window-rules.md#granting-privileges) for the full -list of capabilities and more advanced approaches using connection tags. - ## Wrong keyboard layout The default keyboard layout is US QWERTY. To change it: @@ -132,45 +81,6 @@ layout = "de" This takes effect immediately but does not persist across restarts unless configured in the config file. -## Screen sharing doesn't work - -Screen sharing requires PipeWire and the Jay desktop portal. - -**1. Check that PipeWire is running:** - -```shell -~$ systemctl --user status pipewire -``` - -If it is not running, start it: - -```shell -~$ systemctl --user start pipewire -``` - -**2. Check that the portal files are installed:** - -Jay needs two files to be found by the XDG desktop portal framework: - -- A portal definition file (e.g. `/usr/share/xdg-desktop-portal/portals/jay.portal`). -- A portal configuration file (e.g. `/usr/share/xdg-desktop-portal/jay-portals.conf`). - -These files are included in the Jay repository under `etc/`. If you built Jay -from source and did not install them, copy them manually: - -```shell -~$ sudo cp etc/jay.portal /usr/share/xdg-desktop-portal/portals/ -~$ sudo cp etc/jay-portals.conf /usr/share/xdg-desktop-portal/ -``` - -**3. Restart the portal:** - -```shell -~$ systemctl --user restart xdg-desktop-portal -``` - -See the [Screen Sharing](screen-sharing.md) chapter for more details. - ## X11 applications don't work Jay uses Xwayland to run X11 applications. diff --git a/book/src/window-rules.md b/book/src/window-rules.md index 43770b46..6d3cfca1 100644 --- a/book/src/window-rules.md +++ b/book/src/window-rules.md @@ -31,12 +31,6 @@ Each client rule can have the following fields: `latch` : An action to run when a client stops matching. -`capabilities` -: Wayland protocol access granted to matching clients. - -`sandbox-bounding-capabilities` -: Upper bounds for protocols available to child sandboxes. - ### Client Match Criteria All client match criteria are constant over the lifetime of a client. If no @@ -70,142 +64,6 @@ implicitly AND-combined. `exe` / `exe-regex` : The client's `/proc/pid/exe` path. -`tag` / `tag-regex` -: The connection tag of the client. - -### Granting Privileges - -Jay splits Wayland protocols into unprivileged and privileged. By default, -applications only have access to unprivileged protocols. This means that tools -like screen lockers, status bars, screen-capture utilities, and clipboard -managers will not work unless you explicitly grant them the necessary -privileges. - -See the [Protocol Support](features.md#protocol-support) table in the Features -chapter for the full list of protocols and whether they are privileged. - -There are three ways to grant privileges, from simplest to most fine-grained. - -#### 1. Grant all privileges via `privileged = true` (exec) or `jay run-privileged` - -The simplest approach gives a program access to **all** privileged protocols. -This is appropriate for trusted tools like screen lockers where you don't want -to think about which specific protocols they need. - -In the config, set `privileged = true` in the exec table: - -```toml -on-idle = { - type = "exec", - exec = { - prog = "swaylock", - privileged = true, - }, -} -``` - -From the command line, use `jay run-privileged`: - -```shell -~$ jay run-privileged waybar -``` - -Both methods connect the program to a privileged Wayland socket that grants -access to all privileged protocols. - -#### 2. Grant capabilities via connection tags - -Connection tags let you combine the CLI with client rules for precise control. -You tag a program at launch time, then write a client rule that matches -the tag and grants specific capabilities. - -First, launch the program with a tag -- either from the command line: - -```shell -~$ jay run-tagged bar waybar -``` - -Or from the config using the `tag` field in an exec action: - -```toml -[shortcuts] -alt-w = { - type = "exec", - exec = { - prog = "waybar", - tag = "bar", - }, -} -``` - -Then write a client rule that matches the tag and grants capabilities: - -```toml -[[clients]] -match.tag = "bar" -capabilities = ["layer-shell", "foreign-toplevel-list"] -``` - -This way, only the specific instance you launched with the tag receives the -privileges -- other programs with the same binary name do not. - -Available capability values: `none`, `all`, `data-control`, -`virtual-keyboard`, `foreign-toplevel-list`, `idle-notifier`, `session-lock`, -`layer-shell`, `screencopy`, `seat-manager`, `drm-lease`, `input-method`, -`workspace-manager`, `foreign-toplevel-manager`, `head-manager`, -`gamma-control-manager`, `virtual-pointer`. - -**Default capabilities:** unsandboxed clients receive `layer-shell` and -`drm-lease`. Sandboxed clients receive only `drm-lease`. If any client rule -matches, its capabilities **replace** the defaults entirely. If multiple rules -match, their capabilities are unioned together, but the defaults are not -included unless a matching rule also grants them. - -#### 3. Grant capabilities via client match rules - -Client rules can also match programs by properties like their executable name -instead of a tag. This is convenient when you always want a given program to -have certain capabilities, regardless of how it was launched: - -```toml -[[clients]] -match.comm = "waybar" -capabilities = ["layer-shell", "foreign-toplevel-list"] - -# Vim 9.2 uses the data-control protocol for seamless wayland integration. -[[clients]] -match.comm = "vim" -match.sandboxed = false -capabilities = "data-control" - -# Older versions use wl-copy and wl-paste. -[[clients]] -match.any = [ - { comm = "wl-copy" }, - { comm = "wl-paste" }, -] -match.sandboxed = false -capabilities = "data-control" -``` - -> [!NOTE] -> Client match criteria like `comm`, `exe`, and `pid` are checked when a -> client connects. Any process with a matching name receives the specified -> capabilities. If you need to restrict privileges to programs you launch -> yourself, use connection tags (method 2) instead. - -#### Bounding capabilities (sandboxes) - -Capabilities can never exceed the client's **bounding capabilities**. Use -`sandbox-bounding-capabilities` on a client rule to set the upper bound for -protocols available to sandboxes created by that client: - -```toml -[[clients]] -match.comm = "flatpak-portal" -sandbox-bounding-capabilities = ["drm-lease", "layer-shell"] -``` - ## Window Rules Window rules operate on individual windows. They are defined with `[[windows]]` @@ -456,18 +314,6 @@ action = { } ``` -### Grant Protocol Access to a Trusted App - -```toml -[[clients]] -match.comm = "swaylock" -capabilities = ["session-lock", "layer-shell"] - -[[clients]] -match.comm = "waybar" -capabilities = ["layer-shell", "foreign-toplevel-list"] -``` - ### Suppress Focus Stealing for Chromium Screen-Share Windows ```toml diff --git a/book/src/workspaces.md b/book/src/workspaces.md index 100e13b7..eb1bcfd3 100644 --- a/book/src/workspaces.md +++ b/book/src/workspaces.md @@ -123,16 +123,15 @@ laptop. ## Workspace Capture -By default, newly created workspaces can be captured for screen sharing. You -can disable this globally: +By default, newly created workspaces can be captured by capture clients. You can +disable this globally: ```toml workspace-capture = false ``` -When workspace capture is enabled, screen-sharing applications can share -individual workspaces (in addition to full outputs and individual windows). See -[Screen Sharing](screen-sharing.md) for more details. +When workspace capture is enabled, compositor-native capture clients may capture +individual workspaces instead of whole outputs. ## Matching Windows by Workspace diff --git a/build/enums.rs b/build/enums.rs index 8be06bf1..dc784903 100644 --- a/build/enums.rs +++ b/build/enums.rs @@ -4,18 +4,27 @@ use { std::{env, io::Write}, }; -#[expect(unused_macros)] -#[macro_use] -#[path = "../src/macros.rs"] -mod macros; +#[allow(unused_macros)] +macro_rules! cenum { + ($name:ident, $uc:ident; $($name2:ident = $val:expr,)*) => { + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub struct $name(pub i32); -#[path = "../src/libinput/consts.rs"] -mod libinput; + impl $name { + pub fn raw(self) -> i32 { + self.0 + } + } -#[path = "../src/pango/consts.rs"] -mod pango; + pub const $uc: &[i32] = &[$($val,)*]; -#[path = "../src/fontconfig/consts.rs"] + $( + pub const $name2: $name = $name($val); + )* + } +} + +#[path = "fontconfig_consts.rs"] mod fontconfig; fn get_target() -> repc::Target { @@ -49,108 +58,6 @@ fn write_ty(f: &mut W, vals: &[i32], ty: &str) -> anyhow::Result<()> { } pub fn main() -> anyhow::Result<()> { - let mut f = open("libinput_tys.rs")?; - write_ty( - &mut f, - libinput::LIBINPUT_LOG_PRIORITY, - "libinput_log_priority", - )?; - write_ty( - &mut f, - libinput::LIBINPUT_DEVICE_CAPABILITY, - "libinput_device_capability", - )?; - write_ty(&mut f, libinput::LIBINPUT_KEY_STATE, "libinput_key_state")?; - write_ty(&mut f, libinput::LIBINPUT_LED, "libinput_led")?; - write_ty( - &mut f, - libinput::LIBINPUT_BUTTON_STATE, - "libinput_button_state", - )?; - write_ty( - &mut f, - libinput::LIBINPUT_POINTER_AXIS, - "libinput_pointer_axis", - )?; - write_ty( - &mut f, - libinput::LIBINPUT_POINTER_AXIS_SOURCE, - "libinput_pointer_axis_source", - )?; - write_ty( - &mut f, - libinput::LIBINPUT_TABLET_PAD_RING_AXIS_SOURCE, - "libinput_tablet_pad_ring_axis_source", - )?; - write_ty( - &mut f, - libinput::LIBINPUT_TABLET_PAD_STRIP_AXIS_SOURCE, - "libinput_tablet_pad_strip_axis_source", - )?; - write_ty( - &mut f, - libinput::LIBINPUT_TABLET_TOOL_TYPE, - "libinput_tablet_tool_type", - )?; - write_ty( - &mut f, - libinput::LIBINPUT_TABLET_TOOL_PROXIMITY_STATE, - "libinput_tablet_tool_proximity_state", - )?; - write_ty( - &mut f, - libinput::LIBINPUT_TABLET_TOOL_TIP_STATE, - "libinput_tablet_tool_tip_state", - )?; - write_ty( - &mut f, - libinput::LIBINPUT_SWITCH_STATE, - "libinput_switch_state", - )?; - write_ty(&mut f, libinput::LIBINPUT_SWITCH, "libinput_switch")?; - write_ty(&mut f, libinput::LIBINPUT_EVENT_TYPE, "libinput_event_type")?; - write_ty( - &mut f, - libinput::LIBINPUT_CONFIG_STATUS, - "libinput_config_status", - )?; - write_ty( - &mut f, - libinput::LIBINPUT_CONFIG_ACCEL_PROFILE, - "libinput_config_accel_profile", - )?; - write_ty( - &mut f, - libinput::LIBINPUT_CONFIG_TAP_STATE, - "libinput_config_tap_state", - )?; - write_ty( - &mut f, - libinput::LIBINPUT_CONFIG_DRAG_STATE, - "libinput_config_drag_state", - )?; - write_ty( - &mut f, - libinput::LIBINPUT_CONFIG_DRAG_LOCK_STATE, - "libinput_config_drag_lock_state", - )?; - write_ty( - &mut f, - libinput::LIBINPUT_CONFIG_CLICK_METHOD, - "libinput_config_click_method", - )?; - write_ty( - &mut f, - libinput::LIBINPUT_CONFIG_MIDDLE_EMULATION_STATE, - "libinput_config_middle_emulation_state", - )?; - - let mut f = open("pango_tys.rs")?; - write_ty(&mut f, pango::CAIRO_FORMATS, "cairo_format_t")?; - write_ty(&mut f, pango::CAIRO_STATUSES, "cairo_status_t")?; - write_ty(&mut f, pango::CAIRO_OPERATORS, "cairo_operator_t")?; - write_ty(&mut f, pango::PANGO_ELLIPSIZE_MODES, "PangoEllipsizeMode_")?; - let mut f = open("fontconfig_tys.rs")?; write_ty(&mut f, fontconfig::FC_MATCH_KINDS, "FcMatchKind")?; write_ty(&mut f, fontconfig::FC_RESULTS, "FcResult")?; diff --git a/src/fontconfig/consts.rs b/build/fontconfig_consts.rs similarity index 100% rename from src/fontconfig/consts.rs rename to build/fontconfig_consts.rs diff --git a/build/logging.rs b/build/logging.rs index 7cae1ec4..5c7ce1f8 100644 --- a/build/logging.rs +++ b/build/logging.rs @@ -4,17 +4,10 @@ use { }; pub fn main() -> anyhow::Result<()> { - create_bridge()?; create_version()?; Ok(()) } -fn create_bridge() -> anyhow::Result<()> { - println!("cargo:rerun-if-changed=src/bridge.c"); - cc::Build::new().file("src/bridge.c").compile("bridge"); - Ok(()) -} - fn create_version() -> anyhow::Result<()> { let mut version_string = env!("CARGO_PKG_VERSION").to_string(); if let Ok(output) = Command::new("git").arg("rev-parse").arg("HEAD").output() diff --git a/build/wire.rs b/build/wire.rs index e5ca40db..139fe8d2 100644 --- a/build/wire.rs +++ b/build/wire.rs @@ -417,6 +417,7 @@ fn write_file( let messages = parse_messages(&contents)?; writeln!(f)?; writeln!(f, "pub mod {} {{", obj_name)?; + writeln!(f, " #![allow(dead_code)]")?; writeln!(f, " use super::*;")?; for message in messages.requests.iter().chain(messages.events.iter()) { write_message(f, &camel_obj_name, &message.val)?; @@ -442,7 +443,7 @@ pub fn main() -> Result<()> { writeln!(f, "use std::rc::Rc;")?; writeln!(f, "use uapi::OwnedFd;")?; writeln!(f, "use bstr::BStr;")?; - writeln!(f, "use crate::fixed::Fixed;")?; + writeln!(f, "use jay_units::fixed::Fixed;")?; writeln!(f, "use crate::client::{{EventFormatter, RequestParser}};")?; writeln!(f, "use crate::object::{{ObjectId, Interface}};")?; writeln!( diff --git a/algorithms/Cargo.toml b/crates/algorithms/Cargo.toml similarity index 76% rename from algorithms/Cargo.toml rename to crates/algorithms/Cargo.toml index 45d5bf11..257e1d09 100644 --- a/algorithms/Cargo.toml +++ b/crates/algorithms/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "jay-algorithms" -version = "0.4.0" -edition = "2024" -license = "GPL-3.0-only" +version.workspace = true +edition.workspace = true +license.workspace = true description = "Internal dependency of the Jay compositor" repository = "https://github.com/mahkoh/jay" diff --git a/algorithms/src/lib.rs b/crates/algorithms/src/lib.rs similarity index 100% rename from algorithms/src/lib.rs rename to crates/algorithms/src/lib.rs diff --git a/algorithms/src/qoi.rs b/crates/algorithms/src/qoi.rs similarity index 100% rename from algorithms/src/qoi.rs rename to crates/algorithms/src/qoi.rs diff --git a/algorithms/src/rect.rs b/crates/algorithms/src/rect.rs similarity index 100% rename from algorithms/src/rect.rs rename to crates/algorithms/src/rect.rs diff --git a/algorithms/src/rect/region.rs b/crates/algorithms/src/rect/region.rs similarity index 100% rename from algorithms/src/rect/region.rs rename to crates/algorithms/src/rect/region.rs diff --git a/algorithms/src/windows.rs b/crates/algorithms/src/windows.rs similarity index 100% rename from algorithms/src/windows.rs rename to crates/algorithms/src/windows.rs diff --git a/crates/allocator/Cargo.toml b/crates/allocator/Cargo.toml new file mode 100644 index 00000000..d9ab5e43 --- /dev/null +++ b/crates/allocator/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "jay-allocator" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +jay-formats = { path = "../formats" } +jay-video-types = { path = "../video-types" } + +thiserror = "2.0.11" +uapi = "0.2.13" diff --git a/crates/allocator/src/lib.rs b/crates/allocator/src/lib.rs new file mode 100644 index 00000000..a5b86a4f --- /dev/null +++ b/crates/allocator/src/lib.rs @@ -0,0 +1,95 @@ +use { + jay_formats::Format, + jay_video_types::{ + Modifier, + dmabuf::{DmaBuf, DmaBufIds}, + }, + std::{ + error::Error, + ops::{BitOr, BitOrAssign, Not}, + rc::Rc, + }, + thiserror::Error, + uapi::{OwnedFd, c}, +}; + +#[derive(Debug, Error)] +#[error(transparent)] +pub struct AllocatorError(#[from] pub Box); + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct BufferUsage(u32); + +impl BufferUsage { + pub fn none() -> Self { + Self(0) + } + + pub fn contains(self, other: Self) -> bool { + self.0 & other.0 == other.0 + } +} + +impl BitOr for BufferUsage { + type Output = Self; + + fn bitor(self, rhs: Self) -> Self::Output { + Self(self.0 | rhs.0) + } +} + +impl BitOrAssign for BufferUsage { + fn bitor_assign(&mut self, rhs: Self) { + self.0 |= rhs.0; + } +} + +impl Not for BufferUsage { + type Output = Self; + + fn not(self) -> Self::Output { + Self(!self.0) + } +} + +pub const BO_USE_SCANOUT: BufferUsage = BufferUsage(1 << 0); +pub const BO_USE_CURSOR: BufferUsage = BufferUsage(1 << 1); +pub const BO_USE_RENDERING: BufferUsage = BufferUsage(1 << 2); +pub const BO_USE_WRITE: BufferUsage = BufferUsage(1 << 3); +pub const BO_USE_LINEAR: BufferUsage = BufferUsage(1 << 4); +pub const BO_USE_PROTECTED: BufferUsage = BufferUsage(1 << 5); + +pub trait Allocator { + fn drm(&self) -> Option<&dyn AllocatorDrm>; + fn create_bo( + &self, + dma_buf_ids: &DmaBufIds, + width: i32, + height: i32, + format: &'static Format, + modifiers: &[Modifier], + usage: BufferUsage, + ) -> Result, AllocatorError>; + fn import_dmabuf( + &self, + dmabuf: &DmaBuf, + usage: BufferUsage, + ) -> Result, AllocatorError>; +} + +pub trait AllocatorDrm { + fn dev(&self) -> c::dev_t; + fn dup_render_fd(&self) -> Result, AllocatorError>; +} + +pub trait BufferObject { + fn dmabuf(&self) -> &DmaBuf; + fn map_read(self: Rc) -> Result, AllocatorError>; + fn map_write(self: Rc) -> Result, AllocatorError>; +} + +pub trait MappedBuffer { + unsafe fn data(&self) -> &[u8]; + fn data_ptr(&self) -> *mut u8; + fn stride(&self) -> i32; +} diff --git a/crates/async-engine/Cargo.toml b/crates/async-engine/Cargo.toml new file mode 100644 index 00000000..b51d6d68 --- /dev/null +++ b/crates/async-engine/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "jay-async-engine" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +jay-time = { path = "../time" } +jay-tracy = { path = "../tracy" } +jay-utils = { path = "../utils" } + +[features] +it = [] +tracy = ["jay-tracy/tracy"] diff --git a/src/async_engine/ae_task.rs b/crates/async-engine/src/ae_task.rs similarity index 96% rename from src/async_engine/ae_task.rs rename to crates/async-engine/src/ae_task.rs index e980c89c..50647187 100644 --- a/src/async_engine/ae_task.rs +++ b/crates/async-engine/src/ae_task.rs @@ -1,11 +1,9 @@ use { - crate::{ - async_engine::{AsyncEngine, Phase}, - tracy::ZoneName, - utils::{ - numcell::NumCell, - ptr_ext::{MutPtrExt, PtrExt}, - }, + crate::{AsyncEngine, Phase}, + jay_tracy::ZoneName, + jay_utils::{ + numcell::NumCell, + ptr_ext::{MutPtrExt, PtrExt}, }, std::{ cell::{Cell, UnsafeCell}, @@ -142,7 +140,7 @@ impl AsyncEngine { }), waker: Cell::new(None), queue: self.clone(), - zone: create_zone_name!("task:{}", name), + zone: jay_tracy::create_zone_name!("task:{}", name), }); unsafe { f.schedule_run(); @@ -254,7 +252,7 @@ impl> Task { let mut ctx = Context::from_waker(&waker); let poll = { - dynamic_zone!(self.zone); + jay_tracy::dynamic_zone!(self.zone); Pin::new_unchecked(&mut *data.future).poll(&mut ctx) }; if let Poll::Ready(d) = poll { diff --git a/src/async_engine/ae_yield.rs b/crates/async-engine/src/ae_yield.rs similarity index 93% rename from src/async_engine/ae_yield.rs rename to crates/async-engine/src/ae_yield.rs index 7ada5c81..41c561c5 100644 --- a/src/async_engine/ae_yield.rs +++ b/crates/async-engine/src/ae_yield.rs @@ -1,5 +1,5 @@ use { - crate::async_engine::AsyncEngine, + crate::AsyncEngine, std::{ future::Future, pin::Pin, diff --git a/src/async_engine.rs b/crates/async-engine/src/lib.rs similarity index 95% rename from src/async_engine.rs rename to crates/async-engine/src/lib.rs index 80e38c52..f482ca2d 100644 --- a/src/async_engine.rs +++ b/crates/async-engine/src/lib.rs @@ -1,13 +1,12 @@ mod ae_task; mod ae_yield; +mod run_toplevel; -pub use {crate::async_engine::ae_yield::Yield, ae_task::SpawnedFuture}; +pub use {ae_task::SpawnedFuture, ae_yield::Yield, run_toplevel::*}; use { - crate::{ - async_engine::ae_task::Runnable, - time::Time, - utils::{array, numcell::NumCell, syncqueue::SyncQueue}, - }, + crate::ae_task::Runnable, + jay_time::Time, + jay_utils::{array, numcell::NumCell, syncqueue::SyncQueue}, std::{ cell::{Cell, RefCell}, collections::VecDeque, diff --git a/src/utils/run_toplevel.rs b/crates/async-engine/src/run_toplevel.rs similarity index 89% rename from src/utils/run_toplevel.rs rename to crates/async-engine/src/run_toplevel.rs index b0422d32..e089fe4e 100644 --- a/src/utils/run_toplevel.rs +++ b/crates/async-engine/src/run_toplevel.rs @@ -1,8 +1,6 @@ use { - crate::{ - async_engine::{AsyncEngine, SpawnedFuture}, - utils::queue::AsyncQueue, - }, + crate::{AsyncEngine, SpawnedFuture}, + jay_utils::queue::AsyncQueue, std::rc::Rc, }; diff --git a/crates/bufio/Cargo.toml b/crates/bufio/Cargo.toml new file mode 100644 index 00000000..e2c964de --- /dev/null +++ b/crates/bufio/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "jay-bufio" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +jay-io-uring = { path = "../io-uring" } +jay-utils = { path = "../utils" } + +thiserror = "2.0.11" +uapi = "0.2.13" diff --git a/src/utils/bufio.rs b/crates/bufio/src/lib.rs similarity index 96% rename from src/utils/bufio.rs rename to crates/bufio/src/lib.rs index 5cbd5260..879b2be5 100644 --- a/src/utils/bufio.rs +++ b/crates/bufio/src/lib.rs @@ -1,11 +1,9 @@ use { - crate::{ - io_uring::{IoUring, IoUringError}, - utils::{ - buf::{Buf, DynamicBuf}, - queue::AsyncQueue, - stack::Stack, - }, + jay_io_uring::{IoUring, IoUringError}, + jay_utils::{ + buf::{Buf, DynamicBuf}, + queue::AsyncQueue, + stack::Stack, }, std::{ collections::VecDeque, diff --git a/crates/bugs/Cargo.toml b/crates/bugs/Cargo.toml new file mode 100644 index 00000000..ca696b8b --- /dev/null +++ b/crates/bugs/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "jay-bugs" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +ahash = "0.8.7" diff --git a/src/bugs.rs b/crates/bugs/src/lib.rs similarity index 100% rename from src/bugs.rs rename to crates/bugs/src/lib.rs diff --git a/crates/clientmem/Cargo.toml b/crates/clientmem/Cargo.toml new file mode 100644 index 00000000..c2e61a56 --- /dev/null +++ b/crates/clientmem/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "jay-clientmem" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +jay-cpu-worker = { path = "../cpu-worker" } +jay-gfx-types = { path = "../gfx-types" } +jay-tracy = { path = "../tracy" } +jay-utils = { path = "../utils" } + +log = "0.4.20" +thiserror = "2.0.11" +uapi = "0.2.13" + +[features] +tracy = ["jay-tracy/tracy"] diff --git a/crates/clientmem/src/lib.rs b/crates/clientmem/src/lib.rs new file mode 100644 index 00000000..7b947944 --- /dev/null +++ b/crates/clientmem/src/lib.rs @@ -0,0 +1,331 @@ +use { + jay_cpu_worker::{AsyncCpuWork, CpuJob, CpuWork, CpuWorker}, + jay_gfx_types::{ShmMemory, ShmMemoryBacking}, + jay_utils::{ + oserror::{OsError, OsErrorExt2}, + page_size::page_size, + vec_ext::VecExt, + }, + std::{ + cell::Cell, + error::Error, + mem::{ManuallyDrop, MaybeUninit}, + ops::Deref, + ptr, + rc::Rc, + sync::atomic::{Ordering, compiler_fence}, + }, + thiserror::Error, + uapi::{ + OwnedFd, Pod, + c::{self, raise}, + ftruncate, + }, +}; + +#[derive(Copy, Clone, Debug)] +pub struct ClientMemClient<'a> { + pub comm: &'a str, + pub id: u64, +} + +#[derive(Debug, Error)] +pub enum ClientMemError { + #[error("Could not install the sigbus handler")] + SigactionFailed(#[source] jay_utils::oserror::OsError), + #[error("A SIGBUS occurred while accessing mapped memory")] + Sigbus, + #[error("mmap failed")] + MmapFailed(#[source] jay_utils::oserror::OsError), + #[error("Length was not a multiple of the data element size")] + InvalidLength, +} + +pub struct ClientMem { + fd: ManuallyDrop>, + failed: Cell, + sigbus_impossible: bool, + data: *const [Cell], + cpu: Option>, +} + +#[derive(Clone)] +pub struct ClientMemOffset { + mem: Rc, + offset: usize, + data: *const [Cell], +} + +impl ClientMem { + pub fn new( + fd: &Rc, + len: usize, + read_only: bool, + client: Option>, + cpu: Option<&Rc>, + is_udmabuf: bool, + ) -> Result { + Self::new2(fd, len, read_only, client, cpu, c::MAP_SHARED, is_udmabuf) + } + + pub fn new_private( + fd: &Rc, + len: usize, + read_only: bool, + client: Option>, + cpu: Option<&Rc>, + ) -> Result { + Self::new2(fd, len, read_only, client, cpu, c::MAP_PRIVATE, false) + } + + fn new2( + fd: &Rc, + len: usize, + read_only: bool, + client: Option>, + cpu: Option<&Rc>, + flags: c::c_int, + is_udmabuf: bool, + ) -> Result { + let mut sigbus_impossible = is_udmabuf; + let mut real_size = None; + if !sigbus_impossible + && let Ok(seals) = uapi::fcntl_get_seals(fd.raw()) + && seals & c::F_SEAL_SHRINK != 0 + && let Ok(stat) = uapi::fstat(fd.raw()) + { + real_size = Some(stat.st_size as usize); + sigbus_impossible = stat.st_size as u64 >= len as u64; + } + if !sigbus_impossible && let Some(client) = client { + log::debug!( + "Client {} ({}) has created a shm buffer that might cause SIGBUS", + client.comm, + client.id, + ); + } + let len = len.next_multiple_of(page_size()); + if let Some(real_size) = real_size + && real_size < len + { + let _ = ftruncate(fd.raw(), len as _); + } + let data = if len == 0 { + &mut [][..] + } else { + let prot = match read_only { + true => c::PROT_READ, + false => c::PROT_READ | c::PROT_WRITE, + }; + unsafe { + let data = c::mmap64(ptr::null_mut(), len, prot, flags, fd.raw(), 0); + if data == c::MAP_FAILED { + return Err(ClientMemError::MmapFailed(OsError::default())); + } + std::slice::from_raw_parts_mut(data as *mut Cell, len) + } + }; + Ok(Self { + fd: ManuallyDrop::new(fd.clone()), + failed: Cell::new(false), + sigbus_impossible, + data, + cpu: cpu.cloned(), + }) + } + + pub fn len(&self) -> usize { + self.data.len() + } + + pub fn offset(self: &Rc, offset: usize, len: usize) -> ClientMemOffset { + let mem = unsafe { &*self.data }; + ClientMemOffset { + mem: self.clone(), + offset, + data: &mem[offset..][..len], + } + } + + pub fn fd(&self) -> &Rc { + &self.fd + } + + pub fn is_sealed_memfd(&self) -> bool { + self.sigbus_impossible + } +} + +impl ClientMemOffset { + pub fn pool(&self) -> &ClientMem { + &self.mem + } + + pub fn offset(&self) -> usize { + self.offset + } + + pub fn ptr(&self) -> *const [Cell] { + self.data + } + + pub fn access]) -> T>(&self, f: F) -> Result { + unsafe { + if self.mem.sigbus_impossible { + return Ok(f(&*self.data)); + } + let mref = MemRef { + mem: &*self.mem, + outer: MEM.get(), + }; + MEM.set(&mref); + compiler_fence(Ordering::SeqCst); + let res = f(&*self.data); + MEM.set(mref.outer); + compiler_fence(Ordering::SeqCst); + match self.mem.failed.get() { + true => Err(ClientMemError::Sigbus), + _ => Ok(res), + } + } + } + + pub fn read(&self, dst: &mut Vec) -> Result<(), ClientMemError> { + if self.data.len().checked_rem(std::mem::size_of::()) != Some(0) { + return Err(ClientMemError::InvalidLength); + } + self.access(|v| { + let len_elements = v.len() / std::mem::size_of::(); + dst.reserve(len_elements); + let (_, unused) = dst.split_at_spare_mut_bytes_ext(); + unused[..v.len()].copy_from_slice(uapi::as_maybe_uninit_bytes(v)); + unsafe { + dst.set_len(dst.len() + len_elements); + } + }) + } +} + +impl Drop for ClientMem { + fn drop(&mut self) { + let fd = unsafe { ManuallyDrop::take(&mut self.fd) }; + if let Some(cpu) = &self.cpu { + let pending = cpu.submit(Box::new(CloseMemWork { + fd: Rc::try_unwrap(fd).ok(), + data: self.data, + })); + pending.detach(); + } else { + unsafe { + c::munmap(self.data as _, self.len()); + } + } + } +} + +struct MemRef { + mem: *const ClientMem, + outer: *const MemRef, +} + +thread_local! { + static MEM: Cell<*const MemRef> = const { Cell::new(ptr::null()) }; +} + +unsafe fn kill() -> ! { + unsafe { + c::signal(c::SIGBUS, c::SIG_DFL); + raise(c::SIGBUS); + } + unreachable!(); +} + +unsafe extern "C" fn sigbus(sig: i32, info: &c::siginfo_t, _ucontext: *mut c::c_void) { + unsafe { + assert_eq!(sig, c::SIGBUS); + let mut memr_ptr = MEM.get(); + while !memr_ptr.is_null() { + let memr = &*memr_ptr; + let mem = &*memr.mem; + let lo = mem.data as *mut u8 as usize; + let hi = lo + mem.len(); + let fault_addr = info.si_addr() as usize; + if fault_addr < lo || fault_addr >= hi { + memr_ptr = memr.outer; + continue; + } + let res = c::mmap64( + lo as _, + hi - lo, + c::PROT_WRITE | c::PROT_READ, + c::MAP_ANONYMOUS | c::MAP_PRIVATE | c::MAP_FIXED, + -1, + 0, + ); + if res == c::MAP_FAILED { + kill(); + } + mem.failed.set(true); + return; + } + kill(); + } +} + +pub fn init() -> Result<(), ClientMemError> { + unsafe { + let mut action: c::sigaction = MaybeUninit::zeroed().assume_init(); + action.sa_sigaction = + sigbus as unsafe extern "C" fn(i32, &c::siginfo_t, *mut c::c_void) as _; + action.sa_flags = c::SA_NODEFER | c::SA_SIGINFO; + let res = c::sigaction(c::SIGBUS, &action, ptr::null_mut()); + uapi::map_err!(res) + .map(drop) + .map_os_err(ClientMemError::SigactionFailed) + } +} + +struct CloseMemWork { + fd: Option, + data: *const [Cell], +} + +unsafe impl Send for CloseMemWork {} + +impl CpuJob for CloseMemWork { + fn work(&mut self) -> &mut dyn CpuWork { + self + } + + fn completed(self: Box) { + // nothing + } +} + +impl CpuWork for CloseMemWork { + fn run(&mut self) -> Option> { + jay_tracy::zone!("CloseMemWork"); + self.fd.take(); + unsafe { + c::munmap(self.data as _, self.data.len()); + } + None + } +} + +impl ShmMemory for ClientMemOffset { + fn len(&self) -> usize { + self.data.len() + } + + fn safe_access(&self) -> ShmMemoryBacking { + match self.mem.is_sealed_memfd() { + true => ShmMemoryBacking::Ptr(self.data), + false => ShmMemoryBacking::Fd(self.mem.fd.deref().clone(), self.offset), + } + } + + fn access(&self, f: &mut dyn FnMut(&[Cell])) -> Result<(), Box> { + self.access(f).map_err(|e| e.into()) + } +} diff --git a/crates/cmm/Cargo.toml b/crates/cmm/Cargo.toml new file mode 100644 index 00000000..ade7abfd --- /dev/null +++ b/crates/cmm/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "jay-cmm" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +jay-utils = { path = "../utils" } diff --git a/src/cmm/cmm_description.rs b/crates/cmm/src/cmm_description.rs similarity index 85% rename from src/cmm/cmm_description.rs rename to crates/cmm/src/cmm_description.rs index 85a0a71a..dc8ecbd4 100644 --- a/src/cmm/cmm_description.rs +++ b/crates/cmm/src/cmm_description.rs @@ -1,15 +1,13 @@ use { crate::{ - cmm::{ - cmm_eotf::Eotf, - cmm_luminance::{Luminance, TargetLuminance, white_balance}, - cmm_manager::Shared, - cmm_primaries::{NamedPrimaries, Primaries}, - cmm_render_intent::RenderIntent, - cmm_transform::{ColorMatrix, Local, Xyz, bradford_adjustment}, - }, - utils::ordered_float::F64, + cmm_eotf::Eotf, + cmm_luminance::{Luminance, TargetLuminance, white_balance}, + cmm_manager::Shared, + cmm_primaries::{NamedPrimaries, Primaries}, + cmm_render_intent::RenderIntent, + cmm_transform::{ColorMatrix, Local, Xyz, bradford_adjustment}, }, + jay_utils::ordered_float::F64, std::rc::Rc, }; diff --git a/src/cmm/cmm_eotf.rs b/crates/cmm/src/cmm_eotf.rs similarity index 97% rename from src/cmm/cmm_eotf.rs rename to crates/cmm/src/cmm_eotf.rs index 89e123aa..afc26d0d 100644 --- a/src/cmm/cmm_eotf.rs +++ b/crates/cmm/src/cmm_eotf.rs @@ -1,4 +1,4 @@ -use crate::utils::ordered_float::F32; +use jay_utils::ordered_float::F32; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum Eotf { diff --git a/src/cmm/cmm_luminance.rs b/crates/cmm/src/cmm_luminance.rs similarity index 93% rename from src/cmm/cmm_luminance.rs rename to crates/cmm/src/cmm_luminance.rs index 8371a33d..584120da 100644 --- a/src/cmm/cmm_luminance.rs +++ b/crates/cmm/src/cmm_luminance.rs @@ -1,10 +1,8 @@ use crate::{ - cmm::{ - cmm_render_intent::RenderIntent, - cmm_transform::{ColorMatrix, Xyz}, - }, - utils::ordered_float::F64, + cmm_render_intent::RenderIntent, + cmm_transform::{ColorMatrix, Xyz}, }; +use jay_utils::ordered_float::F64; #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct Luminance { @@ -38,7 +36,6 @@ impl Luminance { white: F64(203.0), }; - #[expect(dead_code)] pub const HLG: Self = Self { min: F64(0.005), max: F64(1000.0), diff --git a/src/cmm/cmm_manager.rs b/crates/cmm/src/cmm_manager.rs similarity index 94% rename from src/cmm/cmm_manager.rs rename to crates/cmm/src/cmm_manager.rs index f73e2c12..8826f9b5 100644 --- a/src/cmm/cmm_manager.rs +++ b/crates/cmm/src/cmm_manager.rs @@ -1,16 +1,14 @@ use { crate::{ - cmm::{ - cmm_description::{ - ColorDescription, ColorDescriptionIds, LinearColorDescription, - LinearColorDescriptionId, LinearColorDescriptionIds, - }, - cmm_eotf::Eotf, - cmm_luminance::{Luminance, TargetLuminance}, - cmm_primaries::{NamedPrimaries, Primaries}, + cmm_description::{ + ColorDescription, ColorDescriptionIds, LinearColorDescription, + LinearColorDescriptionId, LinearColorDescriptionIds, }, - utils::{copyhashmap::CopyHashMap, numcell::NumCell, ordered_float::F64}, + cmm_eotf::Eotf, + cmm_luminance::{Luminance, TargetLuminance}, + cmm_primaries::{NamedPrimaries, Primaries}, }, + jay_utils::{copyhashmap::CopyHashMap, numcell::NumCell, ordered_float::F64}, std::rc::{Rc, Weak}, }; diff --git a/src/cmm/cmm_primaries.rs b/crates/cmm/src/cmm_primaries.rs similarity index 98% rename from src/cmm/cmm_primaries.rs rename to crates/cmm/src/cmm_primaries.rs index 5541d8ee..a39f7450 100644 --- a/src/cmm/cmm_primaries.rs +++ b/crates/cmm/src/cmm_primaries.rs @@ -1,4 +1,4 @@ -use {crate::utils::ordered_float::F64, std::hash::Hash}; +use {jay_utils::ordered_float::F64, std::hash::Hash}; #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub enum NamedPrimaries { diff --git a/src/cmm/cmm_render_intent.rs b/crates/cmm/src/cmm_render_intent.rs similarity index 50% rename from src/cmm/cmm_render_intent.rs rename to crates/cmm/src/cmm_render_intent.rs index afb16a30..77fbcf51 100644 --- a/src/cmm/cmm_render_intent.rs +++ b/crates/cmm/src/cmm_render_intent.rs @@ -1,11 +1,3 @@ -use crate::{ - ifs::color_management::{ - ABSOLUTE_NO_ADAPTATION_SINCE, RENDER_INTENT_ABSOLUTE_NO_ADAPTATION, - RENDER_INTENT_PERCEPTUAL, RENDER_INTENT_RELATIVE, RENDER_INTENT_RELATIVE_BPC, - }, - object::Version, -}; - #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)] pub enum RenderIntent { #[default] @@ -16,19 +8,6 @@ pub enum RenderIntent { } impl RenderIntent { - pub fn from_wayland(intent: u32, version: Version) -> Option { - let res = match intent { - RENDER_INTENT_PERCEPTUAL => Self::Perceptual, - RENDER_INTENT_RELATIVE => Self::Relative, - RENDER_INTENT_RELATIVE_BPC => Self::RelativeBpc, - RENDER_INTENT_ABSOLUTE_NO_ADAPTATION if version >= ABSOLUTE_NO_ADAPTATION_SINCE => { - Self::AbsoluteNoAdaptation - } - _ => return None, - }; - Some(res) - } - pub fn black_point_compensation(self) -> bool { match self { RenderIntent::Perceptual => true, diff --git a/src/cmm/cmm_tests.rs b/crates/cmm/src/cmm_tests.rs similarity index 98% rename from src/cmm/cmm_tests.rs rename to crates/cmm/src/cmm_tests.rs index 5f3564df..84e5edd6 100644 --- a/src/cmm/cmm_tests.rs +++ b/crates/cmm/src/cmm_tests.rs @@ -1,5 +1,5 @@ mod matrices { - use crate::{cmm::cmm_primaries::Primaries, utils::ordered_float::F64}; + use {crate::cmm_primaries::Primaries, jay_utils::ordered_float::F64}; fn check(primaries: Primaries, expected: [[f64; 4]; 3]) { let (ltg, gtl) = primaries.matrices(); @@ -134,7 +134,7 @@ mod matrices { } mod transforms { - use crate::cmm::{ + use crate::{ cmm_eotf::Eotf, cmm_luminance::Luminance, cmm_manager::ColorManager, cmm_primaries::Primaries, cmm_render_intent::RenderIntent, }; diff --git a/src/cmm/cmm_transform.rs b/crates/cmm/src/cmm_transform.rs similarity index 90% rename from src/cmm/cmm_transform.rs rename to crates/cmm/src/cmm_transform.rs index 64e576ca..eaab4087 100644 --- a/src/cmm/cmm_transform.rs +++ b/crates/cmm/src/cmm_transform.rs @@ -1,10 +1,6 @@ use { - crate::{ - cmm::{cmm_eotf::Eotf, cmm_primaries::Primaries}, - gfx_api::AlphaMode, - theme::Color, - utils::ordered_float::F64, - }, + crate::cmm_primaries::Primaries, + jay_utils::ordered_float::F64, std::{ fmt, fmt::{Debug, Formatter}, @@ -129,29 +125,6 @@ impl Mul<[f64; 3]> for ColorMatrix { } } -impl Mul for ColorMatrix { - type Output = Color; - - fn mul(self, rhs: Color) -> Self::Output { - let mut rgba = rhs.to_array(Eotf::Linear); - let a = rgba[3]; - if a < 1.0 && a > 0.0 { - for c in &mut rgba[..3] { - *c /= a; - } - } - let [r, g, b] = self * [rgba[0] as f64, rgba[1] as f64, rgba[2] as f64]; - Color::new( - Eotf::Linear, - AlphaMode::Straight, - r as f32, - g as f32, - b as f32, - a, - ) - } -} - impl ColorMatrix { pub const fn new(m: [[f64; 4]; 3]) -> Self { let m = [ diff --git a/crates/cmm/src/lib.rs b/crates/cmm/src/lib.rs new file mode 100644 index 00000000..3bc9bf15 --- /dev/null +++ b/crates/cmm/src/lib.rs @@ -0,0 +1,53 @@ +macro_rules! linear_ids { + ($ids:ident, $id:ident, $ty:ty $(,)?) => { + #[derive(Debug)] + pub struct $ids { + next: jay_utils::numcell::NumCell<$ty>, + } + + impl Default for $ids { + fn default() -> Self { + Self { + next: jay_utils::numcell::NumCell::new(1), + } + } + } + + impl $ids { + pub fn next(&self) -> $id { + $id(self.next.fetch_add(1)) + } + } + + #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] + pub struct $id($ty); + + impl $id { + #[allow(dead_code)] + pub fn raw(&self) -> $ty { + self.0 + } + + #[allow(dead_code)] + pub fn from_raw(id: $ty) -> Self { + Self(id) + } + } + + impl std::fmt::Display for $id { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } + } + }; +} + +pub mod cmm_description; +pub mod cmm_eotf; +pub mod cmm_luminance; +pub mod cmm_manager; +pub mod cmm_primaries; +pub mod cmm_render_intent; +#[cfg(test)] +mod cmm_tests; +pub mod cmm_transform; diff --git a/crates/cpu-worker/Cargo.toml b/crates/cpu-worker/Cargo.toml new file mode 100644 index 00000000..0517a242 --- /dev/null +++ b/crates/cpu-worker/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "jay-cpu-worker" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +jay-async-engine = { path = "../async-engine" } +jay-geometry = { path = "../geometry" } +jay-io-uring = { path = "../io-uring" } +jay-tracy = { path = "../tracy" } +jay-utils = { path = "../utils" } + +log = { version = "0.4.20", features = ["std"] } +parking_lot = "0.12.1" +thiserror = "2.0.11" +uapi = "0.2.13" + +[dev-dependencies] +jay-wheel = { path = "../wheel" } + +[features] +it = [] +tracy = ["jay-tracy/tracy", "jay-async-engine/tracy"] diff --git a/src/cpu_worker/jobs.rs b/crates/cpu-worker/src/jobs.rs similarity index 100% rename from src/cpu_worker/jobs.rs rename to crates/cpu-worker/src/jobs.rs diff --git a/src/cpu_worker/jobs/img_copy.rs b/crates/cpu-worker/src/jobs/img_copy.rs similarity index 92% rename from src/cpu_worker/jobs/img_copy.rs rename to crates/cpu-worker/src/jobs/img_copy.rs index f50a7b25..8162cf27 100644 --- a/src/cpu_worker/jobs/img_copy.rs +++ b/crates/cpu-worker/src/jobs/img_copy.rs @@ -1,8 +1,6 @@ use { - crate::{ - cpu_worker::{AsyncCpuWork, CpuWork}, - rect::Rect, - }, + crate::{AsyncCpuWork, CpuWork}, + jay_geometry::Rect, std::ptr, }; @@ -34,7 +32,7 @@ impl ImgCopyWork { impl CpuWork for ImgCopyWork { fn run(&mut self) -> Option> { - zone!("ImgCopyWork"); + jay_tracy::zone!("ImgCopyWork"); for rect in &self.rects { let mut offset = rect.y1() * self.stride + rect.x1() * self.bpp; if rect.width() == self.width { diff --git a/src/cpu_worker/jobs/read_write.rs b/crates/cpu-worker/src/jobs/read_write.rs similarity index 95% rename from src/cpu_worker/jobs/read_write.rs rename to crates/cpu-worker/src/jobs/read_write.rs index 6ebcfba2..79d8cb29 100644 --- a/src/cpu_worker/jobs/read_write.rs +++ b/crates/cpu-worker/src/jobs/read_write.rs @@ -1,9 +1,7 @@ use { - crate::{ - async_engine::{AsyncEngine, SpawnedFuture}, - cpu_worker::{AsyncCpuWork, CompletedWork, CpuWork, WorkCompletion}, - io_uring::{IoUring, IoUringError, IoUringTaskId}, - }, + crate::{AsyncCpuWork, CompletedWork, CpuWork, WorkCompletion}, + jay_async_engine::{AsyncEngine, SpawnedFuture}, + jay_io_uring::{IoUring, IoUringError, IoUringTaskId}, std::{ any::Any, ptr, diff --git a/src/cpu_worker.rs b/crates/cpu-worker/src/lib.rs similarity index 95% rename from src/cpu_worker.rs rename to crates/cpu-worker/src/lib.rs index ff223806..51045df1 100644 --- a/src/cpu_worker.rs +++ b/crates/cpu-worker/src/lib.rs @@ -3,19 +3,18 @@ pub mod jobs; mod tests; use { - crate::{ - async_engine::{AsyncEngine, SpawnedFuture}, - io_uring::IoUring, - utils::{ - buf::TypedBuf, - copyhashmap::CopyHashMap, - errorfmt::ErrorFmt, - oserror::{OsError, OsErrorExt2}, - pipe::{Pipe, pipe}, - ptr_ext::MutPtrExt, - queue::AsyncQueue, - stack::Stack, - }, + jay_async_engine::{AsyncEngine, SpawnedFuture}, + jay_io_uring::IoUring, + jay_utils::{ + buf::TypedBuf, + copyhashmap::CopyHashMap, + errorfmt::ErrorFmt, + numcell::NumCell, + oserror::{OsError, OsErrorExt2}, + pipe::{Pipe, pipe}, + ptr_ext::MutPtrExt, + queue::AsyncQueue, + stack::Stack, }, parking_lot::{Condvar, Mutex}, std::{ @@ -138,7 +137,27 @@ struct CpuWorkerData { sync_wake_condvar: Arc, } -linear_ids!(CpuJobIds, CpuJobId, u64); +#[derive(Debug)] +struct CpuJobIds { + next: NumCell, +} + +impl Default for CpuJobIds { + fn default() -> Self { + Self { + next: NumCell::new(1), + } + } +} + +impl CpuJobIds { + fn next(&self) -> CpuJobId { + CpuJobId(self.next.fetch_add(1)) + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +struct CpuJobId(u64); #[derive(Debug, Error)] pub enum CpuWorkerError { diff --git a/src/cpu_worker/tests.rs b/crates/cpu-worker/src/tests.rs similarity index 90% rename from src/cpu_worker/tests.rs rename to crates/cpu-worker/src/tests.rs index c77a8620..00382e03 100644 --- a/src/cpu_worker/tests.rs +++ b/crates/cpu-worker/src/tests.rs @@ -1,11 +1,9 @@ use { - crate::{ - async_engine::{AsyncEngine, SpawnedFuture}, - cpu_worker::{AsyncCpuWork, CompletedWork, CpuJob, CpuWork, CpuWorker, WorkCompletion}, - io_uring::IoUring, - utils::asyncevent::AsyncEvent, - wheel::Wheel, - }, + crate::{AsyncCpuWork, CompletedWork, CpuJob, CpuWork, CpuWorker, WorkCompletion}, + jay_async_engine::{AsyncEngine, SpawnedFuture}, + jay_io_uring::IoUring, + jay_utils::asyncevent::AsyncEvent, + jay_wheel::Wheel, std::{future::pending, rc::Rc, sync::Arc}, uapi::{OwnedFd, c::EFD_CLOEXEC}, }; diff --git a/crates/criteria/Cargo.toml b/crates/criteria/Cargo.toml new file mode 100644 index 00000000..20af59c2 --- /dev/null +++ b/crates/criteria/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "jay-criteria" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +jay-utils = { path = "../utils" } + +ahash = "0.8.7" +linearize = { version = "0.1.3", features = ["derive"] } +regex = "1.11.1" diff --git a/src/criteria/crit_graph.rs b/crates/criteria/src/crit_graph.rs similarity index 100% rename from src/criteria/crit_graph.rs rename to crates/criteria/src/crit_graph.rs diff --git a/src/criteria/crit_graph/crit_downstream.rs b/crates/criteria/src/crit_graph/crit_downstream.rs similarity index 98% rename from src/criteria/crit_graph/crit_downstream.rs rename to crates/criteria/src/crit_graph/crit_downstream.rs index cf3f50be..939661f8 100644 --- a/src/criteria/crit_graph/crit_downstream.rs +++ b/crates/criteria/src/crit_graph/crit_downstream.rs @@ -1,5 +1,5 @@ use { - crate::criteria::{ + crate::{ CritMatcherId, crit_graph::{CritTarget, crit_upstream::CritUpstreamNode}, }, diff --git a/src/criteria/crit_graph/crit_middle.rs b/crates/criteria/src/crit_graph/crit_middle.rs similarity index 99% rename from src/criteria/crit_graph/crit_middle.rs rename to crates/criteria/src/crit_graph/crit_middle.rs index f8e76041..924c7010 100644 --- a/src/criteria/crit_graph/crit_middle.rs +++ b/crates/criteria/src/crit_graph/crit_middle.rs @@ -1,5 +1,5 @@ use { - crate::criteria::{ + crate::{ CritUpstreamNode, crit_graph::{ CritDownstream, CritDownstreamData, CritTarget, CritUpstreamData, diff --git a/src/criteria/crit_graph/crit_root.rs b/crates/criteria/src/crit_graph/crit_root.rs similarity index 99% rename from src/criteria/crit_graph/crit_root.rs rename to crates/criteria/src/crit_graph/crit_root.rs index 3c2c6f4c..8c6f2c2a 100644 --- a/src/criteria/crit_graph/crit_root.rs +++ b/crates/criteria/src/crit_graph/crit_root.rs @@ -1,5 +1,5 @@ use { - crate::criteria::{ + crate::{ CritMatcherId, CritUpstreamNode, FixedRootMatcher, RootMatcherMap, crit_graph::{ CritTarget, CritUpstreamData, diff --git a/src/criteria/crit_graph/crit_target.rs b/crates/criteria/src/crit_graph/crit_target.rs similarity index 82% rename from src/criteria/crit_graph/crit_target.rs rename to crates/criteria/src/crit_graph/crit_target.rs index 18974748..b77d6796 100644 --- a/src/criteria/crit_graph/crit_target.rs +++ b/crates/criteria/src/crit_graph/crit_target.rs @@ -1,11 +1,9 @@ use { crate::{ - criteria::{ - CritDestroyListener, CritMatcherId, FixedRootMatcher, crit_leaf::CritLeafEvent, - crit_matchers::critm_constant::CritMatchConstant, - }, - utils::{copyhashmap::CopyHashMap, queue::AsyncQueue}, + CritDestroyListener, CritMatcherId, FixedRootMatcher, crit_leaf::CritLeafEvent, + crit_matchers::critm_constant::CritMatchConstant, }, + jay_utils::{copyhashmap::CopyHashMap, queue::AsyncQueue}, std::{ hash::Hash, rc::{Rc, Weak}, diff --git a/src/criteria/crit_graph/crit_upstream.rs b/crates/criteria/src/crit_graph/crit_upstream.rs similarity index 92% rename from src/criteria/crit_graph/crit_upstream.rs rename to crates/criteria/src/crit_graph/crit_upstream.rs index 096a5555..e7bded8e 100644 --- a/src/criteria/crit_graph/crit_upstream.rs +++ b/crates/criteria/src/crit_graph/crit_upstream.rs @@ -1,16 +1,14 @@ use { crate::{ - criteria::{ - CritDestroyListener, CritMatcherId, - crit_graph::{ - WeakCritTargetOwner, - crit_downstream::CritDownstream, - crit_target::{CritTarget, CritTargetOwner}, - }, - crit_per_target_data::CritPerTargetData, + CritDestroyListener, CritMatcherId, + crit_graph::{ + WeakCritTargetOwner, + crit_downstream::CritDownstream, + crit_target::{CritTarget, CritTargetOwner}, }, - utils::copyhashmap::CopyHashMap, + crit_per_target_data::CritPerTargetData, }, + jay_utils::copyhashmap::CopyHashMap, std::{ cell::RefMut, mem, diff --git a/src/criteria/crit_leaf.rs b/crates/criteria/src/crit_leaf.rs similarity index 91% rename from src/criteria/crit_leaf.rs rename to crates/criteria/src/crit_leaf.rs index 72b74ccd..9e22ba47 100644 --- a/src/criteria/crit_leaf.rs +++ b/crates/criteria/src/crit_leaf.rs @@ -1,12 +1,10 @@ use { crate::{ - criteria::{ - CritUpstreamNode, - crit_graph::{CritDownstream, CritDownstreamData, CritMgr, CritTarget}, - crit_per_target_data::{CritDestroyListenerBase, CritPerTargetData}, - }, - utils::{cell_ext::CellExt, queue::AsyncQueue}, + CritUpstreamNode, + crit_graph::{CritDownstream, CritDownstreamData, CritMgr, CritTarget}, + crit_per_target_data::{CritDestroyListenerBase, CritPerTargetData}, }, + jay_utils::{cell_ext::CellExt, queue::AsyncQueue}, std::{ cell::Cell, rc::{Rc, Weak}, @@ -24,7 +22,7 @@ where events: Rc>>, } -pub(in crate::criteria) struct NodeHolder +pub struct NodeHolder where Target: CritTarget, { @@ -77,7 +75,7 @@ impl CritLeafMatcher where Target: CritTarget, { - pub(in crate::criteria) fn new( + pub(crate) fn new( mgr: &Target::Mgr, upstream: &Rc>, on_match: impl Fn(Target::LeafData) -> Box + 'static, diff --git a/src/criteria/crit_matchers.rs b/crates/criteria/src/crit_matchers.rs similarity index 100% rename from src/criteria/crit_matchers.rs rename to crates/criteria/src/crit_matchers.rs diff --git a/src/criteria/crit_matchers/critm_any_or_all.rs b/crates/criteria/src/crit_matchers/critm_any_or_all.rs similarity index 94% rename from src/criteria/crit_matchers/critm_any_or_all.rs rename to crates/criteria/src/crit_matchers/critm_any_or_all.rs index 38c3eaaa..71bc8efe 100644 --- a/src/criteria/crit_matchers/critm_any_or_all.rs +++ b/crates/criteria/src/crit_matchers/critm_any_or_all.rs @@ -1,5 +1,5 @@ use { - crate::criteria::crit_graph::{CritMiddleCriterion, CritTarget, CritUpstreamNode}, + crate::crit_graph::{CritMiddleCriterion, CritTarget, CritUpstreamNode}, std::{marker::PhantomData, rc::Rc}, }; diff --git a/src/criteria/crit_matchers/critm_constant.rs b/crates/criteria/src/crit_matchers/critm_constant.rs similarity index 98% rename from src/criteria/crit_matchers/critm_constant.rs rename to crates/criteria/src/crit_matchers/critm_constant.rs index b45eea19..41d2cda3 100644 --- a/src/criteria/crit_matchers/critm_constant.rs +++ b/crates/criteria/src/crit_matchers/critm_constant.rs @@ -1,5 +1,5 @@ use { - crate::criteria::{ + crate::{ CritMatcherIds, FixedRootMatcher, crit_graph::{ CritFixedRootCriterion, CritFixedRootCriterionBase, CritMgr, CritRoot, CritRootFixed, diff --git a/src/criteria/crit_matchers/critm_exactly.rs b/crates/criteria/src/crit_matchers/critm_exactly.rs similarity index 93% rename from src/criteria/crit_matchers/critm_exactly.rs rename to crates/criteria/src/crit_matchers/critm_exactly.rs index fe4c3e0a..883cb14a 100644 --- a/src/criteria/crit_matchers/critm_exactly.rs +++ b/crates/criteria/src/crit_matchers/critm_exactly.rs @@ -1,5 +1,5 @@ use { - crate::criteria::crit_graph::{CritMiddleCriterion, CritTarget, CritUpstreamNode}, + crate::crit_graph::{CritMiddleCriterion, CritTarget, CritUpstreamNode}, std::{marker::PhantomData, rc::Rc}, }; diff --git a/src/criteria/crit_matchers/critm_string.rs b/crates/criteria/src/crit_matchers/critm_string.rs similarity index 97% rename from src/criteria/crit_matchers/critm_string.rs rename to crates/criteria/src/crit_matchers/critm_string.rs index 1464e2d6..968ceaff 100644 --- a/src/criteria/crit_matchers/critm_string.rs +++ b/crates/criteria/src/crit_matchers/critm_string.rs @@ -1,5 +1,5 @@ use { - crate::criteria::{ + crate::{ CritLiteralOrRegex, RootMatcherMap, crit_graph::{CritRootCriterion, CritTarget}, }, diff --git a/src/criteria/crit_per_target_data.rs b/crates/criteria/src/crit_per_target_data.rs similarity index 97% rename from src/criteria/crit_per_target_data.rs rename to crates/criteria/src/crit_per_target_data.rs index cf527167..26db1c2b 100644 --- a/src/criteria/crit_per_target_data.rs +++ b/crates/criteria/src/crit_per_target_data.rs @@ -1,5 +1,5 @@ use { - crate::criteria::{ + crate::{ CritMatcherId, crit_graph::{CritTarget, CritTargetOwner, WeakCritTargetOwner}, }, @@ -28,7 +28,7 @@ where data: T, } -pub(super) trait CritDestroyListenerBase: 'static +pub trait CritDestroyListenerBase: 'static where Target: CritTarget, { diff --git a/crates/criteria/src/lib.rs b/crates/criteria/src/lib.rs new file mode 100644 index 00000000..f5369669 --- /dev/null +++ b/crates/criteria/src/lib.rs @@ -0,0 +1,131 @@ +pub mod crit_graph; +pub mod crit_leaf; +pub mod crit_matchers; +pub mod crit_per_target_data; + +use { + crate::{ + crit_graph::{CritMgr, CritMiddle, CritRoot, CritRootCriterion, CritRootFixed}, + crit_leaf::CritLeafMatcher, + crit_matchers::{critm_any_or_all::CritMatchAnyOrAll, critm_exactly::CritMatchExactly}, + }, + jay_utils::{copyhashmap::CopyHashMap, numcell::NumCell}, + linearize::StaticMap, + regex::Regex, + std::rc::{Rc, Weak}, +}; +pub use { + crit_graph::{CritTarget, CritUpstreamNode}, + crit_per_target_data::CritDestroyListener, +}; + +#[derive(Debug)] +pub struct CritMatcherIds { + next: NumCell, +} + +impl Default for CritMatcherIds { + fn default() -> Self { + Self { + next: NumCell::new(1), + } + } +} + +impl CritMatcherIds { + pub fn next(&self) -> CritMatcherId { + CritMatcherId(self.next.fetch_add(1)) + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct CritMatcherId(u64); + +impl CritMatcherId { + #[allow(clippy::allow_attributes, dead_code)] + pub fn raw(&self) -> u64 { + self.0 + } + + #[allow(clippy::allow_attributes, dead_code)] + pub fn from_raw(id: u64) -> Self { + Self(id) + } +} + +impl std::fmt::Display for CritMatcherId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } +} + +pub type RootMatcherMap = CopyHashMap>>; +pub type FixedRootMatcher = + StaticMap>>>; + +#[derive(Clone)] +pub enum CritLiteralOrRegex { + Literal(String), + Regex(Regex), +} + +impl CritLiteralOrRegex { + fn matches(&self, string: &str) -> bool { + match self { + CritLiteralOrRegex::Literal(p) => string == p, + CritLiteralOrRegex::Regex(r) => r.is_match(string), + } + } +} + +pub trait CritMgrExt: CritMgr { + fn list( + &self, + upstream: &[Rc>], + all: bool, + ) -> Rc> { + if upstream.is_empty() { + return self.match_constant()[all].clone(); + } + CritMiddle::new(self, upstream, CritMatchAnyOrAll::new(upstream, all)) + } + + fn exactly( + &self, + upstream: &[Rc>], + num: usize, + ) -> Rc> { + if num > upstream.len() { + return self.match_constant()[false].clone(); + } + if num == 0 { + let upstream: Vec<_> = upstream.iter().map(|u| u.not(self)).collect(); + return self.list(&upstream, true); + } + CritMiddle::new(self, upstream, CritMatchExactly::new(upstream, num)) + } + + fn leaf( + &self, + upstream: &Rc>, + on_match: impl Fn(::LeafData) -> Box + 'static, + ) -> Rc> { + CritLeafMatcher::new(self, upstream, on_match) + } + + fn not( + &self, + upstream: &Rc>, + ) -> Rc> { + upstream.not(self) + } + + fn root(&self, criterion: T) -> Rc> + where + T: CritRootCriterion, + { + CritRoot::new(self.roots(), self.id(), criterion) + } +} + +impl CritMgrExt for T where T: CritMgr {} diff --git a/crates/damage/Cargo.toml b/crates/damage/Cargo.toml new file mode 100644 index 00000000..9114f528 --- /dev/null +++ b/crates/damage/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "jay-damage" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +jay-geometry = { path = "../geometry" } +jay-tree-types = { path = "../tree-types" } +jay-units = { path = "../units" } diff --git a/crates/damage/src/lib.rs b/crates/damage/src/lib.rs new file mode 100644 index 00000000..78bb1e60 --- /dev/null +++ b/crates/damage/src/lib.rs @@ -0,0 +1,116 @@ +use { + jay_geometry::Rect, + jay_tree_types::Transform, + jay_units::fixed::Fixed, +}; + +#[derive(Copy, Clone, Debug)] +pub struct DamageMatrix { + transform: Transform, + mx: f64, + my: f64, + dx: f64, + dy: f64, + smear: i32, +} + +impl Default for DamageMatrix { + fn default() -> Self { + Self { + transform: Default::default(), + mx: 1.0, + my: 1.0, + dx: 0.0, + dy: 0.0, + smear: 0, + } + } +} + +impl DamageMatrix { + pub fn apply(&self, dx: i32, dy: i32, rect: Rect) -> Rect { + let x1 = rect.x1() - self.smear; + let x2 = rect.x2() + self.smear; + let y1 = rect.y1() - self.smear; + let y2 = rect.y2() + self.smear; + let [x1, y1, x2, y2] = match self.transform { + Transform::None => [x1, y1, x2, y2], + Transform::Rotate90 => [-y2, x1, -y1, x2], + Transform::Rotate180 => [-x2, -y2, -x1, -y1], + Transform::Rotate270 => [y1, -x2, y2, -x1], + Transform::Flip => [-x2, y1, -x1, y2], + Transform::FlipRotate90 => [y1, x1, y2, x2], + Transform::FlipRotate180 => [x1, -y2, x2, -y1], + Transform::FlipRotate270 => [-y2, -x2, -y1, -x1], + }; + let x1 = (x1 as f64 * self.mx + self.dx).floor() as i32 + dx; + let y1 = (y1 as f64 * self.my + self.dy).floor() as i32 + dy; + let x2 = (x2 as f64 * self.mx + self.dx).ceil() as i32 + dx; + let y2 = (y2 as f64 * self.my + self.dy).ceil() as i32 + dy; + Rect::new_saturating(x1, y1, x2, y2) + } + + pub fn new( + transform: Transform, + legacy_scale: i32, + buffer_width: i32, + buffer_height: i32, + viewport: Option<[Fixed; 4]>, + dst_width: i32, + dst_height: i32, + ) -> DamageMatrix { + let mut buffer_width = buffer_width as f64; + let mut buffer_height = buffer_height as f64; + let dst_width = dst_width as f64; + let dst_height = dst_height as f64; + + let mut mx = 1.0; + let mut my = 1.0; + if legacy_scale != 1 { + let scale_inv = 1.0 / (legacy_scale as f64); + mx = scale_inv; + my = scale_inv; + buffer_width *= scale_inv; + buffer_height *= scale_inv; + } + let (mut buffer_width, mut buffer_height) = + transform.maybe_swap((buffer_width, buffer_height)); + let (mut dx, mut dy) = match transform { + Transform::None => (0.0, 0.0), + Transform::Rotate90 => (buffer_width, 0.0), + Transform::Rotate180 => (buffer_width, buffer_height), + Transform::Rotate270 => (0.0, buffer_height), + Transform::Flip => (buffer_width, 0.0), + Transform::FlipRotate90 => (0.0, 0.0), + Transform::FlipRotate180 => (0.0, buffer_height), + Transform::FlipRotate270 => (buffer_width, buffer_height), + }; + if let Some([x, y, w, h]) = viewport { + dx -= x.to_f64(); + dy -= y.to_f64(); + buffer_width = w.to_f64(); + buffer_height = h.to_f64(); + } + let mut smear = false; + if dst_width != buffer_width { + let scale = dst_width / buffer_width; + mx *= scale; + dx *= scale; + smear |= dst_width > buffer_width; + } + if dst_height != buffer_height { + let scale = dst_height / buffer_height; + my *= scale; + dy *= scale; + smear |= dst_height > buffer_height; + } + DamageMatrix { + transform, + mx, + my, + dx, + dy, + smear: smear as _, + } + } +} diff --git a/crates/dbus-core/Cargo.toml b/crates/dbus-core/Cargo.toml new file mode 100644 index 00000000..8122e63a --- /dev/null +++ b/crates/dbus-core/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "jay-dbus-core" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +jay-bufio = { path = "../bufio" } +jay-io-uring = { path = "../io-uring" } +jay-utils = { path = "../utils" } + +bstr = { version = "1.9.0", default-features = false, features = ["std"] } +thiserror = "2.0.11" +uapi = "0.2.13" diff --git a/src/dbus/dynamic_type.rs b/crates/dbus-core/src/dynamic_type.rs similarity index 95% rename from src/dbus/dynamic_type.rs rename to crates/dbus-core/src/dynamic_type.rs index 765da57f..14a020b5 100644 --- a/src/dbus/dynamic_type.rs +++ b/crates/dbus-core/src/dynamic_type.rs @@ -3,10 +3,8 @@ use { TY_ARRAY, TY_BOOLEAN, TY_BYTE, TY_DOUBLE, TY_INT16, TY_INT32, TY_INT64, TY_OBJECT_PATH, TY_SIGNATURE, TY_STRING, TY_UINT16, TY_UINT32, TY_UINT64, TY_UNIX_FD, TY_VARIANT, }, - crate::{ - dbus::{DbusError, DynamicType, Parser, types::Variant}, - utils::buf::DynamicBuf, - }, + crate::{types::Variant, DbusError, DynamicType, Parser}, + jay_utils::buf::DynamicBuf, std::ops::Deref, }; @@ -156,11 +154,8 @@ impl DynamicType { } let mut vals = vec![]; { - let mut parser = Parser { - buf: &parser.buf[..parser.pos + len], - pos: parser.pos, - fds: parser.fds, - }; + let mut parser = + Parser::new_at(&parser.buf[..parser.pos + len], parser.pos, parser.fds); while !parser.eof() { vals.push(el.parse(&mut parser)?); } diff --git a/src/dbus/formatter.rs b/crates/dbus-core/src/formatter.rs similarity index 97% rename from src/dbus/formatter.rs rename to crates/dbus-core/src/formatter.rs index 798332e2..edf39bba 100644 --- a/src/dbus/formatter.rs +++ b/crates/dbus-core/src/formatter.rs @@ -1,8 +1,6 @@ use { - crate::{ - dbus::{DbusType, Formatter, types::Variant}, - utils::buf::DynamicBuf, - }, + crate::{types::Variant, DbusType, Formatter}, + jay_utils::buf::DynamicBuf, std::rc::Rc, uapi::{OwnedFd, Packed}, }; diff --git a/crates/dbus-core/src/lib.rs b/crates/dbus-core/src/lib.rs new file mode 100644 index 00000000..c75d05a1 --- /dev/null +++ b/crates/dbus-core/src/lib.rs @@ -0,0 +1,232 @@ +pub use {property::{Get, GetReply}, types::*}; +use { + jay_bufio::BufIoError, + jay_io_uring::IoUringError, + jay_utils::{buf::DynamicBuf, oserror::OsError}, + std::{borrow::Cow, fmt::Display, rc::Rc}, + thiserror::Error, + uapi::OwnedFd, +}; + +mod dynamic_type; +mod formatter; +mod parser; +pub mod property; +pub mod types; + +#[derive(Debug)] +pub struct CallError { + pub name: String, + pub msg: Option, +} + +impl Display for CallError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(msg) = &self.msg { + write!(f, "{}: {}", self.name, msg) + } else { + write!(f, "{}", self.name) + } + } +} + +#[derive(Debug, Error)] +pub enum DbusError { + #[error("Encountered an unknown type in a signature")] + UnknownType, + #[error("Function call reply does not contain a reply serial")] + NoReplySerial, + #[error("Signal message contains no interface or member or path")] + MissingSignalHeaders, + #[error("Method call message contains no interface or member or path")] + MissingMethodCallHeaders, + #[error("Error has no error name")] + NoErrorName, + #[error("The socket was killed")] + Killed, + #[error("{0}")] + CallError(CallError), + #[error("FD index is out of bounds")] + OobFds, + #[error("Variant has an invalid type")] + InvalidVariantType, + #[error("Could not create a socket")] + Socket(#[source] OsError), + #[error("Could not connect")] + Connect(#[source] IoUringError), + #[error("Could not write to the dbus socket")] + WriteError(#[source] IoUringError), + #[error("Could not read from the dbus socket")] + ReadError(#[source] IoUringError), + #[error("timeout")] + IoUringError(#[source] Box), + #[error("Server did not send auth challenge")] + NoChallenge, + #[error("Server did not accept our authentication")] + Auth, + #[error("Array length is not a multiple of the element size")] + PodArrayLength, + #[error("Peer did not send enough fds")] + TooFewFds, + #[error("Variant signature is not a single type")] + TrailingVariantSignature, + #[error("Dict signature does not contain a terminating '}}'")] + UnterminatedDict, + #[error("Struct signature does not contain a terminating '}}'")] + UnterminatedStruct, + #[error("Dict signature contains trailing types")] + DictTrailing, + #[error("String does not contain valid UTF-8")] + InvalidUtf8, + #[error("Unexpected end of message")] + UnexpectedEof, + #[error("Boolean value was not 0 or 1")] + InvalidBoolValue, + #[error("Signature is empty")] + EmptySignature, + #[error("The session bus address is not set")] + SessionBusAddressNotSet, + #[error("Server does not support FD passing")] + UnixFd, + #[error("Server message has a different endianess than ourselves")] + InvalidEndianess, + #[error("Server speaks an unexpected protocol version")] + InvalidProtocol, + #[error("Signature contains an invalid type")] + InvalidSignatureType, + #[error("The signal already has a handler")] + AlreadyHandled, + #[error(transparent)] + BufIoError(#[from] BufIoError), + #[error(transparent)] + DbusError(Rc), +} + +impl From for DbusError { + fn from(e: IoUringError) -> Self { + DbusError::IoUringError(Box::new(e)) + } +} + +const TY_BYTE: u8 = b'y'; +const TY_BOOLEAN: u8 = b'b'; +const TY_INT16: u8 = b'n'; +const TY_UINT16: u8 = b'q'; +const TY_INT32: u8 = b'i'; +const TY_UINT32: u8 = b'u'; +const TY_INT64: u8 = b'x'; +const TY_UINT64: u8 = b't'; +const TY_DOUBLE: u8 = b'd'; +const TY_STRING: u8 = b's'; +const TY_OBJECT_PATH: u8 = b'o'; +const TY_SIGNATURE: u8 = b'g'; +const TY_ARRAY: u8 = b'a'; +const TY_VARIANT: u8 = b'v'; +const TY_UNIX_FD: u8 = b'h'; + +#[derive(Clone, Debug)] +pub enum DynamicType { + U8, + Bool, + I16, + U16, + I32, + U32, + I64, + U64, + F64, + String, + ObjectPath, + Signature, + Variant, + Fd, + Array(Box), + DictEntry(Box, Box), + Struct(Vec), +} + +pub struct Parser<'a> { + pub(crate) buf: &'a [u8], + pub(crate) pos: usize, + pub(crate) fds: &'a [Rc], +} + +pub struct Formatter<'a> { + fds: &'a mut Vec>, + buf: &'a mut DynamicBuf, +} + +pub unsafe trait Message<'a>: Sized + 'a { + const SIGNATURE: &'static str; + const INTERFACE: &'static str; + const MEMBER: &'static str; + type Generic<'b>: Message<'b>; + + fn marshal(&self, w: &mut Formatter); + fn unmarshal(p: &mut Parser<'a>) -> Result; + fn num_fds(&self) -> u32; +} + +pub struct ErrorMessage<'a> { + pub msg: Cow<'a, str>, +} + +unsafe impl<'a> Message<'a> for ErrorMessage<'a> { + const SIGNATURE: &'static str = "s"; + const INTERFACE: &'static str = ""; + const MEMBER: &'static str = ""; + type Generic<'b> = ErrorMessage<'b>; + + fn marshal(&self, w: &mut Formatter) { + self.msg.marshal(w) + } + + fn unmarshal(p: &mut Parser<'a>) -> Result { + Ok(Self { + msg: p.unmarshal()?, + }) + } + + fn num_fds(&self) -> u32 { + 0 + } +} + +pub trait Property { + const INTERFACE: &'static str; + const PROPERTY: &'static str; + type Type: DbusType<'static>; +} + +pub trait Signal<'a>: Message<'a> {} + +pub trait MethodCall<'a>: Message<'a> { + type Reply: Message<'static>; +} + +pub unsafe trait DbusType<'a>: Clone + 'a { + const ALIGNMENT: usize; + const IS_POD: bool; + type Generic<'b>: DbusType<'b> + 'b; + + fn consume_signature(s: &mut &[u8]) -> Result<(), DbusError>; + #[allow(clippy::allow_attributes, dead_code)] + fn write_signature(w: &mut Vec); + fn marshal(&self, fmt: &mut Formatter); + fn unmarshal(parser: &mut Parser<'a>) -> Result; + + fn num_fds(&self) -> u32 { + 0 + } +} + +pub mod prelude { + pub use { + super::{ + DbusError, DbusType, Formatter, Message, MethodCall, Parser, Property, Signal, + types::{Bool, DictEntry, ObjectPath, Variant}, + }, + std::{borrow::Cow, rc::Rc}, + uapi::OwnedFd, + }; +} diff --git a/src/dbus/parser.rs b/crates/dbus-core/src/parser.rs similarity index 92% rename from src/dbus/parser.rs rename to crates/dbus-core/src/parser.rs index 95b2525c..1821e283 100644 --- a/src/dbus/parser.rs +++ b/crates/dbus-core/src/parser.rs @@ -1,7 +1,7 @@ use { - crate::dbus::{ + crate::{ + types::{Bool, ObjectPath, Signature, Variant, FALSE, TRUE}, DbusError, DbusType, DynamicType, Parser, - types::{Bool, FALSE, ObjectPath, Signature, TRUE, Variant}, }, bstr::ByteSlice, std::{borrow::Cow, rc::Rc}, @@ -10,7 +10,11 @@ use { impl<'a> Parser<'a> { pub fn new(buf: &'a [u8], fds: &'a [Rc]) -> Self { - Self { buf, pos: 0, fds } + Self::new_at(buf, 0, fds) + } + + pub fn new_at(buf: &'a [u8], pos: usize, fds: &'a [Rc]) -> Self { + Self { buf, pos, fds } } pub fn eof(&self) -> bool { @@ -107,11 +111,7 @@ impl<'a> Parser<'a> { self.pos += len; Ok(Cow::Borrowed(slice)) } else { - let mut parser = Parser { - buf: &self.buf[..self.pos + len], - pos: self.pos, - fds: self.fds, - }; + let mut parser = Parser::new_at(&self.buf[..self.pos + len], self.pos, self.fds); self.pos += len; let mut res = vec![]; while !parser.eof() { diff --git a/src/dbus/property.rs b/crates/dbus-core/src/property.rs similarity index 95% rename from src/dbus/property.rs rename to crates/dbus-core/src/property.rs index 651e1767..3fd454fa 100644 --- a/src/dbus/property.rs +++ b/crates/dbus-core/src/property.rs @@ -1,5 +1,5 @@ use { - crate::dbus::{DbusError, DbusType, Formatter, Message, MethodCall, Parser}, + crate::{DbusError, DbusType, Formatter, Message, MethodCall, Parser}, std::{borrow::Cow, marker::PhantomData}, }; diff --git a/src/dbus/types.rs b/crates/dbus-core/src/types.rs similarity index 89% rename from src/dbus/types.rs rename to crates/dbus-core/src/types.rs index ee811e21..5e00897c 100644 --- a/src/dbus/types.rs +++ b/crates/dbus-core/src/types.rs @@ -1,12 +1,10 @@ use { crate::{ - dbus::{ - DbusError, DbusType, DynamicType, Formatter, Parser, TY_ARRAY, TY_BOOLEAN, TY_BYTE, - TY_DOUBLE, TY_INT16, TY_INT32, TY_INT64, TY_OBJECT_PATH, TY_SIGNATURE, TY_STRING, - TY_UINT16, TY_UINT32, TY_UINT64, TY_UNIX_FD, TY_VARIANT, - }, - utils::buf::DynamicBuf, + DbusError, DbusType, DynamicType, Formatter, Parser, TY_ARRAY, TY_BOOLEAN, TY_BYTE, + TY_DOUBLE, TY_INT16, TY_INT32, TY_INT64, TY_OBJECT_PATH, TY_SIGNATURE, TY_STRING, + TY_UINT16, TY_UINT32, TY_UINT64, TY_UNIX_FD, TY_VARIANT, }, + jay_utils::buf::DynamicBuf, std::{borrow::Cow, ops::Deref, rc::Rc}, uapi::{OwnedFd, Packed, Pod}, }; @@ -501,31 +499,6 @@ impl<'a> Variant<'a> { w.push(c); } - pub fn borrow<'b>(&'b self) -> Variant<'b> { - match self { - Variant::U8(v) => Variant::U8(*v), - Variant::Bool(v) => Variant::Bool(*v), - Variant::I16(v) => Variant::I16(*v), - Variant::U16(v) => Variant::U16(*v), - Variant::I32(v) => Variant::I32(*v), - Variant::U32(v) => Variant::U32(*v), - Variant::I64(v) => Variant::I64(*v), - Variant::U64(v) => Variant::U64(*v), - Variant::F64(v) => Variant::F64(*v), - Variant::String(v) => Variant::String(v.deref().into()), - Variant::ObjectPath(v) => Variant::ObjectPath(ObjectPath(v.0.deref().into())), - Variant::Signature(v) => Variant::Signature(Signature(v.0.deref().into())), - Variant::Variant(v) => Variant::Variant(Box::new(v.deref().borrow())), - Variant::Fd(v) => Variant::Fd(v.clone()), - Variant::Array(t, v) => { - Variant::Array(t.clone(), v.iter().map(|v| v.borrow()).collect()) - } - Variant::DictEntry(k, v) => { - Variant::DictEntry(Box::new(k.deref().borrow()), Box::new(v.deref().borrow())) - } - Variant::Struct(v) => Variant::Struct(v.iter().map(|v| v.borrow()).collect()), - } - } } unsafe impl<'a> DbusType<'a> for Variant<'a> { diff --git a/crates/drm-feedback/Cargo.toml b/crates/drm-feedback/Cargo.toml new file mode 100644 index 00000000..ac912d4b --- /dev/null +++ b/crates/drm-feedback/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "jay-drm-feedback" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +jay-video-types = { path = "../video-types" } + +ahash = "0.8.7" +byteorder = "1.5.0" +thiserror = "2.0.11" +uapi = "0.2.13" diff --git a/crates/drm-feedback/src/lib.rs b/crates/drm-feedback/src/lib.rs new file mode 100644 index 00000000..69ac676d --- /dev/null +++ b/crates/drm-feedback/src/lib.rs @@ -0,0 +1,149 @@ +use { + ahash::AHashMap, + byteorder::{NativeEndian, WriteBytesExt}, + jay_video_types::Modifier, + std::{io::Write, rc::Rc}, + thiserror::Error, + uapi::{OwnedFd, c}, +}; + +#[derive(Debug)] +pub struct DrmFeedbackIds { + next: std::cell::Cell, +} + +impl Default for DrmFeedbackIds { + fn default() -> Self { + Self { + next: std::cell::Cell::new(1), + } + } +} + +impl DrmFeedbackIds { + pub fn next(&self) -> DrmFeedbackId { + let id = self.next.get(); + self.next.set(id + 1); + DrmFeedbackId(id) + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct DrmFeedbackId(u64); + +#[derive(Debug)] +pub struct DrmFeedbackShared { + pub fd: Rc, + pub size: usize, + pub main_device: c::dev_t, + pub indices: AHashMap<(u32, Modifier), u16>, +} + +#[derive(Debug)] +pub struct DrmFeedback { + pub id: DrmFeedbackId, + pub shared: Rc, + pub tranches: Vec, +} + +#[derive(Clone, Debug)] +pub struct DrmFeedbackTranche { + pub device: c::dev_t, + pub indices: Vec, + pub scanout: bool, +} + +impl DrmFeedback { + pub fn new( + ids: &DrmFeedbackIds, + render_ctx: &C, + ) -> Result { + let main_device = match render_ctx.main_device() { + Some(dev) => dev, + _ => return Err(DrmFeedbackError::NoDrmDevice), + }; + let (data, index_map) = create_fd_data(render_ctx); + let mut memfd = + uapi::memfd_create("drm_feedback", c::MFD_CLOEXEC | c::MFD_ALLOW_SEALING).unwrap(); + memfd.write_all(&data).unwrap(); + uapi::lseek(memfd.raw(), 0, c::SEEK_SET).unwrap(); + uapi::fcntl_add_seals( + memfd.raw(), + c::F_SEAL_SEAL | c::F_SEAL_GROW | c::F_SEAL_SHRINK | c::F_SEAL_WRITE, + ) + .unwrap(); + Ok(Self { + id: ids.next(), + tranches: vec![DrmFeedbackTranche { + device: main_device, + indices: (0..index_map.len()).map(|v| v as u16).collect(), + scanout: false, + }], + shared: Rc::new(DrmFeedbackShared { + fd: Rc::new(memfd), + size: data.len(), + main_device, + indices: index_map, + }), + }) + } + + pub fn for_scanout( + &self, + ids: &DrmFeedbackIds, + devnum: c::dev_t, + formats: &[(u32, Modifier)], + ) -> Result, DrmFeedbackError> { + let mut tranches = vec![]; + { + let mut indices = vec![]; + for (format, modifier) in formats { + if let Some(idx) = self.shared.indices.get(&(*format, *modifier)) { + indices.push(*idx); + } + } + if indices.len() > 0 { + tranches.push(DrmFeedbackTranche { + device: devnum, + indices, + scanout: true, + }); + } else { + return Ok(None); + } + } + tranches.extend(self.tranches.iter().cloned()); + Ok(Some(Self { + id: ids.next(), + shared: self.shared.clone(), + tranches, + })) + } +} + +pub trait DrmFeedbackContext { + fn main_device(&self) -> Option; + fn for_each_read_format(&self, f: &mut dyn FnMut(u32, Modifier)); +} + +fn create_fd_data( + ctx: &C, +) -> (Vec, AHashMap<(u32, Modifier), u16>) { + let mut vec = vec![]; + let mut map = AHashMap::new(); + let mut pos = 0; + ctx.for_each_read_format(&mut |format, modifier| { + vec.write_u32::(format).unwrap(); + vec.write_u32::(0).unwrap(); + vec.write_u64::(modifier).unwrap(); + map.insert((format, modifier), pos); + pos += 1; + }); + (vec, map) +} + +#[derive(Debug, Error)] +pub enum DrmFeedbackError { + #[error("Graphics API does not have a DRM device")] + NoDrmDevice, +} diff --git a/crates/edid/Cargo.toml b/crates/edid/Cargo.toml new file mode 100644 index 00000000..b7f3416c --- /dev/null +++ b/crates/edid/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "jay-edid" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "EDID parsing for Jay" +repository = "https://github.com/mahkoh/jay" + +[dependencies] +bstr = { version = "1.9.0", default-features = false, features = ["std"] } +thiserror = "2.0.11" diff --git a/src/edid.rs b/crates/edid/src/lib.rs similarity index 98% rename from src/edid.rs rename to crates/edid/src/lib.rs index 685ac0a1..1272e8a7 100644 --- a/src/edid.rs +++ b/crates/edid/src/lib.rs @@ -1,15 +1,48 @@ use { - crate::utils::{ - bitflags::BitflagsExt, clonecell::UnsafeCellCloneSafe, ptr_ext::PtrExt, stack::Stack, - }, bstr::{BString, ByteSlice}, std::{ + cell::RefCell, fmt::{Debug, Formatter}, rc::Rc, }, thiserror::Error, }; +trait BitflagsExt { + fn contains(self, other: Self) -> bool; +} + +impl BitflagsExt for u8 { + fn contains(self, other: Self) -> bool { + self & other == other + } +} + +struct Stack(RefCell>); + +impl Default for Stack { + fn default() -> Self { + Self(Default::default()) + } +} + +impl Stack { + fn push(&self, v: T) { + self.0.borrow_mut().push(v); + } + + fn pop(&self) -> Option { + self.0.borrow_mut().pop() + } + + fn to_vec(&self) -> Vec + where + T: Clone, + { + self.0.borrow().clone() + } +} + #[derive(Copy, Clone, Debug)] pub enum ColorBitDepth { Undefined, @@ -30,7 +63,6 @@ pub enum DigitalVideoInterfaceStandard { HdmiB, MDDI, DisplayPort, - #[expect(dead_code)] Unknown(u8), } @@ -50,7 +82,6 @@ impl Debug for SignalLevelStandard { } #[derive(Copy, Clone, Debug)] -#[expect(dead_code)] pub enum VideoInputDefinition { Analog { signal_level_standard: SignalLevelStandard, @@ -87,7 +118,6 @@ pub struct ChromaticityCoordinates { } #[derive(Copy, Clone, Debug)] -#[expect(dead_code)] pub struct EstablishedTimings { pub s_720x400_70: bool, pub s_720x400_88: bool, @@ -118,7 +148,6 @@ pub enum AspectRatio { } #[derive(Copy, Clone, Debug)] -#[expect(dead_code)] pub struct StandardTiming { pub x_resolution: u16, pub aspect_ratio: AspectRatio, @@ -132,7 +161,6 @@ pub enum AnalogSyncType { } #[derive(Copy, Clone, Debug)] -#[expect(dead_code)] pub enum SyncSignal { Analog { ty: AnalogSyncType, @@ -184,7 +212,6 @@ impl Debug for StereoViewingSupport { } #[derive(Copy, Clone, Debug)] -#[expect(dead_code)] pub struct DisplayRangeLimitsAndAdditionalTiming { pub vertical_field_rate_min: u16, pub vertical_field_rate_max: u16, @@ -201,12 +228,10 @@ pub enum AspectRatioPreference { A16_10, A5_4, A15_9, - #[expect(dead_code)] Unknown(u8), } #[derive(Copy, Clone, Debug)] -#[expect(dead_code)] pub enum ExtendedTimingInformation { DefaultGtf, NoTimingInformation, @@ -240,7 +265,6 @@ pub enum ExtendedTimingInformation { } #[derive(Copy, Clone, Debug, Default)] -#[expect(dead_code)] pub struct ColorPoint { pub white_point_index: u8, pub white_point_x: u16, @@ -249,7 +273,6 @@ pub struct ColorPoint { } #[derive(Copy, Clone, Debug)] -#[expect(dead_code)] pub struct EstablishedTimings3 { pub s640x350_85: bool, pub s640x400_85: bool, @@ -298,7 +321,6 @@ pub struct EstablishedTimings3 { } #[derive(Copy, Clone, Debug)] -#[expect(dead_code)] pub struct ColorManagementData { pub red_a3: u16, pub red_a2: u16, @@ -325,7 +347,6 @@ pub enum CvtPreferredVerticalRate { } #[derive(Copy, Clone, Debug)] -#[expect(dead_code)] pub struct Cvt3ByteCode { pub addressable_lines_per_field: u16, pub aspect_ration: CvtAspectRatio, @@ -338,7 +359,6 @@ pub struct Cvt3ByteCode { } #[derive(Copy, Clone, Debug)] -#[expect(dead_code)] pub struct DetailedTimingDescriptor { pub pixel_clock_khz: u32, pub horizontal_addressable_pixels: u16, @@ -359,7 +379,6 @@ pub struct DetailedTimingDescriptor { } #[derive(Clone, Debug)] -#[expect(dead_code)] pub enum Descriptor { Unknown(u8), DetailedTimingDescriptor(DetailedTimingDescriptor), @@ -393,7 +412,6 @@ macro_rules! bail { #[derive(Clone, Debug)] pub enum EdidParseContext { - #[expect(dead_code)] ReadingBytes(usize), BaseBlock, Descriptors, @@ -410,8 +428,6 @@ pub enum EdidParseContext { VideoInputDefinition, } -unsafe impl UnsafeCellCloneSafe for EdidParseContext {} - struct EdidPushedContext { stack: Rc>, } @@ -453,7 +469,7 @@ impl<'a> EdidParser<'a> { if self.data.len() - self.pos < N { bail!(self, EdidError::UnexpectedEof); } - let v = unsafe { self.data[self.pos..].as_ptr().cast::<[u8; N]>().deref() }; + let v = self.data[self.pos..self.pos + N].try_into().unwrap(); self.pos += N; Ok(v) } @@ -1161,7 +1177,6 @@ pub enum DisplayColorType { } #[derive(Debug)] -#[expect(dead_code)] pub enum FeatureSupport2 { Analog { display_color_type: DisplayColorType, @@ -1174,7 +1189,6 @@ pub enum FeatureSupport2 { } #[derive(Debug)] -#[expect(dead_code)] pub struct FeatureSupport { pub standby_supported: bool, pub suspend_supported: bool, @@ -1186,7 +1200,6 @@ pub struct FeatureSupport { } #[derive(Debug)] -#[expect(dead_code)] pub struct EdidBaseBlock { pub id_manufacturer_name: BString, pub id_product_code: u16, @@ -1229,12 +1242,10 @@ pub enum CtaDataBlock { #[derive(Debug)] pub struct CtaAmdVendorDataBlock { pub minimum_refresh_hz: u8, - #[expect(dead_code)] pub maximum_refresh_hz: u8, } #[derive(Copy, Clone, Debug)] -#[expect(dead_code)] pub struct CtaColorimetryDataBlock { pub bt2020_rgb: bool, pub bt2020_ycc: bool, @@ -1248,7 +1259,6 @@ pub struct CtaColorimetryDataBlock { } #[derive(Copy, Clone, Debug)] -#[expect(dead_code)] pub struct CtaStaticHdrMetadataDataBlock { pub traditional_gamma_sdr_luminance: bool, pub traditional_gamma_hdr_luminance: bool, diff --git a/crates/eventfd-cache/Cargo.toml b/crates/eventfd-cache/Cargo.toml new file mode 100644 index 00000000..03604f58 --- /dev/null +++ b/crates/eventfd-cache/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "jay-eventfd-cache" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +jay-async-engine = { path = "../async-engine" } +jay-io-uring = { path = "../io-uring" } +jay-utils = { path = "../utils" } + +log = { version = "0.4.20", features = ["std"] } +thiserror = "2.0.11" +uapi = "0.2.13" diff --git a/src/eventfd_cache.rs b/crates/eventfd-cache/src/lib.rs similarity index 93% rename from src/eventfd_cache.rs rename to crates/eventfd-cache/src/lib.rs index d2e8b55a..336b2fde 100644 --- a/src/eventfd_cache.rs +++ b/crates/eventfd-cache/src/lib.rs @@ -1,14 +1,12 @@ use { - crate::{ - async_engine::{AsyncEngine, SpawnedFuture}, - io_uring::{IoUring, IoUringError}, - utils::{ - buf::Buf, - errorfmt::ErrorFmt, - oserror::{OsError, OsErrorExt, OsErrorExt2}, - queue::AsyncQueue, - stack::Stack, - }, + jay_async_engine::{AsyncEngine, SpawnedFuture}, + jay_io_uring::{IoUring, IoUringError}, + jay_utils::{ + buf::Buf, + errorfmt::ErrorFmt, + oserror::{OsError, OsErrorExt, OsErrorExt2}, + queue::AsyncQueue, + stack::Stack, }, std::{cell::Cell, future::poll_fn, pin::Pin, rc::Rc, slice, task::Poll}, thiserror::Error, diff --git a/src/eventfd_cache/tests.rs b/crates/eventfd-cache/src/tests.rs similarity index 94% rename from src/eventfd_cache/tests.rs rename to crates/eventfd-cache/src/tests.rs index 2c536f6d..2e6ad069 100644 --- a/src/eventfd_cache/tests.rs +++ b/crates/eventfd-cache/src/tests.rs @@ -1,7 +1,8 @@ use { - crate::{ - async_engine::AsyncEngine, eventfd_cache::EventfdCache, io_uring::IoUring, utils::array, - }, + crate::EventfdCache, + jay_async_engine::AsyncEngine, + jay_io_uring::IoUring, + jay_utils::array, std::{rc::Rc, slice}, uapi::c, }; diff --git a/crates/formats/Cargo.toml b/crates/formats/Cargo.toml new file mode 100644 index 00000000..d44f7a3d --- /dev/null +++ b/crates/formats/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "jay-formats" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "Pixel format tables for Jay" +repository = "https://github.com/mahkoh/jay" + +[dependencies] +ahash = "0.8.7" +ash = { package = "jay-ash", version = "0.3.0" } +clap = { version = "4.4.18", features = ["derive", "wrap_help"] } +jay-config = { path = "../jay-config" } diff --git a/crates/formats/src/lib.rs b/crates/formats/src/lib.rs new file mode 100644 index 00000000..0599e878 --- /dev/null +++ b/crates/formats/src/lib.rs @@ -0,0 +1,559 @@ +use { + ahash::AHashMap, + ash::vk, + clap::{ValueEnum, builder::PossibleValue}, + jay_config::video::Format as ConfigFormat, + std::{ + fmt::{self, Debug, Write}, + sync::LazyLock, + }, +}; + +pub type GLenum = u32; +pub type GLint = i32; + +const GL_RGBA: GLint = 0x1908; +const GL_RGBA8: GLenum = 0x8058; +const GL_BGRA_EXT: GLint = 0x80E1; +const GL_UNSIGNED_BYTE: GLint = 0x1401; + +#[derive(Copy, Clone, Debug)] +pub struct FormatShmInfo { + pub gl_format: GLint, + pub gl_internal_format: GLenum, + pub gl_type: GLint, +} + +#[derive(Copy, Clone, Debug)] +pub struct Format { + pub name: &'static str, + pub vk_format: vk::Format, + pub drm: u32, + pub wl_id: Option, + pub external_only_guess: bool, + pub has_alpha: bool, + pub opaque: Option<&'static Format>, + pub shm_info: Option, + pub config: ConfigFormat, + pub bpp: u32, +} + +const fn default(config: ConfigFormat) -> Format { + Format { + name: "", + vk_format: vk::Format::UNDEFINED, + drm: 0, + wl_id: None, + external_only_guess: false, + has_alpha: false, + opaque: None, + shm_info: None, + config, + bpp: 4, + } +} + +impl PartialEq for Format { + fn eq(&self, other: &Self) -> bool { + self.drm == other.drm + } +} + +impl Eq for Format {} + +impl ValueEnum for &'static Format { + fn value_variants<'a>() -> &'a [Self] { + ref_formats() + } + + fn to_possible_value(&self) -> Option { + Some(PossibleValue::new(self.name)) + } +} + +static FORMATS_MAP: LazyLock> = LazyLock::new(|| { + let mut map = AHashMap::new(); + for format in FORMATS { + assert!(map.insert(format.drm, format).is_none()); + } + map +}); + +static FORMATS_REFS: LazyLock> = LazyLock::new(|| FORMATS.iter().collect()); + +static FORMATS_NAMES: LazyLock> = LazyLock::new(|| { + let mut map = AHashMap::new(); + for format in FORMATS { + assert!(map.insert(format.name, format).is_none()); + } + map +}); + +static FORMATS_CONFIG: LazyLock> = LazyLock::new(|| { + let mut map = AHashMap::new(); + for format in FORMATS { + assert!(map.insert(format.config, format).is_none()); + } + map +}); + +#[test] +fn formats_dont_panic() { + formats(); + named_formats(); + config_formats(); +} + +pub fn formats() -> &'static AHashMap { + &FORMATS_MAP +} + +pub fn ref_formats() -> &'static [&'static Format] { + &FORMATS_REFS +} + +pub fn named_formats() -> &'static AHashMap<&'static str, &'static Format> { + &FORMATS_NAMES +} + +pub fn config_formats() -> &'static AHashMap { + &FORMATS_CONFIG +} + +const fn fourcc_code(a: char, b: char, c: char, d: char) -> u32 { + (a as u32) | ((b as u32) << 8) | ((c as u32) << 16) | ((d as u32) << 24) +} + +pub fn debug(fourcc: u32) -> impl Debug { + fmt::from_fn(move |fmt| { + fmt.write_char(fourcc as u8 as char)?; + fmt.write_char((fourcc >> 8) as u8 as char)?; + fmt.write_char((fourcc >> 16) as u8 as char)?; + fmt.write_char((fourcc >> 24) as u8 as char)?; + Ok(()) + }) +} + +const ARGB8888_ID: u32 = 0; +const ARGB8888_DRM: u32 = fourcc_code('A', 'R', '2', '4'); + +const XRGB8888_ID: u32 = 1; +const XRGB8888_DRM: u32 = fourcc_code('X', 'R', '2', '4'); + +pub fn map_wayland_format_id(id: u32) -> u32 { + match id { + ARGB8888_ID => ARGB8888_DRM, + XRGB8888_ID => XRGB8888_DRM, + _ => id, + } +} + +pub static ARGB8888: &Format = &Format { + name: "argb8888", + shm_info: Some(FormatShmInfo { + gl_format: GL_BGRA_EXT, + gl_internal_format: GL_RGBA8, + gl_type: GL_UNSIGNED_BYTE, + }), + vk_format: vk::Format::B8G8R8A8_UNORM, + bpp: 4, + drm: ARGB8888_DRM, + wl_id: Some(ARGB8888_ID), + external_only_guess: false, + has_alpha: true, + opaque: Some(XRGB8888), + config: ConfigFormat::ARGB8888, +}; + +pub static XRGB8888: &Format = &Format { + name: "xrgb8888", + shm_info: Some(FormatShmInfo { + gl_format: GL_BGRA_EXT, + gl_internal_format: GL_RGBA8, + gl_type: GL_UNSIGNED_BYTE, + }), + vk_format: vk::Format::B8G8R8A8_UNORM, + bpp: 4, + drm: XRGB8888_DRM, + wl_id: Some(XRGB8888_ID), + external_only_guess: false, + has_alpha: false, + opaque: None, + config: ConfigFormat::XRGB8888, +}; + +static ABGR8888: &Format = &Format { + name: "abgr8888", + shm_info: Some(FormatShmInfo { + gl_format: GL_RGBA, + gl_internal_format: GL_RGBA8, + gl_type: GL_UNSIGNED_BYTE, + }), + vk_format: vk::Format::R8G8B8A8_UNORM, + bpp: 4, + drm: fourcc_code('A', 'B', '2', '4'), + wl_id: None, + external_only_guess: false, + has_alpha: true, + opaque: Some(XBGR8888), + config: ConfigFormat::ABGR8888, +}; + +static XBGR8888: &Format = &Format { + name: "xbgr8888", + shm_info: Some(FormatShmInfo { + gl_format: GL_RGBA, + gl_internal_format: GL_RGBA8, + gl_type: GL_UNSIGNED_BYTE, + }), + vk_format: vk::Format::R8G8B8A8_UNORM, + bpp: 4, + drm: fourcc_code('X', 'B', '2', '4'), + wl_id: None, + external_only_guess: false, + has_alpha: false, + opaque: None, + config: ConfigFormat::XBGR8888, +}; + +static R8: &Format = &Format { + name: "r8", + vk_format: vk::Format::R8_UNORM, + bpp: 1, + drm: fourcc_code('R', '8', ' ', ' '), + ..default(ConfigFormat::R8) +}; + +static GR88: &Format = &Format { + name: "gr88", + vk_format: vk::Format::R8G8_UNORM, + bpp: 2, + drm: fourcc_code('G', 'R', '8', '8'), + ..default(ConfigFormat::GR88) +}; + +static RGB888: &Format = &Format { + name: "rgb888", + vk_format: vk::Format::B8G8R8_UNORM, + bpp: 3, + drm: fourcc_code('R', 'G', '2', '4'), + ..default(ConfigFormat::RGB888) +}; + +static BGR888: &Format = &Format { + name: "bgr888", + vk_format: vk::Format::R8G8B8_UNORM, + bpp: 3, + drm: fourcc_code('B', 'G', '2', '4'), + ..default(ConfigFormat::BGR888) +}; + +static RGBA4444: &Format = &Format { + name: "rgba4444", + vk_format: vk::Format::R4G4B4A4_UNORM_PACK16, + bpp: 2, + drm: fourcc_code('R', 'A', '1', '2'), + has_alpha: true, + opaque: Some(RGBX4444), + ..default(ConfigFormat::RGBA4444) +}; + +static RGBX4444: &Format = &Format { + name: "rgbx4444", + vk_format: vk::Format::R4G4B4A4_UNORM_PACK16, + bpp: 2, + drm: fourcc_code('R', 'X', '1', '2'), + ..default(ConfigFormat::RGBX4444) +}; + +static BGRA4444: &Format = &Format { + name: "bgra4444", + vk_format: vk::Format::B4G4R4A4_UNORM_PACK16, + bpp: 2, + drm: fourcc_code('B', 'A', '1', '2'), + has_alpha: true, + opaque: Some(BGRX4444), + ..default(ConfigFormat::BGRA4444) +}; + +static BGRX4444: &Format = &Format { + name: "bgrx4444", + vk_format: vk::Format::B4G4R4A4_UNORM_PACK16, + bpp: 2, + drm: fourcc_code('B', 'X', '1', '2'), + ..default(ConfigFormat::BGRX4444) +}; + +static RGB565: &Format = &Format { + name: "rgb565", + vk_format: vk::Format::R5G6B5_UNORM_PACK16, + bpp: 2, + drm: fourcc_code('R', 'G', '1', '6'), + ..default(ConfigFormat::RGB565) +}; + +static BGR565: &Format = &Format { + name: "bgr565", + vk_format: vk::Format::B5G6R5_UNORM_PACK16, + bpp: 2, + drm: fourcc_code('B', 'G', '1', '6'), + ..default(ConfigFormat::BGR565) +}; + +static RGBA5551: &Format = &Format { + name: "rgba5551", + vk_format: vk::Format::R5G5B5A1_UNORM_PACK16, + bpp: 2, + drm: fourcc_code('R', 'A', '1', '5'), + has_alpha: true, + opaque: Some(RGBX5551), + ..default(ConfigFormat::RGBA5551) +}; + +static RGBX5551: &Format = &Format { + name: "rgbx5551", + vk_format: vk::Format::R5G5B5A1_UNORM_PACK16, + bpp: 2, + drm: fourcc_code('R', 'X', '1', '5'), + ..default(ConfigFormat::RGBX5551) +}; + +static BGRA5551: &Format = &Format { + name: "bgra5551", + vk_format: vk::Format::B5G5R5A1_UNORM_PACK16, + bpp: 2, + drm: fourcc_code('B', 'A', '1', '5'), + has_alpha: true, + opaque: Some(BGRX5551), + ..default(ConfigFormat::BGRA5551) +}; + +static BGRX5551: &Format = &Format { + name: "bgrx5551", + vk_format: vk::Format::B5G5R5A1_UNORM_PACK16, + bpp: 2, + drm: fourcc_code('B', 'X', '1', '5'), + ..default(ConfigFormat::BGRX5551) +}; + +static ARGB1555: &Format = &Format { + name: "argb1555", + vk_format: vk::Format::A1R5G5B5_UNORM_PACK16, + bpp: 2, + drm: fourcc_code('A', 'R', '1', '5'), + has_alpha: true, + opaque: Some(XRGB1555), + ..default(ConfigFormat::ARGB1555) +}; + +static XRGB1555: &Format = &Format { + name: "xrgb1555", + vk_format: vk::Format::A1R5G5B5_UNORM_PACK16, + bpp: 2, + drm: fourcc_code('X', 'R', '1', '5'), + ..default(ConfigFormat::XRGB1555) +}; + +static ARGB2101010: &Format = &Format { + name: "argb2101010", + vk_format: vk::Format::A2R10G10B10_UNORM_PACK32, + bpp: 4, + drm: fourcc_code('A', 'R', '3', '0'), + has_alpha: true, + opaque: Some(XRGB2101010), + ..default(ConfigFormat::ARGB2101010) +}; + +static XRGB2101010: &Format = &Format { + name: "xrgb2101010", + vk_format: vk::Format::A2R10G10B10_UNORM_PACK32, + bpp: 4, + drm: fourcc_code('X', 'R', '3', '0'), + ..default(ConfigFormat::XRGB2101010) +}; + +static ABGR2101010: &Format = &Format { + name: "abgr2101010", + vk_format: vk::Format::A2B10G10R10_UNORM_PACK32, + bpp: 4, + drm: fourcc_code('A', 'B', '3', '0'), + has_alpha: true, + opaque: Some(XBGR2101010), + ..default(ConfigFormat::ABGR2101010) +}; + +static XBGR2101010: &Format = &Format { + name: "xbgr2101010", + vk_format: vk::Format::A2B10G10R10_UNORM_PACK32, + bpp: 4, + drm: fourcc_code('X', 'B', '3', '0'), + ..default(ConfigFormat::XBGR2101010) +}; + +static ABGR16161616: &Format = &Format { + name: "abgr16161616", + vk_format: vk::Format::R16G16B16A16_UNORM, + bpp: 8, + drm: fourcc_code('A', 'B', '4', '8'), + has_alpha: true, + opaque: Some(XBGR16161616), + ..default(ConfigFormat::ABGR16161616) +}; + +static XBGR16161616: &Format = &Format { + name: "xbgr16161616", + vk_format: vk::Format::R16G16B16A16_UNORM, + bpp: 8, + drm: fourcc_code('X', 'B', '4', '8'), + ..default(ConfigFormat::XBGR16161616) +}; + +pub static ABGR16161616F: &Format = &Format { + name: "abgr16161616f", + vk_format: vk::Format::R16G16B16A16_SFLOAT, + bpp: 8, + drm: fourcc_code('A', 'B', '4', 'H'), + has_alpha: true, + opaque: Some(XBGR16161616F), + ..default(ConfigFormat::ABGR16161616F) +}; + +static XBGR16161616F: &Format = &Format { + name: "xbgr16161616f", + vk_format: vk::Format::R16G16B16A16_SFLOAT, + bpp: 8, + drm: fourcc_code('X', 'B', '4', 'H'), + ..default(ConfigFormat::XBGR16161616F) +}; + +static BGR161616: &Format = &Format { + name: "bgr161616", + vk_format: vk::Format::R16G16B16_UNORM, + bpp: 6, + drm: fourcc_code('B', 'G', '4', '8'), + ..default(ConfigFormat::BGR161616) +}; + +static R16F: &Format = &Format { + name: "r16f", + vk_format: vk::Format::R16_SFLOAT, + bpp: 2, + drm: fourcc_code('R', ' ', ' ', 'H'), + ..default(ConfigFormat::R16F) +}; + +static GR1616F: &Format = &Format { + name: "gr1616f", + vk_format: vk::Format::R16G16_SFLOAT, + bpp: 4, + drm: fourcc_code('G', 'R', ' ', 'H'), + ..default(ConfigFormat::GR1616F) +}; + +static BGR161616F: &Format = &Format { + name: "bgr161616f", + vk_format: vk::Format::R16G16B16_SFLOAT, + bpp: 6, + drm: fourcc_code('B', 'G', 'R', 'H'), + ..default(ConfigFormat::BGR161616F) +}; + +static R32F: &Format = &Format { + name: "r32f", + vk_format: vk::Format::R32_SFLOAT, + bpp: 4, + drm: fourcc_code('R', ' ', ' ', 'F'), + ..default(ConfigFormat::R32F) +}; + +static GR3232F: &Format = &Format { + name: "gr3232f", + vk_format: vk::Format::R32G32_SFLOAT, + bpp: 8, + drm: fourcc_code('G', 'R', ' ', 'F'), + ..default(ConfigFormat::GR3232F) +}; + +static BGR323232F: &Format = &Format { + name: "bgr323232f", + vk_format: vk::Format::R32G32B32_SFLOAT, + bpp: 12, + drm: fourcc_code('B', 'G', 'R', 'F'), + ..default(ConfigFormat::BGR323232F) +}; + +static ABGR32323232F: &Format = &Format { + name: "abgr32323232f", + vk_format: vk::Format::R32G32B32A32_SFLOAT, + bpp: 16, + drm: fourcc_code('A', 'B', '8', 'F'), + has_alpha: true, + ..default(ConfigFormat::ABGR32323232F) +}; + +pub static FORMATS: &[Format] = &[ + *ARGB8888, + *XRGB8888, + *ABGR8888, + *XBGR8888, + *R8, + *GR88, + *RGB888, + *BGR888, + #[cfg(target_endian = "little")] + *RGBA4444, + #[cfg(target_endian = "little")] + *RGBX4444, + #[cfg(target_endian = "little")] + *BGRA4444, + #[cfg(target_endian = "little")] + *BGRX4444, + #[cfg(target_endian = "little")] + *RGB565, + #[cfg(target_endian = "little")] + *BGR565, + #[cfg(target_endian = "little")] + *RGBA5551, + #[cfg(target_endian = "little")] + *RGBX5551, + #[cfg(target_endian = "little")] + *BGRA5551, + #[cfg(target_endian = "little")] + *BGRX5551, + #[cfg(target_endian = "little")] + *ARGB1555, + #[cfg(target_endian = "little")] + *XRGB1555, + #[cfg(target_endian = "little")] + *ARGB2101010, + #[cfg(target_endian = "little")] + *XRGB2101010, + #[cfg(target_endian = "little")] + *ABGR2101010, + #[cfg(target_endian = "little")] + *XBGR2101010, + #[cfg(target_endian = "little")] + *ABGR16161616, + #[cfg(target_endian = "little")] + *XBGR16161616, + #[cfg(target_endian = "little")] + *ABGR16161616F, + #[cfg(target_endian = "little")] + *XBGR16161616F, + #[cfg(target_endian = "little")] + *BGR161616, + #[cfg(target_endian = "little")] + *R16F, + #[cfg(target_endian = "little")] + *GR1616F, + #[cfg(target_endian = "little")] + *BGR161616F, + #[cfg(target_endian = "little")] + *R32F, + #[cfg(target_endian = "little")] + *GR3232F, + #[cfg(target_endian = "little")] + *BGR323232F, + #[cfg(target_endian = "little")] + *ABGR32323232F, +]; diff --git a/crates/geometry/Cargo.toml b/crates/geometry/Cargo.toml new file mode 100644 index 00000000..a69caa9b --- /dev/null +++ b/crates/geometry/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "jay-geometry" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "Geometry primitives for Jay" +repository = "https://github.com/mahkoh/jay" + +[dependencies] +jay-algorithms = { path = "../algorithms" } +smallvec = { version = "1.11.1", features = ["const_generics", "const_new", "union"] } diff --git a/src/rect.rs b/crates/geometry/src/lib.rs similarity index 99% rename from src/rect.rs rename to crates/geometry/src/lib.rs index 938c7b3f..e18ba86f 100644 --- a/src/rect.rs +++ b/crates/geometry/src/lib.rs @@ -246,7 +246,6 @@ where dx * dx + dy * dy } - #[expect(dead_code)] pub fn contains_rect(&self, rect: &Rect) -> bool where U: Tag, @@ -273,12 +272,10 @@ where self.raw.x1 == self.raw.x2 || self.raw.y1 == self.raw.y2 } - #[expect(dead_code)] pub fn is_not_empty(&self) -> bool { !self.is_empty() } - #[expect(dead_code)] pub fn to_origin(&self) -> Self { Self { raw: RectRaw { diff --git a/src/rect/region.rs b/crates/geometry/src/region.rs similarity index 94% rename from src/rect/region.rs rename to crates/geometry/src/region.rs index bc1613c4..32425f21 100644 --- a/src/rect/region.rs +++ b/crates/geometry/src/region.rs @@ -1,11 +1,5 @@ use { - crate::{ - rect::{Rect, Region}, - utils::{ - array, - ptr_ext::{MutPtrExt, PtrExt}, - }, - }, + crate::{Rect, Region}, jay_algorithms::rect::{ RectRaw, Tag, region::{ @@ -15,6 +9,7 @@ use { }, smallvec::SmallVec, std::{ + array, borrow::Cow, cell::UnsafeCell, fmt::{Debug, Formatter}, @@ -176,7 +171,6 @@ where } } - #[cfg_attr(not(feature = "it"), expect(dead_code))] pub fn extents(&self) -> Rect { self.extents } @@ -274,7 +268,6 @@ impl RegionBuilder { self.base.clone() } - #[expect(dead_code)] pub fn clear(&mut self) { self.pending.clear(); self.base = Region::empty(); @@ -321,26 +314,26 @@ impl DamageQueue { } pub fn damage(&self, rects: &[Rect]) { - let datas = unsafe { self.datas.get().deref_mut() }; + let datas = unsafe { &mut *self.datas.get() }; for data in datas { data.extend(rects); } } pub fn clear(&self) { - let data = unsafe { &mut self.datas.get().deref_mut()[self.this] }; + let data = unsafe { &mut (&mut *self.datas.get())[self.this] }; data.clear(); } pub fn clear_all(&self) { - let datas = unsafe { self.datas.get().deref_mut() }; + let datas = unsafe { &mut *self.datas.get() }; for data in datas { data.clear(); } } pub fn get(&self) -> Region { - let data = unsafe { &self.datas.get().deref()[self.this] }; + let data = unsafe { &(&*self.datas.get())[self.this] }; Region::from_rects2(data) } } diff --git a/src/rect/tests.rs b/crates/geometry/src/tests.rs similarity index 99% rename from src/rect/tests.rs rename to crates/geometry/src/tests.rs index c673ef5b..1b2d205f 100644 --- a/src/rect/tests.rs +++ b/crates/geometry/src/tests.rs @@ -1,5 +1,5 @@ use { - crate::rect::{Rect, Region}, + crate::{Rect, Region}, jay_algorithms::rect::{NoTag, RectRaw}, }; diff --git a/crates/gfx-types/Cargo.toml b/crates/gfx-types/Cargo.toml new file mode 100644 index 00000000..52d9e565 --- /dev/null +++ b/crates/gfx-types/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "jay-gfx-types" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +uapi = "0.2.13" diff --git a/crates/gfx-types/src/lib.rs b/crates/gfx-types/src/lib.rs new file mode 100644 index 00000000..6fd3016f --- /dev/null +++ b/crates/gfx-types/src/lib.rs @@ -0,0 +1,38 @@ +use { + std::{cell::Cell, error::Error, rc::Rc}, + uapi::OwnedFd, +}; + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)] +pub enum AlphaMode { + #[default] + PremultipliedElectrical, + PremultipliedOptical, + Straight, +} + +pub trait ShmMemory { + fn len(&self) -> usize; + fn safe_access(&self) -> ShmMemoryBacking; + fn access(&self, f: &mut dyn FnMut(&[Cell])) -> Result<(), Box>; +} + +pub enum ShmMemoryBacking { + Ptr(*const [Cell]), + Fd(Rc, usize), +} + +impl ShmMemory for Vec> { + fn len(&self) -> usize { + self.len() + } + + fn safe_access(&self) -> ShmMemoryBacking { + ShmMemoryBacking::Ptr(&**self) + } + + fn access(&self, f: &mut dyn FnMut(&[Cell])) -> Result<(), Box> { + f(self); + Ok(()) + } +} diff --git a/crates/input-types/Cargo.toml b/crates/input-types/Cargo.toml new file mode 100644 index 00000000..9683f580 --- /dev/null +++ b/crates/input-types/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "jay-input-types" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "Input data types for the Jay compositor" +repository = "https://github.com/mahkoh/jay" + +[dependencies] +jay-output-types = { path = "../output-types" } +jay-units = { path = "../units" } +jay-utils = { path = "../utils" } + +linearize = { version = "0.1.3", features = ["derive"] } diff --git a/crates/input-types/src/lib.rs b/crates/input-types/src/lib.rs new file mode 100644 index 00000000..ee58c13f --- /dev/null +++ b/crates/input-types/src/lib.rs @@ -0,0 +1,482 @@ +use { + jay_output_types::ConnectorId, + jay_units::Fixed, + jay_utils::{numcell::NumCell, static_text::StaticText}, + linearize::Linearize, + std::{ + fmt::{Display, Formatter}, + ops::{BitOr, BitOrAssign}, + }, +}; + +macro_rules! linear_ids { + ($ids:ident, $id:ident $(,)?) => { + linear_ids!($ids, $id, u32); + }; + ($ids:ident, $id:ident, $ty:ty $(,)?) => { + #[derive(Debug)] + pub struct $ids { + next: NumCell<$ty>, + } + + impl Default for $ids { + fn default() -> Self { + Self { + next: NumCell::new(1), + } + } + } + + impl $ids { + pub fn next(&self) -> $id { + $id(self.next.fetch_add(1)) + } + } + + #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] + pub struct $id($ty); + + impl $id { + pub fn raw(&self) -> $ty { + self.0 + } + + pub fn from_raw(id: $ty) -> Self { + Self(id) + } + } + + impl Display for $id { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.0, f) + } + } + }; +} + +#[derive(Debug, Copy, Clone, PartialEq, Linearize)] +pub enum InputDeviceAccelProfile { + Flat, + Adaptive, +} + +impl StaticText for InputDeviceAccelProfile { + fn text(&self) -> &'static str { + match self { + InputDeviceAccelProfile::Flat => "Flat", + InputDeviceAccelProfile::Adaptive => "Adaptive", + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Linearize)] +pub enum InputDeviceClickMethod { + None, + ButtonAreas, + Clickfinger, +} + +impl StaticText for InputDeviceClickMethod { + fn text(&self) -> &'static str { + match self { + InputDeviceClickMethod::None => "none", + InputDeviceClickMethod::ButtonAreas => "button-areas", + InputDeviceClickMethod::Clickfinger => "clickfinger", + } + } +} + +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Linearize)] +pub enum InputDeviceCapability { + Keyboard, + Pointer, + Touch, + TabletTool, + TabletPad, + Gesture, + Switch, +} + +impl StaticText for InputDeviceCapability { + fn text(&self) -> &'static str { + match self { + InputDeviceCapability::Keyboard => "keyboard", + InputDeviceCapability::Pointer => "pointer", + InputDeviceCapability::Touch => "touch", + InputDeviceCapability::TabletTool => "tablet tool", + InputDeviceCapability::TabletPad => "tablet pad", + InputDeviceCapability::Gesture => "gesture", + InputDeviceCapability::Switch => "switch", + } + } +} + +linear_ids!(InputDeviceGroupIds, InputDeviceGroupId, usize); +linear_ids!(InputDeviceIds, InputDeviceId); + +pub type TransformMatrix = [[f64; 2]; 2]; + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum KeyState { + Released, + Pressed, + Repeated, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum ButtonState { + Released, + Pressed, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Linearize)] +pub enum ScrollAxis { + Vertical = 0, + Horizontal = 1, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum AxisSource { + Wheel, + Finger, + Continuous, +} + +pub const AXIS_120: i32 = 120; + +#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)] +pub struct Leds(pub u32); + +pub const LED_NUM_LOCK: Leds = Leds(1 << 0); +pub const LED_CAPS_LOCK: Leds = Leds(1 << 1); +pub const LED_SCROLL_LOCK: Leds = Leds(1 << 2); +pub const LED_COMPOSE: Leds = Leds(1 << 3); +pub const LED_KANA: Leds = Leds(1 << 4); + +impl Leds { + pub const fn none() -> Self { + Self(0) + } + + pub const fn raw(self) -> u32 { + self.0 + } +} + +impl BitOr for Leds { + type Output = Self; + + fn bitor(self, rhs: Self) -> Self::Output { + Self(self.0 | rhs.0) + } +} + +impl BitOrAssign for Leds { + fn bitor_assign(&mut self, rhs: Self) { + self.0 |= rhs.0; + } +} + +linear_ids!(TabletIds, TabletId); + +#[derive(Debug, Clone)] +pub struct TabletInit { + pub id: TabletId, + pub group: InputDeviceGroupId, + pub name: String, + pub pid: u32, + pub vid: u32, + pub bustype: Option, + pub path: String, +} + +linear_ids!(TabletToolIds, TabletToolId, usize); + +#[derive(Debug, Clone)] +pub struct TabletToolInit { + pub tablet_id: TabletId, + pub id: TabletToolId, + pub type_: TabletToolType, + pub hardware_serial: u64, + pub hardware_id_wacom: u64, + pub capabilities: Vec, +} + +linear_ids!(TabletPadIds, TabletPadId); + +#[derive(Debug, Clone)] +pub struct TabletPadInit { + pub id: TabletPadId, + pub group: InputDeviceGroupId, + pub path: String, + pub buttons: u32, + pub strips: u32, + pub rings: u32, + pub dials: u32, + pub groups: Vec, +} + +#[derive(Debug, Clone)] +pub struct TabletPadGroupInit { + pub buttons: Vec, + pub rings: Vec, + pub strips: Vec, + pub dials: Vec, + pub modes: u32, + pub mode: u32, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum PadButtonState { + Released, + Pressed, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum ToolButtonState { + Released, + Pressed, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum TabletToolType { + Pen, + Eraser, + Brush, + Pencil, + Airbrush, + Finger, + Mouse, + Lens, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum TabletToolCapability { + Tilt, + Pressure, + Distance, + Rotation, + Slider, + Wheel, +} + +#[derive(Copy, Clone, Debug)] +pub enum TabletRingEventSource { + Finger, +} + +#[derive(Copy, Clone, Debug)] +pub enum TabletStripEventSource { + Finger, +} + +#[derive(Debug, Default)] +pub struct TabletToolChanges { + pub down: Option, + pub pos: Option>, + pub pressure: Option, + pub distance: Option, + pub tilt: Option>, + pub rotation: Option, + pub slider: Option, + pub wheel: Option, +} + +#[derive(Copy, Clone, Debug)] +pub struct TabletTool2dChange { + pub x: T, + pub y: T, +} + +#[derive(Copy, Clone, Debug)] +pub struct TabletToolPositionChange { + pub x: f64, + pub dx: f64, +} + +#[derive(Copy, Clone, Debug)] +pub struct TabletToolWheelChange { + pub degrees: f64, + pub clicks: i32, +} + +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] +pub enum SwitchEvent { + LidOpened, + LidClosed, + ConvertedToLaptop, + ConvertedToTablet, +} + +#[derive(Debug)] +pub enum InputEvent { + Key { + time_usec: u64, + key: u32, + state: KeyState, + }, + ConnectorPosition { + time_usec: u64, + connector: ConnectorId, + x: Fixed, + y: Fixed, + }, + Motion { + time_usec: u64, + dx: Fixed, + dy: Fixed, + dx_unaccelerated: Fixed, + dy_unaccelerated: Fixed, + }, + MotionAbsolute { + time_usec: u64, + x_normed: f32, + y_normed: f32, + }, + Button { + time_usec: u64, + button: u32, + state: ButtonState, + }, + + AxisPx { + dist: Fixed, + axis: ScrollAxis, + inverted: bool, + }, + AxisSource { + source: AxisSource, + }, + AxisStop { + axis: ScrollAxis, + }, + Axis120 { + dist: i32, + axis: ScrollAxis, + inverted: bool, + }, + AxisFrame { + time_usec: u64, + }, + SwipeBegin { + time_usec: u64, + finger_count: u32, + }, + SwipeUpdate { + time_usec: u64, + dx: Fixed, + dy: Fixed, + dx_unaccelerated: Fixed, + dy_unaccelerated: Fixed, + }, + SwipeEnd { + time_usec: u64, + cancelled: bool, + }, + PinchBegin { + time_usec: u64, + finger_count: u32, + }, + PinchUpdate { + time_usec: u64, + dx: Fixed, + dy: Fixed, + dx_unaccelerated: Fixed, + dy_unaccelerated: Fixed, + scale: Fixed, + rotation: Fixed, + }, + PinchEnd { + time_usec: u64, + cancelled: bool, + }, + HoldBegin { + time_usec: u64, + finger_count: u32, + }, + HoldEnd { + time_usec: u64, + cancelled: bool, + }, + + SwitchEvent { + time_usec: u64, + event: SwitchEvent, + }, + + TabletToolAdded { + time_usec: u64, + init: Box, + }, + TabletToolChanged { + time_usec: u64, + id: TabletToolId, + changes: Box, + }, + TabletToolButton { + time_usec: u64, + id: TabletToolId, + button: u32, + state: ToolButtonState, + }, + TabletToolRemoved { + time_usec: u64, + id: TabletToolId, + }, + + TabletPadButton { + time_usec: u64, + id: TabletPadId, + button: u32, + state: PadButtonState, + }, + TabletPadModeSwitch { + time_usec: u64, + pad: TabletPadId, + group: u32, + mode: u32, + }, + TabletPadRing { + time_usec: u64, + pad: TabletPadId, + ring: u32, + source: Option, + angle: Option, + }, + TabletPadStrip { + time_usec: u64, + pad: TabletPadId, + strip: u32, + source: Option, + position: Option, + }, + TabletPadDial { + time_usec: u64, + pad: TabletPadId, + dial: u32, + value120: i32, + }, + TouchDown { + time_usec: u64, + id: i32, + x_normed: Fixed, + y_normed: Fixed, + }, + TouchUp { + time_usec: u64, + id: i32, + }, + TouchMotion { + time_usec: u64, + id: i32, + x_normed: Fixed, + y_normed: Fixed, + }, + TouchCancel { + time_usec: u64, + id: i32, + }, + TouchFrame { + time_usec: u64, + }, +} diff --git a/crates/io-uring/Cargo.toml b/crates/io-uring/Cargo.toml new file mode 100644 index 00000000..126aac38 --- /dev/null +++ b/crates/io-uring/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "jay-io-uring" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +jay-async-engine = { path = "../async-engine" } +jay-time = { path = "../time" } +jay-utils = { path = "../utils" } + +log = { version = "0.4.20", features = ["std"] } +run-on-drop = "1.0.0" +thiserror = "2.0.11" +uapi = "0.2.13" diff --git a/src/io_uring/debounce.rs b/crates/io-uring/src/debounce.rs similarity index 93% rename from src/io_uring/debounce.rs rename to crates/io-uring/src/debounce.rs index f5b65f40..816a9d38 100644 --- a/src/io_uring/debounce.rs +++ b/crates/io-uring/src/debounce.rs @@ -1,5 +1,6 @@ use { - crate::{io_uring::IoUringData, utils::numcell::NumCell}, + crate::IoUringData, + jay_utils::numcell::NumCell, std::{cell::Cell, future::poll_fn, rc::Rc, task::Poll}, }; diff --git a/src/io_uring.rs b/crates/io-uring/src/lib.rs similarity index 87% rename from src/io_uring.rs rename to crates/io-uring/src/lib.rs index 49f5a2ea..c6c623fa 100644 --- a/src/io_uring.rs +++ b/crates/io-uring/src/lib.rs @@ -5,38 +5,36 @@ pub use ops::{ }; use { crate::{ - async_engine::AsyncEngine, - io_uring::{ - debounce::Debouncer, - ops::{ - accept::AcceptTask, async_cancel::AsyncCancelTask, connect::ConnectTask, - poll::PollTask, poll_external::PollExternalTask, read_write::ReadWriteTask, - read_write_no_cancel::ReadWriteNoCancelTask, recvmsg::RecvmsgTask, - sendmsg::SendmsgTask, timeout::TimeoutTask, timeout_external::TimeoutExternalTask, - timeout_link::TimeoutLinkTask, - }, - pending_result::PendingResults, - sys::{ - IORING_ENTER_GETEVENTS, IORING_FEAT_NODROP, IORING_OFF_CQ_RING, IORING_OFF_SQ_RING, - IORING_OFF_SQES, IORING_SETUP_COOP_TASKRUN, IORING_SETUP_DEFER_TASKRUN, - IORING_SETUP_SINGLE_ISSUER, IORING_SETUP_SUBMIT_ALL, IOSQE_IO_LINK, io_uring_cqe, - io_uring_enter, io_uring_params, io_uring_setup, io_uring_sqe, - }, + debounce::Debouncer, + ops::{ + accept::AcceptTask, async_cancel::AsyncCancelTask, connect::ConnectTask, + poll::PollTask, poll_external::PollExternalTask, read_write::ReadWriteTask, + read_write_no_cancel::ReadWriteNoCancelTask, recvmsg::RecvmsgTask, + sendmsg::SendmsgTask, timeout::TimeoutTask, timeout_external::TimeoutExternalTask, + timeout_link::TimeoutLinkTask, }, - utils::{ - asyncevent::AsyncEvent, - bitflags::BitflagsExt, - buf::Buf, - copyhashmap::CopyHashMap, - errorfmt::ErrorFmt, - mmap::{Mmapped, mmap}, - numcell::NumCell, - oserror::OsError, - ptr_ext::{MutPtrExt, PtrExt}, - stack::Stack, - syncqueue::SyncQueue, + pending_result::PendingResults, + sys::{ + IORING_ENTER_GETEVENTS, IORING_FEAT_NODROP, IORING_OFF_CQ_RING, IORING_OFF_SQ_RING, + IORING_OFF_SQES, IORING_SETUP_COOP_TASKRUN, IORING_SETUP_DEFER_TASKRUN, + IORING_SETUP_SINGLE_ISSUER, IORING_SETUP_SUBMIT_ALL, IOSQE_IO_LINK, io_uring_cqe, + io_uring_enter, io_uring_params, io_uring_setup, io_uring_sqe, }, }, + jay_async_engine::AsyncEngine, + jay_utils::{ + asyncevent::AsyncEvent, + bitflags::BitflagsExt, + buf::Buf, + copyhashmap::CopyHashMap, + errorfmt::ErrorFmt, + mmap::{Mmapped, mmap}, + numcell::NumCell, + oserror::OsError, + ptr_ext::{MutPtrExt, PtrExt}, + stack::Stack, + syncqueue::SyncQueue, + }, std::{ cell::{Cell, RefCell, UnsafeCell}, rc::Rc, @@ -57,7 +55,7 @@ macro_rules! map_err { ($n:expr) => {{ let n = $n; if n < 0 { - Err(crate::utils::oserror::OsError::from(-n as uapi::c::c_int)) + Err(jay_utils::oserror::OsError::from(-n as uapi::c::c_int)) } else { Ok(n) } @@ -65,9 +63,12 @@ macro_rules! map_err { } mod debounce; +pub mod line_logger; +pub mod object_drop_queue; mod ops; mod pending_result; mod sys; +pub mod timer; #[derive(Debug, Error)] pub enum IoUringError { @@ -380,7 +381,7 @@ impl IoUringData { to_submit -= n; } Err(e) => { - if not_matches!(e.0, c::EAGAIN | c::EBUSY | c::EINTR) { + if !matches!(e.0, c::EAGAIN | c::EBUSY | c::EINTR) { return Err(IoUringError::Enter(e)); } } @@ -530,7 +531,45 @@ impl IoUringData { } } -linear_ids!(IoUringTaskIds, IoUringTaskId, u64); +#[derive(Debug)] +struct IoUringTaskIds { + next: NumCell, +} + +impl Default for IoUringTaskIds { + fn default() -> Self { + Self { + next: NumCell::new(1), + } + } +} + +impl IoUringTaskIds { + fn next(&self) -> IoUringTaskId { + IoUringTaskId(self.next.fetch_add(1)) + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct IoUringTaskId(u64); + +impl IoUringTaskId { + #[allow(clippy::allow_attributes, dead_code)] + pub fn raw(&self) -> u64 { + self.0 + } + + #[allow(clippy::allow_attributes, dead_code)] + pub fn from_raw(id: u64) -> Self { + Self(id) + } +} + +impl std::fmt::Display for IoUringTaskId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } +} #[expect(clippy::derivable_impls)] impl Default for IoUringTaskId { diff --git a/src/utils/line_logger.rs b/crates/io-uring/src/line_logger.rs similarity index 79% rename from src/utils/line_logger.rs rename to crates/io-uring/src/line_logger.rs index 91c75d42..aa5306f5 100644 --- a/src/utils/line_logger.rs +++ b/crates/io-uring/src/line_logger.rs @@ -1,9 +1,6 @@ use { - crate::{ - io_uring::{IoUring, IoUringError}, - utils::{buf::Buf, vecdeque_ext::VecDequeExt}, - }, - isnt::std_1::collections::IsntVecDequeExt, + crate::{IoUring, IoUringError}, + jay_utils::{buf::Buf, vecdeque_ext::VecDequeExt}, std::{collections::VecDeque, rc::Rc}, uapi::OwnedFd, }; @@ -28,7 +25,7 @@ pub async fn log_lines( buf.drain(..=pos); } } - if buf.is_not_empty() { + if !buf.is_empty() { let (left, right) = buf.as_slices(); f(left, right); } diff --git a/src/utils/object_drop_queue.rs b/crates/io-uring/src/object_drop_queue.rs similarity index 93% rename from src/utils/object_drop_queue.rs rename to crates/io-uring/src/object_drop_queue.rs index 56010e85..7417f544 100644 --- a/src/utils/object_drop_queue.rs +++ b/crates/io-uring/src/object_drop_queue.rs @@ -1,8 +1,6 @@ use { - crate::{ - io_uring::{IoUring, PendingPoll, PollCallback}, - utils::{errorfmt::ErrorFmt, oserror::OsError, stack::Stack}, - }, + crate::{IoUring, PendingPoll, PollCallback}, + jay_utils::{errorfmt::ErrorFmt, oserror::OsError, stack::Stack}, std::{ cell::{Cell, RefCell}, rc::Rc, diff --git a/src/io_uring/ops.rs b/crates/io-uring/src/ops.rs similarity index 91% rename from src/io_uring/ops.rs rename to crates/io-uring/src/ops.rs index e6abdbee..f77768d0 100644 --- a/src/io_uring/ops.rs +++ b/crates/io-uring/src/ops.rs @@ -1,4 +1,4 @@ -use crate::{io_uring::IoUringError, utils::oserror::OsError}; +use {crate::IoUringError, jay_utils::oserror::OsError}; pub mod accept; pub mod async_cancel; diff --git a/src/io_uring/ops/accept.rs b/crates/io-uring/src/ops/accept.rs similarity index 98% rename from src/io_uring/ops/accept.rs rename to crates/io-uring/src/ops/accept.rs index 0acee0d5..5fd47152 100644 --- a/src/io_uring/ops/accept.rs +++ b/crates/io-uring/src/ops/accept.rs @@ -1,5 +1,5 @@ use { - crate::io_uring::{ + crate::{ IoUring, IoUringData, IoUringError, IoUringTaskId, Task, TaskResultExt, pending_result::PendingResult, sys::{IORING_OP_ACCEPT, io_uring_sqe}, diff --git a/src/io_uring/ops/async_cancel.rs b/crates/io-uring/src/ops/async_cancel.rs similarity index 85% rename from src/io_uring/ops/async_cancel.rs rename to crates/io-uring/src/ops/async_cancel.rs index f21e7024..4a74b43d 100644 --- a/src/io_uring/ops/async_cancel.rs +++ b/crates/io-uring/src/ops/async_cancel.rs @@ -1,11 +1,9 @@ use { crate::{ - io_uring::{ - IoUringData, IoUringTaskId, Task, - sys::{IORING_OP_ASYNC_CANCEL, io_uring_sqe}, - }, - utils::errorfmt::ErrorFmt, + IoUringData, IoUringTaskId, Task, + sys::{IORING_OP_ASYNC_CANCEL, io_uring_sqe}, }, + jay_utils::errorfmt::ErrorFmt, uapi::c, }; diff --git a/src/io_uring/ops/connect.rs b/crates/io-uring/src/ops/connect.rs similarity index 98% rename from src/io_uring/ops/connect.rs rename to crates/io-uring/src/ops/connect.rs index cfb89039..4c0512f1 100644 --- a/src/io_uring/ops/connect.rs +++ b/crates/io-uring/src/ops/connect.rs @@ -1,5 +1,5 @@ use { - crate::io_uring::{ + crate::{ IoUring, IoUringData, IoUringError, IoUringTaskId, Task, TaskResultExt, pending_result::PendingResult, sys::{IORING_OP_CONNECT, io_uring_sqe}, diff --git a/src/io_uring/ops/poll.rs b/crates/io-uring/src/ops/poll.rs similarity index 97% rename from src/io_uring/ops/poll.rs rename to crates/io-uring/src/ops/poll.rs index 01e77679..dd7a88e4 100644 --- a/src/io_uring/ops/poll.rs +++ b/crates/io-uring/src/ops/poll.rs @@ -1,5 +1,5 @@ use { - crate::io_uring::{ + crate::{ IoUring, IoUringData, IoUringError, IoUringTaskId, Task, TaskResultExt, ops::TaskResult, pending_result::PendingResult, @@ -32,7 +32,6 @@ impl IoUring { self.poll(fd, c::POLLIN).await.merge() } - #[expect(dead_code)] pub async fn writable(&self, fd: &Rc) -> Result { self.poll(fd, c::POLLOUT).await.merge() } diff --git a/src/io_uring/ops/poll_external.rs b/crates/io-uring/src/ops/poll_external.rs similarity index 92% rename from src/io_uring/ops/poll_external.rs rename to crates/io-uring/src/ops/poll_external.rs index 6a86a125..be25e053 100644 --- a/src/io_uring/ops/poll_external.rs +++ b/crates/io-uring/src/ops/poll_external.rs @@ -1,11 +1,9 @@ use { crate::{ - io_uring::{ - IoUring, IoUringData, IoUringError, IoUringTaskId, Task, - sys::{IORING_OP_POLL_ADD, io_uring_sqe}, - }, - utils::oserror::OsError, + IoUring, IoUringData, IoUringError, IoUringTaskId, Task, + sys::{IORING_OP_POLL_ADD, io_uring_sqe}, }, + jay_utils::oserror::OsError, std::{cell::Cell, rc::Rc}, uapi::{OwnedFd, c}, }; @@ -61,7 +59,6 @@ impl IoUring { self.poll_external(fd, c::POLLIN, callback) } - #[expect(dead_code)] pub fn writable_external( &self, fd: &Rc, diff --git a/src/io_uring/ops/read_write.rs b/crates/io-uring/src/ops/read_write.rs similarity index 89% rename from src/io_uring/ops/read_write.rs rename to crates/io-uring/src/ops/read_write.rs index ddece5a3..e7af42fc 100644 --- a/src/io_uring/ops/read_write.rs +++ b/crates/io-uring/src/ops/read_write.rs @@ -1,13 +1,11 @@ use { crate::{ - io_uring::{ - IoUring, IoUringData, IoUringError, IoUringTaskId, Task, TaskResultExt, - pending_result::PendingResult, - sys::{IORING_OP_READ, IORING_OP_WRITE, io_uring_sqe}, - }, - time::Time, - utils::buf::Buf, + IoUring, IoUringData, IoUringError, IoUringTaskId, Task, TaskResultExt, + pending_result::PendingResult, + sys::{IORING_OP_READ, IORING_OP_WRITE, io_uring_sqe}, }, + jay_time::Time, + jay_utils::buf::Buf, std::rc::Rc, uapi::{OwnedFd, c}, }; diff --git a/src/io_uring/ops/read_write_no_cancel.rs b/crates/io-uring/src/ops/read_write_no_cancel.rs similarity index 92% rename from src/io_uring/ops/read_write_no_cancel.rs rename to crates/io-uring/src/ops/read_write_no_cancel.rs index a152d752..609aa464 100644 --- a/src/io_uring/ops/read_write_no_cancel.rs +++ b/crates/io-uring/src/ops/read_write_no_cancel.rs @@ -3,13 +3,11 @@ mod tests; use { crate::{ - io_uring::{ - IoUring, IoUringData, IoUringError, IoUringTaskId, Task, TaskResultExt, - pending_result::PendingResult, - sys::{IORING_OP_READ, IORING_OP_WRITE, io_uring_sqe}, - }, - time::Time, + IoUring, IoUringData, IoUringError, IoUringTaskId, Task, TaskResultExt, + pending_result::PendingResult, + sys::{IORING_OP_READ, IORING_OP_WRITE, io_uring_sqe}, }, + jay_time::Time, run_on_drop::on_drop, uapi::{Fd, c}, }; diff --git a/src/io_uring/ops/read_write_no_cancel/tests.rs b/crates/io-uring/src/ops/read_write_no_cancel/tests.rs similarity index 78% rename from src/io_uring/ops/read_write_no_cancel/tests.rs rename to crates/io-uring/src/ops/read_write_no_cancel/tests.rs index ef50bba0..20094f96 100644 --- a/src/io_uring/ops/read_write_no_cancel/tests.rs +++ b/crates/io-uring/src/ops/read_write_no_cancel/tests.rs @@ -1,10 +1,7 @@ use { - crate::{ - async_engine::AsyncEngine, - io_uring::{IoUring, IoUringError}, - utils::{oserror::OsError, queue::AsyncQueue}, - wheel::Wheel, - }, + crate::{IoUring, IoUringError}, + jay_async_engine::AsyncEngine, + jay_utils::{oserror::OsError, queue::AsyncQueue}, std::rc::Rc, uapi::c::ECANCELED, }; @@ -14,7 +11,6 @@ fn cancel(timeout: bool) { let ring = IoUring::new(&eng, 32).unwrap(); let ring2 = ring.clone(); let ring3 = ring.clone(); - let wheel = Wheel::new(&eng, &ring).unwrap(); let queue = Rc::new(AsyncQueue::new()); let queue2 = queue.clone(); let _fut1 = eng.spawn("", async move { @@ -32,7 +28,7 @@ fn cancel(timeout: bool) { let _fut2 = eng.spawn("", async move { let id = queue2.pop().await; if timeout { - wheel.timeout(1).await.unwrap(); + ring2.timeout(1).await.unwrap(); } ring2.cancel(id); }); diff --git a/src/io_uring/ops/recvmsg.rs b/crates/io-uring/src/ops/recvmsg.rs similarity index 94% rename from src/io_uring/ops/recvmsg.rs rename to crates/io-uring/src/ops/recvmsg.rs index 1c7e4f81..0aa2f883 100644 --- a/src/io_uring/ops/recvmsg.rs +++ b/crates/io-uring/src/ops/recvmsg.rs @@ -1,12 +1,10 @@ use { crate::{ - io_uring::{ - IoUring, IoUringData, IoUringError, IoUringTaskId, Task, - pending_result::PendingResult, - sys::{IORING_OP_RECVMSG, io_uring_sqe}, - }, - utils::buf::Buf, + IoUring, IoUringData, IoUringError, IoUringTaskId, Task, + pending_result::PendingResult, + sys::{IORING_OP_RECVMSG, io_uring_sqe}, }, + jay_utils::buf::Buf, std::{cell::Cell, collections::VecDeque, mem::MaybeUninit, rc::Rc}, uapi::{OwnedFd, c}, }; diff --git a/src/io_uring/ops/sendmsg.rs b/crates/io-uring/src/ops/sendmsg.rs similarity index 93% rename from src/io_uring/ops/sendmsg.rs rename to crates/io-uring/src/ops/sendmsg.rs index c6231b39..8a245740 100644 --- a/src/io_uring/ops/sendmsg.rs +++ b/crates/io-uring/src/ops/sendmsg.rs @@ -1,13 +1,11 @@ use { crate::{ - io_uring::{ - IoUring, IoUringData, IoUringError, IoUringTaskId, Task, - pending_result::PendingResult, - sys::{IORING_OP_SENDMSG, io_uring_sqe}, - }, - time::Time, - utils::{buf::Buf, compat::IovLength, vec_ext::UninitVecExt}, + IoUring, IoUringData, IoUringError, IoUringTaskId, Task, + pending_result::PendingResult, + sys::{IORING_OP_SENDMSG, io_uring_sqe}, }, + jay_time::Time, + jay_utils::{buf::Buf, compat::IovLength, vec_ext::UninitVecExt}, std::{mem::MaybeUninit, ptr, rc::Rc}, uapi::{OwnedFd, c}, }; diff --git a/src/io_uring/ops/timeout.rs b/crates/io-uring/src/ops/timeout.rs similarity index 98% rename from src/io_uring/ops/timeout.rs rename to crates/io-uring/src/ops/timeout.rs index d7e0c2fb..74e7be1c 100644 --- a/src/io_uring/ops/timeout.rs +++ b/crates/io-uring/src/ops/timeout.rs @@ -1,5 +1,5 @@ use { - crate::io_uring::{ + crate::{ IoUring, IoUringData, IoUringError, IoUringTaskId, Task, pending_result::PendingResult, sys::{IORING_OP_TIMEOUT, IORING_TIMEOUT_ABS, io_uring_sqe}, diff --git a/src/io_uring/ops/timeout_external.rs b/crates/io-uring/src/ops/timeout_external.rs similarity index 90% rename from src/io_uring/ops/timeout_external.rs rename to crates/io-uring/src/ops/timeout_external.rs index 860811de..1034d703 100644 --- a/src/io_uring/ops/timeout_external.rs +++ b/crates/io-uring/src/ops/timeout_external.rs @@ -1,12 +1,9 @@ use { crate::{ - io_uring::{ - IoUring, IoUringData, IoUringError, IoUringTaskId, Task, - ops::timeout::timespec64, - sys::{IORING_OP_TIMEOUT, IORING_TIMEOUT_ABS, io_uring_sqe}, - }, - utils::oserror::OsError, + IoUring, IoUringData, IoUringError, IoUringTaskId, Task, ops::timeout::timespec64, + sys::{IORING_OP_TIMEOUT, IORING_TIMEOUT_ABS, io_uring_sqe}, }, + jay_utils::oserror::OsError, std::{cell::Cell, rc::Rc}, uapi::c, }; diff --git a/src/io_uring/ops/timeout_link.rs b/crates/io-uring/src/ops/timeout_link.rs similarity index 83% rename from src/io_uring/ops/timeout_link.rs rename to crates/io-uring/src/ops/timeout_link.rs index edb9faa4..f349c879 100644 --- a/src/io_uring/ops/timeout_link.rs +++ b/crates/io-uring/src/ops/timeout_link.rs @@ -1,11 +1,8 @@ use crate::{ - io_uring::{ - IoUring, IoUringData, IoUringTaskId, Task, - ops::timeout::timespec64, - sys::{IORING_OP_LINK_TIMEOUT, IORING_TIMEOUT_ABS, io_uring_sqe}, - }, - time::Time, + IoUring, IoUringData, IoUringTaskId, Task, ops::timeout::timespec64, + sys::{IORING_OP_LINK_TIMEOUT, IORING_TIMEOUT_ABS, io_uring_sqe}, }; +use jay_time::Time; #[derive(Default)] pub struct TimeoutLinkTask { diff --git a/src/io_uring/pending_result.rs b/crates/io-uring/src/pending_result.rs similarity index 96% rename from src/io_uring/pending_result.rs rename to crates/io-uring/src/pending_result.rs index 544c182e..4d7c6a87 100644 --- a/src/io_uring/pending_result.rs +++ b/crates/io-uring/src/pending_result.rs @@ -1,5 +1,5 @@ use { - crate::utils::{numcell::NumCell, oserror::OsError, ptr_ext::PtrExt, stack::Stack}, + jay_utils::{numcell::NumCell, oserror::OsError, ptr_ext::PtrExt, stack::Stack}, std::{ cell::Cell, future::Future, diff --git a/src/io_uring/sys.rs b/crates/io-uring/src/sys.rs similarity index 99% rename from src/io_uring/sys.rs rename to crates/io-uring/src/sys.rs index d9ba4197..9a9b9665 100644 --- a/src/io_uring/sys.rs +++ b/crates/io-uring/src/sys.rs @@ -1,7 +1,7 @@ #![allow(non_camel_case_types, dead_code)] use { - crate::utils::oserror::OsError, + jay_utils::oserror::OsError, std::mem::MaybeUninit, uapi::{OwnedFd, c}, }; diff --git a/src/utils/timer.rs b/crates/io-uring/src/timer.rs similarity index 92% rename from src/utils/timer.rs rename to crates/io-uring/src/timer.rs index 1bff0c64..7eadc374 100644 --- a/src/utils/timer.rs +++ b/crates/io-uring/src/timer.rs @@ -1,10 +1,8 @@ use { - crate::{ - io_uring::{IoUring, IoUringError}, - utils::{ - buf::TypedBuf, - oserror::{OsError, OsErrorExt2}, - }, + crate::{IoUring, IoUringError}, + jay_utils::{ + buf::TypedBuf, + oserror::{OsError, OsErrorExt2}, }, std::{cell::RefCell, rc::Rc, time::Duration}, thiserror::Error, diff --git a/crates/jay-config-schema/Cargo.toml b/crates/jay-config-schema/Cargo.toml new file mode 100644 index 00000000..0ecc60fa --- /dev/null +++ b/crates/jay-config-schema/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "jay-config-schema" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "Shared configuration schema declarations for the Jay compositor" +repository = "https://github.com/mahkoh/jay" + +[dependencies] +ahash = "0.8.11" +jay-config = { path = "../jay-config" } diff --git a/crates/jay-config-schema/src/action.rs b/crates/jay-config-schema/src/action.rs new file mode 100644 index 00000000..8a774a06 --- /dev/null +++ b/crates/jay-config-schema/src/action.rs @@ -0,0 +1,59 @@ +use jay_config::{ + Direction, + input::{LayerDirection, Timeline}, +}; + +#[derive(Debug, Copy, Clone)] +pub enum SimpleCommand { + Close, + DisablePointerConstraint, + Focus(Direction), + FocusParent, + Move(Direction), + None, + Quit, + ReloadConfigToml, + ToggleFloating, + SetFloating(bool), + ToggleFullscreen, + SetFullscreen(bool), + SendToScratchpad, + ToggleScratchpad, + CycleScratchpad, + Forward(bool), + EnableWindowManagement(bool), + SetFloatAboveFullscreen(bool), + ToggleFloatAboveFullscreen, + SetFloatPinned(bool), + ToggleFloatPinned, + KillClient, + ShowBar(bool), + ToggleBar, + ShowTitles(bool), + ToggleTitles, + FloatTitles(bool), + ToggleFloatTitles, + FocusHistory(Timeline), + FocusLayerRel(LayerDirection), + FocusTiles, + ToggleFocusFloatTiled, + CreateMark, + JumpToMark, + PopMode(bool), + EnableSimpleIm(bool), + ToggleSimpleImEnabled, + ReloadSimpleIm, + EnableUnicodeInput, + WarpMouseToFocus, + ToggleTab, + MakeGroupH, + MakeGroupV, + MakeGroupTab, + ChangeGroupOpposite, + Equalize, + EqualizeRecursive, + MoveTabLeft, + MoveTabRight, + SetAutotile(bool), + ToggleAutotile, +} diff --git a/crates/jay-config-schema/src/animations.rs b/crates/jay-config-schema/src/animations.rs new file mode 100644 index 00000000..a60ba034 --- /dev/null +++ b/crates/jay-config-schema/src/animations.rs @@ -0,0 +1,13 @@ +#[derive(Debug, Clone, Default)] +pub struct Animations { + pub enabled: Option, + pub duration_ms: Option, + pub style: Option, + pub curve: Option, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum AnimationCurveConfig { + Preset(String), + CubicBezier([f32; 4]), +} diff --git a/crates/jay-config-schema/src/command.rs b/crates/jay-config-schema/src/command.rs new file mode 100644 index 00000000..062dee52 --- /dev/null +++ b/crates/jay-config-schema/src/command.rs @@ -0,0 +1,16 @@ +use jay_config::status::MessageFormat; + +#[derive(Debug, Clone)] +pub struct Exec { + pub prog: String, + pub args: Vec, + pub envs: Vec<(String, String)>, + pub tag: Option, +} + +#[derive(Debug, Clone)] +pub struct Status { + pub format: MessageFormat, + pub exec: Exec, + pub separator: Option, +} diff --git a/crates/jay-config-schema/src/input.rs b/crates/jay-config-schema/src/input.rs new file mode 100644 index 00000000..e35cd6bc --- /dev/null +++ b/crates/jay-config-schema/src/input.rs @@ -0,0 +1,17 @@ +#[derive(Debug, Clone)] +pub enum InputMatch { + Any(Vec), + All { + tag: Option, + name: Option, + syspath: Option, + devnode: Option, + is_keyboard: Option, + is_pointer: Option, + is_touch: Option, + is_tablet_tool: Option, + is_tablet_pad: Option, + is_gesture: Option, + is_switch: Option, + }, +} diff --git a/crates/jay-config-schema/src/keymap.rs b/crates/jay-config-schema/src/keymap.rs new file mode 100644 index 00000000..30035a9f --- /dev/null +++ b/crates/jay-config-schema/src/keymap.rs @@ -0,0 +1,8 @@ +use jay_config::keyboard::Keymap; + +#[derive(Debug, Clone)] +pub enum ConfigKeymap { + Named(String), + Literal(Keymap), + Defined { name: String, map: Keymap }, +} diff --git a/crates/jay-config-schema/src/lib.rs b/crates/jay-config-schema/src/lib.rs new file mode 100644 index 00000000..051a5ee7 --- /dev/null +++ b/crates/jay-config-schema/src/lib.rs @@ -0,0 +1,34 @@ +//! Shared configuration schema declarations for Jay. +//! +//! This crate is the target home for option structs, defaults, validation +//! policy, and docs metadata that need to be consumed by TOML parsing, +//! generated config documentation, and compositor-side application code. + +pub mod action; +pub mod animations; +pub mod command; +pub mod input; +pub mod keymap; +pub mod model; +pub mod options; +pub mod output; +pub mod rules; +pub mod theme; + +pub use action::SimpleCommand; +pub use animations::{AnimationCurveConfig, Animations}; +pub use command::{Exec, Status}; +pub use input::InputMatch; +pub use keymap::ConfigKeymap; +pub use model::{ + Action, ClientRule, Config, Input, InputMode, NamedAction, Scratchpad, Shortcut, WindowRule, +}; +pub use options::{ + ColorManagement, Float, FocusHistory, Libei, RepeatRate, SimpleIm, Tearing, UiDrag, Vrr, + Xwayland, +}; +pub use output::{ + ConfigConnector, ConfigDrmDevice, ConnectorMatch, DrmDeviceMatch, Mode, Output, OutputMatch, +}; +pub use rules::{ClientMatch, GenericMatch, MatchExactly, WindowMatch}; +pub use theme::Theme; diff --git a/crates/jay-config-schema/src/model.rs b/crates/jay-config-schema/src/model.rs new file mode 100644 index 00000000..291bb448 --- /dev/null +++ b/crates/jay-config-schema/src/model.rs @@ -0,0 +1,256 @@ +use { + crate::{ + Animations, ClientMatch, ColorManagement, ConfigConnector, ConfigDrmDevice, ConfigKeymap, + DrmDeviceMatch, Exec, Float, FocusHistory, InputMatch, Libei, Output, OutputMatch, + RepeatRate, SimpleCommand, SimpleIm, Status, Tearing, Theme, UiDrag, Vrr, WindowMatch, + Xwayland, + }, + ahash::AHashMap, + jay_config::{ + Direction, Workspace, + input::{ + FallbackOutputMode, SwitchEvent, acceleration::AccelProfile, clickmethod::ClickMethod, + }, + keyboard::{ModifiedKeySym, mods::Modifiers, syms::KeySym}, + logging::LogLevel, + video::GfxApi, + window::TileState, + workspace::WorkspaceDisplayOrder, + }, + std::{rc::Rc, time::Duration}, +}; + +#[derive(Debug, Clone)] +#[expect(clippy::enum_variant_names)] +pub enum Action { + ConfigureConnector { + con: ConfigConnector, + }, + ConfigureDirectScanout { + enabled: bool, + }, + ConfigureDrmDevice { + dev: ConfigDrmDevice, + }, + ConfigureIdle { + idle: Option, + grace_period: Option, + }, + ConfigureInput { + input: Box, + }, + ConfigureOutput { + out: Output, + }, + Exec { + exec: Exec, + }, + MoveToWorkspace { + name: String, + }, + SendToScratchpad { + name: String, + }, + ToggleScratchpad { + name: String, + }, + CycleScratchpad { + name: String, + }, + Multi { + actions: Vec, + }, + SetEnv { + env: Vec<(String, String)>, + }, + SetGfxApi { + api: GfxApi, + }, + SetKeymap { + map: ConfigKeymap, + }, + SetLogLevel { + level: LogLevel, + }, + SetRenderDevice { + dev: Box, + }, + SetStatus { + status: Option, + }, + SetTheme { + theme: Box, + }, + ShowWorkspace { + name: String, + output: Option, + }, + SimpleCommand { + cmd: SimpleCommand, + }, + SwitchToVt { + num: u32, + }, + UnsetEnv { + env: Vec, + }, + MoveToOutput { + workspace: Option, + output: Option, + direction: Option, + }, + SetRepeatRate { + rate: RepeatRate, + }, + DefineAction { + name: String, + action: Box, + }, + UndefineAction { + name: String, + }, + NamedAction { + name: String, + }, + CreateMark(u32), + JumpToMark(u32), + CopyMark(u32, u32), + SetMode { + name: String, + latch: bool, + }, + CreateVirtualOutput { + name: String, + }, + RemoveVirtualOutput { + name: String, + }, + Resize { + dx1: i32, + dy1: i32, + dx2: i32, + dy2: i32, + }, +} + +#[derive(Debug, Clone)] +pub struct ClientRule { + pub name: Option, + pub match_: ClientMatch, + pub action: Option, + pub latch: Option, +} + +#[derive(Debug, Clone)] +pub struct WindowRule { + pub name: Option, + pub match_: WindowMatch, + pub action: Option, + pub latch: Option, + pub auto_focus: Option, + pub initial_tile_state: Option, +} + +#[derive(Debug, Clone)] +pub struct Input { + pub tag: Option, + pub match_: InputMatch, + pub accel_profile: Option, + pub accel_speed: Option, + pub tap_enabled: Option, + pub tap_drag_enabled: Option, + pub tap_drag_lock_enabled: Option, + pub left_handed: Option, + pub natural_scrolling: Option, + pub click_method: Option, + pub middle_button_emulation: Option, + pub px_per_wheel_scroll: Option, + pub transform_matrix: Option<[[f64; 2]; 2]>, + pub keymap: Option, + pub switch_actions: AHashMap, + pub output: Option>, + pub calibration_matrix: Option<[[f32; 3]; 2]>, +} + +#[derive(Debug, Clone)] +pub struct Shortcut { + pub mask: Modifiers, + pub keysym: ModifiedKeySym, + pub action: Action, + pub latch: Option, +} + +#[derive(Debug, Clone)] +pub struct NamedAction { + pub name: Rc, + pub action: Action, +} + +#[derive(Clone, Debug)] +pub struct InputMode { + pub parent: Option, + pub shortcuts: Vec, +} + +#[derive(Debug, Clone)] +pub struct Config { + pub keymap: Option, + pub repeat_rate: Option, + pub shortcuts: Vec, + pub on_graphics_initialized: Option, + pub on_idle: Option, + pub status: Option, + pub connectors: Vec, + pub outputs: Vec, + pub workspace_capture: bool, + pub env: Vec<(String, String)>, + pub on_startup: Option, + pub keymaps: Vec, + pub auto_reload: Option, + pub log_level: Option, + pub clean_logs_older_than: Option, + pub theme: Theme, + pub gfx_api: Option, + pub direct_scanout_enabled: Option, + pub drm_devices: Vec, + pub render_device: Option, + pub inputs: Vec, + pub idle: Option, + pub grace_period: Option, + pub key_press_enables_dpms: Option, + pub mouse_move_enables_dpms: Option, + pub explicit_sync_enabled: Option, + pub focus_follows_mouse: bool, + pub window_management_key: Option, + pub vrr: Option, + pub tearing: Option, + pub libei: Libei, + pub ui_drag: UiDrag, + pub animations: Animations, + pub xwayland: Option, + pub color_management: Option, + pub float: Option, + pub named_actions: Vec, + pub max_action_depth: u64, + pub client_rules: Vec, + pub window_rules: Vec, + pub pointer_revert_key: Option, + pub use_hardware_cursor: Option, + pub show_bar: Option, + pub show_titles: Option, + pub focus_history: Option, + pub middle_click_paste: Option, + pub input_modes: AHashMap, + pub workspace_display_order: Option, + pub simple_im: Option, + pub fallback_output_mode: Option, + pub mouse_follows_focus: Option, + pub scratchpads: Vec, + pub autotile: Option, +} + +#[derive(Debug, Clone)] +pub struct Scratchpad { + pub name: String, + pub exec: Option, +} diff --git a/crates/jay-config-schema/src/options.rs b/crates/jay-config-schema/src/options.rs new file mode 100644 index 00000000..b528dcf5 --- /dev/null +++ b/crates/jay-config-schema/src/options.rs @@ -0,0 +1,59 @@ +use jay_config::{ + video::{TearingMode, VrrMode}, + xwayland::XScalingMode, +}; + +#[derive(Debug, Clone, Default)] +pub struct UiDrag { + pub enabled: Option, + pub threshold: Option, +} + +#[derive(Clone, Debug)] +pub struct ColorManagement { + pub enabled: Option, +} + +#[derive(Debug, Clone)] +pub struct Float { + pub show_pin_icon: Option, +} + +#[derive(Debug, Clone)] +pub struct FocusHistory { + pub only_visible: Option, + pub same_workspace: Option, +} + +#[derive(Debug, Clone)] +pub struct RepeatRate { + pub rate: i32, + pub delay: i32, +} + +#[derive(Debug, Clone)] +pub struct Vrr { + pub mode: Option, + pub cursor_hz: Option, +} + +#[derive(Debug, Clone)] +pub struct SimpleIm { + pub enabled: Option, +} + +#[derive(Debug, Clone)] +pub struct Xwayland { + pub enabled: Option, + pub scaling_mode: Option, +} + +#[derive(Debug, Clone)] +pub struct Tearing { + pub mode: Option, +} + +#[derive(Debug, Clone, Default)] +pub struct Libei { + pub enable_socket: Option, +} diff --git a/crates/jay-config-schema/src/output.rs b/crates/jay-config-schema/src/output.rs new file mode 100644 index 00000000..93c1343f --- /dev/null +++ b/crates/jay-config-schema/src/output.rs @@ -0,0 +1,88 @@ +use { + crate::{Tearing, Vrr}, + jay_config::video::{BlendSpace, ColorSpace, Eotf, Format, GfxApi, Transform}, + std::fmt::{Display, Formatter}, +}; + +#[derive(Debug, Clone)] +pub enum OutputMatch { + Any(Vec), + All { + name: Option, + connector: Option, + serial_number: Option, + manufacturer: Option, + model: Option, + }, +} + +#[derive(Debug, Clone)] +pub enum DrmDeviceMatch { + Any(Vec), + All { + name: Option, + syspath: Option, + vendor: Option, + vendor_name: Option, + model: Option, + model_name: Option, + devnode: Option, + }, +} + +#[derive(Debug, Clone)] +pub struct Mode { + pub width: i32, + pub height: i32, + pub refresh_rate: Option, +} + +impl Display for Mode { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{} x {}", self.width, self.height)?; + if let Some(rr) = self.refresh_rate { + write!(f, " @ {rr}")?; + } + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub struct Output { + pub name: Option, + pub match_: OutputMatch, + pub x: Option, + pub y: Option, + pub scale: Option, + pub transform: Option, + pub mode: Option, + pub vrr: Option, + pub tearing: Option, + pub format: Option, + pub color_space: Option, + pub eotf: Option, + pub brightness: Option>, + pub blend_space: Option, + pub use_native_gamut: Option, +} + +#[derive(Debug, Clone)] +pub enum ConnectorMatch { + Any(Vec), + All { connector: Option }, +} + +#[derive(Debug, Clone)] +pub struct ConfigConnector { + pub match_: ConnectorMatch, + pub enabled: bool, +} + +#[derive(Debug, Clone)] +pub struct ConfigDrmDevice { + pub name: Option, + pub match_: DrmDeviceMatch, + pub gfx_api: Option, + pub direct_scanout_enabled: Option, + pub flip_margin_ms: Option, +} diff --git a/crates/jay-config-schema/src/rules.rs b/crates/jay-config-schema/src/rules.rs new file mode 100644 index 00000000..563c60e1 --- /dev/null +++ b/crates/jay-config-schema/src/rules.rs @@ -0,0 +1,65 @@ +use jay_config::window::{ContentType, WindowType}; + +#[derive(Default, Debug, Clone)] +pub struct GenericMatch { + pub name: Option, + pub not: Option>, + pub all: Option>, + pub any: Option>, + pub exactly: Option>, +} + +#[derive(Debug, Clone)] +pub struct MatchExactly { + pub num: usize, + pub list: Vec, +} + +#[derive(Default, Debug, Clone)] +pub struct ClientMatch { + pub generic: GenericMatch, + pub sandbox_engine: Option, + pub sandbox_engine_regex: Option, + pub sandbox_app_id: Option, + pub sandbox_app_id_regex: Option, + pub sandbox_instance_id: Option, + pub sandbox_instance_id_regex: Option, + pub sandboxed: Option, + pub uid: Option, + pub pid: Option, + pub is_xwayland: Option, + pub comm: Option, + pub comm_regex: Option, + pub exe: Option, + pub exe_regex: Option, + pub tag: Option, + pub tag_regex: Option, +} + +#[derive(Default, Debug, Clone)] +pub struct WindowMatch { + pub generic: GenericMatch, + pub types: Option, + pub client: Option, + pub title: Option, + pub title_regex: Option, + pub app_id: Option, + pub app_id_regex: Option, + pub floating: Option, + pub visible: Option, + pub urgent: Option, + pub focused: Option, + pub fullscreen: Option, + pub just_mapped: Option, + pub tag: Option, + pub tag_regex: Option, + pub x_class: Option, + pub x_class_regex: Option, + pub x_instance: Option, + pub x_instance_regex: Option, + pub x_role: Option, + pub x_role_regex: Option, + pub workspace: Option, + pub workspace_regex: Option, + pub content_types: Option, +} diff --git a/crates/jay-config-schema/src/theme.rs b/crates/jay-config-schema/src/theme.rs new file mode 100644 index 00000000..ed21242d --- /dev/null +++ b/crates/jay-config-schema/src/theme.rs @@ -0,0 +1,48 @@ +use jay_config::theme::{BarPosition, Color}; + +#[derive(Debug, Clone, Default)] +pub struct Theme { + pub attention_requested_bg_color: Option, + pub bg_color: Option, + pub bar_bg_color: Option, + pub bar_status_text_color: Option, + pub border_color: Option, + pub active_border_color: Option, + pub captured_focused_title_bg_color: Option, + pub captured_unfocused_title_bg_color: Option, + pub focused_inactive_title_bg_color: Option, + pub focused_inactive_title_text_color: Option, + pub focused_title_bg_color: Option, + pub focused_title_text_color: Option, + pub separator_color: Option, + pub unfocused_title_bg_color: Option, + pub unfocused_title_text_color: Option, + pub highlight_color: Option, + pub border_width: Option, + pub title_height: Option, + pub bar_height: Option, + pub font: Option, + pub title_font: Option, + pub bar_font: Option, + pub bar_position: Option, + pub bar_separator_width: Option, + pub gap: Option, + pub floating_titles: Option, + pub title_gap: Option, + pub corner_radius: Option, + pub tab_active_bg_color: Option, + pub tab_active_border_color: Option, + pub tab_inactive_bg_color: Option, + pub tab_inactive_border_color: Option, + pub tab_active_text_color: Option, + pub tab_inactive_text_color: Option, + pub tab_bar_bg_color: Option, + pub tab_attention_bg_color: Option, + pub tab_bar_height: Option, + pub tab_bar_padding: Option, + pub tab_bar_radius: Option, + pub tab_bar_border_width: Option, + pub tab_bar_text_padding: Option, + pub tab_bar_gap: Option, + pub tab_title_align: Option, +} diff --git a/jay-config/Cargo.toml b/crates/jay-config/Cargo.toml similarity index 85% rename from jay-config/Cargo.toml rename to crates/jay-config/Cargo.toml index ce616ea3..cb9341f3 100644 --- a/jay-config/Cargo.toml +++ b/crates/jay-config/Cargo.toml @@ -1,13 +1,12 @@ [package] name = "jay-config" -version = "1.10.0" -edition = "2024" -license = "GPL-3.0-only" +version.workspace = true +edition.workspace = true +license.workspace = true description = "Configuration crate for the Jay compositor" repository = "https://github.com/mahkoh/jay" [dependencies] -bincode = "1.3.3" serde = { version = "1.0.196", features = ["derive"] } log = "0.4.14" futures-util = { version = "0.3.30", features = ["io"] } diff --git a/crates/jay-config/src/_private.rs b/crates/jay-config/src/_private.rs new file mode 100644 index 00000000..20ddec34 --- /dev/null +++ b/crates/jay-config/src/_private.rs @@ -0,0 +1,15 @@ +pub mod client; +mod logging; + +pub use crate::protocol::{ + ClientCriterionPayload, ClientCriterionStringField, ConfigEntry, ConfigHandler, + DEFAULT_SEAT_NAME, GenericCriterionPayload, PollableId, ServerHandler, Unref, VERSION, + WindowCriterionPayload, WindowCriterionStringField, WireMode, +}; + +pub mod messages { + pub use crate::protocol::{ + ClientMessage, InitMessage, Response, ServerFeature, ServerMessage, V1InitMessage, + WorkspaceSource, + }; +} diff --git a/jay-config/src/_private/client.rs b/crates/jay-config/src/_private/client.rs similarity index 93% rename from jay-config/src/_private/client.rs rename to crates/jay-config/src/_private/client.rs index 8ef87476..21659395 100644 --- a/jay-config/src/_private/client.rs +++ b/crates/jay-config/src/_private/client.rs @@ -3,16 +3,10 @@ use { crate::{ _private::{ - ClientCriterionIpc, ClientCriterionStringField, Config, ConfigEntry, ConfigEntryGen, - GenericCriterionIpc, PollableId, VERSION, WindowCriterionIpc, - WindowCriterionStringField, WireMode, bincode_ops, - ipc::{ - ClientMessage, InitMessage, Response, ServerFeature, ServerMessage, WorkspaceSource, - }, - logging, + ServerHandler, Unref, }, Axis, Direction, ModifiedKeySym, PciId, Workspace, - client::{Client, ClientCapabilities, ClientCriterion, ClientMatcher, MatchedClient}, + client::{Client, ClientCriterion, ClientMatcher, MatchedClient}, exec::Command, input::{ FallbackOutputMode, FocusFollowsMouseMode, InputDevice, LayerDirection, Seat, @@ -25,6 +19,12 @@ use { syms::KeySym, }, logging::LogLevel, + protocol::{ + ClientCriterionPayload, ClientCriterionStringField, ClientMessage, + GenericCriterionPayload, InitMessage, PollableId, Response, ServerFeature, + ServerMessage, WindowCriterionPayload, WindowCriterionStringField, WireMode, + WorkspaceSource, + }, tasks::{JoinHandle, JoinSlot}, theme::{BarPosition, Color, colors::Colorable, sized::Resizable}, timer::Timer, @@ -40,7 +40,6 @@ use { workspace::WorkspaceDisplayOrder, xwayland::XScalingMode, }, - bincode::Options, futures_util::task::ArcWake, run_on_drop::{OnDrop, on_drop}, std::{ @@ -54,7 +53,6 @@ use { pin::Pin, ptr, rc::Rc, - slice, sync::{ Arc, Mutex, atomic::{AtomicBool, Ordering::Relaxed}, @@ -91,10 +89,10 @@ struct KeyHandler { } pub(crate) struct ConfigClient { - configure: extern "C" fn(), + configure: fn(), srv_data: *const u8, - srv_unref: unsafe extern "C" fn(data: *const u8), - srv_handler: unsafe extern "C" fn(data: *const u8, msg: *const u8, size: usize), + srv_unref: Unref, + srv_handler: ServerHandler, key_handlers: RefCell>, timer_handlers: RefCell>, response: RefCell>, @@ -111,7 +109,6 @@ pub(crate) struct ConfigClient { on_idle: RefCell>, on_switch_event: RefCell>>, on_unload: Cell>>>, - bufs: RefCell>>, reload: Cell, read_interests: RefCell>, write_interests: RefCell>, @@ -199,43 +196,14 @@ unsafe fn with_client T>(data: *const u8, f: F) - }) } -impl ConfigEntryGen { - pub const ENTRY: ConfigEntry = ConfigEntry { - version: VERSION, - init: Self::init, - unref, - handle_msg, - }; - - pub unsafe extern "C" fn init( - srv_data: *const u8, - srv_unref: unsafe extern "C" fn(data: *const u8), - srv_handler: unsafe extern "C" fn(data: *const u8, msg: *const u8, size: usize), - init_data: *const u8, - size: usize, - ) -> *const u8 { - logging::init(); - unsafe { - init( - srv_data, - srv_unref, - srv_handler, - init_data, - size, - T::configure, - ) - } - } -} - -pub unsafe extern "C" fn init( +pub unsafe fn init( srv_data: *const u8, - srv_unref: unsafe extern "C" fn(data: *const u8), - srv_handler: unsafe extern "C" fn(data: *const u8, msg: *const u8, size: usize), - init: *const u8, - size: usize, - f: extern "C" fn(), + srv_unref: Unref, + srv_handler: ServerHandler, + init: InitMessage, + f: fn(), ) -> *const u8 { + super::logging::init(); let client = Rc::new(ConfigClient { configure: f, srv_data, @@ -257,7 +225,6 @@ pub unsafe extern "C" fn init( on_idle: Default::default(), on_switch_event: Default::default(), on_unload: Default::default(), - bufs: Default::default(), reload: Cell::new(false), read_interests: Default::default(), write_interests: Default::default(), @@ -270,22 +237,20 @@ pub unsafe extern "C" fn init( feat_mod_mask: Cell::new(false), feat_show_workspace_on: Cell::new(false), }); - let init = unsafe { slice::from_raw_parts(init, size) }; client.handle_init_msg(init); Rc::into_raw(client) as *const u8 } -pub unsafe extern "C" fn unref(data: *const u8) { +pub unsafe fn unref(data: *const u8) { let client = data as *const ConfigClient; unsafe { drop(Rc::from_raw(client)); } } -pub unsafe extern "C" fn handle_msg(data: *const u8, msg: *const u8, size: usize) { +pub unsafe fn handle_msg(data: *const u8, msg: &ServerMessage) { unsafe { with_client(data, |client| { - let msg = slice::from_raw_parts(msg, size); client.handle_msg(msg); }); } @@ -315,13 +280,9 @@ enum GenericCriterion<'a, Crit, Matcher> { impl ConfigClient { fn send(&self, msg: &ClientMessage) { - let mut buf = self.bufs.borrow_mut().pop().unwrap_or_default(); - buf.clear(); - bincode_ops().serialize_into(&mut buf, msg).unwrap(); unsafe { - (self.srv_handler)(self.srv_data, buf.as_ptr(), buf.len()); + (self.srv_handler)(self.srv_data, msg); } - self.bufs.borrow_mut().push(buf); } fn send_with_response(&self, msg: &ClientMessage) -> Response { @@ -640,6 +601,22 @@ impl ConfigClient { self.send(&ClientMessage::SetWindowWorkspace { window, workspace }); } + pub fn seat_send_to_scratchpad(&self, seat: Seat, name: &str) { + self.send(&ClientMessage::SeatSendToScratchpad { seat, name }); + } + + pub fn seat_toggle_scratchpad(&self, seat: Seat, name: &str) { + self.send(&ClientMessage::SeatToggleScratchpad { seat, name }); + } + + pub fn seat_cycle_scratchpad(&self, seat: Seat, name: &str) { + self.send(&ClientMessage::SeatCycleScratchpad { seat, name }); + } + + pub fn window_send_to_scratchpad(&self, window: Window, name: &str) { + self.send(&ClientMessage::WindowSendToScratchpad { window, name }); + } + pub fn seat_split(&self, seat: Seat) -> Axis { let res = self.send_with_response(&ClientMessage::GetSeatSplit { seat }); get_response!(res, Axis::Horizontal, GetSplit { axis }); @@ -1023,6 +1000,26 @@ impl ConfigClient { self.send(&ClientMessage::SetUiDragThreshold { threshold }); } + pub fn set_animations_enabled(&self, enabled: bool) { + self.send(&ClientMessage::SetAnimationsEnabled { enabled }); + } + + pub fn set_animation_duration_ms(&self, duration_ms: u32) { + self.send(&ClientMessage::SetAnimationDurationMs { duration_ms }); + } + + pub fn set_animation_curve(&self, curve: u32) { + self.send(&ClientMessage::SetAnimationCurve { curve }); + } + + pub fn set_animation_style(&self, style: u32) { + self.send(&ClientMessage::SetAnimationStyle { style }); + } + + pub fn set_animation_cubic_bezier(&self, x1: f32, y1: f32, x2: f32, y2: f32) { + self.send(&ClientMessage::SetAnimationCubicBezier { x1, y1, x2, y2 }); + } + pub fn set_color_management_enabled(&self, enabled: bool) { self.send(&ClientMessage::SetColorManagementEnabled { enabled }); } @@ -1327,6 +1324,14 @@ impl ConfigClient { self.send(&ClientMessage::SetIdle { timeout }) } + pub fn set_key_press_enables_dpms(&self, enabled: bool) { + self.send(&ClientMessage::SetKeyPressEnablesDpms { enabled }) + } + + pub fn set_mouse_move_enables_dpms(&self, enabled: bool) { + self.send(&ClientMessage::SetMouseMoveEnablesDpms { enabled }) + } + pub fn set_idle_grace_period(&self, period: Duration) { self.send(&ClientMessage::SetIdleGracePeriod { period }) } @@ -1528,22 +1533,6 @@ impl ConfigClient { connector } - pub fn set_client_matcher_capabilities( - &self, - matcher: ClientMatcher, - caps: ClientCapabilities, - ) { - self.send(&ClientMessage::SetClientMatcherCapabilities { matcher, caps }); - } - - pub fn set_client_matcher_bounding_capabilities( - &self, - matcher: ClientMatcher, - caps: ClientCapabilities, - ) { - self.send(&ClientMessage::SetClientMatcherBoundingCapabilities { matcher, caps }); - } - pub fn latch(&self, seat: Seat, f: F) { if !self.feat_mod_mask.get() { log::error!("compositor does not support latching"); @@ -1636,6 +1625,7 @@ impl ConfigClient { } } + #[allow(dead_code)] pub fn log(&self, level: LogLevel, msg: &str, file: Option<&str>, line: Option) { self.send(&ClientMessage::Log { level, @@ -1645,12 +1635,6 @@ impl ConfigClient { }) } - pub fn get_socket_path(&self) -> Option { - let res = self.send_with_response(&ClientMessage::GetSocketPath); - get_response!(res, None, GetSocketPath { path }); - Some(path) - } - pub fn create_pollable(&self, fd: i32) -> Result { let res = self.send_with_response(&ClientMessage::AddPollable { fd }); get_response!( @@ -1732,7 +1716,7 @@ impl ConfigClient { criterion: GenericCriterion<'_, Crit, Matcher>, child: bool, create_child_matcher: impl Fn(Crit) -> (Matcher, bool), - create_matcher: impl Fn(GenericCriterionIpc) -> Matcher, + create_matcher: impl Fn(GenericCriterionPayload) -> Matcher, destroy_matcher: impl Fn(Matcher), ) -> (Matcher, bool) where @@ -1759,18 +1743,18 @@ impl ConfigClient { if child { return (m, false); } - GenericCriterionIpc::Matcher(m) + GenericCriterionPayload::Matcher(m) } - GenericCriterion::Not(c) => GenericCriterionIpc::Not(create_child_matcher(*c)), - GenericCriterion::All(l) => GenericCriterionIpc::List { + GenericCriterion::Not(c) => GenericCriterionPayload::Not(create_child_matcher(*c)), + GenericCriterion::All(l) => GenericCriterionPayload::List { list: create_vec(l), all: true, }, - GenericCriterion::Any(l) => GenericCriterionIpc::List { + GenericCriterion::Any(l) => GenericCriterionPayload::List { list: create_vec(l), all: false, }, - GenericCriterion::Exactly(num, l) => GenericCriterionIpc::Exactly { + GenericCriterion::Exactly(num, l) => GenericCriterionPayload::Exactly { list: create_vec(l), num, }, @@ -1793,7 +1777,7 @@ impl ConfigClient { ) -> (ClientMatcher, bool) { macro_rules! string { ($t:expr, $field:ident, $regex:expr) => { - ClientCriterionIpc::String { + ClientCriterionPayload::String { string: $t.to_string(), field: ClientCriterionStringField::$field, regex: $regex, @@ -1802,7 +1786,7 @@ impl ConfigClient { } let create_matcher = |criterion| { let res = self.send_with_response(&ClientMessage::CreateClientMatcher { - criterion: ClientCriterionIpc::Generic(criterion), + criterion: ClientCriterionPayload::Generic(criterion), }); get_response!(res, ClientMatcher(0), CreateClientMatcher { matcher }); matcher @@ -1831,10 +1815,10 @@ impl ConfigClient { ClientCriterion::SandboxAppIdRegex(t) => string!(t, SandboxAppId, true), ClientCriterion::SandboxInstanceId(t) => string!(t, SandboxInstanceId, false), ClientCriterion::SandboxInstanceIdRegex(t) => string!(t, SandboxInstanceId, true), - ClientCriterion::Sandboxed => ClientCriterionIpc::Sandboxed, - ClientCriterion::Uid(p) => ClientCriterionIpc::Uid(p), - ClientCriterion::Pid(p) => ClientCriterionIpc::Pid(p), - ClientCriterion::IsXwayland => ClientCriterionIpc::IsXwayland, + ClientCriterion::Sandboxed => ClientCriterionPayload::Sandboxed, + ClientCriterion::Uid(p) => ClientCriterionPayload::Uid(p), + ClientCriterion::Pid(p) => ClientCriterionPayload::Pid(p), + ClientCriterion::IsXwayland => ClientCriterionPayload::IsXwayland, ClientCriterion::Comm(t) => string!(t, Comm, false), ClientCriterion::CommRegex(t) => string!(t, Comm, true), ClientCriterion::Exe(t) => string!(t, Exe, false), @@ -1896,7 +1880,7 @@ impl ConfigClient { ) -> (WindowMatcher, bool) { macro_rules! string { ($t:expr, $field:ident, $regex:expr) => { - WindowCriterionIpc::String { + WindowCriterionPayload::String { string: $t.to_string(), field: WindowCriterionStringField::$field, regex: $regex, @@ -1905,7 +1889,7 @@ impl ConfigClient { } let create_matcher = |criterion| { let res = self.send_with_response(&ClientMessage::CreateWindowMatcher { - criterion: WindowCriterionIpc::Generic(criterion), + criterion: WindowCriterionPayload::Generic(criterion), }); get_response!(res, WindowMatcher(0), CreateWindowMatcher { matcher }); matcher @@ -1929,24 +1913,24 @@ impl ConfigClient { WindowCriterion::All(c) => return generic(GenericCriterion::All(c)), WindowCriterion::Any(c) => return generic(GenericCriterion::Any(c)), WindowCriterion::Exactly(n, c) => return generic(GenericCriterion::Exactly(n, c)), - WindowCriterion::Types(t) => WindowCriterionIpc::Types(t), + WindowCriterion::Types(t) => WindowCriterionPayload::Types(t), WindowCriterion::Client(c) => { let (matcher, original) = self.create_client_matcher_(*c, true); if original { _destroy_client_matcher = on_drop(move || matcher.destroy()); } - WindowCriterionIpc::Client(matcher) + WindowCriterionPayload::Client(matcher) } WindowCriterion::Title(t) => string!(t, Title, false), WindowCriterion::TitleRegex(t) => string!(t, Title, true), WindowCriterion::AppId(t) => string!(t, AppId, false), WindowCriterion::AppIdRegex(t) => string!(t, AppId, true), - WindowCriterion::Floating => WindowCriterionIpc::Floating, - WindowCriterion::Visible => WindowCriterionIpc::Visible, - WindowCriterion::Urgent => WindowCriterionIpc::Urgent, - WindowCriterion::Focus(seat) => WindowCriterionIpc::SeatFocus(seat), - WindowCriterion::Fullscreen => WindowCriterionIpc::Fullscreen, - WindowCriterion::JustMapped => WindowCriterionIpc::JustMapped, + WindowCriterion::Floating => WindowCriterionPayload::Floating, + WindowCriterion::Visible => WindowCriterionPayload::Visible, + WindowCriterion::Urgent => WindowCriterionPayload::Urgent, + WindowCriterion::Focus(seat) => WindowCriterionPayload::SeatFocus(seat), + WindowCriterion::Fullscreen => WindowCriterionPayload::Fullscreen, + WindowCriterion::JustMapped => WindowCriterionPayload::JustMapped, WindowCriterion::Tag(t) => string!(t, Tag, false), WindowCriterion::TagRegex(t) => string!(t, Tag, true), WindowCriterion::XClass(t) => string!(t, XClass, false), @@ -1955,10 +1939,10 @@ impl ConfigClient { WindowCriterion::XInstanceRegex(t) => string!(t, XInstance, true), WindowCriterion::XRole(t) => string!(t, XRole, false), WindowCriterion::XRoleRegex(t) => string!(t, XRole, true), - WindowCriterion::Workspace(t) => WindowCriterionIpc::Workspace(t), + WindowCriterion::Workspace(t) => WindowCriterionPayload::Workspace(t), WindowCriterion::WorkspaceName(t) => string!(t, Workspace, false), WindowCriterion::WorkspaceNameRegex(t) => string!(t, Workspace, true), - WindowCriterion::ContentTypes(t) => WindowCriterionIpc::ContentTypes(t), + WindowCriterion::ContentTypes(t) => WindowCriterionPayload::ContentTypes(t), }; let res = self.send_with_response(&ClientMessage::CreateWindowMatcher { criterion }); get_response!( @@ -2059,6 +2043,12 @@ impl ConfigClient { self.send(&ClientMessage::SetAutotile { enabled }); } + pub fn get_autotile(&self) -> bool { + let res = self.send_with_response(&ClientMessage::GetAutotile); + get_response!(res, false, GetAutotile { enabled }); + enabled + } + pub fn set_tab_title_align(&self, align: u32) { self.send(&ClientMessage::SetTabTitleAlign { align }); } @@ -2067,7 +2057,7 @@ impl ConfigClient { self.send(&ClientMessage::SeatMoveTab { seat, right }); } - fn handle_msg(&self, msg: &[u8]) { + fn handle_msg(&self, msg: &ServerMessage) { self.handle_msg2(msg); self.dispatch_futures(); } @@ -2198,16 +2188,8 @@ impl ConfigClient { } } - fn handle_msg2(&self, msg: &[u8]) { - let res = bincode_ops().deserialize::(msg); - let msg = match res { - Ok(msg) => msg, - Err(e) => { - let msg = format!("could not deserialize message: {}", e); - self.log(LogLevel::Error, &msg, None, None); - return; - } - }; + fn handle_msg2(&self, msg: &ServerMessage) { + let msg = msg.clone(); match msg { ServerMessage::Configure { reload } => { self.reload.set(reload); @@ -2382,15 +2364,7 @@ impl ConfigClient { } } - fn handle_init_msg(&self, msg: &[u8]) { - let init = match bincode_ops().deserialize::(msg) { - Ok(m) => m, - Err(e) => { - let msg = format!("could not deserialize message: {}", e); - self.log(LogLevel::Error, &msg, None, None); - return; - } - }; + fn handle_init_msg(&self, init: InitMessage) { match init { InitMessage::V1(_) => {} } diff --git a/jay-config/src/_private/logging.rs b/crates/jay-config/src/_private/logging.rs similarity index 100% rename from jay-config/src/_private/logging.rs rename to crates/jay-config/src/_private/logging.rs diff --git a/jay-config/src/client.rs b/crates/jay-config/src/client.rs similarity index 51% rename from jay-config/src/client.rs rename to crates/jay-config/src/client.rs index 38a82d42..5a2e4173 100644 --- a/jay-config/src/client.rs +++ b/crates/jay-config/src/client.rs @@ -110,19 +110,6 @@ impl ClientCriterion<'_> { self.to_matcher().bind(cb); } - /// Sets the capabilities granted to clients matching this matcher. - /// - /// This leaks the matcher. - pub fn set_capabilities(self, caps: ClientCapabilities) { - self.to_matcher().set_capabilities(caps); - } - - /// Sets the upper capability bounds for clients in sandboxes created by this client. - /// - /// This leaks the matcher. - pub fn set_sandbox_bounding_capabilities(self, caps: ClientCapabilities) { - self.to_matcher().set_sandbox_bounding_capabilities(caps); - } } impl ClientMatcher { @@ -140,35 +127,6 @@ impl ClientMatcher { get!().set_client_matcher_handler(self, cb); } - /// Sets the capabilities granted to clients matching this matcher. - /// - /// If multiple matchers match a client, the capabilities are added. - /// - /// If no matcher matches a client, it is granted the default capabilities depending - /// on whether it's sandboxed or not. If it is not sandboxed, it is granted the - /// capabilities [`CC_LAYER_SHELL`] and [`CC_DRM_LEASE`]. Otherwise it is granted the - /// capability [`CC_DRM_LEASE`]. - /// - /// Regardless of any capabilities set through this function, the capabilities of the - /// client can never exceed its bounding capabilities. - pub fn set_capabilities(self, caps: ClientCapabilities) { - get!().set_client_matcher_capabilities(self, caps); - } - - /// Sets the upper capability bounds for clients in sandboxes created by this client. - /// - /// If multiple matchers match a client, the capabilities are added. - /// - /// If no matcher matches a client, the bounding capabilities for sandboxes depend on - /// whether the client is itself sandboxed. If it is sandboxed, the bounding - /// capabilities are the effective capabilities of the client. Otherwise the bounding - /// capabilities are all capabilities. - /// - /// Regardless of any capabilities set through this function, the capabilities set - /// through this function can never exceed the client's bounding capabilities. - pub fn set_sandbox_bounding_capabilities(self, caps: ClientCapabilities) { - get!().set_client_matcher_bounding_capabilities(self, caps); - } } impl MatchedClient { @@ -195,45 +153,3 @@ impl Deref for MatchedClient { &self.client } } - -bitflags! { - /// Capabilities granted to a client. - #[derive(Serialize, Deserialize, Copy, Clone, Hash, Eq, PartialEq)] - pub struct ClientCapabilities(pub u64) { - /// Grants access to the `ext_data_control_manager_v1` and - /// `zwlr_data_control_manager_v1` globals. - pub const CC_DATA_CONTROL = 1 << 0, - /// Grants access to the `zwp_virtual_keyboard_manager_v1` global. - pub const CC_VIRTUAL_KEYBOARD = 1 << 1, - /// Grants access to the `ext_foreign_toplevel_list_v1` global. - pub const CC_FOREIGN_TOPLEVEL_LIST = 1 << 2, - /// Grants access to the `ext_idle_notifier_v1` global. - pub const CC_IDLE_NOTIFIER = 1 << 3, - /// Grants access to the `ext_session_lock_manager_v1` global. - pub const CC_SESSION_LOCK = 1 << 4, - /// Grants access to the `zwlr_layer_shell_v1` global. - pub const CC_LAYER_SHELL = 1 << 6, - /// Grants access to the `ext_image_copy_capture_manager_v1` and - /// `zwlr_screencopy_manager_v1` globals. - pub const CC_SCREENCOPY = 1 << 7, - /// Grants access to the `ext_transient_seat_manager_v1` global. - pub const CC_SEAT_MANAGER = 1 << 8, - /// Grants access to the `wp_drm_lease_device_v1` global. - pub const CC_DRM_LEASE = 1 << 9, - /// Grants access to the `zwp_input_method_manager_v2` global. - pub const CC_INPUT_METHOD = 1 << 10, - /// Grants access to the `ext_workspace_manager_v1` global. - pub const CC_WORKSPACE_MANAGER = 1 << 11, - /// Grants access to the `zwlr_foreign_toplevel_manager_v1` global. - pub const CC_FOREIGN_TOPLEVEL_MANAGER = 1 << 12, - /// Grants access to the `jay_head_manager_v1` and `zwlr_output_manager_v1` - /// globals. - pub const CC_HEAD_MANAGER = 1 << 13, - /// Grants access to the `zwlr_gamma_control_manager_v1` global. - pub const CC_GAMMA_CONTROL_MANAGER = 1 << 14, - /// Grants access to the `zwlr_virtual_pointer_manager_v1` global. - pub const CC_VIRTUAL_POINTER = 1 << 15, - /// Grants access to the `ext_foreign_toplevel_geometry_tracking_manager_v1` global. - pub const CC_FOREIGN_TOPLEVEL_GEOMETRY_TRACKING = 1 << 16, - } -} diff --git a/jay-config/src/embedded.rs b/crates/jay-config/src/embedded.rs similarity index 100% rename from jay-config/src/embedded.rs rename to crates/jay-config/src/embedded.rs diff --git a/jay-config/src/exec.rs b/crates/jay-config/src/exec.rs similarity index 86% rename from jay-config/src/exec.rs rename to crates/jay-config/src/exec.rs index 61074167..bedc70c3 100644 --- a/jay-config/src/exec.rs +++ b/crates/jay-config/src/exec.rs @@ -84,21 +84,6 @@ impl Command { self.fd(2, fd) } - /// Runs the application with access to privileged wayland protocols. - /// - /// The default is `false`. - pub fn privileged(&mut self) -> &mut Self { - match get!(self).get_socket_path() { - Some(path) => { - self.env("WAYLAND_DISPLAY", &format!("{path}.jay")); - } - _ => { - log::error!("Compositor did not send the socket path"); - } - } - self - } - /// Adds a tag to Wayland connections created by the spawned command. pub fn tag(&mut self, tag: &str) -> &mut Self { self.tag = Some(tag.to_owned()); diff --git a/jay-config/src/input.rs b/crates/jay-config/src/input.rs similarity index 96% rename from jay-config/src/input.rs rename to crates/jay-config/src/input.rs index dbdef1ba..c052bba7 100644 --- a/jay-config/src/input.rs +++ b/crates/jay-config/src/input.rs @@ -6,10 +6,10 @@ pub mod clickmethod; use { crate::{ - _private::{DEFAULT_SEAT_NAME, ipc::WorkspaceSource}, Axis, Direction, ModifiedKeySym, Workspace, input::{acceleration::AccelProfile, capability::Capability, clickmethod::ClickMethod}, keyboard::{Keymap, mods::Modifiers, syms::KeySym}, + protocol::{DEFAULT_SEAT_NAME, WorkspaceSource}, video::Connector, window::Window, }, @@ -466,6 +466,33 @@ impl Seat { get!().set_seat_workspace(self, workspace) } + /// Sends the currently focused window to a scratchpad. + /// + /// Use an empty string for the default scratchpad. + pub fn send_to_scratchpad(self, name: &str) { + get!().seat_send_to_scratchpad(self, name) + } + + /// Toggles a scratchpad. + /// + /// If the scratchpad has a visible window, that window is hidden. Otherwise, the + /// most recently hidden window in the scratchpad is shown on the current workspace. + /// Scratchpad windows are always shown floating. + /// Use an empty string for the default scratchpad. + pub fn toggle_scratchpad(self, name: &str) { + get!().seat_toggle_scratchpad(self, name) + } + + /// Cycles through the windows of a scratchpad, one at a time. + /// + /// With nothing shown, the first window is brought up; each further invocation + /// hides the current window and shows the next; after the last window the + /// scratchpad is hidden again. Scratchpad windows are always shown floating. + /// Use an empty string for the default scratchpad. + pub fn cycle_scratchpad(self, name: &str) { + get!().seat_cycle_scratchpad(self, name) + } + /// Toggles whether the currently focused window is fullscreen. pub fn toggle_fullscreen(self) { let c = get!(); @@ -832,8 +859,6 @@ pub enum SwitchEvent { /// Enables or disables the unauthenticated libei socket. /// -/// Even if the socket is disabled, application can still request access via the portal. -/// /// The default is `false`. pub fn set_libei_socket_enabled(enabled: bool) { get!().set_ei_socket_enabled(enabled); diff --git a/jay-config/src/input/acceleration.rs b/crates/jay-config/src/input/acceleration.rs similarity index 100% rename from jay-config/src/input/acceleration.rs rename to crates/jay-config/src/input/acceleration.rs diff --git a/jay-config/src/input/capability.rs b/crates/jay-config/src/input/capability.rs similarity index 100% rename from jay-config/src/input/capability.rs rename to crates/jay-config/src/input/capability.rs diff --git a/jay-config/src/input/clickmethod.rs b/crates/jay-config/src/input/clickmethod.rs similarity index 100% rename from jay-config/src/input/clickmethod.rs rename to crates/jay-config/src/input/clickmethod.rs diff --git a/jay-config/src/io.rs b/crates/jay-config/src/io.rs similarity index 99% rename from jay-config/src/io.rs rename to crates/jay-config/src/io.rs index 6eece9ad..936af495 100644 --- a/jay-config/src/io.rs +++ b/crates/jay-config/src/io.rs @@ -1,7 +1,7 @@ //! Tools for IO operations. use { - crate::_private::PollableId, + crate::protocol::PollableId, futures_util::{AsyncWrite, io::AsyncRead}, std::{ future::poll_fn, diff --git a/jay-config/src/keyboard/mod.rs b/crates/jay-config/src/keyboard/mod.rs similarity index 100% rename from jay-config/src/keyboard/mod.rs rename to crates/jay-config/src/keyboard/mod.rs diff --git a/jay-config/src/keyboard/mods.rs b/crates/jay-config/src/keyboard/mods.rs similarity index 100% rename from jay-config/src/keyboard/mods.rs rename to crates/jay-config/src/keyboard/mods.rs diff --git a/jay-config/src/keyboard/syms.rs b/crates/jay-config/src/keyboard/syms.rs similarity index 100% rename from jay-config/src/keyboard/syms.rs rename to crates/jay-config/src/keyboard/syms.rs diff --git a/jay-config/src/lib.rs b/crates/jay-config/src/lib.rs similarity index 80% rename from jay-config/src/lib.rs rename to crates/jay-config/src/lib.rs index e25710f9..91dbbcae 100644 --- a/jay-config/src/lib.rs +++ b/crates/jay-config/src/lib.rs @@ -1,36 +1,5 @@ -//! This crate allows you to configure the Jay compositor. -//! -//! A minimal example configuration looks as follows: -//! -//! ```rust -//! use jay_config::{config, quit, reload}; -//! use jay_config::input::get_default_seat; -//! use jay_config::keyboard::mods::ALT; -//! use jay_config::keyboard::syms::{SYM_q, SYM_r}; -//! -//! fn configure() { -//! let seat = get_default_seat(); -//! // Create a key binding to exit the compositor. -//! seat.bind(ALT | SYM_q, || quit()); -//! // Reload the configuration. -//! seat.bind(ALT | SYM_r, || reload()); -//! } -//! -//! config!(configure); -//! ``` -//! -//! You should configure your crate to be compiled as a shared library: -//! -//! ```toml -//! [lib] -//! crate-type = ["cdylib"] -//! ``` -//! -//! After compiling it, copy the shared library to `$HOME/.config/jay/config.so` and restart -//! the compositor. It should then use your configuration file. -//! -//! Note that you do not have to restart the compositor every time you want to reload your -//! configuration afterwards. Instead, simply invoke the [`reload`] function via a shortcut. +//! Internal Rust configuration API used by Jay's built-in TOML configuration +//! implementation. #![allow( clippy::zero_prefixed_literal, @@ -48,7 +17,7 @@ use crate::input::Seat; use { crate::{ - _private::ipc::WorkspaceSource, keyboard::ModifiedKeySym, video::Connector, window::Window, + keyboard::ModifiedKeySym, protocol::WorkspaceSource, video::Connector, window::Window, }, serde::{Deserialize, Serialize}, std::{ @@ -68,6 +37,7 @@ pub mod input; pub mod io; pub mod keyboard; pub mod logging; +pub mod protocol; pub mod status; pub mod tasks; pub mod theme; @@ -103,6 +73,27 @@ impl Axis { } } +/// The curve used for tiled window animations. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct AnimationCurve(pub u32); + +impl AnimationCurve { + pub const LINEAR: Self = Self(0); + pub const EASE: Self = Self(1); + pub const EASE_IN: Self = Self(2); + pub const EASE_OUT: Self = Self(3); + pub const EASE_IN_OUT: Self = Self(4); +} + +/// The presentation style used for tiled window movement animations. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct AnimationStyle(pub u32); + +impl AnimationStyle { + pub const PLAIN: Self = Self(0); + pub const MULTIPHASE: Self = Self(1); +} + /// Exits the compositor. pub fn quit() { get!().quit() @@ -252,6 +243,20 @@ pub fn set_idle(timeout: Option) { get!().set_idle(timeout.unwrap_or_default()) } +/// Configures whether a key press turns monitors back on after `jay dpms off`. +/// +/// The default is `false`. +pub fn set_key_press_enables_dpms(enabled: bool) { + get!().set_key_press_enables_dpms(enabled) +} + +/// Configures whether mouse movement turns monitors back on after `jay dpms off`. +/// +/// The default is `false`. +pub fn set_mouse_move_enables_dpms(enabled: bool) { + get!().set_mouse_move_enables_dpms(enabled) +} + /// Configures the idle grace period. /// /// The grace period starts after the idle timeout expires. During the grace period, the @@ -287,6 +292,42 @@ pub fn set_ui_drag_threshold(threshold: i32) { get!().set_ui_drag_threshold(threshold); } +/// Enables or disables tiled window animations. +/// +/// The default is `false`. +pub fn set_animations_enabled(enabled: bool) { + get!().set_animations_enabled(enabled); +} + +/// Sets the duration of tiled window animations in milliseconds. +/// +/// The default is `160`. +pub fn set_animation_duration_ms(duration_ms: u32) { + get!().set_animation_duration_ms(duration_ms); +} + +/// Sets the curve used by tiled window animations. +/// +/// The default is [`AnimationCurve::EASE_OUT`]. +pub fn set_animation_curve(curve: AnimationCurve) { + get!().set_animation_curve(curve.0); +} + +/// Sets the presentation style used for tiled window movement animations. +/// +/// The default is [`AnimationStyle::MULTIPHASE`]. +pub fn set_animation_style(style: AnimationStyle) { + get!().set_animation_style(style.0); +} + +/// Sets a custom cubic-bezier curve used by tiled window animations. +/// +/// `x1` and `x2` must be between `0.0` and `1.0`. The curve starts at `(0, 0)` +/// and ends at `(1, 1)`. +pub fn set_animation_cubic_bezier(x1: f32, y1: f32, x2: f32, y2: f32) { + get!().set_animation_cubic_bezier(x1, y1, x2, y2); +} + /// Enables or disables the color-management protocol. /// /// The default is `false`. @@ -396,14 +437,21 @@ pub fn get_corner_radius() -> f32 { /// Enables or disables autotiling. /// -/// When enabled, new windows are automatically placed in a perpendicular -/// sub-container if the predicted body would be narrower than tall (or vice versa). +/// When enabled, newly tiled windows alternate split orientation from the +/// focused tiled window: the first split uses the containing group's direction, +/// then subsequent splits wrap the focused window in the perpendicular +/// direction. /// /// The default is `false`. pub fn set_autotile(enabled: bool) { get!().set_autotile(enabled) } +/// Returns whether autotiling is enabled. +pub fn get_autotile() -> bool { + get!(false).get_autotile() +} + /// Sets the horizontal alignment of title text within tab buttons. /// /// - `"start"` — left-aligned (default) diff --git a/jay-config/src/logging.rs b/crates/jay-config/src/logging.rs similarity index 100% rename from jay-config/src/logging.rs rename to crates/jay-config/src/logging.rs diff --git a/jay-config/src/macros.rs b/crates/jay-config/src/macros.rs similarity index 85% rename from jay-config/src/macros.rs rename to crates/jay-config/src/macros.rs index 03b87581..fca5db04 100644 --- a/jay-config/src/macros.rs +++ b/crates/jay-config/src/macros.rs @@ -1,21 +1,3 @@ -/// Declares the entry point of the configuration. -#[macro_export] -macro_rules! config { - ($f:path) => { - #[unsafe(no_mangle)] - #[used] - pub static mut JAY_CONFIG_ENTRY_V1: $crate::_private::ConfigEntry = { - struct X; - impl $crate::_private::Config for X { - extern "C" fn configure() { - $f(); - } - } - $crate::_private::ConfigEntryGen::::ENTRY - }; - }; -} - macro_rules! try_get { () => {{ unsafe { diff --git a/jay-config/src/_private/ipc.rs b/crates/jay-config/src/protocol.rs similarity index 84% rename from jay-config/src/_private/ipc.rs rename to crates/jay-config/src/protocol.rs index acb5ad81..e45224a0 100644 --- a/jay-config/src/_private/ipc.rs +++ b/crates/jay-config/src/protocol.rs @@ -1,8 +1,7 @@ use { crate::{ - _private::{ClientCriterionIpc, PollableId, WindowCriterionIpc, WireMode}, Axis, Direction, PciId, Workspace, - client::{Client, ClientCapabilities, ClientMatcher}, + client::{Client, ClientMatcher}, input::{ FallbackOutputMode, FocusFollowsMouseMode, InputDevice, LayerDirection, Seat, SwitchEvent, Timeline, acceleration::AccelProfile, capability::Capability, @@ -13,7 +12,7 @@ use { theme::{BarPosition, Color, colors::Colorable, sized::Resizable}, timer::Timer, video::{ - BlendSpace, ColorSpace, Connector, DrmDevice, Eotf, Format, GfxApi, TearingMode, + BlendSpace, ColorSpace, Connector, DrmDevice, Eotf, Format, GfxApi, Mode, TearingMode, Transform, VrrMode, connector_type::ConnectorType, }, window::{ContentType, TileState, Window, WindowMatcher, WindowType}, @@ -24,7 +23,134 @@ use { std::time::{Duration, SystemTime}, }; -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +pub const VERSION: u32 = 1; + +pub type ServerHandler = unsafe fn(data: *const u8, msg: &ClientMessage<'_>); +pub type ConfigHandler = unsafe fn(data: *const u8, msg: &ServerMessage); +pub type Unref = unsafe fn(data: *const u8); + +pub struct ConfigEntry { + pub version: u32, + pub init: unsafe fn( + srv_data: *const u8, + srv_unref: Unref, + srv_handler: ServerHandler, + msg: InitMessage, + ) -> *const u8, + pub unref: Unref, + pub handle_msg: ConfigHandler, +} + +pub unsafe fn init_client( + srv_data: *const u8, + srv_unref: Unref, + srv_handler: ServerHandler, + msg: InitMessage, + configure: fn(), +) -> *const u8 { + unsafe { + crate::_private::client::init(srv_data, srv_unref, srv_handler, msg, configure) + } +} + +pub unsafe fn unref_client(data: *const u8) { + unsafe { + crate::_private::client::unref(data); + } +} + +pub unsafe fn handle_client_message(data: *const u8, msg: &ServerMessage) { + unsafe { + crate::_private::client::handle_msg(data, msg); + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct WireMode { + pub width: i32, + pub height: i32, + pub refresh_millihz: u32, +} + +impl WireMode { + pub fn to_mode(self) -> Mode { + Mode { + width: self.width, + height: self.height, + refresh_millihz: self.refresh_millihz, + } + } +} + +#[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct PollableId(pub u64); + +pub const DEFAULT_SEAT_NAME: &str = "default"; + +#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)] +pub enum GenericCriterionPayload { + Matcher(T), + Not(T), + List { list: Vec, all: bool }, + Exactly { list: Vec, num: usize }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)] +pub enum ClientCriterionPayload { + Generic(GenericCriterionPayload), + String { + string: String, + field: ClientCriterionStringField, + regex: bool, + }, + Sandboxed, + Uid(i32), + Pid(i32), + IsXwayland, +} + +#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)] +pub enum ClientCriterionStringField { + SandboxEngine, + SandboxAppId, + SandboxInstanceId, + Comm, + Exe, + Tag, +} + +#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)] +pub enum WindowCriterionPayload { + Generic(GenericCriterionPayload), + String { + string: String, + field: WindowCriterionStringField, + regex: bool, + }, + Types(WindowType), + Client(ClientMatcher), + Floating, + Visible, + Urgent, + SeatFocus(Seat), + Fullscreen, + JustMapped, + Workspace(Workspace), + ContentTypes(ContentType), +} + +#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)] +pub enum WindowCriterionStringField { + Title, + AppId, + Tag, + XClass, + XInstance, + XRole, + Workspace, +} + +#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq)] #[serde(transparent)] pub struct ServerFeature(u16); @@ -34,7 +160,7 @@ impl ServerFeature { pub const SHOW_WORKSPACE_ON: Self = Self(2); } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub enum ServerMessage { Configure { reload: bool, @@ -115,7 +241,7 @@ pub enum ServerMessage { }, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub enum ClientMessage<'a> { Reload, Quit, @@ -286,6 +412,18 @@ pub enum ClientMessage<'a> { seat: Seat, workspace: Workspace, }, + SeatSendToScratchpad { + seat: Seat, + name: &'a str, + }, + SeatToggleScratchpad { + seat: Seat, + name: &'a str, + }, + SeatCycleScratchpad { + seat: Seat, + name: &'a str, + }, GetTimer { name: &'a str, }, @@ -432,6 +570,13 @@ pub enum ClientMessage<'a> { env: Vec<(String, String)>, fds: Vec<(i32, i32)>, }, + Run3 { + prog: &'a str, + args: Vec, + env: Vec<(String, String)>, + fds: Vec<(i32, i32)>, + tag: Option<&'a str>, + }, DisableDefaultSeat, DestroyKeymap { keymap: Keymap, @@ -475,6 +620,12 @@ pub enum ClientMessage<'a> { SetIdle { timeout: Duration, }, + SetKeyPressEnablesDpms { + enabled: bool, + }, + SetMouseMoveEnablesDpms { + enabled: bool, + }, MoveToOutput { workspace: WorkspaceSource, connector: Connector, @@ -482,7 +633,6 @@ pub enum ClientMessage<'a> { SetExplicitSyncEnabled { enabled: bool, }, - GetSocketPath, DeviceSetKeymap { device: InputDevice, keymap: Keymap, @@ -545,6 +695,24 @@ pub enum ClientMessage<'a> { SetUiDragThreshold { threshold: i32, }, + SetAnimationsEnabled { + enabled: bool, + }, + SetAnimationDurationMs { + duration_ms: u32, + }, + SetAnimationCurve { + curve: u32, + }, + SetAnimationStyle { + style: u32, + }, + SetAnimationCubicBezier { + x1: f32, + y1: f32, + x2: f32, + y2: f32, + }, SetXScalingMode { mode: XScalingMode, }, @@ -669,6 +837,10 @@ pub enum ClientMessage<'a> { window: Window, workspace: Workspace, }, + WindowSendToScratchpad { + window: Window, + name: &'a str, + }, SetWindowFullscreen { window: Window, fullscreen: bool, @@ -684,7 +856,7 @@ pub enum ClientMessage<'a> { pinned: bool, }, CreateClientMatcher { - criterion: ClientCriterionIpc, + criterion: ClientCriterionPayload, }, DestroyClientMatcher { matcher: ClientMatcher, @@ -693,7 +865,7 @@ pub enum ClientMessage<'a> { matcher: ClientMatcher, }, CreateWindowMatcher { - criterion: WindowCriterionIpc, + criterion: WindowCriterionPayload, }, DestroyWindowMatcher { matcher: WindowMatcher, @@ -782,14 +954,6 @@ pub enum ClientMessage<'a> { SetTitleFont { font: &'a str, }, - SetClientMatcherCapabilities { - matcher: ClientMatcher, - caps: ClientCapabilities, - }, - SetClientMatcherBoundingCapabilities { - matcher: ClientMatcher, - caps: ClientCapabilities, - }, ShowWorkspaceOn { seat: Seat, workspace: Workspace, @@ -844,13 +1008,6 @@ pub enum ClientMessage<'a> { SetXWaylandEnabled { enabled: bool, }, - Run3 { - prog: &'a str, - args: Vec, - env: Vec<(String, String)>, - fds: Vec<(i32, i32)>, - tag: Option<&'a str>, - }, ConnectorSupportsArbitraryModes { connector: Connector, }, @@ -905,6 +1062,7 @@ pub enum ClientMessage<'a> { SetAutotile { enabled: bool, }, + GetAutotile, SetTabTitleAlign { align: u32, }, @@ -914,13 +1072,13 @@ pub enum ClientMessage<'a> { }, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub enum WorkspaceSource { Seat(Seat), Explicit(Workspace), } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub enum Response { None, GetSeats { @@ -1057,9 +1215,6 @@ pub enum Response { GetInputDeviceDevnode { devnode: String, }, - GetSocketPath { - path: String, - }, GetFloatAboveFullscreen { above: bool, }, @@ -1171,12 +1326,15 @@ pub enum Response { GetCornerRadius { radius: f32, }, + GetAutotile { + enabled: bool, + }, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub enum InitMessage { V1(V1InitMessage), } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct V1InitMessage {} diff --git a/jay-config/src/status.rs b/crates/jay-config/src/status.rs similarity index 100% rename from jay-config/src/status.rs rename to crates/jay-config/src/status.rs diff --git a/jay-config/src/tasks.rs b/crates/jay-config/src/tasks.rs similarity index 100% rename from jay-config/src/tasks.rs rename to crates/jay-config/src/tasks.rs diff --git a/jay-config/src/theme.rs b/crates/jay-config/src/theme.rs similarity index 100% rename from jay-config/src/theme.rs rename to crates/jay-config/src/theme.rs diff --git a/jay-config/src/timer.rs b/crates/jay-config/src/timer.rs similarity index 100% rename from jay-config/src/timer.rs rename to crates/jay-config/src/timer.rs diff --git a/jay-config/src/video.rs b/crates/jay-config/src/video.rs similarity index 99% rename from jay-config/src/video.rs rename to crates/jay-config/src/video.rs index ff0680eb..5ba15d98 100644 --- a/jay-config/src/video.rs +++ b/crates/jay-config/src/video.rs @@ -2,8 +2,8 @@ use { crate::{ - _private::WireMode, Direction, PciId, Workspace, + protocol::WireMode, video::connector_type::{ CON_9PIN_DIN, CON_COMPONENT, CON_COMPOSITE, CON_DISPLAY_PORT, CON_DPI, CON_DSI, CON_DVIA, CON_DVID, CON_DVII, CON_EDP, CON_EMBEDDED_WINDOW, CON_HDMIA, CON_HDMIB, diff --git a/jay-config/src/window.rs b/crates/jay-config/src/window.rs similarity index 98% rename from jay-config/src/window.rs rename to crates/jay-config/src/window.rs index 662cda44..96e4d3b1 100644 --- a/jay-config/src/window.rs +++ b/crates/jay-config/src/window.rs @@ -205,6 +205,13 @@ impl Window { get!().set_window_workspace(self, workspace) } + /// Sends the window to a scratchpad. + /// + /// Use an empty string for the default scratchpad. + pub fn send_to_scratchpad(self, name: &str) { + get!().window_send_to_scratchpad(self, name) + } + /// Toggles whether the currently focused window is fullscreen. pub fn toggle_fullscreen(self) { self.set_fullscreen(!self.fullscreen()) diff --git a/jay-config/src/workspace.rs b/crates/jay-config/src/workspace.rs similarity index 100% rename from jay-config/src/workspace.rs rename to crates/jay-config/src/workspace.rs diff --git a/jay-config/src/xwayland.rs b/crates/jay-config/src/xwayland.rs similarity index 100% rename from jay-config/src/xwayland.rs rename to crates/jay-config/src/xwayland.rs diff --git a/crates/keyboard/Cargo.toml b/crates/keyboard/Cargo.toml new file mode 100644 index 00000000..427432ac --- /dev/null +++ b/crates/keyboard/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "jay-keyboard" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "Keyboard state and keymap helpers for the Jay compositor" +repository = "https://github.com/mahkoh/jay" + +[dependencies] +jay-input-types = { path = "../input-types" } +jay-utils = { path = "../utils" } + +blake3 = "1.8.2" +kbvm = { version = "0.1.6", features = ["compose"] } +thiserror = "2.0.11" +uapi = "0.2.13" diff --git a/crates/keyboard/src/lib.rs b/crates/keyboard/src/lib.rs new file mode 100644 index 00000000..f584787d --- /dev/null +++ b/crates/keyboard/src/lib.rs @@ -0,0 +1,357 @@ +use { + jay_input_types::{ + LED_CAPS_LOCK, LED_COMPOSE, LED_KANA, LED_NUM_LOCK, LED_SCROLL_LOCK, Leds, + }, + jay_utils::{ + event_listener::EventSource, + numcell::NumCell, + oserror::{OsError, OsErrorExt, OsErrorExt2}, + syncqueue::SyncQueue, + vecset::VecSet, + }, + kbvm::{ + Components, + lookup::LookupTable, + state_machine::{self, Event, StateMachine}, + xkb::{ + self, Keymap, + diagnostic::{Diagnostic, WriteToLog}, + keymap::{Indicator, IndicatorMatcher}, + rmlvo::Group, + }, + }, + std::{ + cell::{Ref, RefCell}, + io::Write, + rc::Rc, + }, + thiserror::Error, + uapi::{OwnedFd, c}, +}; + +#[derive(Debug, Error)] +pub enum KeyboardError { + #[error("Could not create a keymap memfd")] + KeymapMemfd(#[source] OsError), + #[error("Could not copy the keymap")] + KeymapCopy(#[source] OsError), +} + +#[derive(Debug)] +pub struct KeyboardStateIds { + next: NumCell, +} + +impl Default for KeyboardStateIds { + fn default() -> Self { + Self { + next: NumCell::new(1), + } + } +} + +impl KeyboardStateIds { + pub fn next(&self) -> KeyboardStateId { + KeyboardStateId(self.next.fetch_add(1)) + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct KeyboardStateId(u64); + +impl KeyboardStateId { + pub fn raw(&self) -> u64 { + self.0 + } + + pub fn from_raw(id: u64) -> Self { + Self(id) + } +} + +impl std::fmt::Display for KeyboardStateId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } +} + +#[derive(Debug, Error)] +pub enum KbvmError { + #[error("could not parse the keymap")] + CouldNotParseKeymap(#[source] Diagnostic), + #[error("Could not create a keymap memfd")] + KeymapMemfd(#[source] OsError), +} + +pub struct KbvmContext { + pub ctx: xkb::Context, +} + +impl Default for KbvmContext { + fn default() -> Self { + let mut ctx = xkb::Context::builder(); + ctx.enable_environment(true); + Self { ctx: ctx.build() } + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct KbvmMapId([u8; 32]); + +pub struct KbvmMap { + pub id: KbvmMapId, + pub state_machine: StateMachine, + pub lookup_table: LookupTable, + pub map: KeymapFd, + pub xwayland_map: KeymapFd, + pub has_indicators: bool, + pub num_lock: Option, + pub caps_lock: Option, + pub scroll_lock: Option, + pub compose: Option, + pub kana: Option, +} + +pub struct KbvmState { + pub map: Rc, + pub state: state_machine::State, + pub kb_state: KeyboardState, +} + +pub struct KeyboardState { + pub id: KeyboardStateId, + pub map: Rc, + pub pressed_keys: VecSet, + pub mods: Components, + pub leds: Leds, + pub leds_changed: EventSource, +} + +pub trait LedsListener { + fn leds(&self, leds: Leds); +} + +pub trait DynKeyboardState { + fn borrow(&self) -> Ref<'_, KeyboardState>; +} + +impl DynKeyboardState for RefCell { + fn borrow(&self) -> Ref<'_, KeyboardState> { + self.borrow() + } +} + +impl DynKeyboardState for RefCell { + fn borrow(&self) -> Ref<'_, KeyboardState> { + Ref::map(self.borrow(), |v| &v.kb_state) + } +} + +impl KeyboardState { + pub fn apply_event(&mut self, event: Event) -> bool { + let changed = self.mods.apply_event(event); + if changed && self.map.has_indicators { + self.update_leds(); + } + changed + } + + pub fn update_leds(&mut self) { + if !self.map.has_indicators { + return; + } + let mut new = Leds::none(); + macro_rules! map_led { + ($field:ident, $led:ident) => { + if let Some(m) = &self.map.$field + && m.matches(&self.mods) + { + new |= $led; + } + }; + } + map_led!(num_lock, LED_NUM_LOCK); + map_led!(caps_lock, LED_CAPS_LOCK); + map_led!(scroll_lock, LED_SCROLL_LOCK); + map_led!(compose, LED_COMPOSE); + map_led!(kana, LED_KANA); + if new != self.leds { + self.leds = new; + for listener in self.leds_changed.iter() { + listener.leds(new); + } + } + } +} + +#[derive(Clone)] +pub struct KeymapFd { + pub map: Rc, + pub len: usize, +} + +impl KeymapFd { + pub fn create_unprotected_fd(&self) -> Result { + let fd = uapi::memfd_create("shared-keymap", c::MFD_CLOEXEC) + .map_os_err(KeyboardError::KeymapMemfd)?; + let target = self.len as c::off_t; + let mut pos = 0; + while pos < target { + let rem = target - pos; + let res = uapi::sendfile(fd.raw(), self.map.raw(), Some(&mut pos), rem as usize) + .to_os_error(); + match res { + Ok(_) | Err(OsError(c::EINTR)) => {} + Err(e) => return Err(KeyboardError::KeymapCopy(e)), + } + } + Ok(Self { + map: Rc::new(fd), + len: self.len, + }) + } +} + +impl KbvmContext { + pub fn parse_keymap(&self, keymap: &[u8]) -> Result, KbvmError> { + let map = self + .ctx + .keymap_from_bytes(WriteToLog, None, keymap) + .map_err(KbvmError::CouldNotParseKeymap)?; + let id = KbvmMapId(*blake3::hash(keymap).as_bytes()); + self.create_keymap(id, map) + } + + pub fn keymap_from_rmlvo( + &self, + rules: Option<&str>, + model: Option<&str>, + layout: Option<&str>, + variant: Option<&str>, + options: Option<&str>, + ) -> Result, KbvmError> { + let mut groups = None::>; + if layout.is_some() || variant.is_some() { + groups = Some( + Group::from_layouts_and_variants( + layout.unwrap_or_default(), + variant.unwrap_or_default(), + ) + .collect(), + ); + } + let mut options_vec = None::>; + if let Some(options) = options { + options_vec = Some(options.split(",").collect()); + } + self.keymap_from_names(rules, model, groups.as_deref(), options_vec.as_deref()) + } + + pub fn keymap_from_names( + &self, + rules: Option<&str>, + model: Option<&str>, + groups: Option<&[Group<'_>]>, + options: Option<&[&str]>, + ) -> Result, KbvmError> { + let map = self + .ctx + .keymap_from_names(WriteToLog, rules, model, groups, options); + let id = KbvmMapId(*blake3::hash(map.format().to_string().as_bytes()).as_bytes()); + self.create_keymap(id, map) + } + + fn create_keymap(&self, id: KbvmMapId, map: Keymap) -> Result, KbvmError> { + let mut has_indicators = false; + let mut num_lock = None; + let mut caps_lock = None; + let mut scroll_lock = None; + let mut compose = None; + let mut kana = None; + for indicator in map.indicators() { + match indicator.name() { + Indicator::NUM_LOCK => num_lock = Some(indicator.matcher()), + Indicator::CAPS_LOCK => caps_lock = Some(indicator.matcher()), + Indicator::SCROLL_LOCK => scroll_lock = Some(indicator.matcher()), + Indicator::COMPOSE => compose = Some(indicator.matcher()), + Indicator::KANA => kana = Some(indicator.matcher()), + _ => continue, + } + has_indicators = true; + } + let builder = map.to_builder(); + let (_, xwayland_map) = create_keymap_memfd(&map, true).map_err(KbvmError::KeymapMemfd)?; + let (_, map) = create_keymap_memfd(&map, false).map_err(KbvmError::KeymapMemfd)?; + Ok(Rc::new(KbvmMap { + id, + state_machine: builder.build_state_machine(), + map, + xwayland_map, + lookup_table: builder.build_lookup_table(), + has_indicators, + num_lock, + caps_lock, + scroll_lock, + compose, + kana, + })) + } +} + +fn create_keymap_memfd(map: &Keymap, xwayland: bool) -> Result<(String, KeymapFd), OsError> { + let mut format = map.format(); + if xwayland { + format = format.lookup_only(true).rename_long_keys(true); + } + let str = format!("{}\n", format); + let mut memfd = + uapi::memfd_create("keymap", c::MFD_CLOEXEC | c::MFD_ALLOW_SEALING).to_os_error()?; + memfd.write_all(str.as_bytes())?; + memfd.write_all(&[0])?; + uapi::lseek(memfd.raw(), 0, c::SEEK_SET).to_os_error()?; + uapi::fcntl_add_seals( + memfd.raw(), + c::F_SEAL_SEAL | c::F_SEAL_GROW | c::F_SEAL_SHRINK | c::F_SEAL_WRITE, + ) + .to_os_error()?; + let fd = KeymapFd { + map: Rc::new(memfd), + len: str.len() + 1, + }; + Ok((str, fd)) +} + +impl KbvmMap { + pub fn state(self: &Rc, id: KeyboardStateId) -> KbvmState { + KbvmState { + map: self.clone(), + state: self.state_machine.create_state(), + kb_state: KeyboardState { + id, + map: self.clone(), + pressed_keys: Default::default(), + mods: Default::default(), + leds: Default::default(), + leds_changed: Default::default(), + }, + } + } +} + +impl KbvmState { + pub fn apply_events(&mut self, events: &SyncQueue) { + let state = &mut self.kb_state; + while let Some(event) = events.pop() { + state.apply_event(event); + match event { + Event::KeyDown(kc) => { + state.pressed_keys.insert(kc.to_evdev()); + } + Event::KeyUp(kc) => { + state.pressed_keys.remove(&kc.to_evdev()); + } + _ => {} + } + } + } +} diff --git a/crates/layout-animation/Cargo.toml b/crates/layout-animation/Cargo.toml new file mode 100644 index 00000000..b9b2b842 --- /dev/null +++ b/crates/layout-animation/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "jay-layout-animation" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "Layout animation planning for Jay" +repository = "https://github.com/mahkoh/jay" + +[dependencies] +jay-geometry = { path = "../geometry" } diff --git a/crates/layout-animation/src/lib.rs b/crates/layout-animation/src/lib.rs new file mode 100644 index 00000000..c9ad258e --- /dev/null +++ b/crates/layout-animation/src/lib.rs @@ -0,0 +1,3570 @@ +use jay_geometry::Rect; + +const CURVE_MAX_POINTS: usize = 33; +const CURVE_FLATNESS_EPSILON: f32 = 0.001; +const CURVE_MAX_DEPTH: u8 = 8; + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum AnimationCurve { + Linear, + Piecewise(PiecewiseCurve), +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum AnimationStyle { + Plain, + Multiphase, +} + +impl AnimationStyle { + pub fn from_config(value: u32) -> Option { + match value { + 0 => Some(Self::Plain), + 1 => Some(Self::Multiphase), + _ => None, + } + } +} + +impl AnimationCurve { + pub fn from_config(value: u32) -> Self { + match value { + 0 => Self::Linear, + 1 => Self::from_cubic_bezier(0.25, 0.1, 0.25, 1.0).unwrap(), + 2 => Self::from_cubic_bezier(0.42, 0.0, 1.0, 1.0).unwrap(), + 4 => Self::from_cubic_bezier(0.42, 0.0, 0.58, 1.0).unwrap(), + _ => Self::from_cubic_bezier(0.0, 0.0, 0.58, 1.0).unwrap(), + } + } + + pub fn from_cubic_bezier(x1: f32, y1: f32, x2: f32, y2: f32) -> Option { + if !x1.is_finite() + || !y1.is_finite() + || !x2.is_finite() + || !y2.is_finite() + || !(0.0..=1.0).contains(&x1) + || !(0.0..=1.0).contains(&x2) + { + return None; + } + Some(Self::Piecewise(PiecewiseCurve::from_cubic_bezier( + x1, y1, x2, y2, + ))) + } + + pub fn sample(self, t: f64) -> f64 { + let t = t.clamp(0.0, 1.0); + match self { + Self::Linear => t, + Self::Piecewise(curve) => curve.sample(t as f32) as f64, + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct PiecewiseCurve { + len: u8, + points: [CurvePoint; CURVE_MAX_POINTS], +} + +impl PiecewiseCurve { + fn from_cubic_bezier(x1: f32, y1: f32, x2: f32, y2: f32) -> Self { + let mut points = Vec::with_capacity(CURVE_MAX_POINTS); + let p0 = cubic_bezier_point(x1, y1, x2, y2, 0.0); + let p1 = cubic_bezier_point(x1, y1, x2, y2, 1.0); + points.push(p0); + flatten_cubic_bezier(&mut points, (x1, y1, x2, y2), 0.0, p0, 1.0, p1, 0); + let mut array = [CurvePoint::default(); CURVE_MAX_POINTS]; + let len = points.len().min(CURVE_MAX_POINTS); + array[..len].copy_from_slice(&points[..len]); + Self { + len: len as u8, + points: array, + } + } + + fn sample(self, x: f32) -> f32 { + let len = self.len as usize; + if len <= 1 { + return x; + } + let points = &self.points[..len]; + if x <= points[0].x { + return points[0].y; + } + if x >= points[len - 1].x { + return points[len - 1].y; + } + let mut lo = 0; + let mut hi = len - 1; + while lo + 1 < hi { + let mid = (lo + hi) / 2; + if points[mid].x <= x { + lo = mid; + } else { + hi = mid; + } + } + let from = points[lo]; + let to = points[hi]; + if to.x <= from.x { + return to.y; + } + let t = (x - from.x) / (to.x - from.x); + from.y + (to.y - from.y) * t + } +} + +#[derive(Copy, Clone, Debug, Default, PartialEq)] +struct CurvePoint { + x: f32, + y: f32, +} + +fn flatten_cubic_bezier( + points: &mut Vec, + controls: (f32, f32, f32, f32), + t0: f32, + p0: CurvePoint, + t1: f32, + p1: CurvePoint, + depth: u8, +) { + let tm = (t0 + t1) * 0.5; + let pm = cubic_bezier_point(controls.0, controls.1, controls.2, controls.3, tm); + let projected_y = if p1.x <= p0.x { + (p0.y + p1.y) * 0.5 + } else { + let tx = (pm.x - p0.x) / (p1.x - p0.x); + p0.y + (p1.y - p0.y) * tx + }; + if (pm.y - projected_y).abs() > CURVE_FLATNESS_EPSILON + && depth < CURVE_MAX_DEPTH + && points.len() + 2 < CURVE_MAX_POINTS + { + flatten_cubic_bezier(points, controls, t0, p0, tm, pm, depth + 1); + flatten_cubic_bezier(points, controls, tm, pm, t1, p1, depth + 1); + } else { + points.push(p1); + } +} + +fn cubic_bezier_point(x1: f32, y1: f32, x2: f32, y2: f32, t: f32) -> CurvePoint { + fn bezier(a: f32, b: f32, t: f32) -> f32 { + let inv = 1.0 - t; + 3.0 * inv * inv * t * a + 3.0 * inv * t * t * b + t * t * t + } + CurvePoint { + x: bezier(x1, x2, t), + y: bezier(y1, y2, t), + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct NodeId(pub u32); + +const MIN_SHRINK_DENOMINATOR: i32 = 8; +// Integer split remainders can make swapped siblings differ by one pixel. Do +// not spend a full animation phase on that imperceptible bookkeeping step. +const SWAP_AXIS_SNAP_PX: i32 = 1; + +#[derive(Clone, Debug)] +pub struct MultiphaseRequest { + pub bounds: Rect, + pub windows: Vec, + pub clearance: i32, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct MultiphaseWindow { + pub node_id: NodeId, + pub from: Rect, + pub to: Rect, + pub hierarchy: MultiphaseWindowHierarchy, +} + +impl MultiphaseWindow { + pub fn new(node_id: impl Into, from: Rect, to: Rect) -> Self { + Self { + node_id: node_id.into(), + from, + to, + hierarchy: Default::default(), + } + } + + pub fn with_hierarchy( + node_id: impl Into, + from: Rect, + to: Rect, + hierarchy: MultiphaseWindowHierarchy, + ) -> Self { + Self { + node_id: node_id.into(), + from, + to, + hierarchy, + } + } +} + +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +pub struct MultiphaseWindowHierarchy { + pub source: MultiphaseHierarchyPosition, + pub target: MultiphaseHierarchyPosition, + pub transition: MultiphaseHierarchyTransition, +} + +impl MultiphaseWindowHierarchy { + pub fn new(source: MultiphaseHierarchyPosition, target: MultiphaseHierarchyPosition) -> Self { + let transition = if !source.parent_is_mono && target.parent_is_mono { + MultiphaseHierarchyTransition::EnteringMono + } else if source.parent_is_mono && !target.parent_is_mono { + MultiphaseHierarchyTransition::ExitingMono + } else if source.parent.is_none() || target.parent.is_none() { + MultiphaseHierarchyTransition::Unknown + } else if target.depth < source.depth { + MultiphaseHierarchyTransition::Ascending + } else if target.depth > source.depth { + MultiphaseHierarchyTransition::Descending + } else { + MultiphaseHierarchyTransition::SameLevel + }; + Self { + source, + target, + transition, + } + } + + fn reversed(self) -> Self { + Self { + source: self.target, + target: self.source, + transition: self.transition.reversed(), + } + } +} + +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +pub struct MultiphaseHierarchyPosition { + pub parent: Option, + pub depth: u16, + pub sibling_index: Option, + pub split_axis: Option, + pub nearest_horizontal_split_depth: Option, + pub nearest_vertical_split_depth: Option, + pub parent_is_mono: bool, + pub mono_active: bool, +} + +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +pub enum MultiphaseHierarchyTransition { + #[default] + Unknown, + SameLevel, + Ascending, + Descending, + EnteringMono, + ExitingMono, +} + +impl MultiphaseHierarchyTransition { + fn reversed(self) -> Self { + match self { + Self::Unknown => Self::Unknown, + Self::SameLevel => Self::SameLevel, + Self::Ascending => Self::Descending, + Self::Descending => Self::Ascending, + Self::EnteringMono => Self::ExitingMono, + Self::ExitingMono => Self::EnteringMono, + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct MultiphasePlan { + pub phases: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct MultiphasePlanned { + pub plan: MultiphasePlan, + pub explanation: MultiphasePlanExplanation, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct MultiphasePlanExplanation { + pub strategy: PlanStrategy, + pub phases: Vec, + pub validation: ValidationExplanation, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct PhaseExplanation { + pub action: MultiphasePhaseAction, + pub reason: PhaseReason, + pub nodes: Vec, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct ValidationExplanation { + pub continuous_overlap_passed: bool, + pub final_rects_matched: bool, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum PlanStrategy { + NoOp, + SingleAction, + MixedSinglePhase, + HierarchyOrderedScales, + OrientationChange { from_axis: PhaseAxis }, + SwapLanes { axis: PhaseAxis }, + SpaceThenOrthogonalGrowth { axis: PhaseAxis }, + ReversedForwardPlan { original: Box }, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum PlanDirection { + Forward, + Reverse, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RejectedStrategy { + pub direction: PlanDirection, + pub strategy: PlanStrategy, + pub reason: MultiphasePlanFailure, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum PhaseReason { + SingleAction, + SameAxisRedistribution, + MixedAxisActions, + ShrinkIntoLanes { + lane_axis: PhaseAxis, + }, + MoveThroughFreedSpace, + GrowOutOfLanes, + CreateSpaceForAscendingChild, + MoveAscendingChildAfterSpaceExists, + OrthogonalGrowthAfterMove, + ParentAxisBeforeChildAxis { + parent_axis: PhaseAxis, + parent_depth: u16, + child_axis: PhaseAxis, + child_depth: u16, + }, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct MultiphasePhase { + pub action: MultiphasePhaseAction, + pub steps: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum MultiphasePhaseAction { + Uniform(PhaseAction), + Mixed(Vec), +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct MultiphaseStep { + pub node_id: NodeId, + pub from: Rect, + pub to: Rect, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct PhaseAction { + pub kind: PhaseKind, + pub axis: PhaseAxis, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum PhaseKind { + Move, + Scale, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum PhaseAxis { + Horizontal, + Vertical, +} + +impl MultiphasePhaseAction { + fn from_step_actions(actions: Vec) -> Self { + debug_assert!(!actions.is_empty()); + let first = actions[0]; + if actions.iter().all(|action| *action == first) { + Self::Uniform(first) + } else { + Self::Mixed(actions) + } + } + + fn action_for_step(&self, idx: usize) -> Option { + match self { + Self::Uniform(action) => Some(*action), + Self::Mixed(actions) => actions.get(idx).copied(), + } + } + + #[cfg_attr(not(test), expect(dead_code))] + fn as_uniform(&self) -> Option { + match self { + Self::Uniform(action) => Some(*action), + Self::Mixed(_) => None, + } + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum MultiphaseError { + EmptyBounds, + EmptyWindow, + DuplicateWindow, + InitialOverlap, + FinalOverlap, + NoPlan, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct MultiphasePlanDiagnostic { + pub forward: MultiphasePlanFailure, + pub reverse: Option, + pub attempted: Vec, +} + +impl MultiphasePlanDiagnostic { + fn legacy_error(self) -> MultiphaseError { + match self.forward { + MultiphasePlanFailure::Request(error) => error, + _ => MultiphaseError::NoPlan, + } + } +} + +impl ValidationExplanation { + fn passed() -> Self { + Self { + continuous_overlap_passed: true, + final_rects_matched: true, + } + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum MultiphasePlanFailure { + Request(MultiphaseError), + NoPattern, + ShrinkBound { + axis: PhaseAxis, + available: i32, + required: i32, + }, + InvalidPhaseStep { + action: PhaseAction, + node_id: NodeId, + }, + Validation(MultiphaseValidationError), +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum MultiphaseValidationError { + DuplicatePhaseStep { + phase: usize, + node_id: NodeId, + }, + PhaseActionCount { + phase: usize, + actions: usize, + steps: usize, + }, + UnknownPhaseStep { + phase: usize, + node_id: NodeId, + }, + StaleStepStart { + phase: usize, + node_id: NodeId, + }, + PhaseOverlap { + phase: usize, + a: NodeId, + b: NodeId, + }, + FinalMismatch { + node_id: NodeId, + }, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +struct PlanForwardFailure { + reason: MultiphasePlanFailure, + attempted: Vec, +} + +pub fn plan_no_overlap(request: &MultiphaseRequest) -> Result { + plan_no_overlap_with_diagnostics(request).map_err(|diagnostic| diagnostic.legacy_error()) +} + +pub fn plan_no_overlap_with_diagnostics( + request: &MultiphaseRequest, +) -> Result { + plan_no_overlap_explained(request).map(|planned| planned.plan) +} + +pub fn plan_no_overlap_explained( + request: &MultiphaseRequest, +) -> Result { + if let Err(error) = validate_request(request) { + return Err(MultiphasePlanDiagnostic { + forward: MultiphasePlanFailure::Request(error), + reverse: None, + attempted: vec![], + }); + } + if request + .windows + .iter() + .all(|window| window.from == window.to) + { + return Ok(MultiphasePlanned { + plan: MultiphasePlan { phases: vec![] }, + explanation: MultiphasePlanExplanation { + strategy: PlanStrategy::NoOp, + phases: vec![], + validation: ValidationExplanation::passed(), + }, + }); + } + if let Some(failure) = target_shrink_bound_failure(request) { + return Err(MultiphasePlanDiagnostic { + forward: failure, + reverse: None, + attempted: vec![], + }); + } + let forward = match plan_forward(request, PlanDirection::Forward) { + Ok(plan) => return Ok(plan), + Err(error) => error, + }; + let reversed = reverse_request(request); + match plan_forward(&reversed, PlanDirection::Reverse) { + Ok(plan) => Ok(reverse_planned(plan)), + Err(reverse) => { + let mut attempted = forward.attempted; + attempted.extend(reverse.attempted); + Err(MultiphasePlanDiagnostic { + forward: forward.reason, + reverse: Some(reverse.reason), + attempted, + }) + } + } +} + +pub fn validate_phase_paths( + request: &MultiphaseRequest, + paths: &[Vec<(Rect, Rect)>], +) -> Result { + if paths.len() != request.windows.len() { + return Err(MultiphasePlanFailure::NoPattern); + } + let phase_count = paths.iter().map(Vec::len).max().unwrap_or(0); + if phase_count == 0 { + return Err(MultiphasePlanFailure::NoPattern); + } + let mut phases = vec![]; + for phase_idx in 0..phase_count { + let mut steps = vec![]; + let mut actions = vec![]; + for (window_idx, path) in paths.iter().enumerate() { + let Some((from, to)) = path.get(phase_idx).copied() else { + continue; + }; + if from == to { + continue; + } + let step = MultiphaseStep { + node_id: request.windows[window_idx].node_id, + from, + to, + }; + let Some(action) = classify_step(step) else { + return Err(MultiphasePlanFailure::NoPattern); + }; + steps.push(step); + actions.push(action); + } + if !steps.is_empty() { + phases.push(MultiphasePhase { + action: MultiphasePhaseAction::from_step_actions(actions), + steps, + }); + } + } + let plan = MultiphasePlan { phases }; + validate_plan_continuous_diagnostic(request, &plan) + .map(|_| plan) + .map_err(MultiphasePlanFailure::Validation) +} + +pub fn partition_motion_groups( + windows: &[MultiphaseWindow], + clearance: i32, +) -> Vec> { + let clearance = clearance.max(0); + let mut groups = vec![]; + let mut seen = vec![false; windows.len()]; + for start in 0..windows.len() { + if seen[start] { + continue; + } + seen[start] = true; + let mut group = vec![]; + let mut pending = vec![start]; + while let Some(idx) = pending.pop() { + group.push(idx); + let bounds = motion_bounds_with_clearance(windows[idx], clearance); + for other in 0..windows.len() { + if seen[other] + || !bounds.intersects(&motion_bounds_with_clearance(windows[other], clearance)) + { + continue; + } + seen[other] = true; + pending.push(other); + } + } + group.sort_unstable(); + groups.push(group); + } + groups +} + +fn validate_request(request: &MultiphaseRequest) -> Result<(), MultiphaseError> { + if request.bounds.is_empty() { + return Err(MultiphaseError::EmptyBounds); + } + for (idx, window) in request.windows.iter().enumerate() { + if window.from.is_empty() || window.to.is_empty() { + return Err(MultiphaseError::EmptyWindow); + } + for other in &request.windows[..idx] { + if other.node_id == window.node_id { + return Err(MultiphaseError::DuplicateWindow); + } + } + } + if overlaps(request.windows.iter().map(|window| window.from)) { + return Err(MultiphaseError::InitialOverlap); + } + if overlaps(request.windows.iter().map(|window| window.to)) { + return Err(MultiphaseError::FinalOverlap); + } + Ok(()) +} + +fn target_shrink_bound_failure(request: &MultiphaseRequest) -> Option { + let min_width = sane_min_size(request.bounds.width()); + let min_height = sane_min_size(request.bounds.height()); + for window in &request.windows { + if window.to.width() < min_width { + return Some(MultiphasePlanFailure::ShrinkBound { + axis: PhaseAxis::Horizontal, + available: window.to.width(), + required: min_width, + }); + } + if window.to.height() < min_height { + return Some(MultiphasePlanFailure::ShrinkBound { + axis: PhaseAxis::Vertical, + available: window.to.height(), + required: min_height, + }); + } + } + None +} + +fn plan_forward( + request: &MultiphaseRequest, + direction: PlanDirection, +) -> Result { + let mut rejection = None; + let mut attempted = vec![]; + match plan_single_action_phase(request) { + Ok(plan) => return Ok(plan), + Err(error) => { + record_rejection(&mut attempted, direction, PlanStrategy::SingleAction, error); + if error != MultiphasePlanFailure::NoPattern { + rejection.get_or_insert(error); + } + } + } + for axis in [PhaseAxis::Horizontal, PhaseAxis::Vertical] { + match plan_space_then_orthogonal_growth(request, axis) { + Ok(plan) => return Ok(plan), + Err(error) => { + record_rejection( + &mut attempted, + direction, + PlanStrategy::SpaceThenOrthogonalGrowth { axis }, + error, + ); + if error != MultiphasePlanFailure::NoPattern { + rejection.get_or_insert(error); + } + } + } + } + match plan_hierarchy_ordered_axis_scales(request) { + Ok(plan) => return Ok(plan), + Err(error) => { + record_rejection( + &mut attempted, + direction, + PlanStrategy::HierarchyOrderedScales, + error, + ); + if error != MultiphasePlanFailure::NoPattern { + rejection.get_or_insert(error); + } + } + } + for axis in [PhaseAxis::Horizontal, PhaseAxis::Vertical] { + match plan_orientation_change(request, axis) { + Ok(plan) => return Ok(plan), + Err(error) => { + record_rejection( + &mut attempted, + direction, + PlanStrategy::OrientationChange { from_axis: axis }, + error, + ); + if error != MultiphasePlanFailure::NoPattern { + rejection.get_or_insert(error); + } + } + } + } + for axis in [PhaseAxis::Horizontal, PhaseAxis::Vertical] { + match plan_axis_crossing_lanes(request, axis) { + Ok(plan) => return Ok(plan), + Err(error) => { + record_rejection( + &mut attempted, + direction, + PlanStrategy::SwapLanes { axis }, + error, + ); + if error != MultiphasePlanFailure::NoPattern { + rejection.get_or_insert(error); + } + } + } + } + Err(PlanForwardFailure { + reason: rejection.unwrap_or(MultiphasePlanFailure::NoPattern), + attempted, + }) +} + +fn record_rejection( + attempted: &mut Vec, + direction: PlanDirection, + strategy: PlanStrategy, + reason: MultiphasePlanFailure, +) { + attempted.push(RejectedStrategy { + direction, + strategy, + reason, + }); +} + +fn plan_single_action_phase( + request: &MultiphaseRequest, +) -> Result { + let mut uniform_action = None; + let mut is_uniform = true; + let mut steps = vec![]; + let mut step_actions = vec![]; + for window in &request.windows { + if window.from == window.to { + continue; + } + let step = MultiphaseStep { + node_id: window.node_id, + from: window.from, + to: window.to, + }; + let Some(step_action) = classify_step(step) else { + return Err(MultiphasePlanFailure::NoPattern); + }; + if step_action.kind == PhaseKind::Scale { + let (available, required) = match step_action.axis { + PhaseAxis::Horizontal => (window.to.width(), sane_min_size(request.bounds.width())), + PhaseAxis::Vertical => (window.to.height(), sane_min_size(request.bounds.height())), + }; + if available < required { + return Err(MultiphasePlanFailure::ShrinkBound { + axis: step_action.axis, + available, + required, + }); + } + } + if uniform_action.is_some_and(|action| action != step_action) { + is_uniform = false; + } + uniform_action.get_or_insert(step_action); + steps.push(step); + step_actions.push(step_action); + } + if steps.is_empty() { + return Err(MultiphasePlanFailure::NoPattern); + } + if !is_uniform { + return build_validated_plan( + request, + PlanStrategy::MixedSinglePhase, + [phase_draft_mixed( + steps, + step_actions, + PhaseReason::MixedAxisActions, + )], + ); + } + let action = uniform_action.unwrap(); + build_validated_plan( + request, + PlanStrategy::SingleAction, + [phase_draft_uniform( + action, + steps, + single_action_reason(action), + )], + ) +} + +fn plan_hierarchy_ordered_axis_scales( + request: &MultiphaseRequest, +) -> Result { + let mut changed_axes = vec![]; + for axis in [PhaseAxis::Horizontal, PhaseAxis::Vertical] { + if request + .windows + .iter() + .any(|window| interval_changed(window.from, window.to, axis)) + { + changed_axes.push(axis); + } + } + let [first_axis, second_axis] = changed_axes + .try_into() + .map_err(|_| MultiphasePlanFailure::NoPattern)?; + let order = hierarchy_scale_axis_order(request, first_axis, second_axis) + .ok_or(MultiphasePlanFailure::NoPattern)?; + let mut current: Vec<_> = request + .windows + .iter() + .map(|window| (window.node_id, window.from)) + .collect(); + let mut phases = vec![]; + let reason = PhaseReason::ParentAxisBeforeChildAxis { + parent_axis: order.axes[0], + parent_depth: order.depths[0], + child_axis: order.axes[1], + child_depth: order.depths[1], + }; + for axis in order.axes { + let mut steps = vec![]; + for window in &request.windows { + let (_, rect) = current + .iter_mut() + .find(|(node_id, _)| *node_id == window.node_id) + .unwrap(); + let next = with_main_interval( + *rect, + axis, + main_start(window.to, axis), + main_end(window.to, axis), + ); + if next == *rect { + continue; + } + if main_size(*rect, axis) == main_size(next, axis) { + return Err(MultiphasePlanFailure::NoPattern); + } + steps.push(MultiphaseStep { + node_id: window.node_id, + from: *rect, + to: next, + }); + *rect = next; + } + if steps.is_empty() { + return Err(MultiphasePlanFailure::NoPattern); + } + phases.push(phase_draft(PhaseKind::Scale, axis, steps, reason)); + } + let [first, second] = phases + .try_into() + .map_err(|_| MultiphasePlanFailure::NoPattern)?; + build_validated_plan( + request, + PlanStrategy::HierarchyOrderedScales, + [first, second], + ) +} + +fn hierarchy_scale_axis_order( + request: &MultiphaseRequest, + first_axis: PhaseAxis, + second_axis: PhaseAxis, +) -> Option { + let first_priority = hierarchy_axis_priority(request, first_axis)?; + let second_priority = hierarchy_axis_priority(request, second_axis)?; + match first_priority.cmp(&second_priority) { + std::cmp::Ordering::Less => Some(HierarchyScaleAxisOrder { + axes: [first_axis, second_axis], + depths: [first_priority, second_priority], + }), + std::cmp::Ordering::Greater => Some(HierarchyScaleAxisOrder { + axes: [second_axis, first_axis], + depths: [second_priority, first_priority], + }), + std::cmp::Ordering::Equal => None, + } +} + +#[derive(Copy, Clone)] +struct HierarchyScaleAxisOrder { + axes: [PhaseAxis; 2], + depths: [u16; 2], +} + +fn hierarchy_axis_priority(request: &MultiphaseRequest, axis: PhaseAxis) -> Option { + request + .windows + .iter() + .filter(|window| interval_changed(window.from, window.to, axis)) + .flat_map(|window| { + [ + split_depth_for_axis(window.hierarchy.source, axis), + split_depth_for_axis(window.hierarchy.target, axis), + ] + }) + .flatten() + .min() +} + +fn plan_axis_crossing_lanes( + request: &MultiphaseRequest, + axis: PhaseAxis, +) -> Result { + let moving_windows: Vec<_> = request + .windows + .iter() + .copied() + .filter(|window| window.from != window.to) + .collect(); + if moving_windows.len() < 2 { + return Err(MultiphasePlanFailure::NoPattern); + } + let orth_min = request + .windows + .iter() + .map(|window| orth_start(window.from, axis)) + .min() + .ok_or(MultiphasePlanFailure::NoPattern)?; + let orth_max = request + .windows + .iter() + .map(|window| orth_end(window.from, axis)) + .max() + .ok_or(MultiphasePlanFailure::NoPattern)?; + if moving_windows.iter().any(|window| { + orth_start(window.from, axis) != orth_min + || orth_end(window.from, axis) != orth_max + || orth_start(window.to, axis) != orth_min + || orth_end(window.to, axis) != orth_max + || main_start(window.from, axis) == main_start(window.to, axis) + }) { + return Err(MultiphasePlanFailure::NoPattern); + } + let clearance = request.clearance.max(0); + let lane_count = moving_windows.len() as i32; + let available = (orth_max - orth_min) - clearance.saturating_mul(lane_count - 1); + if available <= 0 { + return Err(MultiphasePlanFailure::ShrinkBound { + axis: axis.other(), + available: 0, + required: sane_min_size(orth_max - orth_min), + }); + } + let lane_size = available / lane_count; + let mut lane_remainder = available % lane_count; + let required = sane_min_size(orth_max - orth_min); + if lane_size < required { + return Err(MultiphasePlanFailure::ShrinkBound { + axis: axis.other(), + available: lane_size, + required, + }); + } + + let mut windows = moving_windows; + windows.sort_by_key(|window| lane_sort_key(*window, axis)); + let mut phase1 = vec![]; + let mut phase2 = vec![]; + let mut phase3 = vec![]; + let mut phase4 = vec![]; + let mut lane_start = orth_min; + for (idx, window) in windows.iter().enumerate() { + let extra = if lane_remainder > 0 { + lane_remainder -= 1; + 1 + } else { + 0 + }; + let lane_end = lane_start + lane_size + extra; + let lane_from = with_orth_interval(window.from, axis, lane_start, lane_end); + let lane_to = with_main_interval( + lane_from, + axis, + main_start(window.to, axis), + main_end(window.to, axis), + ); + let mut lane_move = crossing_lane_move_rect(lane_from, window.to, axis); + if same_axis_delta_within(lane_move, lane_to, axis, SWAP_AXIS_SNAP_PX) { + lane_move = lane_to; + } + push_step(&mut phase1, window.node_id, window.from, lane_from); + push_step(&mut phase2, window.node_id, lane_from, lane_move); + push_step(&mut phase3, window.node_id, lane_move, lane_to); + push_step(&mut phase4, window.node_id, lane_to, window.to); + if idx + 1 < windows.len() { + lane_start = lane_end + clearance; + } + } + build_validated_plan( + request, + PlanStrategy::SwapLanes { axis }, + [ + phase_draft( + PhaseKind::Scale, + axis.other(), + phase1, + PhaseReason::ShrinkIntoLanes { + lane_axis: axis.other(), + }, + ), + phase_draft_classified( + phase2, + PhaseReason::MoveThroughFreedSpace, + )?, + phase_draft( + PhaseKind::Scale, + axis, + phase3, + PhaseReason::SameAxisRedistribution, + ), + phase_draft( + PhaseKind::Scale, + axis.other(), + phase4, + PhaseReason::GrowOutOfLanes, + ), + ], + ) +} + +fn phase_draft_classified( + steps: Vec, + reason: PhaseReason, +) -> Result { + let actions = steps + .iter() + .map(|step| classify_step(*step).ok_or(MultiphasePlanFailure::NoPattern)) + .collect::, _>>()?; + Ok(phase_draft_mixed(steps, actions, reason)) +} + +fn crossing_lane_move_rect(from: Rect, target: Rect, axis: PhaseAxis) -> Rect { + let size = main_size(from, axis); + if main_start(target, axis) > main_start(from, axis) { + let end = main_end(target, axis); + with_main_interval(from, axis, end - size, end) + } else { + let start = main_start(target, axis); + with_main_interval(from, axis, start, start + size) + } +} + +fn same_axis_delta_within(from: Rect, to: Rect, axis: PhaseAxis, max_delta: i32) -> bool { + let start_delta = (main_start(from, axis) - main_start(to, axis)).abs(); + let end_delta = (main_end(from, axis) - main_end(to, axis)).abs(); + start_delta.max(end_delta) <= max_delta +} + +fn lane_sort_key(window: MultiphaseWindow, axis: PhaseAxis) -> (usize, i32, i32, u32) { + let delta = main_start(window.to, axis) - main_start(window.from, axis); + let direction = match delta.cmp(&0) { + std::cmp::Ordering::Greater => 0, + std::cmp::Ordering::Less => 1, + std::cmp::Ordering::Equal => 2, + }; + ( + direction, + main_start(window.from, axis), + main_start(window.to, axis), + window.node_id.0, + ) +} + +fn plan_space_then_orthogonal_growth( + request: &MultiphaseRequest, + axis: PhaseAxis, +) -> Result { + if request.windows.len() < 2 { + return Err(MultiphasePlanFailure::NoPattern); + } + let orth_axis = axis.other(); + let min_width = sane_min_size(request.bounds.width()); + let min_height = sane_min_size(request.bounds.height()); + let mut phase1 = vec![]; + let mut phase2 = vec![]; + let mut phase3 = vec![]; + for window in &request.windows { + if window.to.width() < min_width { + return Err(MultiphasePlanFailure::ShrinkBound { + axis: PhaseAxis::Horizontal, + available: window.to.width(), + required: min_width, + }); + } + if window.to.height() < min_height { + return Err(MultiphasePlanFailure::ShrinkBound { + axis: PhaseAxis::Vertical, + available: window.to.height(), + required: min_height, + }); + } + let main_changes = main_start(window.from, axis) != main_start(window.to, axis) + || main_end(window.from, axis) != main_end(window.to, axis); + let orth_changes = orth_start(window.from, axis) != orth_start(window.to, axis) + || orth_end(window.from, axis) != orth_end(window.to, axis); + let mut orth_from = window.from; + if main_changes && main_size(window.from, axis) == main_size(window.to, axis) { + let after_move = with_main_interval( + window.from, + axis, + main_start(window.to, axis), + main_end(window.to, axis), + ); + push_step(&mut phase2, window.node_id, window.from, after_move); + orth_from = after_move; + } else if main_changes { + let target_size = main_size(window.to, axis); + let after_main_scale = if main_start(window.from, axis) == main_start(window.to, axis) + || main_end(window.from, axis) == main_end(window.to, axis) + { + with_main_interval( + window.from, + axis, + main_start(window.to, axis), + main_end(window.to, axis), + ) + } else if main_start(window.to, axis) < main_start(window.from, axis) { + with_main_interval( + window.from, + axis, + main_end(window.from, axis) - target_size, + main_end(window.from, axis), + ) + } else { + with_main_interval( + window.from, + axis, + main_start(window.from, axis), + main_start(window.from, axis) + target_size, + ) + }; + push_step(&mut phase1, window.node_id, window.from, after_main_scale); + orth_from = after_main_scale; + if main_start(after_main_scale, axis) != main_start(window.to, axis) + || main_end(after_main_scale, axis) != main_end(window.to, axis) + { + let after_move = with_main_interval( + after_main_scale, + axis, + main_start(window.to, axis), + main_end(window.to, axis), + ); + push_step(&mut phase2, window.node_id, after_main_scale, after_move); + orth_from = after_move; + } + } + if orth_changes { + push_step(&mut phase3, window.node_id, orth_from, window.to); + } + } + if phase1.is_empty() || phase2.is_empty() || phase3.is_empty() { + return Err(MultiphasePlanFailure::NoPattern); + } + build_validated_plan( + request, + PlanStrategy::SpaceThenOrthogonalGrowth { axis }, + [ + phase_draft( + PhaseKind::Scale, + axis, + phase1, + PhaseReason::CreateSpaceForAscendingChild, + ), + phase_draft( + PhaseKind::Move, + axis, + phase2, + PhaseReason::MoveAscendingChildAfterSpaceExists, + ), + phase_draft( + PhaseKind::Scale, + orth_axis, + phase3, + PhaseReason::OrthogonalGrowthAfterMove, + ), + ], + ) +} + +fn plan_orientation_change( + request: &MultiphaseRequest, + from_axis: PhaseAxis, +) -> Result { + if request.windows.len() < 2 { + return Err(MultiphasePlanFailure::NoPattern); + } + let to_axis = from_axis.other(); + let min_lane_size = sane_min_size(main_size(request.bounds, to_axis)); + let target_start = request + .windows + .first() + .map(|window| main_start(window.to, from_axis)) + .ok_or(MultiphasePlanFailure::NoPattern)?; + let target_end = request + .windows + .first() + .map(|window| main_end(window.to, from_axis)) + .ok_or(MultiphasePlanFailure::NoPattern)?; + let source_start = request + .windows + .first() + .map(|window| main_start(window.from, to_axis)) + .ok_or(MultiphasePlanFailure::NoPattern)?; + let source_end = request + .windows + .first() + .map(|window| main_end(window.from, to_axis)) + .ok_or(MultiphasePlanFailure::NoPattern)?; + if request.windows.iter().any(|window| { + main_start(window.from, to_axis) != source_start + || main_end(window.from, to_axis) != source_end + || main_start(window.to, from_axis) != target_start + || main_end(window.to, from_axis) != target_end + || main_size(window.to, to_axis) < min_lane_size + }) { + return Err(MultiphasePlanFailure::NoPattern); + } + + let mut phase1 = vec![]; + let mut phase2 = vec![]; + let mut phase3 = vec![]; + for window in &request.windows { + let lane = with_main_interval( + window.from, + to_axis, + main_start(window.to, to_axis), + main_end(window.to, to_axis), + ); + let moved = with_main_interval( + lane, + from_axis, + main_start(window.to, from_axis), + main_start(window.to, from_axis) + main_size(lane, from_axis), + ); + push_step(&mut phase1, window.node_id, window.from, lane); + push_step(&mut phase2, window.node_id, lane, moved); + push_step(&mut phase3, window.node_id, moved, window.to); + } + if phase1.is_empty() || phase3.is_empty() { + return Err(MultiphasePlanFailure::NoPattern); + } + build_validated_plan( + request, + PlanStrategy::OrientationChange { from_axis }, + [ + phase_draft( + PhaseKind::Scale, + to_axis, + phase1, + PhaseReason::ShrinkIntoLanes { lane_axis: to_axis }, + ), + phase_draft( + PhaseKind::Move, + from_axis, + phase2, + PhaseReason::MoveThroughFreedSpace, + ), + phase_draft( + PhaseKind::Scale, + from_axis, + phase3, + PhaseReason::GrowOutOfLanes, + ), + ], + ) +} + +struct MultiphasePhaseDraft { + action: MultiphasePhaseActionDraft, + steps: Vec, + reason: PhaseReason, +} + +enum MultiphasePhaseActionDraft { + Uniform(PhaseAction), + Mixed(Vec), +} + +fn phase_draft_uniform( + action: PhaseAction, + steps: Vec, + reason: PhaseReason, +) -> MultiphasePhaseDraft { + MultiphasePhaseDraft { + action: MultiphasePhaseActionDraft::Uniform(action), + steps, + reason, + } +} + +fn phase_draft( + kind: PhaseKind, + axis: PhaseAxis, + steps: Vec, + reason: PhaseReason, +) -> MultiphasePhaseDraft { + phase_draft_uniform(PhaseAction { kind, axis }, steps, reason) +} + +fn phase_draft_mixed( + steps: Vec, + actions: Vec, + reason: PhaseReason, +) -> MultiphasePhaseDraft { + MultiphasePhaseDraft { + action: MultiphasePhaseActionDraft::Mixed(actions), + steps, + reason, + } +} + +fn build_validated_plan( + request: &MultiphaseRequest, + strategy: PlanStrategy, + phases: [MultiphasePhaseDraft; N], +) -> Result { + let mut explanations = vec![]; + let phases: Vec<_> = phases + .into_iter() + .filter_map(|draft| { + if draft.steps.is_empty() { + return None; + } + let mut nodes: Vec<_> = draft.steps.iter().map(|step| step.node_id).collect(); + nodes.sort_by_key(|node_id| node_id.0); + let action = match draft.action { + MultiphasePhaseActionDraft::Uniform(action) => { + MultiphasePhaseAction::Uniform(action) + } + MultiphasePhaseActionDraft::Mixed(actions) => { + debug_assert_eq!(actions.len(), draft.steps.len()); + MultiphasePhaseAction::from_step_actions(actions) + } + }; + explanations.push(PhaseExplanation { + action: action.clone(), + reason: draft.reason, + nodes, + }); + Some(MultiphasePhase { + action, + steps: draft.steps, + }) + }) + .collect(); + for phase in &phases { + for (idx, step) in phase.steps.iter().enumerate() { + let action = phase.action.action_for_step(idx).unwrap(); + if classify_step(*step) != Some(action) { + return Err(MultiphasePlanFailure::InvalidPhaseStep { + action, + node_id: step.node_id, + }); + } + } + } + let plan = MultiphasePlan { phases }; + validate_plan_continuous_diagnostic(request, &plan) + .map(|_| MultiphasePlanned { + plan, + explanation: MultiphasePlanExplanation { + strategy, + phases: explanations, + validation: ValidationExplanation::passed(), + }, + }) + .map_err(MultiphasePlanFailure::Validation) +} + +#[cfg_attr(not(test), expect(dead_code))] +fn validate_plan_continuous(request: &MultiphaseRequest, plan: &MultiphasePlan) -> bool { + validate_plan_continuous_diagnostic(request, plan).is_ok() +} + +fn validate_plan_continuous_diagnostic( + request: &MultiphaseRequest, + plan: &MultiphasePlan, +) -> Result<(), MultiphaseValidationError> { + let mut current: Vec<_> = request + .windows + .iter() + .map(|window| (window.node_id, window.from)) + .collect(); + for (phase_idx, phase) in plan.phases.iter().enumerate() { + if let MultiphasePhaseAction::Mixed(actions) = &phase.action + && actions.len() != phase.steps.len() + { + return Err(MultiphaseValidationError::PhaseActionCount { + phase: phase_idx, + actions: actions.len(), + steps: phase.steps.len(), + }); + } + for (idx, step) in phase.steps.iter().enumerate() { + if phase.steps[..idx] + .iter() + .any(|prev| prev.node_id == step.node_id) + { + return Err(MultiphaseValidationError::DuplicatePhaseStep { + phase: phase_idx, + node_id: step.node_id, + }); + } + let Some((_, rect)) = current.iter().find(|(node_id, _)| *node_id == step.node_id) + else { + return Err(MultiphaseValidationError::UnknownPhaseStep { + phase: phase_idx, + node_id: step.node_id, + }); + }; + if *rect != step.from { + return Err(MultiphaseValidationError::StaleStepStart { + phase: phase_idx, + node_id: step.node_id, + }); + } + } + let motions: Vec<_> = current + .iter() + .map(|(node_id, rect)| { + let to = phase + .steps + .iter() + .find(|step| step.node_id == *node_id) + .map(|step| step.to) + .unwrap_or(*rect); + RectMotion { from: *rect, to } + }) + .collect(); + for (idx, motion) in motions.iter().enumerate() { + if let Some((other_idx, _)) = motions[idx + 1..] + .iter() + .enumerate() + .find(|(_, other)| motions_overlap_during_phase(*motion, **other)) + { + return Err(MultiphaseValidationError::PhaseOverlap { + phase: phase_idx, + a: current[idx].0, + b: current[idx + 1 + other_idx].0, + }); + } + } + for step in &phase.steps { + let (_, rect) = current + .iter_mut() + .find(|(node_id, _)| *node_id == step.node_id) + .unwrap(); + *rect = step.to; + } + } + for window in &request.windows { + if !current + .iter() + .find(|(node_id, _)| *node_id == window.node_id) + .is_some_and(|(_, rect)| *rect == window.to) + { + return Err(MultiphaseValidationError::FinalMismatch { + node_id: window.node_id, + }); + } + } + Ok(()) +} + +#[derive(Copy, Clone)] +struct RectMotion { + from: Rect, + to: Rect, +} + +fn motions_overlap_during_phase(a: RectMotion, b: RectMotion) -> bool { + let mut interval = TimeInterval::unit(); + interval.intersect_less_than(edge_delta(a.from.x1(), a.to.x1(), b.from.x2(), b.to.x2())) + && interval.intersect_less_than(edge_delta(b.from.x1(), b.to.x1(), a.from.x2(), a.to.x2())) + && interval.intersect_less_than(edge_delta(a.from.y1(), a.to.y1(), b.from.y2(), b.to.y2())) + && interval.intersect_less_than(edge_delta(b.from.y1(), b.to.y1(), a.from.y2(), a.to.y2())) + && interval.is_non_empty() +} + +fn edge_delta(a0: i32, a1: i32, b0: i32, b1: i32) -> LinearDelta { + let from = a0 as i64 - b0 as i64; + let to = a1 as i64 - b1 as i64; + LinearDelta { + start: from, + velocity: to - from, + } +} + +#[derive(Copy, Clone)] +struct LinearDelta { + start: i64, + velocity: i64, +} + +#[derive(Copy, Clone)] +struct TimeInterval { + lower: Rational, + lower_open: bool, + upper: Rational, + upper_open: bool, +} + +impl TimeInterval { + fn unit() -> Self { + Self { + lower: Rational::new(0, 1), + lower_open: false, + upper: Rational::new(1, 1), + upper_open: false, + } + } + + fn intersect_less_than(&mut self, delta: LinearDelta) -> bool { + if delta.velocity == 0 { + return delta.start < 0; + } + let boundary = Rational::new(-delta.start, delta.velocity); + if delta.velocity > 0 { + self.tighten_upper(boundary, true); + } else { + self.tighten_lower(boundary, true); + } + self.is_non_empty() + } + + fn tighten_lower(&mut self, value: Rational, open: bool) { + match value.cmp(&self.lower) { + std::cmp::Ordering::Greater => { + self.lower = value; + self.lower_open = open; + } + std::cmp::Ordering::Equal => { + self.lower_open |= open; + } + std::cmp::Ordering::Less => {} + } + } + + fn tighten_upper(&mut self, value: Rational, open: bool) { + match value.cmp(&self.upper) { + std::cmp::Ordering::Less => { + self.upper = value; + self.upper_open = open; + } + std::cmp::Ordering::Equal => { + self.upper_open |= open; + } + std::cmp::Ordering::Greater => {} + } + } + + fn is_non_empty(&self) -> bool { + match self.lower.cmp(&self.upper) { + std::cmp::Ordering::Less => true, + std::cmp::Ordering::Equal => !self.lower_open && !self.upper_open, + std::cmp::Ordering::Greater => false, + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq)] +struct Rational { + num: i64, + den: i64, +} + +impl Rational { + fn new(mut num: i64, mut den: i64) -> Self { + if den < 0 { + num = -num; + den = -den; + } + Self { num, den } + } + + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + (self.num as i128 * other.den as i128).cmp(&(other.num as i128 * self.den as i128)) + } +} + +fn classify_step(step: MultiphaseStep) -> Option { + let same_x = step.from.x1() == step.to.x1() && step.from.x2() == step.to.x2(); + let same_y = step.from.y1() == step.to.y1() && step.from.y2() == step.to.y2(); + let same_size = step.from.size() == step.to.size(); + match (same_x, same_y, same_size) { + (false, true, true) => Some(PhaseAction { + kind: PhaseKind::Move, + axis: PhaseAxis::Horizontal, + }), + (true, false, true) => Some(PhaseAction { + kind: PhaseKind::Move, + axis: PhaseAxis::Vertical, + }), + (false, true, false) => Some(PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Horizontal, + }), + (true, false, false) => Some(PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Vertical, + }), + _ => None, + } +} + +fn single_action_reason(action: PhaseAction) -> PhaseReason { + match action.kind { + PhaseKind::Move => PhaseReason::SingleAction, + PhaseKind::Scale => PhaseReason::SameAxisRedistribution, + } +} + +fn reverse_request(request: &MultiphaseRequest) -> MultiphaseRequest { + MultiphaseRequest { + bounds: request.bounds, + clearance: request.clearance, + windows: request + .windows + .iter() + .map(|window| MultiphaseWindow { + node_id: window.node_id, + from: window.to, + to: window.from, + hierarchy: window.hierarchy.reversed(), + }) + .collect(), + } +} + +fn reverse_plan(plan: MultiphasePlan) -> MultiphasePlan { + MultiphasePlan { + phases: plan + .phases + .into_iter() + .rev() + .map(|phase| MultiphasePhase { + action: phase.action, + steps: phase + .steps + .into_iter() + .map(|step| MultiphaseStep { + node_id: step.node_id, + from: step.to, + to: step.from, + }) + .collect(), + }) + .collect(), + } +} + +fn reverse_planned(planned: MultiphasePlanned) -> MultiphasePlanned { + let mut phases = planned.explanation.phases; + phases.reverse(); + MultiphasePlanned { + plan: reverse_plan(planned.plan), + explanation: MultiphasePlanExplanation { + strategy: PlanStrategy::ReversedForwardPlan { + original: Box::new(planned.explanation.strategy), + }, + phases, + validation: planned.explanation.validation, + }, + } +} + +fn overlaps(rects: impl IntoIterator) -> bool { + let rects: Vec<_> = rects.into_iter().collect(); + for (idx, rect) in rects.iter().enumerate() { + if rects[idx + 1..].iter().any(|other| rect.intersects(other)) { + return true; + } + } + false +} + +fn motion_bounds(window: MultiphaseWindow) -> Rect { + window.from.union(window.to) +} + +fn motion_bounds_with_clearance(window: MultiphaseWindow, clearance: i32) -> Rect { + let bounds = motion_bounds(window); + Rect::new_saturating( + bounds.x1().saturating_sub(clearance), + bounds.y1().saturating_sub(clearance), + bounds.x2().saturating_add(clearance), + bounds.y2().saturating_add(clearance), + ) +} + +fn interval_changed(from: Rect, to: Rect, axis: PhaseAxis) -> bool { + main_start(from, axis) != main_start(to, axis) || main_end(from, axis) != main_end(to, axis) +} + +fn split_depth_for_axis(position: MultiphaseHierarchyPosition, axis: PhaseAxis) -> Option { + match axis { + PhaseAxis::Horizontal => position.nearest_horizontal_split_depth, + PhaseAxis::Vertical => position.nearest_vertical_split_depth, + } +} + +fn push_step(steps: &mut Vec, node_id: NodeId, from: Rect, to: Rect) { + if from != to { + steps.push(MultiphaseStep { node_id, from, to }); + } +} + +fn sane_min_size(size: i32) -> i32 { + (size / MIN_SHRINK_DENOMINATOR).max(1) +} + +fn main_start(rect: Rect, axis: PhaseAxis) -> i32 { + match axis { + PhaseAxis::Horizontal => rect.x1(), + PhaseAxis::Vertical => rect.y1(), + } +} + +fn main_end(rect: Rect, axis: PhaseAxis) -> i32 { + match axis { + PhaseAxis::Horizontal => rect.x2(), + PhaseAxis::Vertical => rect.y2(), + } +} + +fn main_size(rect: Rect, axis: PhaseAxis) -> i32 { + main_end(rect, axis) - main_start(rect, axis) +} + +fn orth_start(rect: Rect, axis: PhaseAxis) -> i32 { + main_start(rect, axis.other()) +} + +fn orth_end(rect: Rect, axis: PhaseAxis) -> i32 { + main_end(rect, axis.other()) +} + +fn with_main_interval(rect: Rect, axis: PhaseAxis, start: i32, end: i32) -> Rect { + match axis { + PhaseAxis::Horizontal => Rect::new_saturating(start, rect.y1(), end, rect.y2()), + PhaseAxis::Vertical => Rect::new_saturating(rect.x1(), start, rect.x2(), end), + } +} + +fn with_orth_interval(rect: Rect, axis: PhaseAxis, start: i32, end: i32) -> Rect { + with_main_interval(rect, axis.other(), start, end) +} + +impl PhaseAxis { + fn other(self) -> Self { + match self { + Self::Horizontal => Self::Vertical, + Self::Vertical => Self::Horizontal, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn id(raw: u32) -> NodeId { + NodeId(raw) + } + + fn rect(x1: i32, y1: i32, x2: i32, y2: i32) -> Rect { + Rect::new_saturating(x1, y1, x2, y2) + } + + fn window(raw: u32, from: Rect, to: Rect) -> MultiphaseWindow { + MultiphaseWindow::new(id(raw), from, to) + } + + #[derive(Clone)] + enum TestTree { + Leaf(u32), + Split { + id: u32, + axis: PhaseAxis, + weights: Vec, + children: Vec, + }, + } + + struct TestLeaf { + node_id: NodeId, + rect: Rect, + hierarchy: MultiphaseHierarchyPosition, + } + + fn leaf(raw: u32) -> TestTree { + TestTree::Leaf(raw) + } + + fn split(id: u32, axis: PhaseAxis, weights: &[i32], children: Vec) -> TestTree { + TestTree::Split { + id, + axis, + weights: weights.to_vec(), + children, + } + } + + fn layout_tree(tree: &TestTree, bounds: Rect) -> Vec { + let mut leaves = vec![]; + layout_tree_inner( + tree, + bounds, + TestHierarchy { + parent: None, + depth: 0, + sibling_index: None, + split_axis: None, + nearest_horizontal_split_depth: None, + nearest_vertical_split_depth: None, + }, + &mut leaves, + ); + leaves.sort_by_key(|leaf| leaf.node_id.0); + leaves + } + + #[derive(Copy, Clone)] + struct TestHierarchy { + parent: Option, + depth: u16, + sibling_index: Option, + split_axis: Option, + nearest_horizontal_split_depth: Option, + nearest_vertical_split_depth: Option, + } + + fn layout_tree_inner( + tree: &TestTree, + bounds: Rect, + hierarchy: TestHierarchy, + leaves: &mut Vec, + ) { + match tree { + TestTree::Leaf(raw) => leaves.push(TestLeaf { + node_id: id(*raw), + rect: bounds, + hierarchy: MultiphaseHierarchyPosition { + parent: hierarchy.parent, + depth: hierarchy.depth, + sibling_index: hierarchy.sibling_index, + split_axis: hierarchy.split_axis, + nearest_horizontal_split_depth: hierarchy.nearest_horizontal_split_depth, + nearest_vertical_split_depth: hierarchy.nearest_vertical_split_depth, + ..Default::default() + }, + }), + TestTree::Split { + id: split_id, + axis, + weights, + children, + } => { + assert_eq!(weights.len(), children.len()); + let rects = split_rect_by_weights(bounds, *axis, weights); + for (idx, (child, rect)) in children.iter().zip(rects).enumerate() { + let depth = hierarchy.depth.saturating_add(1); + let mut child_hierarchy = TestHierarchy { + parent: Some(id(*split_id)), + depth, + sibling_index: Some(idx.min(u16::MAX as usize) as u16), + split_axis: Some(*axis), + nearest_horizontal_split_depth: hierarchy.nearest_horizontal_split_depth, + nearest_vertical_split_depth: hierarchy.nearest_vertical_split_depth, + }; + match axis { + PhaseAxis::Horizontal => { + child_hierarchy.nearest_horizontal_split_depth = Some(depth); + } + PhaseAxis::Vertical => { + child_hierarchy.nearest_vertical_split_depth = Some(depth); + } + } + layout_tree_inner(child, rect, child_hierarchy, leaves); + } + } + } + } + + fn split_rect_by_weights(bounds: Rect, axis: PhaseAxis, weights: &[i32]) -> Vec { + let total_weight: i32 = weights.iter().sum(); + assert!(total_weight > 0); + let total_size = match axis { + PhaseAxis::Horizontal => bounds.width(), + PhaseAxis::Vertical => bounds.height(), + }; + let mut pos = match axis { + PhaseAxis::Horizontal => bounds.x1(), + PhaseAxis::Vertical => bounds.y1(), + }; + let mut remaining_size = total_size; + let mut remaining_weight = total_weight; + let mut rects = vec![]; + for (idx, weight) in weights.iter().enumerate() { + let size = if idx + 1 == weights.len() { + remaining_size + } else { + total_size * *weight / total_weight + }; + let rect = match axis { + PhaseAxis::Horizontal => { + Rect::new_sized_saturating(pos, bounds.y1(), size, bounds.height()) + } + PhaseAxis::Vertical => { + Rect::new_sized_saturating(bounds.x1(), pos, bounds.width(), size) + } + }; + rects.push(rect); + pos += size; + remaining_size -= size; + remaining_weight -= *weight; + if remaining_weight == 0 { + assert_eq!(remaining_size, 0); + } + } + rects + } + + fn generated_request(old: &TestTree, new: &TestTree, bounds: Rect) -> MultiphaseRequest { + let old_leaves = layout_tree(old, bounds); + let new_leaves = layout_tree(new, bounds); + assert_eq!(old_leaves.len(), new_leaves.len()); + let mut windows = vec![]; + for old_leaf in &old_leaves { + let new_leaf = new_leaves + .iter() + .find(|leaf| leaf.node_id == old_leaf.node_id) + .unwrap(); + windows.push(MultiphaseWindow::with_hierarchy( + old_leaf.node_id, + old_leaf.rect, + new_leaf.rect, + MultiphaseWindowHierarchy::new(old_leaf.hierarchy, new_leaf.hierarchy), + )); + } + MultiphaseRequest { + bounds, + windows, + clearance: 0, + } + } + + fn assert_generated_case_plans(old: &TestTree, new: &TestTree, bounds: Rect) { + assert_generated_case_plans_deterministically(old, new, bounds); + } + + fn assert_generated_case_plans_deterministically( + old: &TestTree, + new: &TestTree, + bounds: Rect, + ) -> MultiphasePlanned { + let req = generated_request(old, new, bounds); + assert!(!overlaps(req.windows.iter().map(|window| window.from))); + assert!(!overlaps(req.windows.iter().map(|window| window.to))); + let first = plan_no_overlap_explained(&req).unwrap(); + let second = plan_no_overlap_explained(&req).unwrap(); + assert_eq!(first, second); + assert_eq!(first.plan.phases.len(), first.explanation.phases.len()); + assert_eq!( + first.explanation.validation, + ValidationExplanation::passed() + ); + for phase in &first.explanation.phases { + assert!(phase.nodes.windows(2).all(|pair| pair[0].0 < pair[1].0)); + } + assert!(validate_plan_continuous(&req, &first.plan)); + first + } + + fn bounds_for_axis(axis: PhaseAxis) -> Rect { + match axis { + PhaseAxis::Horizontal => rect(0, 0, 400, 100), + PhaseAxis::Vertical => rect(0, 0, 100, 400), + } + } + + fn push_generated_case_bidirectional( + cases: &mut Vec<(TestTree, TestTree, Rect)>, + old: TestTree, + new: TestTree, + bounds: Rect, + ) { + cases.push((old.clone(), new.clone(), bounds)); + cases.push((new, old, bounds)); + } + + fn request(windows: Vec) -> MultiphaseRequest { + let bounds = windows + .iter() + .map(|window| window.from.union(window.to)) + .reduce(|bounds, rect| bounds.union(rect)) + .unwrap_or_else(|| rect(0, 0, 1, 1)); + MultiphaseRequest { + bounds, + windows, + clearance: 0, + } + } + + fn actions(plan: &MultiphasePlan) -> Vec { + plan.phases + .iter() + .map(|phase| phase.action.as_uniform().unwrap()) + .collect() + } + + fn step_to(plan: &MultiphasePlan, phase: usize, node_id: NodeId) -> Rect { + plan.phases[phase] + .steps + .iter() + .find(|step| step.node_id == node_id) + .unwrap() + .to + } + + fn no_pattern_attempts(direction: PlanDirection) -> Vec { + vec![ + RejectedStrategy { + direction, + strategy: PlanStrategy::SingleAction, + reason: MultiphasePlanFailure::NoPattern, + }, + RejectedStrategy { + direction, + strategy: PlanStrategy::SpaceThenOrthogonalGrowth { + axis: PhaseAxis::Horizontal, + }, + reason: MultiphasePlanFailure::NoPattern, + }, + RejectedStrategy { + direction, + strategy: PlanStrategy::SpaceThenOrthogonalGrowth { + axis: PhaseAxis::Vertical, + }, + reason: MultiphasePlanFailure::NoPattern, + }, + RejectedStrategy { + direction, + strategy: PlanStrategy::HierarchyOrderedScales, + reason: MultiphasePlanFailure::NoPattern, + }, + RejectedStrategy { + direction, + strategy: PlanStrategy::OrientationChange { + from_axis: PhaseAxis::Horizontal, + }, + reason: MultiphasePlanFailure::NoPattern, + }, + RejectedStrategy { + direction, + strategy: PlanStrategy::OrientationChange { + from_axis: PhaseAxis::Vertical, + }, + reason: MultiphasePlanFailure::NoPattern, + }, + RejectedStrategy { + direction, + strategy: PlanStrategy::SwapLanes { + axis: PhaseAxis::Horizontal, + }, + reason: MultiphasePlanFailure::NoPattern, + }, + RejectedStrategy { + direction, + strategy: PlanStrategy::SwapLanes { + axis: PhaseAxis::Vertical, + }, + reason: MultiphasePlanFailure::NoPattern, + }, + ] + } + + #[test] + fn horizontal_swap_shrinks_moves_then_grows_without_overlap() { + let req = request(vec![ + window(1, rect(0, 0, 100, 100), rect(100, 0, 200, 100)), + MultiphaseWindow { + node_id: id(2), + from: rect(100, 0, 200, 100), + to: rect(0, 0, 100, 100), + hierarchy: Default::default(), + }, + ]); + let planned = plan_no_overlap_explained(&req).unwrap(); + let plan = &planned.plan; + assert_eq!( + actions(plan), + vec![ + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Vertical + }, + PhaseAction { + kind: PhaseKind::Move, + axis: PhaseAxis::Horizontal + }, + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Vertical + }, + ] + ); + assert_eq!(plan.phases[0].steps[0].to, rect(0, 0, 100, 50)); + assert_eq!(plan.phases[0].steps[1].to, rect(100, 50, 200, 100)); + assert_eq!( + planned.explanation.strategy, + PlanStrategy::SwapLanes { + axis: PhaseAxis::Horizontal + } + ); + assert_eq!( + planned + .explanation + .phases + .iter() + .map(|phase| phase.reason) + .collect::>(), + vec![ + PhaseReason::ShrinkIntoLanes { + lane_axis: PhaseAxis::Vertical + }, + PhaseReason::MoveThroughFreedSpace, + PhaseReason::GrowOutOfLanes, + ] + ); + assert_eq!(planned.explanation.phases[0].nodes, vec![id(1), id(2)]); + assert!(validate_plan_continuous(&req, plan)); + } + + #[test] + fn horizontal_swap_reverse_uses_equivalent_lanes() { + let req = request(vec![ + MultiphaseWindow { + node_id: id(1), + from: rect(100, 0, 200, 100), + to: rect(0, 0, 100, 100), + hierarchy: Default::default(), + }, + MultiphaseWindow { + node_id: id(2), + from: rect(0, 0, 100, 100), + to: rect(100, 0, 200, 100), + hierarchy: Default::default(), + }, + ]); + let plan = plan_no_overlap(&req).unwrap(); + assert_eq!(step_to(&plan, 0, id(2)), rect(0, 0, 100, 50)); + assert_eq!(step_to(&plan, 0, id(1)), rect(100, 50, 200, 100)); + assert!(validate_plan_continuous(&req, &plan)); + } + + #[test] + fn horizontal_swap_lanes_follow_motion_direction_not_node_id() { + let req = request(vec![ + MultiphaseWindow { + node_id: id(1), + from: rect(100, 0, 200, 100), + to: rect(0, 0, 100, 100), + hierarchy: Default::default(), + }, + MultiphaseWindow { + node_id: id(2), + from: rect(0, 0, 100, 100), + to: rect(100, 0, 200, 100), + hierarchy: Default::default(), + }, + ]); + let plan = plan_no_overlap(&req).unwrap(); + assert_eq!(step_to(&plan, 0, id(2)), rect(0, 0, 100, 50)); + assert_eq!(step_to(&plan, 0, id(1)), rect(100, 50, 200, 100)); + assert!(validate_plan_continuous(&req, &plan)); + } + + #[test] + fn swap_lanes_respect_requested_clearance() { + let mut req = request(vec![ + window(1, rect(0, 0, 100, 100), rect(100, 0, 200, 100)), + window(2, rect(100, 0, 200, 100), rect(0, 0, 100, 100)), + ]); + req.clearance = 10; + + let plan = plan_no_overlap(&req).unwrap(); + assert_eq!(step_to(&plan, 0, id(1)), rect(0, 0, 100, 45)); + assert_eq!(step_to(&plan, 0, id(2)), rect(100, 55, 200, 100)); + assert!(validate_plan_continuous(&req, &plan)); + } + + #[test] + fn swap_lanes_tolerate_stationary_siblings_in_request() { + let req = request(vec![ + window(1, rect(0, 0, 100, 100), rect(100, 0, 200, 100)), + window(2, rect(100, 0, 200, 100), rect(0, 0, 100, 100)), + window(3, rect(200, 0, 300, 100), rect(200, 0, 300, 100)), + ]); + + let planned = plan_no_overlap_explained(&req).unwrap(); + assert_eq!( + planned.explanation.strategy, + PlanStrategy::SwapLanes { + axis: PhaseAxis::Horizontal, + } + ); + assert!(validate_plan_continuous(&req, &planned.plan)); + } + + #[test] + fn one_pixel_uneven_swap_lanes_fold_remainder_into_crossing_phase() { + let req = request(vec![ + window(1, rect(0, 0, 101, 100), rect(101, 0, 201, 100)), + window(2, rect(101, 0, 201, 100), rect(0, 0, 101, 100)), + ]); + let planned = plan_no_overlap_explained(&req).unwrap(); + let plan = &planned.plan; + + assert_eq!( + planned.explanation.strategy, + PlanStrategy::SwapLanes { + axis: PhaseAxis::Horizontal, + } + ); + assert_eq!( + actions(plan), + vec![ + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Vertical, + }, + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Horizontal, + }, + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Vertical, + }, + ] + ); + assert_eq!(step_to(plan, 1, id(1)), rect(101, 0, 201, 50)); + assert_eq!(step_to(plan, 1, id(2)), rect(0, 50, 101, 100)); + assert!(validate_plan_continuous(&req, plan)); + } + + #[test] + fn large_uneven_swap_lanes_split_move_and_same_axis_scale() { + let req = request(vec![ + window(1, rect(0, 0, 120, 100), rect(120, 0, 200, 100)), + window(2, rect(120, 0, 200, 100), rect(0, 0, 120, 100)), + ]); + let planned = plan_no_overlap_explained(&req).unwrap(); + let plan = &planned.plan; + + assert_eq!( + planned.explanation.strategy, + PlanStrategy::SwapLanes { + axis: PhaseAxis::Horizontal, + } + ); + assert_eq!( + actions(plan), + vec![ + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Vertical, + }, + PhaseAction { + kind: PhaseKind::Move, + axis: PhaseAxis::Horizontal, + }, + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Horizontal, + }, + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Vertical, + }, + ] + ); + assert_eq!(step_to(plan, 1, id(1)), rect(80, 0, 200, 50)); + assert_eq!(step_to(plan, 1, id(2)), rect(0, 50, 80, 100)); + assert_eq!(step_to(plan, 2, id(1)), rect(120, 0, 200, 50)); + assert_eq!(step_to(plan, 2, id(2)), rect(0, 50, 120, 100)); + assert!(validate_plan_continuous(&req, plan)); + } + + #[test] + fn uneven_swap_lanes_tolerate_stationary_sibling_in_odd_layout() { + let req = request(vec![ + window(1, rect(0, 0, 101, 100), rect(101, 0, 201, 100)), + window(2, rect(101, 0, 201, 100), rect(0, 0, 101, 100)), + window(3, rect(201, 0, 300, 100), rect(201, 0, 300, 100)), + ]); + let planned = plan_no_overlap_explained(&req).unwrap(); + + assert_eq!( + planned.explanation.strategy, + PlanStrategy::SwapLanes { + axis: PhaseAxis::Horizontal, + } + ); + assert_eq!( + actions(&planned.plan), + vec![ + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Vertical, + }, + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Horizontal, + }, + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Vertical, + }, + ] + ); + assert!(validate_plan_continuous(&req, &planned.plan)); + } + + #[test] + fn horizontal_rotation_uses_crossing_lanes() { + let req = request(vec![ + window(1, rect(0, 0, 100, 100), rect(100, 0, 200, 100)), + window(2, rect(100, 0, 200, 100), rect(200, 0, 300, 100)), + window(3, rect(200, 0, 300, 100), rect(0, 0, 100, 100)), + ]); + let planned = plan_no_overlap_explained(&req).unwrap(); + + assert_eq!( + planned.explanation.strategy, + PlanStrategy::SwapLanes { + axis: PhaseAxis::Horizontal, + } + ); + assert_eq!( + actions(&planned.plan), + vec![ + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Vertical, + }, + PhaseAction { + kind: PhaseKind::Move, + axis: PhaseAxis::Horizontal, + }, + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Vertical, + }, + ] + ); + assert!(validate_plan_continuous(&req, &planned.plan)); + } + + #[test] + fn vertical_swap_lanes_follow_motion_direction_not_node_id() { + let req = request(vec![ + MultiphaseWindow { + node_id: id(1), + from: rect(0, 100, 100, 200), + to: rect(0, 0, 100, 100), + hierarchy: Default::default(), + }, + MultiphaseWindow { + node_id: id(2), + from: rect(0, 0, 100, 100), + to: rect(0, 100, 100, 200), + hierarchy: Default::default(), + }, + ]); + let plan = plan_no_overlap(&req).unwrap(); + assert_eq!(step_to(&plan, 0, id(2)), rect(0, 0, 50, 100)); + assert_eq!(step_to(&plan, 0, id(1)), rect(50, 100, 100, 200)); + assert!(validate_plan_continuous(&req, &plan)); + } + + #[test] + fn generated_sibling_swaps_plan_for_both_axes() { + let bounds = rect(0, 0, 240, 240); + for axis in [PhaseAxis::Horizontal, PhaseAxis::Vertical] { + let old = split(10, axis, &[1, 1], vec![leaf(1), leaf(2)]); + let new = split(10, axis, &[1, 1], vec![leaf(2), leaf(1)]); + assert_generated_case_plans(&old, &new, bounds); + } + } + + #[test] + fn generated_size_redistributions_plan_as_single_axis_scale() { + let horizontal_old = split( + 10, + PhaseAxis::Horizontal, + &[1, 1, 1], + vec![leaf(1), leaf(2), leaf(3)], + ); + let horizontal_new = split( + 10, + PhaseAxis::Horizontal, + &[1, 2, 1], + vec![leaf(1), leaf(2), leaf(3)], + ); + let horizontal_req = + generated_request(&horizontal_old, &horizontal_new, rect(0, 0, 400, 100)); + let horizontal_plan = plan_no_overlap(&horizontal_req).unwrap(); + assert_eq!( + actions(&horizontal_plan), + vec![PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Horizontal, + }] + ); + assert!(validate_plan_continuous(&horizontal_req, &horizontal_plan)); + + let vertical_old = split( + 10, + PhaseAxis::Vertical, + &[1, 1, 1], + vec![leaf(1), leaf(2), leaf(3)], + ); + let vertical_new = split( + 10, + PhaseAxis::Vertical, + &[1, 2, 1], + vec![leaf(1), leaf(2), leaf(3)], + ); + let vertical_req = generated_request(&vertical_old, &vertical_new, rect(0, 0, 100, 400)); + let vertical_plan = plan_no_overlap(&vertical_req).unwrap(); + assert_eq!( + actions(&vertical_plan), + vec![PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Vertical, + }] + ); + assert!(validate_plan_continuous(&vertical_req, &vertical_plan)); + } + + #[test] + fn mixed_single_phase_accepts_distinct_axis_actions_when_proven() { + let req = request(vec![ + window(1, rect(0, 0, 100, 100), rect(0, 0, 150, 100)), + window(2, rect(200, 0, 300, 100), rect(200, 0, 300, 150)), + ]); + let planned = plan_no_overlap_explained(&req).unwrap(); + + assert_eq!(planned.explanation.strategy, PlanStrategy::MixedSinglePhase); + assert_eq!(planned.plan.phases.len(), 1); + assert_eq!( + planned.plan.phases[0].action, + MultiphasePhaseAction::Mixed(vec![ + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Horizontal, + }, + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Vertical, + }, + ]) + ); + assert_eq!( + planned.explanation.phases[0].reason, + PhaseReason::MixedAxisActions + ); + assert_eq!(planned.explanation.phases[0].nodes, vec![id(1), id(2)]); + assert!(validate_plan_continuous(&req, &planned.plan)); + } + + #[test] + fn mixed_single_phase_accepts_move_and_scale_when_proven() { + let req = request(vec![ + window(1, rect(0, 0, 80, 80), rect(40, 0, 120, 80)), + window(2, rect(200, 0, 280, 80), rect(200, 0, 280, 120)), + ]); + let planned = plan_no_overlap_explained(&req).unwrap(); + + assert_eq!(planned.explanation.strategy, PlanStrategy::MixedSinglePhase); + assert_eq!( + planned.plan.phases[0].action, + MultiphasePhaseAction::Mixed(vec![ + PhaseAction { + kind: PhaseKind::Move, + axis: PhaseAxis::Horizontal, + }, + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Vertical, + }, + ]) + ); + assert_eq!( + planned.explanation.phases[0].reason, + PhaseReason::MixedAxisActions + ); + assert_eq!(planned.explanation.phases[0].nodes, vec![id(1), id(2)]); + assert!(validate_plan_continuous(&req, &planned.plan)); + } + + #[test] + fn single_window_one_axis_group_is_still_multiphase_plannable() { + let req = request(vec![window(1, rect(0, 0, 100, 100), rect(40, 0, 140, 100))]); + let planned = plan_no_overlap_explained(&req).unwrap(); + + assert_eq!(planned.explanation.strategy, PlanStrategy::SingleAction); + assert_eq!( + planned.plan.phases[0].action, + MultiphasePhaseAction::Uniform(PhaseAction { + kind: PhaseKind::Move, + axis: PhaseAxis::Horizontal, + }) + ); + assert_eq!(planned.explanation.phases[0].nodes, vec![id(1)]); + assert!(validate_plan_continuous(&req, &planned.plan)); + } + + #[test] + fn mixed_single_phase_still_rejects_diagonal_per_window_motion() { + let req = request(vec![ + window(1, rect(0, 0, 80, 80), rect(40, 40, 120, 120)), + window(2, rect(200, 0, 280, 80), rect(200, 0, 280, 120)), + ]); + + assert_eq!(plan_no_overlap(&req), Err(MultiphaseError::NoPlan)); + let diagnostic = plan_no_overlap_with_diagnostics(&req).unwrap_err(); + let rejection = MultiphasePlanFailure::InvalidPhaseStep { + action: PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Horizontal, + }, + node_id: id(1), + }; + assert_eq!(diagnostic.forward, rejection); + assert_eq!(diagnostic.reverse, Some(rejection)); + } + + #[test] + fn generated_nested_size_redistribution_scales_parent_axis_first() { + let old = split( + 10, + PhaseAxis::Horizontal, + &[1, 1], + vec![ + leaf(1), + split(11, PhaseAxis::Vertical, &[1, 1], vec![leaf(2), leaf(3)]), + ], + ); + let new = split( + 10, + PhaseAxis::Horizontal, + &[1, 3], + vec![ + leaf(1), + split(11, PhaseAxis::Vertical, &[3, 1], vec![leaf(2), leaf(3)]), + ], + ); + let req = generated_request(&old, &new, rect(0, 0, 400, 100)); + let planned = plan_no_overlap_explained(&req).unwrap(); + let plan = &planned.plan; + assert_eq!( + actions(plan), + vec![ + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Horizontal, + }, + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Vertical, + }, + ] + ); + assert_eq!(step_to(plan, 0, id(1)), rect(0, 0, 100, 100)); + assert_eq!(step_to(plan, 0, id(2)), rect(100, 0, 400, 50)); + assert_eq!(step_to(plan, 0, id(3)), rect(100, 50, 400, 100)); + assert_eq!( + planned.explanation.strategy, + PlanStrategy::HierarchyOrderedScales + ); + assert_eq!( + planned.explanation.phases[0].reason, + PhaseReason::ParentAxisBeforeChildAxis { + parent_axis: PhaseAxis::Horizontal, + parent_depth: 1, + child_axis: PhaseAxis::Vertical, + child_depth: 2, + } + ); + assert_eq!( + planned.explanation.phases[0].nodes, + vec![id(1), id(2), id(3)] + ); + assert_eq!(planned.explanation.phases[1].nodes, vec![id(2), id(3)]); + assert_eq!( + planned.explanation.validation, + ValidationExplanation::passed() + ); + assert!(validate_plan_continuous(&req, plan)); + } + + #[test] + fn orientation_change_shrinks_moves_then_grows() { + let req = request(vec![ + window(1, rect(0, 0, 200, 400), rect(0, 0, 400, 200)), + window(2, rect(200, 0, 400, 400), rect(0, 200, 400, 400)), + ]); + let planned = plan_no_overlap_explained(&req).unwrap(); + let plan = &planned.plan; + + assert_eq!( + planned.explanation.strategy, + PlanStrategy::OrientationChange { + from_axis: PhaseAxis::Horizontal, + } + ); + assert_eq!( + actions(plan), + vec![ + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Vertical, + }, + PhaseAction { + kind: PhaseKind::Move, + axis: PhaseAxis::Horizontal, + }, + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Horizontal, + }, + ] + ); + assert_eq!(step_to(plan, 0, id(1)), rect(0, 0, 200, 200)); + assert_eq!(step_to(plan, 0, id(2)), rect(200, 200, 400, 400)); + assert_eq!(step_to(plan, 1, id(2)), rect(0, 200, 200, 400)); + assert_eq!(step_to(plan, 2, id(1)), rect(0, 0, 400, 200)); + assert_eq!(step_to(plan, 2, id(2)), rect(0, 200, 400, 400)); + assert!(validate_plan_continuous(&req, plan)); + } + + #[test] + fn two_axis_redistribution_without_hierarchy_still_falls_back() { + let req = request(vec![ + window(1, rect(0, 0, 200, 100), rect(0, 0, 100, 100)), + window(2, rect(200, 0, 400, 50), rect(100, 0, 400, 75)), + window(3, rect(200, 50, 400, 100), rect(100, 75, 400, 100)), + ]); + assert_eq!(plan_no_overlap(&req), Err(MultiphaseError::NoPlan)); + } + + #[test] + fn generated_stack_extractions_plan_for_both_axes_and_directions() { + let horizontal_old = split( + 10, + PhaseAxis::Horizontal, + &[1, 1], + vec![ + leaf(1), + split(11, PhaseAxis::Vertical, &[1, 1], vec![leaf(2), leaf(3)]), + ], + ); + let horizontal_new = split( + 10, + PhaseAxis::Horizontal, + &[1, 2, 1], + vec![leaf(1), leaf(2), leaf(3)], + ); + assert_generated_case_plans(&horizontal_old, &horizontal_new, rect(0, 0, 400, 100)); + assert_generated_case_plans(&horizontal_new, &horizontal_old, rect(0, 0, 400, 100)); + + let vertical_old = split( + 20, + PhaseAxis::Vertical, + &[1, 1], + vec![ + leaf(1), + split(21, PhaseAxis::Horizontal, &[1, 1], vec![leaf(2), leaf(3)]), + ], + ); + let vertical_new = split( + 20, + PhaseAxis::Vertical, + &[1, 2, 1], + vec![leaf(1), leaf(2), leaf(3)], + ); + assert_generated_case_plans(&vertical_old, &vertical_new, rect(0, 0, 100, 400)); + assert_generated_case_plans(&vertical_new, &vertical_old, rect(0, 0, 100, 400)); + } + + #[test] + fn stack_extraction_with_resized_moving_child_still_moves_before_growth() { + let old = split( + 10, + PhaseAxis::Horizontal, + &[1, 1], + vec![ + leaf(1), + split(11, PhaseAxis::Vertical, &[1, 1], vec![leaf(2), leaf(3)]), + ], + ); + let new = split( + 10, + PhaseAxis::Horizontal, + &[1, 1, 1], + vec![leaf(1), leaf(2), leaf(3)], + ); + let req = generated_request(&old, &new, rect(0, 0, 300, 120)); + let planned = plan_no_overlap_explained(&req).unwrap(); + let plan = &planned.plan; + + assert_eq!( + planned.explanation.strategy, + PlanStrategy::SpaceThenOrthogonalGrowth { + axis: PhaseAxis::Horizontal, + } + ); + assert_eq!( + actions(plan), + vec![ + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Horizontal, + }, + PhaseAction { + kind: PhaseKind::Move, + axis: PhaseAxis::Horizontal, + }, + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Vertical, + }, + ] + ); + assert_eq!(step_to(plan, 0, id(1)), rect(0, 0, 100, 120)); + assert_eq!(step_to(plan, 0, id(2)), rect(200, 0, 300, 60)); + assert_eq!(step_to(plan, 0, id(3)), rect(200, 60, 300, 120)); + assert_eq!(step_to(plan, 1, id(2)), rect(100, 0, 200, 60)); + assert_eq!(step_to(plan, 2, id(2)), rect(100, 0, 200, 120)); + assert_eq!(step_to(plan, 2, id(3)), rect(200, 0, 300, 120)); + assert!(validate_plan_continuous(&req, plan)); + } + + #[test] + fn three_child_stack_extraction_plans_without_linear_fallback() { + let old = split( + 10, + PhaseAxis::Horizontal, + &[1, 1], + vec![ + leaf(1), + split( + 11, + PhaseAxis::Vertical, + &[1, 1, 1], + vec![leaf(2), leaf(3), leaf(4)], + ), + ], + ); + let new = split( + 10, + PhaseAxis::Horizontal, + &[1, 1, 1], + vec![ + leaf(1), + leaf(3), + split(11, PhaseAxis::Vertical, &[1, 1], vec![leaf(2), leaf(4)]), + ], + ); + let req = generated_request(&old, &new, rect(0, 0, 600, 300)); + let planned = plan_no_overlap_explained(&req).unwrap(); + + assert_eq!( + planned.explanation.strategy, + PlanStrategy::SpaceThenOrthogonalGrowth { + axis: PhaseAxis::Horizontal, + } + ); + assert_eq!( + actions(&planned.plan), + vec![ + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Horizontal, + }, + PhaseAction { + kind: PhaseKind::Move, + axis: PhaseAxis::Horizontal, + }, + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Vertical, + }, + ] + ); + assert!(validate_plan_continuous(&req, &planned.plan)); + } + + #[test] + fn validated_phase_paths_accept_interrupted_reverse_route() { + let a_current = rect(50, 0, 150, 50); + let b_current = rect(50, 50, 150, 100); + let req = request(vec![ + window(1, a_current, rect(0, 0, 100, 100)), + window(2, b_current, rect(100, 0, 200, 100)), + ]); + let paths = vec![ + vec![ + (a_current, rect(0, 0, 100, 50)), + (rect(0, 0, 100, 50), rect(0, 0, 100, 100)), + ], + vec![ + (b_current, rect(100, 50, 200, 100)), + (rect(100, 50, 200, 100), rect(100, 0, 200, 100)), + ], + ]; + + let plan = validate_phase_paths(&req, &paths).unwrap(); + assert_eq!( + actions(&plan), + vec![ + PhaseAction { + kind: PhaseKind::Move, + axis: PhaseAxis::Horizontal, + }, + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Vertical, + }, + ] + ); + assert!(validate_plan_continuous(&req, &plan)); + } + + #[test] + fn bounded_generated_supported_split_tree_corpus_is_deterministic() { + let mut cases = vec![]; + for axis in [PhaseAxis::Horizontal, PhaseAxis::Vertical] { + let child_axis = axis.other(); + let bounds = bounds_for_axis(axis); + + push_generated_case_bidirectional( + &mut cases, + split(10, axis, &[1, 1], vec![leaf(1), leaf(2)]), + split(10, axis, &[1, 1], vec![leaf(2), leaf(1)]), + bounds, + ); + push_generated_case_bidirectional( + &mut cases, + split(10, axis, &[1, 1, 1], vec![leaf(1), leaf(2), leaf(3)]), + split(10, axis, &[1, 2, 1], vec![leaf(1), leaf(2), leaf(3)]), + bounds, + ); + push_generated_case_bidirectional( + &mut cases, + split( + 10, + axis, + &[1, 1], + vec![ + leaf(1), + split(11, child_axis, &[1, 1], vec![leaf(2), leaf(3)]), + ], + ), + split(10, axis, &[1, 2, 1], vec![leaf(1), leaf(2), leaf(3)]), + bounds, + ); + push_generated_case_bidirectional( + &mut cases, + split( + 10, + axis, + &[1, 1], + vec![ + split(11, child_axis, &[1, 1], vec![leaf(1), leaf(2)]), + leaf(3), + ], + ), + split(10, axis, &[1, 2, 1], vec![leaf(1), leaf(2), leaf(3)]), + bounds, + ); + push_generated_case_bidirectional( + &mut cases, + split( + 10, + axis, + &[1, 1], + vec![ + leaf(1), + split(11, child_axis, &[1, 1], vec![leaf(2), leaf(3)]), + ], + ), + split( + 10, + axis, + &[1, 3], + vec![ + leaf(1), + split(11, child_axis, &[3, 1], vec![leaf(2), leaf(3)]), + ], + ), + bounds, + ); + push_generated_case_bidirectional( + &mut cases, + split( + 10, + axis, + &[1, 1], + vec![ + split(11, child_axis, &[1, 1], vec![leaf(1), leaf(2)]), + leaf(3), + ], + ), + split( + 10, + axis, + &[3, 1], + vec![ + split(11, child_axis, &[1, 3], vec![leaf(1), leaf(2)]), + leaf(3), + ], + ), + bounds, + ); + } + + assert_eq!(cases.len(), 24); + for (old, new, bounds) in cases { + assert_generated_case_plans_deterministically(&old, &new, bounds); + } + } + + #[test] + fn stack_extraction_creates_space_before_moving_child() { + let req = request(vec![ + MultiphaseWindow { + node_id: id(1), + from: rect(0, 0, 200, 100), + to: rect(0, 0, 100, 100), + hierarchy: Default::default(), + }, + MultiphaseWindow { + node_id: id(2), + from: rect(200, 0, 400, 50), + to: rect(100, 0, 300, 100), + hierarchy: Default::default(), + }, + MultiphaseWindow { + node_id: id(3), + from: rect(200, 50, 400, 100), + to: rect(300, 0, 400, 100), + hierarchy: Default::default(), + }, + ]); + let plan = plan_no_overlap(&req).unwrap(); + assert_eq!( + actions(&plan), + vec![ + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Horizontal + }, + PhaseAction { + kind: PhaseKind::Move, + axis: PhaseAxis::Horizontal + }, + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Vertical + }, + ] + ); + assert_eq!(plan.phases[0].steps[0].to, rect(0, 0, 100, 100)); + assert_eq!(plan.phases[0].steps[1].to, rect(300, 50, 400, 100)); + assert_eq!(plan.phases[1].steps[0].to, rect(100, 0, 300, 50)); + assert_eq!(plan.phases[2].steps[0].to, rect(100, 0, 300, 100)); + assert_eq!(plan.phases[2].steps[1].to, rect(300, 0, 400, 100)); + assert!(validate_plan_continuous(&req, &plan)); + } + + #[test] + fn stack_extraction_reverse_replays_phases_in_reverse() { + let req = request(vec![ + MultiphaseWindow { + node_id: id(1), + from: rect(0, 0, 100, 100), + to: rect(0, 0, 200, 100), + hierarchy: Default::default(), + }, + MultiphaseWindow { + node_id: id(2), + from: rect(100, 0, 300, 100), + to: rect(200, 0, 400, 50), + hierarchy: Default::default(), + }, + MultiphaseWindow { + node_id: id(3), + from: rect(300, 0, 400, 100), + to: rect(200, 50, 400, 100), + hierarchy: Default::default(), + }, + ]); + let planned = plan_no_overlap_explained(&req).unwrap(); + let plan = &planned.plan; + assert_eq!( + actions(plan), + vec![ + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Vertical + }, + PhaseAction { + kind: PhaseKind::Move, + axis: PhaseAxis::Horizontal + }, + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Horizontal + }, + ] + ); + assert_eq!( + planned.explanation.strategy, + PlanStrategy::ReversedForwardPlan { + original: Box::new(PlanStrategy::SpaceThenOrthogonalGrowth { + axis: PhaseAxis::Horizontal + }) + } + ); + assert!(validate_plan_continuous(&req, plan)); + } + + #[test] + fn vertical_stack_extraction_creates_space_before_moving_child() { + let req = request(vec![ + MultiphaseWindow { + node_id: id(1), + from: rect(0, 0, 100, 200), + to: rect(0, 0, 100, 100), + hierarchy: Default::default(), + }, + MultiphaseWindow { + node_id: id(2), + from: rect(0, 200, 50, 400), + to: rect(0, 100, 100, 300), + hierarchy: Default::default(), + }, + MultiphaseWindow { + node_id: id(3), + from: rect(50, 200, 100, 400), + to: rect(0, 300, 100, 400), + hierarchy: Default::default(), + }, + ]); + let plan = plan_no_overlap(&req).unwrap(); + assert_eq!( + actions(&plan), + vec![ + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Vertical + }, + PhaseAction { + kind: PhaseKind::Move, + axis: PhaseAxis::Vertical + }, + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Horizontal + }, + ] + ); + assert_eq!(plan.phases[0].steps[0].to, rect(0, 0, 100, 100)); + assert_eq!(plan.phases[0].steps[1].to, rect(50, 300, 100, 400)); + assert_eq!(plan.phases[1].steps[0].to, rect(0, 100, 50, 300)); + assert_eq!(plan.phases[2].steps[0].to, rect(0, 100, 100, 300)); + assert_eq!(plan.phases[2].steps[1].to, rect(0, 300, 100, 400)); + assert!(validate_plan_continuous(&req, &plan)); + } + + #[test] + fn vertical_stack_extraction_with_clearance_still_plans() { + let old = split( + 20, + PhaseAxis::Vertical, + &[1, 1], + vec![ + leaf(1), + split(21, PhaseAxis::Horizontal, &[1, 1], vec![leaf(2), leaf(3)]), + ], + ); + let new = split( + 20, + PhaseAxis::Vertical, + &[1, 2, 1], + vec![leaf(1), leaf(2), leaf(3)], + ); + let mut req = generated_request(&old, &new, rect(0, 0, 100, 400)); + req.clearance = 10; + let planned = plan_no_overlap_explained(&req).unwrap(); + + assert_eq!( + planned.explanation.strategy, + PlanStrategy::SpaceThenOrthogonalGrowth { + axis: PhaseAxis::Vertical, + } + ); + assert!(validate_plan_continuous(&req, &planned.plan)); + } + + #[test] + fn vertical_stack_extraction_reverse_replays_phases_in_reverse() { + let req = request(vec![ + MultiphaseWindow { + node_id: id(1), + from: rect(0, 0, 100, 100), + to: rect(0, 0, 100, 200), + hierarchy: Default::default(), + }, + MultiphaseWindow { + node_id: id(2), + from: rect(0, 100, 100, 300), + to: rect(0, 200, 50, 400), + hierarchy: Default::default(), + }, + MultiphaseWindow { + node_id: id(3), + from: rect(0, 300, 100, 400), + to: rect(50, 200, 100, 400), + hierarchy: Default::default(), + }, + ]); + let plan = plan_no_overlap(&req).unwrap(); + assert_eq!( + actions(&plan), + vec![ + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Horizontal + }, + PhaseAction { + kind: PhaseKind::Move, + axis: PhaseAxis::Vertical + }, + PhaseAction { + kind: PhaseKind::Scale, + axis: PhaseAxis::Vertical + }, + ] + ); + assert!(validate_plan_continuous(&req, &plan)); + } + + #[test] + fn unsupported_diagonal_motion_falls_back_to_linear() { + let req = request(vec![MultiphaseWindow { + node_id: id(1), + from: rect(0, 0, 100, 100), + to: rect(100, 100, 200, 200), + hierarchy: Default::default(), + }]); + assert_eq!(plan_no_overlap(&req), Err(MultiphaseError::NoPlan)); + let diagnostic = plan_no_overlap_with_diagnostics(&req).unwrap_err(); + assert_eq!(diagnostic.forward, MultiphasePlanFailure::NoPattern); + assert_eq!(diagnostic.reverse, Some(MultiphasePlanFailure::NoPattern)); + let mut expected = no_pattern_attempts(PlanDirection::Forward); + expected.extend(no_pattern_attempts(PlanDirection::Reverse)); + assert_eq!(diagnostic.attempted, expected); + } + + #[test] + fn diagnostics_report_shrink_bound_rejections() { + let req = MultiphaseRequest { + bounds: rect(0, 0, 400, 100), + clearance: 0, + windows: vec![ + MultiphaseWindow { + node_id: id(1), + from: rect(0, 0, 200, 100), + to: rect(0, 0, 10, 100), + hierarchy: Default::default(), + }, + MultiphaseWindow { + node_id: id(2), + from: rect(200, 0, 400, 100), + to: rect(10, 0, 400, 100), + hierarchy: Default::default(), + }, + ], + }; + + assert!(matches!( + plan_no_overlap_with_diagnostics(&req).unwrap_err().forward, + MultiphasePlanFailure::ShrinkBound { + axis: PhaseAxis::Horizontal, + available: 10, + required: 50, + } + )); + } + + #[test] + fn diagnostics_report_candidate_validation_rejections() { + let req = request(vec![ + MultiphaseWindow { + node_id: id(1), + from: rect(0, 0, 60, 60), + to: rect(180, 0, 240, 60), + hierarchy: Default::default(), + }, + MultiphaseWindow { + node_id: id(2), + from: rect(90, 0, 150, 60), + to: rect(90, 0, 150, 60), + hierarchy: Default::default(), + }, + ]); + let rejection = + MultiphasePlanFailure::Validation(MultiphaseValidationError::PhaseOverlap { + phase: 0, + a: id(1), + b: id(2), + }); + let diagnostic = plan_no_overlap_with_diagnostics(&req).unwrap_err(); + + assert_eq!(diagnostic.forward, rejection); + assert_eq!(diagnostic.reverse, Some(rejection)); + assert_eq!( + diagnostic.attempted[0], + RejectedStrategy { + direction: PlanDirection::Forward, + strategy: PlanStrategy::SingleAction, + reason: rejection, + } + ); + assert!(diagnostic.attempted.iter().any(|attempt| *attempt + == RejectedStrategy { + direction: PlanDirection::Reverse, + strategy: PlanStrategy::SingleAction, + reason: rejection, + })); + } + + #[test] + fn hierarchy_metadata_classifies_depth_and_mono_transitions() { + let source = MultiphaseHierarchyPosition { + parent: Some(id(10)), + depth: 2, + sibling_index: Some(0), + split_axis: Some(PhaseAxis::Vertical), + nearest_horizontal_split_depth: Some(1), + nearest_vertical_split_depth: Some(2), + ..Default::default() + }; + let target = MultiphaseHierarchyPosition { + parent: Some(id(11)), + depth: 1, + sibling_index: Some(2), + split_axis: Some(PhaseAxis::Horizontal), + nearest_horizontal_split_depth: Some(1), + ..Default::default() + }; + assert_eq!( + MultiphaseWindowHierarchy::new(source, target).transition, + MultiphaseHierarchyTransition::Ascending + ); + assert_eq!(source.nearest_vertical_split_depth, Some(2)); + + let entering_mono = MultiphaseWindowHierarchy::new( + source, + MultiphaseHierarchyPosition { + parent_is_mono: true, + mono_active: true, + ..target + }, + ); + assert_eq!( + entering_mono.transition, + MultiphaseHierarchyTransition::EnteringMono + ); + assert_eq!( + entering_mono.reversed().transition, + MultiphaseHierarchyTransition::ExitingMono + ); + } + + #[test] + fn continuous_validation_rejects_narrow_mid_phase_overlap() { + let req = request(vec![ + MultiphaseWindow { + node_id: id(1), + from: rect(0, 0, 10, 10), + to: rect(100, 0, 110, 10), + hierarchy: Default::default(), + }, + MultiphaseWindow { + node_id: id(2), + from: rect(13, 0, 14, 10), + to: rect(13, 0, 14, 10), + hierarchy: Default::default(), + }, + ]); + let plan = MultiphasePlan { + phases: vec![MultiphasePhase { + action: MultiphasePhaseAction::Uniform(PhaseAction { + kind: PhaseKind::Move, + axis: PhaseAxis::Horizontal, + }), + steps: vec![MultiphaseStep { + node_id: id(1), + from: rect(0, 0, 10, 10), + to: rect(100, 0, 110, 10), + }], + }], + }; + + assert_eq!( + validate_plan_continuous_diagnostic(&req, &plan), + Err(MultiphaseValidationError::PhaseOverlap { + phase: 0, + a: id(1), + b: id(2), + }) + ); + } + + #[test] + fn continuous_validation_allows_edge_touching_motion() { + let req = request(vec![ + MultiphaseWindow { + node_id: id(1), + from: rect(0, 0, 10, 10), + to: rect(10, 0, 20, 10), + hierarchy: Default::default(), + }, + MultiphaseWindow { + node_id: id(2), + from: rect(20, 0, 30, 10), + to: rect(20, 0, 30, 10), + hierarchy: Default::default(), + }, + ]); + let plan = MultiphasePlan { + phases: vec![MultiphasePhase { + action: MultiphasePhaseAction::Uniform(PhaseAction { + kind: PhaseKind::Move, + axis: PhaseAxis::Horizontal, + }), + steps: vec![MultiphaseStep { + node_id: id(1), + from: rect(0, 0, 10, 10), + to: rect(10, 0, 20, 10), + }], + }], + }; + + assert!(validate_plan_continuous(&req, &plan)); + } + + #[test] + fn continuous_validation_rejects_mixed_phase_action_count_mismatch() { + let req = request(vec![ + window(1, rect(0, 0, 40, 40), rect(40, 0, 80, 40)), + window(2, rect(100, 0, 140, 40), rect(100, 0, 140, 80)), + ]); + let plan = MultiphasePlan { + phases: vec![MultiphasePhase { + action: MultiphasePhaseAction::Mixed(vec![PhaseAction { + kind: PhaseKind::Move, + axis: PhaseAxis::Horizontal, + }]), + steps: vec![ + MultiphaseStep { + node_id: id(1), + from: rect(0, 0, 40, 40), + to: rect(40, 0, 80, 40), + }, + MultiphaseStep { + node_id: id(2), + from: rect(100, 0, 140, 40), + to: rect(100, 0, 140, 80), + }, + ], + }], + }; + + assert_eq!( + validate_plan_continuous_diagnostic(&req, &plan), + Err(MultiphaseValidationError::PhaseActionCount { + phase: 0, + actions: 1, + steps: 2, + }) + ); + } + + #[test] + fn continuous_validation_rejects_stale_step_start_rect() { + let req = request(vec![MultiphaseWindow { + node_id: id(1), + from: rect(0, 0, 10, 10), + to: rect(20, 0, 30, 10), + hierarchy: Default::default(), + }]); + let plan = MultiphasePlan { + phases: vec![MultiphasePhase { + action: MultiphasePhaseAction::Uniform(PhaseAction { + kind: PhaseKind::Move, + axis: PhaseAxis::Horizontal, + }), + steps: vec![MultiphaseStep { + node_id: id(1), + from: rect(5, 0, 15, 10), + to: rect(20, 0, 30, 10), + }], + }], + }; + + assert_eq!( + validate_plan_continuous_diagnostic(&req, &plan), + Err(MultiphaseValidationError::StaleStepStart { + phase: 0, + node_id: id(1), + }) + ); + } + + #[test] + fn motion_groups_split_disjoint_layout_changes() { + let windows = vec![ + MultiphaseWindow { + node_id: id(1), + from: rect(0, 0, 100, 100), + to: rect(100, 0, 200, 100), + hierarchy: Default::default(), + }, + MultiphaseWindow { + node_id: id(2), + from: rect(100, 0, 200, 100), + to: rect(0, 0, 100, 100), + hierarchy: Default::default(), + }, + MultiphaseWindow { + node_id: id(3), + from: rect(300, 0, 400, 100), + to: rect(400, 0, 500, 100), + hierarchy: Default::default(), + }, + ]; + assert_eq!(partition_motion_groups(&windows, 0), vec![vec![0, 1], vec![2]]); + } + + #[test] + fn motion_groups_are_transitive() { + let windows = vec![ + MultiphaseWindow { + node_id: id(1), + from: rect(0, 0, 100, 100), + to: rect(80, 0, 180, 100), + hierarchy: Default::default(), + }, + MultiphaseWindow { + node_id: id(2), + from: rect(170, 0, 270, 100), + to: rect(250, 0, 350, 100), + hierarchy: Default::default(), + }, + MultiphaseWindow { + node_id: id(3), + from: rect(90, 0, 180, 100), + to: rect(180, 0, 260, 100), + hierarchy: Default::default(), + }, + ]; + assert_eq!(partition_motion_groups(&windows, 0), vec![vec![0, 1, 2]]); + } + + #[test] + fn motion_groups_join_across_animation_clearance() { + let windows = vec![ + MultiphaseWindow { + node_id: id(1), + from: rect(0, 0, 100, 100), + to: rect(0, 0, 80, 100), + hierarchy: Default::default(), + }, + MultiphaseWindow { + node_id: id(2), + from: rect(120, 0, 220, 100), + to: rect(110, 0, 210, 100), + hierarchy: Default::default(), + }, + ]; + assert_eq!(partition_motion_groups(&windows, 0), vec![vec![0], vec![1]]); + assert_eq!(partition_motion_groups(&windows, 10), vec![vec![0, 1]]); + } +} diff --git a/crates/libinput/Cargo.toml b/crates/libinput/Cargo.toml new file mode 100644 index 00000000..9fff6e9d --- /dev/null +++ b/crates/libinput/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "jay-libinput" +version.workspace = true +edition.workspace = true +license.workspace = true +build = "build.rs" + +[dependencies] +jay-utils = { path = "../utils" } + +bstr = { version = "1.9.0", default-features = false, features = ["std"] } +isnt = "0.2.0" +libloading = "0.9.0" +log = { version = "0.4.20", features = ["std"] } +thiserror = "2.0.11" +uapi = "0.2.13" + +[build-dependencies] +anyhow = "1.0.79" +cc = "1.0.86" +repc = "0.1.1" diff --git a/crates/libinput/build.rs b/crates/libinput/build.rs new file mode 100644 index 00000000..19fd83f3 --- /dev/null +++ b/crates/libinput/build.rs @@ -0,0 +1,181 @@ +use { + repc::layout::{Type, TypeVariant}, + std::{ + env, + fs::{File, OpenOptions}, + io::{self, BufWriter, Write}, + path::PathBuf, + }, +}; + +#[allow(unused_macros)] +macro_rules! cenum { + ($name:ident, $uc:ident; $($name2:ident = $val:expr,)*) => { + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub struct $name(pub i32); + + impl $name { + pub fn raw(self) -> i32 { + self.0 + } + } + + $( + pub const $name2: $name = $name($val); + )* + + pub const $uc: &[i32] = &[$($val,)*]; + }; +} + +#[path = "src/consts.rs"] +mod consts; + +fn open(s: &str) -> io::Result> { + let mut path = PathBuf::from(env::var("OUT_DIR").unwrap()); + path.push(s); + Ok(BufWriter::new( + OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(path)?, + )) +} + +fn get_target() -> repc::Target { + let rustc_target = env::var("TARGET").unwrap(); + repc::TARGET_MAP + .iter() + .cloned() + .find(|t| t.0 == rustc_target) + .unwrap() + .1 +} + +fn get_enum_ty(variants: Vec) -> anyhow::Result { + let target = get_target(); + let ty = Type { + layout: (), + annotations: vec![], + variant: TypeVariant::Enum(variants), + }; + let ty = repc::compute_layout(target, &ty)?; + assert!(ty.layout.pointer_alignment_bits <= ty.layout.size_bits); + Ok(ty.layout.size_bits) +} + +fn write_ty(f: &mut W, vals: &[i32], ty: &str) -> anyhow::Result<()> { + let variants: Vec<_> = vals.iter().cloned().map(|v| v as i128).collect(); + let size = get_enum_ty(variants)?; + writeln!(f, "#[allow(clippy::allow_attributes, dead_code)]")?; + writeln!(f, "pub type {} = i{};", ty, size)?; + Ok(()) +} + +fn main() -> anyhow::Result<()> { + println!("cargo:rerun-if-changed=src/bridge.c"); + cc::Build::new() + .file("src/bridge.c") + .opt_level(2) + .compile("jay-libinput-bridge"); + + let mut f = open("libinput_tys.rs")?; + write_ty( + &mut f, + consts::LIBINPUT_LOG_PRIORITY, + "libinput_log_priority", + )?; + write_ty( + &mut f, + consts::LIBINPUT_DEVICE_CAPABILITY, + "libinput_device_capability", + )?; + write_ty(&mut f, consts::LIBINPUT_KEY_STATE, "libinput_key_state")?; + write_ty(&mut f, consts::LIBINPUT_LED, "libinput_led")?; + write_ty( + &mut f, + consts::LIBINPUT_BUTTON_STATE, + "libinput_button_state", + )?; + write_ty( + &mut f, + consts::LIBINPUT_POINTER_AXIS, + "libinput_pointer_axis", + )?; + write_ty( + &mut f, + consts::LIBINPUT_POINTER_AXIS_SOURCE, + "libinput_pointer_axis_source", + )?; + write_ty( + &mut f, + consts::LIBINPUT_TABLET_PAD_RING_AXIS_SOURCE, + "libinput_tablet_pad_ring_axis_source", + )?; + write_ty( + &mut f, + consts::LIBINPUT_TABLET_PAD_STRIP_AXIS_SOURCE, + "libinput_tablet_pad_strip_axis_source", + )?; + write_ty( + &mut f, + consts::LIBINPUT_TABLET_TOOL_TYPE, + "libinput_tablet_tool_type", + )?; + write_ty( + &mut f, + consts::LIBINPUT_TABLET_TOOL_PROXIMITY_STATE, + "libinput_tablet_tool_proximity_state", + )?; + write_ty( + &mut f, + consts::LIBINPUT_TABLET_TOOL_TIP_STATE, + "libinput_tablet_tool_tip_state", + )?; + write_ty( + &mut f, + consts::LIBINPUT_SWITCH_STATE, + "libinput_switch_state", + )?; + write_ty(&mut f, consts::LIBINPUT_SWITCH, "libinput_switch")?; + write_ty(&mut f, consts::LIBINPUT_EVENT_TYPE, "libinput_event_type")?; + write_ty( + &mut f, + consts::LIBINPUT_CONFIG_STATUS, + "libinput_config_status", + )?; + write_ty( + &mut f, + consts::LIBINPUT_CONFIG_ACCEL_PROFILE, + "libinput_config_accel_profile", + )?; + write_ty( + &mut f, + consts::LIBINPUT_CONFIG_TAP_STATE, + "libinput_config_tap_state", + )?; + write_ty( + &mut f, + consts::LIBINPUT_CONFIG_DRAG_STATE, + "libinput_config_drag_state", + )?; + write_ty( + &mut f, + consts::LIBINPUT_CONFIG_DRAG_LOCK_STATE, + "libinput_config_drag_lock_state", + )?; + write_ty( + &mut f, + consts::LIBINPUT_CONFIG_CLICK_METHOD, + "libinput_config_click_method", + )?; + write_ty( + &mut f, + consts::LIBINPUT_CONFIG_MIDDLE_EMULATION_STATE, + "libinput_config_middle_emulation_state", + )?; + + println!("cargo:rerun-if-changed=src/consts.rs"); + Ok(()) +} diff --git a/src/bridge.c b/crates/libinput/src/bridge.c similarity index 100% rename from src/bridge.c rename to crates/libinput/src/bridge.c diff --git a/src/libinput/consts.rs b/crates/libinput/src/consts.rs similarity index 100% rename from src/libinput/consts.rs rename to crates/libinput/src/consts.rs diff --git a/src/libinput/device.rs b/crates/libinput/src/device.rs similarity index 99% rename from src/libinput/device.rs rename to crates/libinput/src/device.rs index 9d398880..596c047d 100644 --- a/src/libinput/device.rs +++ b/crates/libinput/src/device.rs @@ -1,5 +1,5 @@ use { - crate::libinput::{ + crate::{ LibInput, consts::{ AccelProfile, ConfigClickMethod, ConfigDragLockState, ConfigDragState, diff --git a/src/libinput/event.rs b/crates/libinput/src/event.rs similarity index 98% rename from src/libinput/event.rs rename to crates/libinput/src/event.rs index c296c56f..7fb16146 100644 --- a/src/libinput/event.rs +++ b/crates/libinput/src/event.rs @@ -1,5 +1,5 @@ use { - crate::libinput::{ + crate::{ consts::{ ButtonState, EventType, KeyState, PointerAxis, Switch, SwitchState, TabletPadRingAxisSource, TabletPadStripAxisSource, TabletToolProximityState, @@ -292,7 +292,7 @@ impl<'a> LibInputEventSwitch<'a> { macro_rules! has_changed { ($name:ident, $f:ident) => { pub fn $name(&self) -> bool { - unsafe { crate::libinput::sys::$f(self.event) != 0 } + unsafe { crate::sys::$f(self.event) != 0 } } }; } @@ -300,7 +300,7 @@ macro_rules! has_changed { macro_rules! get_double { ($name:ident, $f:ident) => { pub fn $name(&self) -> f64 { - unsafe { crate::libinput::sys::$f(self.event) } + unsafe { crate::sys::$f(self.event) } } }; } @@ -308,7 +308,7 @@ macro_rules! get_double { macro_rules! has_capability { ($name:ident, $f:ident) => { pub fn $name(&self) -> bool { - unsafe { crate::libinput::sys::$f(self.tool) != 0 } + unsafe { crate::sys::$f(self.tool) != 0 } } }; } diff --git a/crates/libinput/src/lib.rs b/crates/libinput/src/lib.rs new file mode 100644 index 00000000..421b13cd --- /dev/null +++ b/crates/libinput/src/lib.rs @@ -0,0 +1,205 @@ +#![allow(non_camel_case_types)] + +macro_rules! cenum { + ($name:ident, $uc:ident; $($name2:ident = $val:expr,)*) => { + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub struct $name(pub i32); + + impl $name { + #[allow(dead_code)] + pub fn raw(self) -> i32 { + self.0 + } + } + + $( + pub const $name2: $name = $name($val); + )* + + pub const $uc: &[i32] = &[$($val,)*]; + }; +} + +pub mod consts; +pub mod device; +pub mod event; +mod sys; + +use { + crate::{ + consts::{ + LIBINPUT_LOG_PRIORITY_DEBUG, LIBINPUT_LOG_PRIORITY_ERROR, LIBINPUT_LOG_PRIORITY_INFO, + LogPriority, + }, + device::RegisteredDevice, + event::LibInputEvent, + sys::{ + libinput, libinput_device_ref, libinput_dispatch, libinput_get_event, libinput_get_fd, + libinput_interface, libinput_log_priority, libinput_log_set_handler, + libinput_log_set_priority, libinput_path_add_device, libinput_path_create_context, + libinput_unref, + }, + }, + bstr::ByteSlice, + isnt::std_1::primitive::IsntConstPtrExt, + jay_utils::{errorfmt::ErrorFmt, oserror::OsError, ptr_ext::PtrExt}, + std::{ffi::CStr, rc::Rc}, + thiserror::Error, + uapi::{IntoUstr, OwnedFd, c}, +}; + +static INTERFACE: libinput_interface = libinput_interface { + open_restricted, + close_restricted, +}; + +unsafe extern "C" fn open_restricted( + path: *const c::c_char, + _flags: c::c_int, + user_data: *mut c::c_void, +) -> c::c_int { + unsafe { + let ud = (user_data as *const UserData).deref(); + match ud.adapter.open(CStr::from_ptr(path)) { + Ok(f) => f.unwrap(), + Err(e) => { + log::error!("Could not open device for libinput: {}", ErrorFmt(e)); + -1 + } + } + } +} + +unsafe extern "C" fn close_restricted(fd: c::c_int, _user_data: *mut c::c_void) { + drop(OwnedFd::new(fd)); +} + +struct UserData { + adapter: Rc, +} + +pub trait LibInputAdapter { + fn open(&self, path: &CStr) -> Result; +} + +#[derive(Debug, Error)] +pub enum LibInputError { + #[error("Could not create a libinput instance")] + New, + #[error("Could not open a libinput device")] + Open, + #[error("Could not dispatch libinput events")] + Dispatch(#[source] OsError), + #[error("The requested device is not available")] + DeviceUnavailable, + #[error("Dupfd failed")] + DupFd(#[source] OsError), + #[error("Stat failed")] + Stat(#[source] OsError), +} + +pub struct LibInput { + _data: Box, + li: *mut libinput, +} + +unsafe extern "C" { + fn jay_libinput_log_handler_bridge(); +} + +impl LibInput { + pub fn new(adapter: Rc) -> Result { + let mut ud = Box::new(UserData { adapter }); + let li = unsafe { + libinput_path_create_context(&INTERFACE, &mut *ud as *mut _ as *mut c::c_void) + }; + if li.is_null() { + return Err(LibInputError::New); + } + unsafe { + libinput_log_set_handler(li, jay_libinput_log_handler_bridge); + let priority = if log::log_enabled!(log::Level::Debug) { + LIBINPUT_LOG_PRIORITY_DEBUG + } else if log::log_enabled!(log::Level::Info) { + LIBINPUT_LOG_PRIORITY_INFO + } else { + LIBINPUT_LOG_PRIORITY_ERROR + }; + libinput_log_set_priority(li, priority.raw() as _); + } + Ok(Self { _data: ud, li }) + } + + pub fn fd(&self) -> c::c_int { + unsafe { libinput_get_fd(self.li) } + } + + pub fn open<'a>( + self: &Rc, + path: impl IntoUstr<'a>, + ) -> Result { + let path = path.into_ustr(); + let res = unsafe { libinput_path_add_device(self.li, path.as_ptr()) }; + if res.is_null() { + Err(LibInputError::Open) + } else { + unsafe { + libinput_device_ref(res); + } + Ok(RegisteredDevice { + _li: self.clone(), + dev: res, + }) + } + } + + pub fn dispatch(&self) -> Result<(), LibInputError> { + let res = unsafe { libinput_dispatch(self.li) }; + if res < 0 { + Err(LibInputError::Dispatch(OsError(-res))) + } else { + Ok(()) + } + } + + pub fn event(&self) -> Option> { + let res = unsafe { libinput_get_event(self.li) }; + if res.is_null() { + None + } else { + Some(LibInputEvent { + event: res, + _phantom: Default::default(), + }) + } + } +} + +impl Drop for LibInput { + fn drop(&mut self) { + unsafe { + libinput_unref(self.li); + } + } +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn jay_libinput_log_handler( + _libinput: *mut libinput, + priority: libinput_log_priority, + line: *const c::c_char, +) { + assert!(line.is_not_null()); + let str = unsafe { CStr::from_ptr(line) }; + let priority = match LogPriority(priority as _) { + LIBINPUT_LOG_PRIORITY_DEBUG => log::Level::Debug, + LIBINPUT_LOG_PRIORITY_INFO => log::Level::Info, + LIBINPUT_LOG_PRIORITY_ERROR => log::Level::Error, + _ => log::Level::Error, + }; + log::log!( + priority, + "libinput: {}", + str.to_bytes().trim_ascii().as_bstr() + ); +} diff --git a/src/libinput/sys.rs b/crates/libinput/src/sys.rs similarity index 100% rename from src/libinput/sys.rs rename to crates/libinput/src/sys.rs diff --git a/crates/logger/Cargo.toml b/crates/logger/Cargo.toml new file mode 100644 index 00000000..50e39217 --- /dev/null +++ b/crates/logger/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "jay-logger" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +jay-config = { path = "../jay-config" } +jay-utils = { path = "../utils" } + +backtrace = "0.3.69" +bstr = { version = "1.9.0", default-features = false, features = ["std"] } +clap = { version = "4.4.18", features = ["derive", "wrap_help"] } +dirs = "6.0.0" +humantime = "2.1.0" +linearize = { version = "0.1.3", features = ["derive"] } +log = { version = "0.4.20", features = ["std"] } +parking_lot = "0.12.1" +thiserror = "2.0.11" +uapi = "0.2.13" diff --git a/src/logger.rs b/crates/logger/src/lib.rs similarity index 78% rename from src/logger.rs rename to crates/logger/src/lib.rs index f5e2266d..8752fdee 100644 --- a/src/logger.rs +++ b/crates/logger/src/lib.rs @@ -1,18 +1,20 @@ use { - crate::{ - compositor::LogLevel, - utils::{ - atomic_enum::AtomicEnum, - errorfmt::ErrorFmt, - oserror::{OsError, OsErrorExt, OsErrorExt2}, - }, + jay_config::logging::LogLevel as ConfigLogLevel, + jay_utils::{ + atomic_enum::AtomicEnum, + errorfmt::ErrorFmt, + oserror::{OsError, OsErrorExt, OsErrorExt2}, + static_text::StaticText, }, backtrace::Backtrace, bstr::{BStr, BString, ByteSlice}, + clap::ValueEnum, + linearize::Linearize, log::{LevelFilter, Log, Metadata, Record}, parking_lot::Mutex, std::{ cell::Cell, + fmt::Arguments, fs::DirBuilder, io::Write, os::unix::{ffi::OsStringExt, fs::DirBuilderExt}, @@ -28,6 +30,73 @@ use { uapi::{AsUstr, Dirent, Fd, OwnedFd, Ustring, c, format_ustr}, }; +#[derive(ValueEnum, Debug, Copy, Clone, Hash, Default, Eq, PartialEq, Linearize)] +pub enum LogLevel { + Trace, + Debug, + #[default] + Info, + Warn, + Error, + Off, +} + +impl From for LevelFilter { + fn from(value: LogLevel) -> Self { + match value { + LogLevel::Trace => LevelFilter::Trace, + LogLevel::Debug => LevelFilter::Debug, + LogLevel::Info => LevelFilter::Info, + LogLevel::Warn => LevelFilter::Warn, + LogLevel::Error => LevelFilter::Error, + LogLevel::Off => LevelFilter::Off, + } + } +} + +impl From for LogLevel { + fn from(value: LevelFilter) -> Self { + match value { + LevelFilter::Trace => LogLevel::Trace, + LevelFilter::Debug => LogLevel::Debug, + LevelFilter::Info => LogLevel::Info, + LevelFilter::Warn => LogLevel::Warn, + LevelFilter::Error => LogLevel::Error, + LevelFilter::Off => LogLevel::Off, + } + } +} + +impl StaticText for LogLevel { + fn text(&self) -> &'static str { + match self { + LogLevel::Off => "Off", + LogLevel::Error => "Error", + LogLevel::Warn => "Warn", + LogLevel::Info => "Info", + LogLevel::Debug => "Debug", + LogLevel::Trace => "Trace", + } + } +} + +impl From for LogLevel { + fn from(value: ConfigLogLevel) -> Self { + match value { + ConfigLogLevel::Trace => LogLevel::Trace, + ConfigLogLevel::Debug => LogLevel::Debug, + ConfigLogLevel::Info => LogLevel::Info, + ConfigLogLevel::Warn => LogLevel::Warn, + ConfigLogLevel::Error => LogLevel::Error, + } + } +} + +fn fatal(args: Arguments<'_>) -> ! { + log::error!("{}", args); + std::process::exit(1); +} + thread_local! { static BUFFER: Cell<*mut Vec> = const { Cell::new(ptr::null_mut()) }; } @@ -45,7 +114,7 @@ impl Logger { let file = match uapi::fcntl_dupfd_cloexec(2, 0).to_os_error() { Ok(fd) => fd, Err(e) => { - fatal!("Error: Could not dup stderr: {}", ErrorFmt(e)); + fatal(format_args!("Error: Could not dup stderr: {}", ErrorFmt(e))); } }; Self::install(level, b"STDERR", file) @@ -150,7 +219,10 @@ pub fn open_log_file(ty: &str) -> (Ustring, OwnedFd) { } Err(OsError(c::EEXIST)) => {} Err(e) => { - fatal!("Error: Could not create log file: {}", ErrorFmt(e)); + fatal(format_args!( + "Error: Could not create log file: {}", + ErrorFmt(e) + )); } } } @@ -160,7 +232,7 @@ pub fn open_log_file(ty: &str) -> (Ustring, OwnedFd) { fn create_log_dir(ty: &str) -> BString { let mut log_dir = match dirs::data_local_dir() { Some(d) => d, - None => fatal!("Error: $HOME is not set"), + None => fatal(format_args!("Error: $HOME is not set")), }; log_dir.push("jay"); log_dir.push("logs"); @@ -170,11 +242,11 @@ fn create_log_dir(ty: &str) -> BString { .mode(0o755) .create(&log_dir); if let Err(e) = res { - fatal!( + fatal(format_args!( "Error: Could not create log directory {}: {}", log_dir.display(), ErrorFmt(e) - ); + )); } log_dir.into_os_string().into_vec().into() } diff --git a/crates/output-schedule/Cargo.toml b/crates/output-schedule/Cargo.toml new file mode 100644 index 00000000..7d08af93 --- /dev/null +++ b/crates/output-schedule/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "jay-output-schedule" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +jay-async-engine = { path = "../async-engine" } +jay-io-uring = { path = "../io-uring" } +jay-utils = { path = "../utils" } + +futures-util = "0.3.30" +log = "0.4.20" +num-traits = "0.2.17" diff --git a/crates/output-schedule/src/lib.rs b/crates/output-schedule/src/lib.rs new file mode 100644 index 00000000..69933f2b --- /dev/null +++ b/crates/output-schedule/src/lib.rs @@ -0,0 +1,219 @@ +use { + jay_async_engine::AsyncEngine, + jay_io_uring::{IoUring, IoUringError}, + jay_utils::{ + asyncevent::AsyncEvent, cell_ext::CellExt, clonecell::CloneCell, errorfmt::ErrorFmt, + numcell::NumCell, + }, + futures_util::{FutureExt, select}, + num_traits::ToPrimitive, + std::{cell::Cell, rc::Rc}, +}; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +enum Change { + /// The backend has applied the latest changes. + None, + /// There are changes that the backend is not yet aware of. + Scheduled, + /// The backend is aware that there are changes and will apply them as part of the + /// next latch event. + AwaitingLatch, +} + +pub struct OutputSchedule { + changed: AsyncEvent, + run: Cell, + + damage_connector: Rc, + hardware_cursor_damage: CloneCell>>, + cursor_hz_changed: Rc)>, + + persistent: Rc, + + last_present_nsec: Cell, + cursor_delta_nsec: Cell>, + + ring: Rc, + eng: Rc, + + vrr_enabled: Cell, + + hardware_cursor_change: Cell, + software_cursor_change: Cell, + + iteration: NumCell, +} + +pub trait OutputSchedulePersistent { + fn vrr_cursor_hz(&self) -> Option; + fn set_vrr_cursor_hz(&self, hz: Option); +} + +impl OutputSchedule { + pub fn new( + ring: Rc, + eng: Rc, + persistent: Rc, + damage_connector: Rc, + cursor_hz_changed: Rc)>, + ) -> Self { + let slf = Self { + changed: Default::default(), + run: Default::default(), + damage_connector, + cursor_hz_changed, + ring, + eng, + vrr_enabled: Default::default(), + hardware_cursor_change: Cell::new(Change::None), + software_cursor_change: Cell::new(Change::None), + hardware_cursor_damage: Default::default(), + persistent: persistent.clone(), + last_present_nsec: Default::default(), + cursor_delta_nsec: Default::default(), + iteration: Default::default(), + }; + if let Some(hz) = persistent.vrr_cursor_hz() { + slf.set_cursor_hz(hz); + } + slf + } + + pub async fn drive(self: Rc) { + loop { + self.run_once().await; + while !self.run.take() { + self.changed.triggered().await; + } + } + } + + fn trigger(&self) { + let trigger = self.vrr_enabled.get() + && self.cursor_delta_nsec.is_some() + && (self.software_cursor_change.get() == Change::Scheduled + || self.hardware_cursor_change.get() == Change::Scheduled); + if trigger { + self.run.set(true); + self.changed.trigger(); + } + } + + pub fn latched(&self) { + self.last_present_nsec.set(self.eng.now().nsec()); + if self.software_cursor_change.get() == Change::AwaitingLatch { + self.software_cursor_change.set(Change::None); + } + if self.hardware_cursor_change.get() == Change::AwaitingLatch { + self.hardware_cursor_change.set(Change::None); + } + self.iteration.fetch_add(1); + self.trigger(); + } + + pub fn vrr_enabled(&self) -> bool { + self.vrr_enabled.get() + } + + pub fn set_vrr_enabled(&self, enabled: bool) { + self.vrr_enabled.set(enabled); + self.trigger(); + } + + pub fn set_cursor_hz(&self, hz: f64) { + let (hz, delta) = match map_cursor_hz(hz) { + None => { + log::warn!("Ignoring cursor frequency {hz}"); + return; + } + Some(v) => v, + }; + self.persistent.set_vrr_cursor_hz(hz); + (self.cursor_hz_changed)(hz); + self.cursor_delta_nsec.set(delta); + self.trigger(); + } + + pub fn set_hardware_cursor_damage(&self, damage: &Option>) { + self.hardware_cursor_damage.set(damage.clone()); + } + + pub fn defer_cursor_updates(&self) -> bool { + self.vrr_enabled.get() && self.cursor_delta_nsec.is_some() + } + + pub fn hardware_cursor_changed(&self) { + if self.hardware_cursor_change.get() == Change::None { + self.hardware_cursor_change.set(Change::Scheduled); + self.trigger(); + } + } + + pub fn software_cursor_changed(&self) { + if self.software_cursor_change.get() == Change::None { + self.software_cursor_change.set(Change::Scheduled); + self.trigger(); + } + } + + async fn run_once(&self) { + loop { + if self.hardware_cursor_change.get() != Change::Scheduled + && self.software_cursor_change.get() != Change::Scheduled + { + return; + } + if !self.vrr_enabled.get() { + return; + } + let Some(duration) = self.cursor_delta_nsec.get() else { + return; + }; + let iteration = self.iteration.get(); + let next_present = self.last_present_nsec.get().saturating_add(duration); + let res: Result<(), IoUringError> = select! { + _ = self.changed.triggered().fuse() => continue, + v = self.ring.timeout(next_present).fuse() => v, + }; + if let Err(e) = res { + log::error!("Could not wait for timer to expire: {}", ErrorFmt(e)); + return; + } + if iteration == self.iteration.get() { + break; + } + } + self.commit_cursor(); + } + + pub fn commit_cursor(&self) { + if self.hardware_cursor_change.get() == Change::Scheduled { + if let Some(damage) = self.hardware_cursor_damage.get() { + damage(); + } + self.hardware_cursor_change.set(Change::AwaitingLatch); + } + if self.software_cursor_change.get() == Change::Scheduled { + (self.damage_connector)(); + self.software_cursor_change.set(Change::AwaitingLatch); + } + } +} + +pub fn map_cursor_hz(hz: f64) -> Option<(Option, Option)> { + if hz <= 0.0 { + return Some((Some(0.0), Some(u64::MAX))); + } + let delta = (1_000_000_000.0 / hz).to_u64(); + if delta.is_none() { + if hz > 0.0 { + return Some((None, None)); + } + return None; + } + if delta == Some(0) { + return Some((None, None)); + } + Some((Some(hz), delta)) +} diff --git a/crates/output-types/Cargo.toml b/crates/output-types/Cargo.toml new file mode 100644 index 00000000..b0acc23d --- /dev/null +++ b/crates/output-types/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "jay-output-types" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "Output identity types for the Jay compositor" +repository = "https://github.com/mahkoh/jay" + +[dependencies] +jay-cmm = { path = "../cmm" } +jay-formats = { path = "../formats" } +jay-utils = { path = "../utils" } + +blake3 = "1.8.2" +linearize = { version = "0.1.3", features = ["derive"] } +uapi = "0.2.13" diff --git a/crates/output-types/src/lib.rs b/crates/output-types/src/lib.rs new file mode 100644 index 00000000..7e4029c3 --- /dev/null +++ b/crates/output-types/src/lib.rs @@ -0,0 +1,348 @@ +use { + jay_cmm::cmm_primaries::Primaries, + jay_formats::Format, + jay_utils::numcell::NumCell, + linearize::Linearize, + std::{ + fmt::{self, Debug, Display, Formatter}, + hash::{Hash, Hasher}, + ops::{BitOr, BitOrAssign}, + rc::Rc, + }, + uapi::{Packed, Pod}, +}; + +macro_rules! linear_ids { + ($ids:ident, $id:ident $(,)?) => { + linear_ids!($ids, $id, u32); + }; + ($ids:ident, $id:ident, $ty:ty $(,)?) => { + #[derive(Debug)] + pub struct $ids { + next: NumCell<$ty>, + } + + impl Default for $ids { + fn default() -> Self { + Self { + next: NumCell::new(1), + } + } + } + + impl $ids { + pub fn next(&self) -> $id { + $id(self.next.fetch_add(1)) + } + } + + #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] + pub struct $id($ty); + + impl $id { + pub fn raw(&self) -> $ty { + self.0 + } + + pub fn from_raw(id: $ty) -> Self { + Self(id) + } + } + + impl Display for $id { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.0, f) + } + } + }; +} + +linear_ids!(ConnectorIds, ConnectorId); +linear_ids!(DrmDeviceIds, DrmDeviceId); +linear_ids!( + BackendConnectorStateSerials, + BackendConnectorStateSerial, + u64 +); + +#[derive(Copy, Clone, Eq, PartialEq, Default)] +pub struct ConnectorCaps(pub u32); + +pub const CONCAP_CONNECTOR: ConnectorCaps = ConnectorCaps(1 << 0); +pub const CONCAP_MODE_SETTING: ConnectorCaps = ConnectorCaps(1 << 1); +pub const CONCAP_PHYSICAL_DISPLAY: ConnectorCaps = ConnectorCaps(1 << 2); + +impl ConnectorCaps { + pub fn none() -> Self { + Self(0) + } + + pub fn contains(self, other: Self) -> bool { + self.0 & other.0 == other.0 + } +} + +impl BitOr for ConnectorCaps { + type Output = Self; + + fn bitor(self, rhs: Self) -> Self::Output { + Self(self.0 | rhs.0) + } +} + +impl BitOrAssign for ConnectorCaps { + fn bitor_assign(&mut self, rhs: Self) { + self.0 |= rhs.0; + } +} + +impl Debug for ConnectorCaps { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut any = false; + let mut v = self.0; + for (cap, name) in [ + (CONCAP_CONNECTOR, "CONCAP_CONNECTOR"), + (CONCAP_MODE_SETTING, "CONCAP_MODE_SETTING"), + (CONCAP_PHYSICAL_DISPLAY, "CONCAP_PHYSICAL_DISPLAY"), + ] { + if v & cap.0 == cap.0 { + if any { + write!(f, "|")?; + } + any = true; + write!(f, "{}", name)?; + v &= !cap.0; + } + } + if !any || v != 0 { + if any { + write!(f, "|")?; + } + write!(f, "0x{:x}", v)?; + } + Ok(()) + } +} + +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)] +pub struct Mode { + pub width: i32, + pub height: i32, + pub refresh_rate_millihz: u32, +} + +impl Mode { + pub fn refresh_nsec(&self) -> u64 { + match self.refresh_rate_millihz { + 0 => u64::MAX, + n => 1_000_000_000_000 / (n as u64), + } + } + + pub fn size(&self) -> (i32, i32) { + (self.width, self.height) + } +} + +impl Display for Mode { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}x{}@{}", + self.width, + self.height, + self.refresh_rate_millihz as f64 / 1000.0, + ) + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Linearize)] +pub enum BackendEotfs { + #[default] + Default, + Pq, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Linearize)] +pub enum BackendColorSpace { + #[default] + Default, + Bt2020, +} + +#[derive(Copy, Clone, Debug)] +pub struct BackendLuminance { + pub min: f64, + pub max: f64, + pub max_fall: f64, +} + +impl BackendEotfs { + pub fn to_drm(self) -> u8 { + match self { + BackendEotfs::Default => 0, + BackendEotfs::Pq => 2, + } + } + + pub const fn name(self) -> &'static str { + match self { + BackendEotfs::Default => "default", + BackendEotfs::Pq => "pq", + } + } +} + +impl BackendColorSpace { + pub fn to_drm(self) -> u64 { + match self { + BackendColorSpace::Default => 0, + BackendColorSpace::Bt2020 => 9, + } + } + + pub const fn name(self) -> &'static str { + match self { + BackendColorSpace::Default => "default", + BackendColorSpace::Bt2020 => "bt2020", + } + } +} + +// kernel: struct drm_color_lut +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)] +#[repr(C)] +pub struct BackendGammaLutElement { + pub red: u16, + pub green: u16, + pub blue: u16, + pub reserved: u16, +} + +unsafe impl Pod for BackendGammaLutElement {} +unsafe impl Packed for BackendGammaLutElement {} + +#[derive(Debug, Eq)] +pub struct BackendGammaLut { + id: [u8; 32], + pub gamma_lut: Vec, +} + +impl BackendGammaLut { + pub fn new(mut gamma_lut: Vec) -> Self { + for element in &mut gamma_lut { + element.reserved = 0; + } + let gamma_lut_bytes = uapi::as_bytes(&gamma_lut as &[_]); + let id = *blake3::hash(gamma_lut_bytes).as_bytes(); + Self { id, gamma_lut } + } +} + +impl PartialEq for BackendGammaLut { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct BackendConnectorState { + pub serial: BackendConnectorStateSerial, + pub enabled: bool, + pub active: bool, + pub mode: Mode, + pub non_desktop_override: Option, + pub vrr: bool, + pub tearing: bool, + pub format: &'static Format, + pub color_space: BackendColorSpace, + pub eotf: BackendEotfs, + pub gamma_lut: Option>, +} + +#[derive(Clone, Debug)] +pub struct MonitorInfo { + pub modes: Option>, + pub output_id: Rc, + pub width_mm: i32, + pub height_mm: i32, + pub non_desktop: bool, + pub non_desktop_effective: bool, + pub vrr_capable: bool, + pub eotfs: Vec, + pub color_spaces: Vec, + pub primaries: Primaries, + pub luminance: Option, + pub state: BackendConnectorState, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct OutputIdHash(pub [u8; 32]); + +impl OutputIdHash { + pub fn hash(t: impl AsRef<[u8]>) -> Self { + Self(*blake3::hash(t.as_ref()).as_bytes()) + } +} + +#[derive(Eq, Debug)] +pub struct OutputId { + pub _connector: Option, + pub manufacturer: String, + pub model: String, + pub serial_number: String, + pub hash: OutputIdHash, +} + +impl PartialEq for OutputId { + fn eq(&self, other: &Self) -> bool { + self.hash == other.hash + } +} + +impl Hash for OutputId { + fn hash(&self, state: &mut H) { + self.hash.hash(state); + } +} + +impl OutputId { + pub fn new( + connector: impl Into, + manufacturer: impl Into, + model: impl Into, + serial_number: impl Into, + ) -> Rc { + let connector = connector.into(); + let manufacturer = manufacturer.into(); + let model = model.into(); + let serial_number = serial_number.into(); + Self::new_(connector, manufacturer, model, serial_number) + } + + fn new_( + connector: String, + manufacturer: String, + model: String, + serial_number: String, + ) -> Rc { + let connector = serial_number.is_empty().then_some(connector); + let mut hasher = blake3::Hasher::new(); + hasher.update(&[connector.is_some() as u8]); + let mut hash = |s: &str| { + hasher.update(&(s.len() as u64).to_le_bytes()); + hasher.update(s.as_bytes()); + }; + connector.as_deref().map(&mut hash); + hash(&manufacturer); + hash(&model); + hash(&serial_number); + Rc::new(Self { + _connector: connector, + manufacturer, + model, + serial_number, + hash: OutputIdHash(*hasher.finalize().as_bytes()), + }) + } +} diff --git a/crates/pango/Cargo.toml b/crates/pango/Cargo.toml new file mode 100644 index 00000000..f06b5e08 --- /dev/null +++ b/crates/pango/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "jay-pango" +version.workspace = true +edition.workspace = true +license.workspace = true +build = "build.rs" + +[dependencies] +jay-geometry = { path = "../geometry" } + +thiserror = "2.0.11" +uapi = "0.2.13" + +[build-dependencies] +anyhow = "1.0.79" +repc = "0.1.1" diff --git a/crates/pango/build.rs b/crates/pango/build.rs new file mode 100644 index 00000000..9eaf108a --- /dev/null +++ b/crates/pango/build.rs @@ -0,0 +1,85 @@ +use { + repc::layout::{Type, TypeVariant}, + std::{ + env, + fs::{File, OpenOptions}, + io::{self, BufWriter, Write}, + path::PathBuf, + }, +}; + +#[allow(unused_macros)] +macro_rules! cenum { + ($name:ident, $uc:ident; $($name2:ident = $val:expr,)*) => { + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub struct $name(pub i32); + + impl $name { + pub fn raw(self) -> i32 { + self.0 + } + } + + $( + pub const $name2: $name = $name($val); + )* + + pub const $uc: &[i32] = &[$($val,)*]; + }; +} + +#[path = "src/consts.rs"] +mod consts; + +fn open(s: &str) -> io::Result> { + let mut path = PathBuf::from(env::var("OUT_DIR").unwrap()); + path.push(s); + Ok(BufWriter::new( + OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(path)?, + )) +} + +fn get_target() -> repc::Target { + let rustc_target = env::var("TARGET").unwrap(); + repc::TARGET_MAP + .iter() + .cloned() + .find(|t| t.0 == rustc_target) + .unwrap() + .1 +} + +fn get_enum_ty(variants: Vec) -> anyhow::Result { + let target = get_target(); + let ty = Type { + layout: (), + annotations: vec![], + variant: TypeVariant::Enum(variants), + }; + let ty = repc::compute_layout(target, &ty)?; + assert!(ty.layout.pointer_alignment_bits <= ty.layout.size_bits); + Ok(ty.layout.size_bits) +} + +fn write_ty(f: &mut W, vals: &[i32], ty: &str) -> anyhow::Result<()> { + let variants: Vec<_> = vals.iter().cloned().map(|v| v as i128).collect(); + let size = get_enum_ty(variants)?; + writeln!(f, "#[allow(clippy::allow_attributes, dead_code)]")?; + writeln!(f, "pub type {} = i{};", ty, size)?; + Ok(()) +} + +fn main() -> anyhow::Result<()> { + let mut f = open("pango_tys.rs")?; + write_ty(&mut f, consts::CAIRO_FORMATS, "cairo_format_t")?; + write_ty(&mut f, consts::CAIRO_STATUSES, "cairo_status_t")?; + write_ty(&mut f, consts::CAIRO_OPERATORS, "cairo_operator_t")?; + write_ty(&mut f, consts::PANGO_ELLIPSIZE_MODES, "PangoEllipsizeMode_")?; + + println!("cargo:rerun-if-changed=src/consts.rs"); + Ok(()) +} diff --git a/src/pango/consts.rs b/crates/pango/src/consts.rs similarity index 100% rename from src/pango/consts.rs rename to crates/pango/src/consts.rs diff --git a/src/pango.rs b/crates/pango/src/lib.rs similarity index 95% rename from src/pango.rs rename to crates/pango/src/lib.rs index cd78b351..c1eba34c 100644 --- a/src/pango.rs +++ b/crates/pango/src/lib.rs @@ -1,10 +1,8 @@ #![allow(non_camel_case_types)] use { - crate::{ - pango::consts::{CairoFormat, CairoOperator, PangoEllipsizeMode}, - rect::Rect, - }, + crate::consts::{CairoFormat, CairoOperator, PangoEllipsizeMode}, + jay_geometry::Rect, std::{cell::Cell, ptr, rc::Rc}, thiserror::Error, uapi::{ @@ -13,6 +11,26 @@ use { }, }; +macro_rules! cenum { + ($name:ident, $uc:ident; $($name2:ident = $val:expr,)*) => { + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub struct $name(pub i32); + + impl $name { + #[allow(dead_code)] + pub fn raw(self) -> i32 { + self.0 + } + } + + $( + pub const $name2: $name = $name($val); + )* + + pub const $uc: &[i32] = &[$($val,)*]; + }; +} + pub mod consts; include!(concat!(env!("OUT_DIR"), "/pango_tys.rs")); diff --git a/crates/pr-caps/Cargo.toml b/crates/pr-caps/Cargo.toml new file mode 100644 index 00000000..d58f93cd --- /dev/null +++ b/crates/pr-caps/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "jay-pr-caps" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +jay-utils = { path = "../utils" } + +opera = "1.0.1" +parking_lot = "0.12.1" +uapi = "0.2.13" diff --git a/src/pr_caps.rs b/crates/pr-caps/src/lib.rs similarity index 96% rename from src/pr_caps.rs rename to crates/pr-caps/src/lib.rs index 1c262557..256167b1 100644 --- a/src/pr_caps.rs +++ b/crates/pr-caps/src/lib.rs @@ -1,11 +1,9 @@ use { - crate::{ - pr_caps::sys::{ - _LINUX_CAPABILITY_U32S_3, _LINUX_CAPABILITY_VERSION_3, CAP_SYS_NICE, cap_user_data_t, - cap_user_header_t, - }, - utils::{bitflags::BitflagsExt, errorfmt::ErrorFmt, oserror::OsErrorExt}, + crate::sys::{ + _LINUX_CAPABILITY_U32S_3, _LINUX_CAPABILITY_VERSION_3, CAP_SYS_NICE, cap_user_data_t, + cap_user_header_t, }, + jay_utils::{bitflags::BitflagsExt, errorfmt::ErrorFmt, oserror::OsErrorExt}, opera::PhantomNotSend, parking_lot::{Condvar, Mutex}, std::{ diff --git a/crates/sighand/Cargo.toml b/crates/sighand/Cargo.toml new file mode 100644 index 00000000..16140807 --- /dev/null +++ b/crates/sighand/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "jay-sighand" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +jay-async-engine = { path = "../async-engine" } +jay-io-uring = { path = "../io-uring" } +jay-utils = { path = "../utils" } + +log = { version = "0.4.20", features = ["std"] } +thiserror = "2.0.11" +uapi = "0.2.13" diff --git a/src/sighand.rs b/crates/sighand/src/lib.rs similarity index 88% rename from src/sighand.rs rename to crates/sighand/src/lib.rs index 484a9145..f58d46df 100644 --- a/src/sighand.rs +++ b/crates/sighand/src/lib.rs @@ -1,12 +1,10 @@ use { - crate::{ - async_engine::{AsyncEngine, SpawnedFuture}, - io_uring::IoUring, - utils::{ - buf::TypedBuf, - errorfmt::ErrorFmt, - oserror::{OsError, OsErrorExt2}, - }, + jay_async_engine::{AsyncEngine, SpawnedFuture}, + jay_io_uring::IoUring, + jay_utils::{ + buf::TypedBuf, + errorfmt::ErrorFmt, + oserror::{OsError, OsErrorExt2}, }, std::rc::Rc, thiserror::Error, diff --git a/crates/theme/Cargo.toml b/crates/theme/Cargo.toml new file mode 100644 index 00000000..2ca9d23c --- /dev/null +++ b/crates/theme/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "jay-theme" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +jay-cmm = { path = "../cmm" } +jay-config = { path = "../jay-config" } +jay-gfx-types = { path = "../gfx-types" } +jay-utils = { path = "../utils" } + +linearize = { version = "0.1.3", features = ["derive"] } +num-traits = "0.2.17" diff --git a/src/theme.rs b/crates/theme/src/lib.rs similarity index 99% rename from src/theme.rs rename to crates/theme/src/lib.rs index 056d88f2..94fde7b4 100644 --- a/src/theme.rs +++ b/crates/theme/src/lib.rs @@ -1,12 +1,10 @@ #![expect(clippy::excessive_precision)] use { - crate::{ - cmm::cmm_eotf::{Eotf, bt1886_eotf_args, bt1886_inv_eotf_args}, - gfx_api::AlphaMode, - utils::{clonecell::CloneCell, static_text::StaticText}, - }, + jay_cmm::cmm_eotf::{Eotf, bt1886_eotf_args, bt1886_inv_eotf_args}, jay_config::theme::BarPosition as ConfigBarPosition, + jay_gfx_types::AlphaMode, + jay_utils::{clonecell::CloneCell, static_text::StaticText}, linearize::Linearize, num_traits::Float, std::{ diff --git a/crates/time/Cargo.toml b/crates/time/Cargo.toml new file mode 100644 index 00000000..d962a7b1 --- /dev/null +++ b/crates/time/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "jay-time" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +uapi = "0.2.13" diff --git a/src/time.rs b/crates/time/src/lib.rs similarity index 100% rename from src/time.rs rename to crates/time/src/lib.rs diff --git a/toml-config/Cargo.toml b/crates/toml-config/Cargo.toml similarity index 70% rename from toml-config/Cargo.toml rename to crates/toml-config/Cargo.toml index 8dc010f3..a6ed86f9 100644 --- a/toml-config/Cargo.toml +++ b/crates/toml-config/Cargo.toml @@ -1,16 +1,15 @@ [package] name = "jay-toml-config" -version = "0.12.0" -edition = "2024" -license = "GPL-3.0-only" +version.workspace = true +edition.workspace = true +license.workspace = true description = "Internal dependency of the Jay compositor" repository = "https://github.com/mahkoh/jay" -[lib] -crate-type = ["lib", "cdylib"] - [dependencies] -jay-config = { version = "1.10.0", path = "../jay-config" } +jay-config = { path = "../jay-config" } +jay-config-schema = { path = "../jay-config-schema" } +jay-toml = { path = "../toml-parser" } log = "0.4.14" thiserror = "2.0.11" error_reporter = "1.0.0" @@ -24,5 +23,3 @@ kbvm = "0.1.6" [dev-dependencies] simplelog = { version = "0.12.2", features = ["test"] } -serde_json = "1.0.114" -walkdir = "2.5.0" diff --git a/crates/toml-config/src/config.rs b/crates/toml-config/src/config.rs new file mode 100644 index 00000000..f081f3fe --- /dev/null +++ b/crates/toml-config/src/config.rs @@ -0,0 +1,121 @@ +mod context; +pub mod error; +mod extractor; +mod keycodes; +mod parser; +mod parsers; +mod spanned; + +use { + crate::{ + config::{ + context::Context, + parsers::{ + config::{ConfigParser, ConfigParserError}, + }, + }, + }, + ahash::AHashMap, + std::{ + cell::RefCell, + error::Error, + }, + thiserror::Error, + jay_toml::toml_parser, +}; + +pub use jay_config_schema::{ + Action, AnimationCurveConfig, Animations, ClientMatch, ClientRule, ColorManagement, Config, + ConfigConnector, ConfigDrmDevice, ConfigKeymap, ConnectorMatch, DrmDeviceMatch, Exec, Float, + FocusHistory, GenericMatch, Input, InputMatch, InputMode, Libei, MatchExactly, Mode, + NamedAction, Output, OutputMatch, RepeatRate, Scratchpad, Shortcut, SimpleCommand, SimpleIm, + Status, Tearing, Theme, UiDrag, Vrr, WindowMatch, WindowRule, Xwayland, +}; + +#[derive(Debug, Error)] +pub enum ConfigError { + #[error("Could not parse the toml document")] + Toml(#[from] toml_parser::ParserError), + #[error("Could not interpret the toml as a config document")] + Parser(#[from] ConfigParserError), +} + +pub fn parse_config( + input: &[u8], + mark_names: &RefCell>, + handle_error: F, +) -> Option +where + F: FnOnce(&dyn Error), +{ + let cx = Context { + input, + used: Default::default(), + mark_names, + }; + macro_rules! fatal { + ($e:expr) => {{ + let e = ConfigError::from($e.value); + let e = cx.error2($e.span, e); + handle_error(&e); + return None; + }}; + } + let toml = match toml_parser::parse(input, &cx) { + Ok(t) => t, + Err(e) => fatal!(e), + }; + let config = match toml.parse(&mut ConfigParser(&cx)) { + Ok(c) => c, + Err(e) => fatal!(e), + }; + let used = cx.used.take(); + macro_rules! check_defined { + ($name:expr, $used:ident, $defined:ident) => { + for spanned in &used.$used { + if !used.$defined.contains(spanned) { + log::warn!( + "{} {} used but not defined: {}", + $name, + spanned.value, + cx.error3(spanned.span), + ); + } + } + }; + } + check_defined!("Keymap", keymaps, defined_keymaps); + check_defined!("DRM device", drm_devices, defined_drm_devices); + check_defined!("Output", outputs, defined_outputs); + check_defined!("Input", inputs, defined_inputs); + Some(config) +} + +#[test] +fn default_config_parses() { + let input = include_bytes!("default-config.toml"); + parse_config(input, &Default::default(), |_| ()).unwrap(); +} + +#[test] +fn custom_animation_curve_parses() { + let input = b" + [animations] + curve = [0.25, 0.1, 0.25, 1.0] + "; + let config = parse_config(input, &Default::default(), |_| ()).unwrap(); + assert_eq!( + config.animations.curve, + Some(AnimationCurveConfig::CubicBezier([0.25, 0.1, 0.25, 1.0])) + ); +} + +#[test] +fn animation_style_parses() { + let input = b" + [animations] + style = \"plain\" + "; + let config = parse_config(input, &Default::default(), |_| ()).unwrap(); + assert_eq!(config.animations.style.as_deref(), Some("plain")); +} diff --git a/toml-config/src/config/context.rs b/crates/toml-config/src/config/context.rs similarity index 98% rename from toml-config/src/config/context.rs rename to crates/toml-config/src/config/context.rs index 5ffb015c..710fa481 100644 --- a/toml-config/src/config/context.rs +++ b/crates/toml-config/src/config/context.rs @@ -1,7 +1,7 @@ use { crate::{ config::error::SpannedError, - toml::{ + jay_toml::{ toml_parser::{ErrorHandler, ParserError}, toml_span::{Span, Spanned}, }, diff --git a/toml-config/src/config/error.rs b/crates/toml-config/src/config/error.rs similarity index 98% rename from toml-config/src/config/error.rs rename to crates/toml-config/src/config/error.rs index 4c615179..2374ab05 100644 --- a/toml-config/src/config/error.rs +++ b/crates/toml-config/src/config/error.rs @@ -1,5 +1,5 @@ use { - crate::toml::toml_span::Span, + jay_toml::toml_span::Span, bstr::ByteSlice, error_reporter::Report, std::{ diff --git a/toml-config/src/config/extractor.rs b/crates/toml-config/src/config/extractor.rs similarity index 99% rename from toml-config/src/config/extractor.rs rename to crates/toml-config/src/config/extractor.rs index 09e5d29c..57d91d74 100644 --- a/toml-config/src/config/extractor.rs +++ b/crates/toml-config/src/config/extractor.rs @@ -1,7 +1,7 @@ use { crate::{ config::context::Context, - toml::{ + jay_toml::{ toml_span::{Span, Spanned, SpannedExt}, toml_value::Value, }, diff --git a/toml-config/src/config/keycodes.rs b/crates/toml-config/src/config/keycodes.rs similarity index 100% rename from toml-config/src/config/keycodes.rs rename to crates/toml-config/src/config/keycodes.rs diff --git a/crates/toml-config/src/config/parser.rs b/crates/toml-config/src/config/parser.rs new file mode 100644 index 00000000..19733c41 --- /dev/null +++ b/crates/toml-config/src/config/parser.rs @@ -0,0 +1,3 @@ +pub use jay_toml::value_parser::{ + DataType, ParseResult, Parser, UnexpectedDataType, +}; diff --git a/toml-config/src/config/parsers.rs b/crates/toml-config/src/config/parsers.rs similarity index 95% rename from toml-config/src/config/parsers.rs rename to crates/toml-config/src/config/parsers.rs index 4c5e337b..4b9cdebd 100644 --- a/toml-config/src/config/parsers.rs +++ b/crates/toml-config/src/config/parsers.rs @@ -1,14 +1,14 @@ use { crate::{ config::parser::{DataType, ParseResult, Parser, UnexpectedDataType}, - toml::toml_span::Span, + jay_toml::toml_span::Span, }, thiserror::Error, }; pub mod action; mod actions; -mod capabilities; +mod animations; mod clean_logs_older_than; mod client_match; mod client_rule; @@ -40,6 +40,7 @@ pub mod modified_keysym; mod output; mod output_match; mod repeat_rate; +mod scratchpad; pub mod shortcuts; mod simple_im; mod status; diff --git a/toml-config/src/config/parsers/action.rs b/crates/toml-config/src/config/parsers/action.rs similarity index 94% rename from toml-config/src/config/parsers/action.rs rename to crates/toml-config/src/config/parsers/action.rs index 7581198d..d1cf153a 100644 --- a/toml-config/src/config/parsers/action.rs +++ b/crates/toml-config/src/config/parsers/action.rs @@ -26,7 +26,7 @@ use { }, spanned::SpannedErrorExt, }, - toml::{ + jay_toml::{ toml_span::{DespanExt, Span, Spanned, SpannedExt}, toml_value::Value, }, @@ -117,6 +117,9 @@ impl ActionParser<'_> { "toggle-fullscreen" => ToggleFullscreen, "enter-fullscreen" => SetFullscreen(true), "exit-fullscreen" => SetFullscreen(false), + "send-to-scratchpad" => SendToScratchpad, + "toggle-scratchpad" => ToggleScratchpad, + "cycle-scratchpad" => CycleScratchpad, "focus-parent" => FocusParent, "close" => Close, "disable-pointer-constraint" => DisablePointerConstraint, @@ -125,7 +128,6 @@ impl ActionParser<'_> { "tile" => SetFloating(false), "quit" => Quit, "reload-config-toml" => ReloadConfigToml, - "reload-config-so" => ReloadConfigSo, "none" => None, "forward" => Forward(true), "consume" => Forward(false), @@ -222,6 +224,33 @@ impl ActionParser<'_> { Ok(Action::MoveToWorkspace { name }) } + fn parse_send_to_scratchpad(&mut self, ext: &mut Extractor<'_>) -> ParseResult { + let name = ext + .extract(opt(str("name")))? + .map(|name| name.value) + .unwrap_or("") + .to_string(); + Ok(Action::SendToScratchpad { name }) + } + + fn parse_toggle_scratchpad(&mut self, ext: &mut Extractor<'_>) -> ParseResult { + let name = ext + .extract(opt(str("name")))? + .map(|name| name.value) + .unwrap_or("") + .to_string(); + Ok(Action::ToggleScratchpad { name }) + } + + fn parse_cycle_scratchpad(&mut self, ext: &mut Extractor<'_>) -> ParseResult { + let name = ext + .extract(opt(str("name")))? + .map(|name| name.value) + .unwrap_or("") + .to_string(); + Ok(Action::CycleScratchpad { name }) + } + fn parse_configure_connector(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let con = ext .extract(val("connector"))? @@ -551,6 +580,9 @@ impl Parser for ActionParser<'_> { "switch-to-vt" => self.parse_switch_to_vt(&mut ext), "show-workspace" => self.parse_show_workspace(&mut ext), "move-to-workspace" => self.parse_move_to_workspace(&mut ext), + "send-to-scratchpad" => self.parse_send_to_scratchpad(&mut ext), + "toggle-scratchpad" => self.parse_toggle_scratchpad(&mut ext), + "cycle-scratchpad" => self.parse_cycle_scratchpad(&mut ext), "configure-connector" => self.parse_configure_connector(&mut ext), "configure-input" => self.parse_configure_input(&mut ext), "configure-output" => self.parse_configure_output(&mut ext), diff --git a/toml-config/src/config/parsers/actions.rs b/crates/toml-config/src/config/parsers/actions.rs similarity index 99% rename from toml-config/src/config/parsers/actions.rs rename to crates/toml-config/src/config/parsers/actions.rs index 0743b5e7..3958dfa4 100644 --- a/toml-config/src/config/parsers/actions.rs +++ b/crates/toml-config/src/config/parsers/actions.rs @@ -7,7 +7,7 @@ use { parser::{DataType, ParseResult, Parser, UnexpectedDataType}, parsers::action::ActionParser, }, - toml::{ + jay_toml::{ toml_span::{Span, Spanned, SpannedExt}, toml_value::Value, }, diff --git a/crates/toml-config/src/config/parsers/animations.rs b/crates/toml-config/src/config/parsers/animations.rs new file mode 100644 index 00000000..9af8a497 --- /dev/null +++ b/crates/toml-config/src/config/parsers/animations.rs @@ -0,0 +1,99 @@ +use { + crate::{ + config::{ + AnimationCurveConfig, Animations, + context::Context, + extractor::{Extractor, ExtractorError, bol, n32, opt, recover, str, val}, + parser::{DataType, ParseResult, Parser, UnexpectedDataType}, + }, + jay_toml::{ + toml_span::{DespanExt, Span, Spanned, SpannedExt}, + toml_value::Value, + }, + }, + indexmap::IndexMap, + thiserror::Error, +}; + +#[derive(Debug, Error)] +pub enum AnimationsParserError { + #[error(transparent)] + Expected(#[from] UnexpectedDataType), + #[error(transparent)] + Extract(#[from] ExtractorError), + #[error("Expected animation curve to be a string or an array")] + CurveType, + #[error("Cubic-bezier animation curves must contain exactly four values")] + CubicBezierLen, + #[error("Cubic-bezier animation curve entries must be finite floats or integers")] + CubicBezierValue, + #[error("Cubic-bezier x control points must be between 0 and 1")] + CubicBezierXRange, +} + +pub struct AnimationsParser<'a>(pub &'a Context<'a>); + +impl Parser for AnimationsParser<'_> { + type Value = Animations; + type Error = AnimationsParserError; + const EXPECTED: &'static [DataType] = &[DataType::Table]; + + fn parse_table( + &mut self, + span: Span, + table: &IndexMap, Spanned>, + ) -> ParseResult { + let mut ext = Extractor::new(self.0, span, table); + let (enabled, duration_ms, style, curve) = ext.extract(( + recover(opt(bol("enabled"))), + recover(opt(n32("duration-ms"))), + recover(opt(str("style"))), + opt(val("curve")), + ))?; + let curve = match curve { + Some(curve) => Some(parse_curve(curve)?), + None => None, + }; + Ok(Animations { + enabled: enabled.despan(), + duration_ms: duration_ms.despan(), + style: style.despan().map(|style| style.to_string()), + curve, + }) + } +} + +fn parse_curve( + curve: Spanned<&Value>, +) -> Result> { + match curve.value { + Value::String(s) => Ok(AnimationCurveConfig::Preset(s.clone())), + Value::Array(values) => parse_cubic_bezier(curve.span, values), + _ => Err(AnimationsParserError::CurveType.spanned(curve.span)), + } +} + +fn parse_cubic_bezier( + span: Span, + values: &[Spanned], +) -> Result> { + if values.len() != 4 { + return Err(AnimationsParserError::CubicBezierLen.spanned(span)); + } + let mut points = [0.0; 4]; + for (idx, value) in values.iter().enumerate() { + let f = match value.value { + Value::Float(f) => f, + Value::Integer(i) => i as f64, + _ => return Err(AnimationsParserError::CubicBezierValue.spanned(value.span)), + }; + if !f.is_finite() { + return Err(AnimationsParserError::CubicBezierValue.spanned(value.span)); + } + points[idx] = f as f32; + } + if !(0.0..=1.0).contains(&points[0]) || !(0.0..=1.0).contains(&points[2]) { + return Err(AnimationsParserError::CubicBezierXRange.spanned(span)); + } + Ok(AnimationCurveConfig::CubicBezier(points)) +} diff --git a/toml-config/src/config/parsers/clean_logs_older_than.rs b/crates/toml-config/src/config/parsers/clean_logs_older_than.rs similarity index 98% rename from toml-config/src/config/parsers/clean_logs_older_than.rs rename to crates/toml-config/src/config/parsers/clean_logs_older_than.rs index 0c3bb4a7..c81324f3 100644 --- a/toml-config/src/config/parsers/clean_logs_older_than.rs +++ b/crates/toml-config/src/config/parsers/clean_logs_older_than.rs @@ -5,7 +5,7 @@ use { extractor::{Extractor, ExtractorError, fltorint, opt}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, }, - toml::{ + jay_toml::{ toml_span::{DespanExt, Span, Spanned, SpannedExt}, toml_value::Value, }, diff --git a/toml-config/src/config/parsers/client_match.rs b/crates/toml-config/src/config/parsers/client_match.rs similarity index 97% rename from toml-config/src/config/parsers/client_match.rs rename to crates/toml-config/src/config/parsers/client_match.rs index 24ecbc08..ab61f653 100644 --- a/toml-config/src/config/parsers/client_match.rs +++ b/crates/toml-config/src/config/parsers/client_match.rs @@ -6,7 +6,7 @@ use { extractor::{Extractor, ExtractorError, arr, bol, n32, opt, s32, str, val}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, }, - toml::{ + jay_toml::{ toml_span::{DespanExt, Span, Spanned}, toml_value::Value, }, @@ -58,10 +58,8 @@ impl Parser for ClientMatchParser<'_> { comm_regex, exe, exe_regex, - tag, - tag_regex, ), - (is_xwayland,), + (is_xwayland, tag, tag_regex), ) = ext.extract(( ( opt(str("name")), @@ -84,10 +82,12 @@ impl Parser for ClientMatchParser<'_> { opt(str("comm-regex")), opt(str("exe")), opt(str("exe-regex")), + ), + ( + opt(bol("is-xwayland")), opt(str("tag")), opt(str("tag-regex")), ), - (opt(bol("is-xwayland")),), ))?; let mut not = None; if let Some(value) = not_val { diff --git a/toml-config/src/config/parsers/client_rule.rs b/crates/toml-config/src/config/parsers/client_rule.rs similarity index 68% rename from toml-config/src/config/parsers/client_rule.rs rename to crates/toml-config/src/config/parsers/client_rule.rs index 978c63c3..239b1a75 100644 --- a/toml-config/src/config/parsers/client_rule.rs +++ b/crates/toml-config/src/config/parsers/client_rule.rs @@ -7,12 +7,11 @@ use { parser::{DataType, ParseResult, Parser, UnexpectedDataType}, parsers::{ action::{ActionParser, ActionParserError}, - capabilities::CapabilitiesParser, client_match::{ClientMatchParser, ClientMatchParserError}, }, spanned::SpannedErrorExt, }, - toml::{ + jay_toml::{ toml_span::{DespanExt, Span, Spanned}, toml_value::Value, }, @@ -48,15 +47,12 @@ impl Parser for ClientRuleParser<'_> { table: &IndexMap, Spanned>, ) -> ParseResult { let mut ext = Extractor::new(self.0, span, table); - let (name, match_val, action_val, latch_val, capabilities_val, bounding_capabilities_val) = - ext.extract(( - opt(str("name")), - opt(val("match")), - opt(val("action")), - opt(val("latch")), - opt(val("capabilities")), - opt(val("sandbox-bounding-capabilities")), - ))?; + let (name, match_val, action_val, latch_val) = ext.extract(( + opt(str("name")), + opt(val("match")), + opt(val("action")), + opt(val("latch")), + ))?; let mut action = None; if let Some(value) = action_val { action = Some( @@ -77,34 +73,11 @@ impl Parser for ClientRuleParser<'_> { None => ClientMatch::default(), Some(m) => m.parse_map(&mut ClientMatchParser(self.0))?, }; - let mut capabilities = None; - if let Some(value) = capabilities_val { - match value.parse(&mut CapabilitiesParser) { - Ok(v) => capabilities = Some(v), - Err(e) => { - log::warn!("Could not parse the capabilities: {}", self.0.error(e)); - } - } - } - let mut bounding_capabilities = None; - if let Some(value) = bounding_capabilities_val { - match value.parse(&mut CapabilitiesParser) { - Ok(v) => bounding_capabilities = Some(v), - Err(e) => { - log::warn!( - "Could not parse the bounding capabilities: {}", - self.0.error(e) - ); - } - } - } Ok(ClientRule { name: name.despan_into(), match_, action, latch, - capabilities, - bounding_capabilities, }) } } diff --git a/toml-config/src/config/parsers/color.rs b/crates/toml-config/src/config/parsers/color.rs similarity index 97% rename from toml-config/src/config/parsers/color.rs rename to crates/toml-config/src/config/parsers/color.rs index 25073b20..96edb5ab 100644 --- a/toml-config/src/config/parsers/color.rs +++ b/crates/toml-config/src/config/parsers/color.rs @@ -4,7 +4,7 @@ use { extractor::ExtractorError, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, }, - toml::toml_span::{Span, SpannedExt}, + jay_toml::toml_span::{Span, SpannedExt}, }, jay_config::theme::Color, std::{num::ParseIntError, ops::Range}, diff --git a/toml-config/src/config/parsers/color_management.rs b/crates/toml-config/src/config/parsers/color_management.rs similarity index 91% rename from toml-config/src/config/parsers/color_management.rs rename to crates/toml-config/src/config/parsers/color_management.rs index 53a64bb9..1857a376 100644 --- a/toml-config/src/config/parsers/color_management.rs +++ b/crates/toml-config/src/config/parsers/color_management.rs @@ -1,11 +1,12 @@ use { crate::{ config::{ + ColorManagement, context::Context, extractor::{Extractor, ExtractorError, bol, opt}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, }, - toml::{ + jay_toml::{ toml_span::{DespanExt, Span, Spanned}, toml_value::Value, }, @@ -24,11 +25,6 @@ pub enum ColorManagementParserError { pub struct ColorManagementParser<'a>(pub &'a Context<'a>); -#[derive(Clone, Debug)] -pub struct ColorManagement { - pub enabled: Option, -} - impl Parser for ColorManagementParser<'_> { type Value = ColorManagement; type Error = ColorManagementParserError; diff --git a/toml-config/src/config/parsers/config.rs b/crates/toml-config/src/config/parsers/config.rs similarity index 93% rename from toml-config/src/config/parsers/config.rs rename to crates/toml-config/src/config/parsers/config.rs index b9d34e74..ed3b1e70 100644 --- a/toml-config/src/config/parsers/config.rs +++ b/crates/toml-config/src/config/parsers/config.rs @@ -1,13 +1,14 @@ use { crate::{ config::{ - Action, Config, Libei, Theme, UiDrag, + Action, Animations, Config, Libei, Theme, UiDrag, context::Context, extractor::{Extractor, ExtractorError, arr, bol, int, opt, recover, str, val}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, parsers::{ action::ActionParser, actions::ActionsParser, + animations::AnimationsParser, clean_logs_older_than::CleanLogsOlderThanParser, client_rule::ClientRulesParser, color_management::ColorManagementParser, @@ -27,6 +28,7 @@ use { log_level::LogLevelParser, output::OutputsParser, repeat_rate::RepeatRateParser, + scratchpad::ScratchpadsParser, shortcuts::{ ComplexShortcutsParser, ShortcutsParser, ShortcutsParserError, parse_modified_keysym_str, @@ -43,7 +45,7 @@ use { }, spanned::SpannedErrorExt, }, - toml::{ + jay_toml::{ toml_span::{DespanExt, Span, Spanned}, toml_value::Value, }, @@ -153,7 +155,9 @@ impl Parser for ConfigParser<'_> { fallback_output_mode_val, clean_logs_older_than_val, mouse_follows_focus, + animations_val, ), + (scratchpads_val, autotile), ) = ext.extract(( ( opt(val("keymap")), @@ -213,7 +217,9 @@ impl Parser for ConfigParser<'_> { opt(val("fallback-output-mode")), opt(val("clean-logs-older-than")), recover(opt(bol("unstable-mouse-follows-focus"))), + opt(val("animations")), ), + (opt(val("scratchpads")), recover(opt(bol("autotile")))), ))?; let mut keymap = None; if let Some(value) = keymap_val { @@ -367,11 +373,15 @@ impl Parser for ConfigParser<'_> { } let mut idle = None; let mut grace_period = None; + let mut key_press_enables_dpms = None; + let mut mouse_move_enables_dpms = None; if let Some(value) = idle_val { match value.parse(&mut IdleParser(self.0)) { Ok(v) => { idle = v.timeout; grace_period = v.grace_period; + key_press_enables_dpms = v.key_press_enables_dpms; + mouse_move_enables_dpms = v.mouse_move_enables_dpms; } Err(e) => { log::warn!("Could not parse the idle timeout: {}", self.0.error(e)); @@ -429,6 +439,15 @@ impl Parser for ConfigParser<'_> { } } } + let mut animations = Animations::default(); + if let Some(value) = animations_val { + match value.parse(&mut AnimationsParser(self.0)) { + Ok(v) => animations = v, + Err(e) => { + log::warn!("Could not parse animations setting: {}", self.0.error(e)); + } + } + } let mut xwayland = None; if let Some(value) = xwayland_val { match value.parse(&mut XwaylandParser(self.0)) { @@ -556,6 +575,13 @@ impl Parser for ConfigParser<'_> { } } } + let mut scratchpads = vec![]; + if let Some(value) = scratchpads_val { + match value.parse(&mut ScratchpadsParser(self.0)) { + Ok(v) => scratchpads = v, + Err(e) => log::warn!("Could not parse the scratchpads: {}", self.0.error(e)), + } + } Ok(Config { keymap, repeat_rate, @@ -581,12 +607,15 @@ impl Parser for ConfigParser<'_> { inputs, idle, grace_period, + key_press_enables_dpms, + mouse_move_enables_dpms, focus_follows_mouse: focus_follows_mouse.despan().unwrap_or(true), window_management_key, vrr, tearing, libei, ui_drag, + animations, xwayland, color_management, float, @@ -605,6 +634,8 @@ impl Parser for ConfigParser<'_> { simple_im, fallback_output_mode, mouse_follows_focus: mouse_follows_focus.despan(), + scratchpads, + autotile: autotile.despan(), }) } } diff --git a/toml-config/src/config/parsers/connector.rs b/crates/toml-config/src/config/parsers/connector.rs similarity index 99% rename from toml-config/src/config/parsers/connector.rs rename to crates/toml-config/src/config/parsers/connector.rs index c1618221..5545a4b7 100644 --- a/toml-config/src/config/parsers/connector.rs +++ b/crates/toml-config/src/config/parsers/connector.rs @@ -7,7 +7,7 @@ use { parser::{DataType, ParseResult, Parser, UnexpectedDataType}, parsers::connector_match::{ConnectorMatchParser, ConnectorMatchParserError}, }, - toml::{ + jay_toml::{ toml_span::{DespanExt, Span, Spanned}, toml_value::Value, }, diff --git a/toml-config/src/config/parsers/connector_match.rs b/crates/toml-config/src/config/parsers/connector_match.rs similarity index 98% rename from toml-config/src/config/parsers/connector_match.rs rename to crates/toml-config/src/config/parsers/connector_match.rs index b9eb4e9b..ac8d8c83 100644 --- a/toml-config/src/config/parsers/connector_match.rs +++ b/crates/toml-config/src/config/parsers/connector_match.rs @@ -6,7 +6,7 @@ use { extractor::{Extractor, ExtractorError, opt, str}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, }, - toml::{ + jay_toml::{ toml_span::{Span, Spanned}, toml_value::Value, }, diff --git a/toml-config/src/config/parsers/content_type.rs b/crates/toml-config/src/config/parsers/content_type.rs similarity index 98% rename from toml-config/src/config/parsers/content_type.rs rename to crates/toml-config/src/config/parsers/content_type.rs index c7038bbf..c0af74b1 100644 --- a/toml-config/src/config/parsers/content_type.rs +++ b/crates/toml-config/src/config/parsers/content_type.rs @@ -1,7 +1,7 @@ use { crate::{ config::parser::{DataType, ParseResult, Parser, UnexpectedDataType}, - toml::{ + jay_toml::{ toml_span::{Span, Spanned, SpannedExt}, toml_value::Value, }, diff --git a/toml-config/src/config/parsers/drm_device.rs b/crates/toml-config/src/config/parsers/drm_device.rs similarity index 99% rename from toml-config/src/config/parsers/drm_device.rs rename to crates/toml-config/src/config/parsers/drm_device.rs index d2030adf..6de886b4 100644 --- a/toml-config/src/config/parsers/drm_device.rs +++ b/crates/toml-config/src/config/parsers/drm_device.rs @@ -10,7 +10,7 @@ use { gfx_api::GfxApiParser, }, }, - toml::{ + jay_toml::{ toml_span::{DespanExt, Span, Spanned}, toml_value::Value, }, diff --git a/toml-config/src/config/parsers/drm_device_match.rs b/crates/toml-config/src/config/parsers/drm_device_match.rs similarity index 99% rename from toml-config/src/config/parsers/drm_device_match.rs rename to crates/toml-config/src/config/parsers/drm_device_match.rs index 3b0aeac7..7d50b67f 100644 --- a/toml-config/src/config/parsers/drm_device_match.rs +++ b/crates/toml-config/src/config/parsers/drm_device_match.rs @@ -6,7 +6,7 @@ use { extractor::{Extractor, ExtractorError, n32, opt, recover, str}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, }, - toml::{ + jay_toml::{ toml_span::{DespanExt, Span, Spanned}, toml_value::Value, }, diff --git a/toml-config/src/config/parsers/env.rs b/crates/toml-config/src/config/parsers/env.rs similarity index 98% rename from toml-config/src/config/parsers/env.rs rename to crates/toml-config/src/config/parsers/env.rs index a2a0f9ae..4c80e917 100644 --- a/toml-config/src/config/parsers/env.rs +++ b/crates/toml-config/src/config/parsers/env.rs @@ -4,7 +4,7 @@ use { parser::{DataType, ParseResult, Parser, UnexpectedDataType}, parsers::{StringParser, StringParserError}, }, - toml::{ + jay_toml::{ toml_span::{Span, Spanned}, toml_value::Value, }, diff --git a/toml-config/src/config/parsers/exec.rs b/crates/toml-config/src/config/parsers/exec.rs similarity index 84% rename from toml-config/src/config/parsers/exec.rs rename to crates/toml-config/src/config/parsers/exec.rs index 2faf2397..6924bb4a 100644 --- a/toml-config/src/config/parsers/exec.rs +++ b/crates/toml-config/src/config/parsers/exec.rs @@ -3,14 +3,14 @@ use { config::{ Exec, context::Context, - extractor::{Extractor, ExtractorError, arr, bol, opt, recover, str, val}, + extractor::{Extractor, ExtractorError, arr, opt, str, val}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, parsers::{ StringParser, StringParserError, env::{EnvParser, EnvParserError}, }, }, - toml::{ + jay_toml::{ toml_span::{DespanExt, Span, Spanned, SpannedExt}, toml_value::Value, }, @@ -52,7 +52,6 @@ impl Parser for ExecParser<'_> { prog: string.to_string(), args: vec![], envs: vec![], - privileged: false, tag: None, }) } @@ -70,7 +69,6 @@ impl Parser for ExecParser<'_> { prog, args, envs: vec![], - privileged: false, tag: None, }) } @@ -81,12 +79,11 @@ impl Parser for ExecParser<'_> { table: &IndexMap, Spanned>, ) -> ParseResult { let mut ext = Extractor::new(self.0, span, table); - let (prog_opt, shell_opt, args_val, envs_val, privileged, tag) = ext.extract(( + let (prog_opt, shell_opt, args_val, envs_val, tag) = ext.extract(( opt(str("prog")), opt(str("shell")), opt(arr("args")), opt(val("env")), - recover(opt(bol("privileged"))), opt(str("tag")), ))?; let prog; @@ -115,20 +112,10 @@ impl Parser for ExecParser<'_> { None => vec![], Some(e) => e.parse_map(&mut EnvParser)?, }; - if let Some(privileged) = privileged - && privileged.value - && tag.is_some() - { - log::warn!( - "Exec is privileged and tagged but tagged execs are always unprivileged: {}", - self.0.error3(privileged.span), - ); - } Ok(Exec { prog, args, envs, - privileged: privileged.despan().unwrap_or(false), tag: tag.despan_into(), }) } diff --git a/toml-config/src/config/parsers/fallback_output_mode.rs b/crates/toml-config/src/config/parsers/fallback_output_mode.rs similarity index 95% rename from toml-config/src/config/parsers/fallback_output_mode.rs rename to crates/toml-config/src/config/parsers/fallback_output_mode.rs index 3918a5db..91381b38 100644 --- a/toml-config/src/config/parsers/fallback_output_mode.rs +++ b/crates/toml-config/src/config/parsers/fallback_output_mode.rs @@ -1,7 +1,7 @@ use { crate::{ config::parser::{DataType, ParseResult, Parser, UnexpectedDataType}, - toml::toml_span::{Span, SpannedExt}, + jay_toml::toml_span::{Span, SpannedExt}, }, jay_config::input::FallbackOutputMode, thiserror::Error, diff --git a/toml-config/src/config/parsers/float.rs b/crates/toml-config/src/config/parsers/float.rs similarity index 92% rename from toml-config/src/config/parsers/float.rs rename to crates/toml-config/src/config/parsers/float.rs index fa3ccbed..f09c1189 100644 --- a/toml-config/src/config/parsers/float.rs +++ b/crates/toml-config/src/config/parsers/float.rs @@ -1,11 +1,12 @@ use { crate::{ config::{ + Float, context::Context, extractor::{Extractor, ExtractorError, bol, opt, recover}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, }, - toml::{ + jay_toml::{ toml_span::{DespanExt, Span, Spanned}, toml_value::Value, }, @@ -24,11 +25,6 @@ pub enum FloatParserError { pub struct FloatParser<'a>(pub &'a Context<'a>); -#[derive(Debug, Clone)] -pub struct Float { - pub show_pin_icon: Option, -} - impl Parser for FloatParser<'_> { type Value = Float; type Error = FloatParserError; diff --git a/toml-config/src/config/parsers/focus_history.rs b/crates/toml-config/src/config/parsers/focus_history.rs similarity index 90% rename from toml-config/src/config/parsers/focus_history.rs rename to crates/toml-config/src/config/parsers/focus_history.rs index 8aef1ebd..15340512 100644 --- a/toml-config/src/config/parsers/focus_history.rs +++ b/crates/toml-config/src/config/parsers/focus_history.rs @@ -1,11 +1,12 @@ use { crate::{ config::{ + FocusHistory, context::Context, extractor::{Extractor, ExtractorError, bol, opt, recover}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, }, - toml::{ + jay_toml::{ toml_span::{DespanExt, Span, Spanned}, toml_value::Value, }, @@ -24,12 +25,6 @@ pub enum FocusHistoryParserError { pub struct FocusHistoryParser<'a>(pub &'a Context<'a>); -#[derive(Debug, Clone)] -pub struct FocusHistory { - pub only_visible: Option, - pub same_workspace: Option, -} - impl Parser for FocusHistoryParser<'_> { type Value = FocusHistory; type Error = FocusHistoryParserError; diff --git a/toml-config/src/config/parsers/format.rs b/crates/toml-config/src/config/parsers/format.rs similarity index 97% rename from toml-config/src/config/parsers/format.rs rename to crates/toml-config/src/config/parsers/format.rs index 7755b555..d9aaf31d 100644 --- a/toml-config/src/config/parsers/format.rs +++ b/crates/toml-config/src/config/parsers/format.rs @@ -1,7 +1,7 @@ use { crate::{ config::parser::{DataType, ParseResult, Parser, UnexpectedDataType}, - toml::toml_span::{Span, SpannedExt}, + jay_toml::toml_span::{Span, SpannedExt}, }, jay_config::video::Format, thiserror::Error, diff --git a/toml-config/src/config/parsers/gfx_api.rs b/crates/toml-config/src/config/parsers/gfx_api.rs similarity index 94% rename from toml-config/src/config/parsers/gfx_api.rs rename to crates/toml-config/src/config/parsers/gfx_api.rs index 69bb6740..b7e75122 100644 --- a/toml-config/src/config/parsers/gfx_api.rs +++ b/crates/toml-config/src/config/parsers/gfx_api.rs @@ -1,7 +1,7 @@ use { crate::{ config::parser::{DataType, ParseResult, Parser, UnexpectedDataType}, - toml::toml_span::{Span, SpannedExt}, + jay_toml::toml_span::{Span, SpannedExt}, }, jay_config::video::GfxApi, thiserror::Error, diff --git a/toml-config/src/config/parsers/idle.rs b/crates/toml-config/src/config/parsers/idle.rs similarity index 78% rename from toml-config/src/config/parsers/idle.rs rename to crates/toml-config/src/config/parsers/idle.rs index 57d03b36..16bb128a 100644 --- a/toml-config/src/config/parsers/idle.rs +++ b/crates/toml-config/src/config/parsers/idle.rs @@ -2,10 +2,10 @@ use { crate::{ config::{ context::Context, - extractor::{Extractor, ExtractorError, n64, opt, val}, + extractor::{Extractor, ExtractorError, bol, n64, opt, recover, val}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, }, - toml::{ + jay_toml::{ toml_span::{DespanExt, Span, Spanned}, toml_value::Value, }, @@ -28,6 +28,8 @@ pub struct IdleParser<'a>(pub &'a Context<'a>); pub struct Idle { pub timeout: Option, pub grace_period: Option, + pub key_press_enables_dpms: Option, + pub mouse_move_enables_dpms: Option, } impl Parser for IdleParser<'_> { @@ -41,10 +43,18 @@ impl Parser for IdleParser<'_> { table: &IndexMap, Spanned>, ) -> ParseResult { let mut ext = Extractor::new(self.0, span, table); - let (minutes, seconds, grace_period_val) = ext.extract(( + let ( + minutes, + seconds, + grace_period_val, + key_press_enables_dpms, + mouse_move_enables_dpms, + ) = ext.extract(( opt(n64("minutes")), opt(n64("seconds")), opt(val("grace-period")), + recover(opt(bol("key-press-enables-dpms"))), + recover(opt(bol("mouse-move-enables-dpms"))), ))?; let mut timeout = None; if minutes.is_some() || seconds.is_some() { @@ -57,6 +67,8 @@ impl Parser for IdleParser<'_> { Ok(Idle { timeout, grace_period, + key_press_enables_dpms: key_press_enables_dpms.despan(), + mouse_move_enables_dpms: mouse_move_enables_dpms.despan(), }) } } diff --git a/toml-config/src/config/parsers/input.rs b/crates/toml-config/src/config/parsers/input.rs similarity index 99% rename from toml-config/src/config/parsers/input.rs rename to crates/toml-config/src/config/parsers/input.rs index 2b4115a3..e810b638 100644 --- a/toml-config/src/config/parsers/input.rs +++ b/crates/toml-config/src/config/parsers/input.rs @@ -12,7 +12,7 @@ use { output_match::OutputMatchParser, }, }, - toml::{ + jay_toml::{ toml_span::{DespanExt, Span, Spanned, SpannedExt}, toml_value::Value, }, diff --git a/toml-config/src/config/parsers/input_match.rs b/crates/toml-config/src/config/parsers/input_match.rs similarity index 99% rename from toml-config/src/config/parsers/input_match.rs rename to crates/toml-config/src/config/parsers/input_match.rs index 0d534cea..9a0998df 100644 --- a/toml-config/src/config/parsers/input_match.rs +++ b/crates/toml-config/src/config/parsers/input_match.rs @@ -6,7 +6,7 @@ use { extractor::{Extractor, ExtractorError, bol, opt, str}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, }, - toml::{ + jay_toml::{ toml_span::{DespanExt, Span, Spanned}, toml_value::Value, }, diff --git a/toml-config/src/config/parsers/input_mode.rs b/crates/toml-config/src/config/parsers/input_mode.rs similarity index 96% rename from toml-config/src/config/parsers/input_mode.rs rename to crates/toml-config/src/config/parsers/input_mode.rs index 4a8d4b6c..fa053912 100644 --- a/toml-config/src/config/parsers/input_mode.rs +++ b/crates/toml-config/src/config/parsers/input_mode.rs @@ -1,14 +1,14 @@ use { crate::{ config::{ - Shortcut, + InputMode, context::Context, extractor::{Extractor, ExtractorError, opt, recover, str, val}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, parsers::shortcuts::{ComplexShortcutsParser, ShortcutsParser, ShortcutsParserError}, spanned::SpannedErrorExt, }, - toml::{ + jay_toml::{ toml_span::{DespanExt, Span, Spanned}, toml_value::Value, }, @@ -29,12 +29,6 @@ pub enum InputModeParserError { ParseShortcuts(#[source] ShortcutsParserError), } -#[derive(Clone, Debug)] -pub struct InputMode { - pub parent: Option, - pub shortcuts: Vec, -} - pub struct InputModesParser<'a>(pub &'a Context<'a>); impl Parser for InputModesParser<'_> { diff --git a/toml-config/src/config/parsers/keymap.rs b/crates/toml-config/src/config/parsers/keymap.rs similarity index 99% rename from toml-config/src/config/parsers/keymap.rs rename to crates/toml-config/src/config/parsers/keymap.rs index ea4afc66..16276b92 100644 --- a/toml-config/src/config/parsers/keymap.rs +++ b/crates/toml-config/src/config/parsers/keymap.rs @@ -6,7 +6,7 @@ use { extractor::{Extractor, ExtractorError, opt, str, val}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, }, - toml::{ + jay_toml::{ toml_span::{DespanExt, Span, Spanned, SpannedExt}, toml_value::Value, }, diff --git a/toml-config/src/config/parsers/libei.rs b/crates/toml-config/src/config/parsers/libei.rs similarity index 98% rename from toml-config/src/config/parsers/libei.rs rename to crates/toml-config/src/config/parsers/libei.rs index 47fd825f..9be10fc0 100644 --- a/toml-config/src/config/parsers/libei.rs +++ b/crates/toml-config/src/config/parsers/libei.rs @@ -6,7 +6,7 @@ use { extractor::{Extractor, ExtractorError, bol, opt, recover}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, }, - toml::{ + jay_toml::{ toml_span::{DespanExt, Span, Spanned}, toml_value::Value, }, diff --git a/toml-config/src/config/parsers/log_level.rs b/crates/toml-config/src/config/parsers/log_level.rs similarity index 95% rename from toml-config/src/config/parsers/log_level.rs rename to crates/toml-config/src/config/parsers/log_level.rs index 73209d1b..5c23030e 100644 --- a/toml-config/src/config/parsers/log_level.rs +++ b/crates/toml-config/src/config/parsers/log_level.rs @@ -1,7 +1,7 @@ use { crate::{ config::parser::{DataType, ParseResult, Parser, UnexpectedDataType}, - toml::toml_span::{Span, SpannedExt}, + jay_toml::toml_span::{Span, SpannedExt}, }, jay_config::logging::LogLevel, thiserror::Error, diff --git a/toml-config/src/config/parsers/mark_id.rs b/crates/toml-config/src/config/parsers/mark_id.rs similarity index 98% rename from toml-config/src/config/parsers/mark_id.rs rename to crates/toml-config/src/config/parsers/mark_id.rs index e3dc0688..cb6ece68 100644 --- a/toml-config/src/config/parsers/mark_id.rs +++ b/crates/toml-config/src/config/parsers/mark_id.rs @@ -6,7 +6,7 @@ use { keycodes::KEYCODES, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, }, - toml::{ + jay_toml::{ toml_span::{Span, Spanned, SpannedExt}, toml_value::Value, }, diff --git a/toml-config/src/config/parsers/mode.rs b/crates/toml-config/src/config/parsers/mode.rs similarity index 98% rename from toml-config/src/config/parsers/mode.rs rename to crates/toml-config/src/config/parsers/mode.rs index 5a64d9ae..31c108e2 100644 --- a/toml-config/src/config/parsers/mode.rs +++ b/crates/toml-config/src/config/parsers/mode.rs @@ -6,7 +6,7 @@ use { extractor::{Extractor, ExtractorError, fltorint, opt, s32}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, }, - toml::{ + jay_toml::{ toml_span::{DespanExt, Span, Spanned}, toml_value::Value, }, diff --git a/toml-config/src/config/parsers/modified_keysym.rs b/crates/toml-config/src/config/parsers/modified_keysym.rs similarity index 98% rename from toml-config/src/config/parsers/modified_keysym.rs rename to crates/toml-config/src/config/parsers/modified_keysym.rs index 102a1c22..a693557a 100644 --- a/toml-config/src/config/parsers/modified_keysym.rs +++ b/crates/toml-config/src/config/parsers/modified_keysym.rs @@ -1,7 +1,7 @@ use { crate::{ config::parser::{DataType, ParseResult, Parser, UnexpectedDataType}, - toml::toml_span::{Span, SpannedExt}, + jay_toml::toml_span::{Span, SpannedExt}, }, jay_config::keyboard::{ ModifiedKeySym, diff --git a/toml-config/src/config/parsers/output.rs b/crates/toml-config/src/config/parsers/output.rs similarity index 99% rename from toml-config/src/config/parsers/output.rs rename to crates/toml-config/src/config/parsers/output.rs index 120bfe25..0ecfef9f 100644 --- a/toml-config/src/config/parsers/output.rs +++ b/crates/toml-config/src/config/parsers/output.rs @@ -13,7 +13,7 @@ use { vrr::VrrParser, }, }, - toml::{ + jay_toml::{ toml_span::{DespanExt, Span, Spanned, SpannedExt}, toml_value::Value, }, diff --git a/toml-config/src/config/parsers/output_match.rs b/crates/toml-config/src/config/parsers/output_match.rs similarity index 99% rename from toml-config/src/config/parsers/output_match.rs rename to crates/toml-config/src/config/parsers/output_match.rs index 4af292f2..4af59515 100644 --- a/toml-config/src/config/parsers/output_match.rs +++ b/crates/toml-config/src/config/parsers/output_match.rs @@ -6,7 +6,7 @@ use { extractor::{Extractor, ExtractorError, opt, str}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, }, - toml::{ + jay_toml::{ toml_span::{DespanExt, Span, Spanned}, toml_value::Value, }, diff --git a/toml-config/src/config/parsers/repeat_rate.rs b/crates/toml-config/src/config/parsers/repeat_rate.rs similarity index 98% rename from toml-config/src/config/parsers/repeat_rate.rs rename to crates/toml-config/src/config/parsers/repeat_rate.rs index 4374d853..f6658299 100644 --- a/toml-config/src/config/parsers/repeat_rate.rs +++ b/crates/toml-config/src/config/parsers/repeat_rate.rs @@ -6,7 +6,7 @@ use { extractor::{Extractor, ExtractorError, s32}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, }, - toml::{ + jay_toml::{ toml_span::{Span, Spanned}, toml_value::Value, }, diff --git a/crates/toml-config/src/config/parsers/scratchpad.rs b/crates/toml-config/src/config/parsers/scratchpad.rs new file mode 100644 index 00000000..139935a1 --- /dev/null +++ b/crates/toml-config/src/config/parsers/scratchpad.rs @@ -0,0 +1,87 @@ +use { + crate::{ + config::{ + Scratchpad, + context::Context, + extractor::{Extractor, ExtractorError, opt, str, val}, + parser::{DataType, ParseResult, Parser, UnexpectedDataType}, + parsers::exec::{ExecParser, ExecParserError}, + }, + jay_toml::{ + toml_span::{Span, Spanned}, + toml_value::Value, + }, + }, + indexmap::IndexMap, + thiserror::Error, +}; + +#[derive(Debug, Error)] +pub enum ScratchpadParserError { + #[error(transparent)] + Expected(#[from] UnexpectedDataType), + #[error(transparent)] + Extract(#[from] ExtractorError), + #[error(transparent)] + Exec(#[from] ExecParserError), +} + +pub struct ScratchpadParser<'a>(pub &'a Context<'a>); + +impl Parser for ScratchpadParser<'_> { + type Value = Scratchpad; + type Error = ScratchpadParserError; + const EXPECTED: &'static [DataType] = &[DataType::Table]; + + fn parse_table( + &mut self, + span: Span, + table: &IndexMap, Spanned>, + ) -> ParseResult { + let mut ext = Extractor::new(self.0, span, table); + let (name, exec_val) = ext.extract((str("name"), opt(val("exec"))))?; + let exec = match exec_val { + None => None, + Some(e) => Some(e.parse_map(&mut ExecParser(self.0))?), + }; + Ok(Scratchpad { + name: name.value.to_string(), + exec, + }) + } +} + +pub struct ScratchpadsParser<'a>(pub &'a Context<'a>); + +impl Parser for ScratchpadsParser<'_> { + type Value = Vec; + type Error = ScratchpadParserError; + const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array]; + + fn parse_array(&mut self, _span: Span, array: &[Spanned]) -> ParseResult { + let mut res = vec![]; + for el in array { + match el.parse(&mut ScratchpadParser(self.0)) { + Ok(o) => res.push(o), + Err(e) => { + log::warn!("Could not parse scratchpad: {}", self.0.error(e)); + } + } + } + Ok(res) + } + + fn parse_table( + &mut self, + span: Span, + table: &IndexMap, Spanned>, + ) -> ParseResult { + log::warn!( + "`scratchpads` value should be an array: {}", + self.0.error3(span) + ); + ScratchpadParser(self.0) + .parse_table(span, table) + .map(|v| vec![v]) + } +} diff --git a/toml-config/src/config/parsers/shortcuts.rs b/crates/toml-config/src/config/parsers/shortcuts.rs similarity index 99% rename from toml-config/src/config/parsers/shortcuts.rs rename to crates/toml-config/src/config/parsers/shortcuts.rs index 46e71813..540d8e96 100644 --- a/toml-config/src/config/parsers/shortcuts.rs +++ b/crates/toml-config/src/config/parsers/shortcuts.rs @@ -13,7 +13,7 @@ use { }, spanned::SpannedErrorExt, }, - toml::{ + jay_toml::{ toml_span::{Span, Spanned, SpannedExt}, toml_value::Value, }, diff --git a/toml-config/src/config/parsers/simple_im.rs b/crates/toml-config/src/config/parsers/simple_im.rs similarity index 98% rename from toml-config/src/config/parsers/simple_im.rs rename to crates/toml-config/src/config/parsers/simple_im.rs index b4c08095..f1a69f61 100644 --- a/toml-config/src/config/parsers/simple_im.rs +++ b/crates/toml-config/src/config/parsers/simple_im.rs @@ -6,7 +6,7 @@ use { extractor::{Extractor, ExtractorError, bol, opt, recover}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, }, - toml::{ + jay_toml::{ toml_span::{DespanExt, Span, Spanned}, toml_value::Value, }, diff --git a/toml-config/src/config/parsers/status.rs b/crates/toml-config/src/config/parsers/status.rs similarity index 99% rename from toml-config/src/config/parsers/status.rs rename to crates/toml-config/src/config/parsers/status.rs index 77e3aa72..77cf08f1 100644 --- a/toml-config/src/config/parsers/status.rs +++ b/crates/toml-config/src/config/parsers/status.rs @@ -7,7 +7,7 @@ use { parser::{DataType, ParseResult, Parser, UnexpectedDataType}, parsers::exec::{ExecParser, ExecParserError}, }, - toml::{ + jay_toml::{ toml_span::{Span, Spanned, SpannedExt}, toml_value::Value, }, diff --git a/toml-config/src/config/parsers/tearing.rs b/crates/toml-config/src/config/parsers/tearing.rs similarity index 99% rename from toml-config/src/config/parsers/tearing.rs rename to crates/toml-config/src/config/parsers/tearing.rs index 7e6061bc..7e0c96bb 100644 --- a/toml-config/src/config/parsers/tearing.rs +++ b/crates/toml-config/src/config/parsers/tearing.rs @@ -6,7 +6,7 @@ use { extractor::{Extractor, ExtractorError, opt, val}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, }, - toml::{ + jay_toml::{ toml_span::{Span, Spanned, SpannedExt}, toml_value::Value, }, diff --git a/toml-config/src/config/parsers/theme.rs b/crates/toml-config/src/config/parsers/theme.rs similarity index 99% rename from toml-config/src/config/parsers/theme.rs rename to crates/toml-config/src/config/parsers/theme.rs index c67fa744..4a2e05c0 100644 --- a/toml-config/src/config/parsers/theme.rs +++ b/crates/toml-config/src/config/parsers/theme.rs @@ -7,7 +7,7 @@ use { parser::{DataType, ParseResult, Parser, UnexpectedDataType}, parsers::color::ColorParser, }, - toml::{ + jay_toml::{ toml_span::{DespanExt, Span, Spanned}, toml_value::Value, }, diff --git a/toml-config/src/config/parsers/tile_state.rs b/crates/toml-config/src/config/parsers/tile_state.rs similarity index 94% rename from toml-config/src/config/parsers/tile_state.rs rename to crates/toml-config/src/config/parsers/tile_state.rs index de9d30eb..0634c840 100644 --- a/toml-config/src/config/parsers/tile_state.rs +++ b/crates/toml-config/src/config/parsers/tile_state.rs @@ -1,7 +1,7 @@ use { crate::{ config::parser::{DataType, ParseResult, Parser, UnexpectedDataType}, - toml::toml_span::{Span, SpannedExt}, + jay_toml::toml_span::{Span, SpannedExt}, }, jay_config::window::TileState, thiserror::Error, diff --git a/toml-config/src/config/parsers/ui_drag.rs b/crates/toml-config/src/config/parsers/ui_drag.rs similarity index 98% rename from toml-config/src/config/parsers/ui_drag.rs rename to crates/toml-config/src/config/parsers/ui_drag.rs index aff14805..6749b4ed 100644 --- a/toml-config/src/config/parsers/ui_drag.rs +++ b/crates/toml-config/src/config/parsers/ui_drag.rs @@ -7,7 +7,7 @@ use { parser::{DataType, ParseResult, Parser, UnexpectedDataType}, parsers::exec::ExecParserError, }, - toml::{ + jay_toml::{ toml_span::{DespanExt, Span, Spanned}, toml_value::Value, }, diff --git a/toml-config/src/config/parsers/vrr.rs b/crates/toml-config/src/config/parsers/vrr.rs similarity index 99% rename from toml-config/src/config/parsers/vrr.rs rename to crates/toml-config/src/config/parsers/vrr.rs index f2f2322a..224225c8 100644 --- a/toml-config/src/config/parsers/vrr.rs +++ b/crates/toml-config/src/config/parsers/vrr.rs @@ -6,7 +6,7 @@ use { extractor::{Extractor, ExtractorError, opt, val}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, }, - toml::{ + jay_toml::{ toml_span::{Span, Spanned, SpannedExt}, toml_value::Value, }, diff --git a/toml-config/src/config/parsers/window_match.rs b/crates/toml-config/src/config/parsers/window_match.rs similarity index 99% rename from toml-config/src/config/parsers/window_match.rs rename to crates/toml-config/src/config/parsers/window_match.rs index a2c11305..d6a911d6 100644 --- a/toml-config/src/config/parsers/window_match.rs +++ b/crates/toml-config/src/config/parsers/window_match.rs @@ -11,7 +11,7 @@ use { window_type::{WindowTypeParser, WindowTypeParserError}, }, }, - toml::{ + jay_toml::{ toml_span::{DespanExt, Span, Spanned}, toml_value::Value, }, diff --git a/toml-config/src/config/parsers/window_rule.rs b/crates/toml-config/src/config/parsers/window_rule.rs similarity index 99% rename from toml-config/src/config/parsers/window_rule.rs rename to crates/toml-config/src/config/parsers/window_rule.rs index 5311641c..b25b632a 100644 --- a/toml-config/src/config/parsers/window_rule.rs +++ b/crates/toml-config/src/config/parsers/window_rule.rs @@ -12,7 +12,7 @@ use { }, spanned::SpannedErrorExt, }, - toml::{ + jay_toml::{ toml_span::{DespanExt, Span, Spanned}, toml_value::Value, }, diff --git a/toml-config/src/config/parsers/window_type.rs b/crates/toml-config/src/config/parsers/window_type.rs similarity index 98% rename from toml-config/src/config/parsers/window_type.rs rename to crates/toml-config/src/config/parsers/window_type.rs index 388fe317..39363069 100644 --- a/toml-config/src/config/parsers/window_type.rs +++ b/crates/toml-config/src/config/parsers/window_type.rs @@ -1,7 +1,7 @@ use { crate::{ config::parser::{DataType, ParseResult, Parser, UnexpectedDataType}, - toml::{ + jay_toml::{ toml_span::{Span, Spanned, SpannedExt}, toml_value::Value, }, diff --git a/toml-config/src/config/parsers/workspace_display_order.rs b/crates/toml-config/src/config/parsers/workspace_display_order.rs similarity index 95% rename from toml-config/src/config/parsers/workspace_display_order.rs rename to crates/toml-config/src/config/parsers/workspace_display_order.rs index 749cff2d..2b69c0ac 100644 --- a/toml-config/src/config/parsers/workspace_display_order.rs +++ b/crates/toml-config/src/config/parsers/workspace_display_order.rs @@ -1,7 +1,7 @@ use { crate::{ config::parser::{DataType, ParseResult, Parser, UnexpectedDataType}, - toml::toml_span::{Span, SpannedExt}, + jay_toml::toml_span::{Span, SpannedExt}, }, jay_config::workspace::WorkspaceDisplayOrder, thiserror::Error, diff --git a/toml-config/src/config/parsers/xwayland.rs b/crates/toml-config/src/config/parsers/xwayland.rs similarity index 99% rename from toml-config/src/config/parsers/xwayland.rs rename to crates/toml-config/src/config/parsers/xwayland.rs index c6420334..9998dd5d 100644 --- a/toml-config/src/config/parsers/xwayland.rs +++ b/crates/toml-config/src/config/parsers/xwayland.rs @@ -6,7 +6,7 @@ use { extractor::{Extractor, ExtractorError, bol, opt, recover, val}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, }, - toml::{ + jay_toml::{ toml_span::{DespanExt, Span, Spanned, SpannedExt}, toml_value::Value, }, diff --git a/crates/toml-config/src/config/spanned.rs b/crates/toml-config/src/config/spanned.rs new file mode 100644 index 00000000..bb4db6ef --- /dev/null +++ b/crates/toml-config/src/config/spanned.rs @@ -0,0 +1 @@ +pub use jay_toml::SpannedErrorExt; diff --git a/toml-config/src/default-config.toml b/crates/toml-config/src/default-config.toml similarity index 100% rename from toml-config/src/default-config.toml rename to crates/toml-config/src/default-config.toml diff --git a/toml-config/src/lib.rs b/crates/toml-config/src/lib.rs similarity index 90% rename from toml-config/src/lib.rs rename to crates/toml-config/src/lib.rs index 391bcee9..33bc3343 100644 --- a/toml-config/src/lib.rs +++ b/crates/toml-config/src/lib.rs @@ -8,14 +8,16 @@ mod config; mod rules; mod shortcuts; -mod toml; + +pub(crate) use jay_toml; use { crate::{ config::{ - Action, ClientRule, Config, ConfigConnector, ConfigDrmDevice, ConfigKeymap, - ConnectorMatch, DrmDeviceMatch, Exec, Input, InputMatch, Output, OutputMatch, - SimpleCommand, Status, Theme, WindowRule, parse_config, + Action, AnimationCurveConfig, ClientMatch, ClientRule, Config, ConfigConnector, + ConfigDrmDevice, ConfigKeymap, ConnectorMatch, DrmDeviceMatch, Exec, Input, + InputMatch, Output, OutputMatch, SimpleCommand, Status, Theme, WindowMatch, + WindowRule, parse_config, }, rules::{MatcherTemp, RuleMapper}, shortcuts::ModeState, @@ -23,11 +25,11 @@ use { ahash::{AHashMap, AHashSet}, error_reporter::Report, jay_config::{ - Axis, + AnimationCurve, AnimationStyle, Axis, client::Client, - config, config_dir, + config_dir, exec::{Command, set_env, unset_env}, - get_workspace, + get_autotile, get_workspace, input::{ FocusFollowsMouseMode, InputDevice, Seat, SwitchEvent, capability::CAP_SWITCH, get_seat, input_devices, on_input_device_removed, on_new_input_device, @@ -37,10 +39,12 @@ use { is_reload, keyboard::Keymap, logging::{clean_logs_older_than, set_log_level}, - on_devices_enumerated, on_idle, on_unload, quit, reload, set_autotile, - set_color_management_enabled, set_corner_radius, set_default_workspace_capture, - set_explicit_sync_enabled, set_float_above_fullscreen, set_floating_titles, set_idle, - set_idle_grace_period, set_middle_click_paste_enabled, set_show_bar, + on_devices_enumerated, on_idle, on_unload, quit, set_animation_cubic_bezier, + set_animation_curve, set_animation_duration_ms, set_animation_style, + set_animations_enabled, set_autotile, set_color_management_enabled, set_corner_radius, + set_default_workspace_capture, set_explicit_sync_enabled, set_float_above_fullscreen, + set_floating_titles, set_idle, set_idle_grace_period, set_key_press_enables_dpms, + set_middle_click_paste_enabled, set_mouse_move_enables_dpms, set_show_bar, set_show_float_pin_icon, set_show_titles, set_tab_title_align, set_ui_drag_enabled, set_ui_drag_threshold, status::{set_i3bar_separator, set_status, set_status_command, unset_status_command}, @@ -126,7 +130,17 @@ impl FnBuilder for ShortcutFnBuilder<'_> { } } -impl Action { +trait ActionExt { + fn into_fn(self, state: &Rc) -> Box; + + fn into_rc_fn(self, state: &Rc) -> Rc; + + fn into_shortcut_fn(self, state: &Rc) -> Rc; + + fn into_fn_impl(self, b: &B, state: &Rc) -> B::Output; +} + +impl ActionExt for Action { fn into_fn(self, state: &Rc) -> Box { self.into_fn_impl(&BoxFnBuilder, state) } @@ -172,6 +186,9 @@ impl Action { SimpleCommand::Move(dir) => window_or_seat!(s, s.move_(dir)), SimpleCommand::ToggleFullscreen => window_or_seat!(s, s.toggle_fullscreen()), SimpleCommand::SetFullscreen(b) => window_or_seat!(s, s.set_fullscreen(b)), + SimpleCommand::SendToScratchpad => window_or_seat!(s, s.send_to_scratchpad("")), + SimpleCommand::ToggleScratchpad => b.new(move || s.toggle_scratchpad("")), + SimpleCommand::CycleScratchpad => b.new(move || s.cycle_scratchpad("")), SimpleCommand::FocusParent => b.new(move || s.focus_parent()), SimpleCommand::Close => window_or_seat!(s, s.close()), SimpleCommand::DisablePointerConstraint => { @@ -184,7 +201,6 @@ impl Action { let persistent = state.persistent.clone(); b.new(move || load_config(false, false, &persistent)) } - SimpleCommand::ReloadConfigSo => b.new(reload), SimpleCommand::None => b.new(|| ()), SimpleCommand::Forward(bool) => b.new(move || s.set_forward(bool)), SimpleCommand::EnableWindowManagement(bool) => { @@ -268,12 +284,7 @@ impl Action { SimpleCommand::MoveTabLeft => b.new(move || s.move_tab(false)), SimpleCommand::MoveTabRight => b.new(move || s.move_tab(true)), SimpleCommand::SetAutotile(enabled) => b.new(move || set_autotile(enabled)), - SimpleCommand::ToggleAutotile => { - b.new(move || { - // Toggle not directly supported; set to true - set_autotile(true) - }) - } + SimpleCommand::ToggleAutotile => b.new(move || set_autotile(!get_autotile())), }, Action::Multi { actions } => { let actions: Vec<_> = actions.into_iter().map(|a| a.into_fn(state)).collect(); @@ -310,6 +321,9 @@ impl Action { let workspace = get_workspace(&name); window_or_seat!(s, s.set_workspace(workspace)) } + Action::SendToScratchpad { name } => window_or_seat!(s, s.send_to_scratchpad(&name)), + Action::ToggleScratchpad { name } => b.new(move || s.toggle_scratchpad(&name)), + Action::CycleScratchpad { name } => b.new(move || s.cycle_scratchpad(&name)), Action::ConfigureConnector { con } => b.new(move || { for c in connectors() { if con.match_.matches(c) { @@ -537,7 +551,11 @@ fn apply_recursive_match<'a, U>( } } -impl ConfigDrmDevice { +trait ConfigDrmDeviceExt { + fn apply(&self, d: DrmDevice); +} + +impl ConfigDrmDeviceExt for ConfigDrmDevice { fn apply(&self, d: DrmDevice) { if let Some(api) = self.gfx_api { d.set_gfx_api(api); @@ -551,7 +569,18 @@ impl ConfigDrmDevice { } } -impl DrmDeviceMatch { +trait DrmDeviceMatchExt { + fn matches(&self, d: DrmDevice, state: &State) -> bool; + + fn matches_<'a>( + &'a self, + d: DrmDevice, + state: &'a State, + active: &mut AHashSet<&'a str>, + ) -> bool; +} + +impl DrmDeviceMatchExt for DrmDeviceMatch { fn matches(&self, d: DrmDevice, state: &State) -> bool { self.matches_(d, state, &mut AHashSet::new()) } @@ -621,7 +650,18 @@ impl DrmDeviceMatch { } } -impl InputMatch { +trait InputMatchExt { + fn matches(&self, d: InputDevice, state: &State) -> bool; + + fn matches_<'a>( + &'a self, + d: InputDevice, + state: &'a State, + active: &mut AHashSet<&'a str>, + ) -> bool; +} + +impl InputMatchExt for InputMatch { fn matches(&self, d: InputDevice, state: &State) -> bool { self.matches_(d, state, &mut AHashSet::new()) } @@ -696,7 +736,11 @@ impl InputMatch { } } -impl Input { +trait InputExt { + fn apply(&self, c: InputDevice, state: &State); +} + +impl InputExt for Input { fn apply(&self, c: InputDevice, state: &State) { if let Some(v) = self.accel_profile { c.set_accel_profile(v); @@ -753,7 +797,18 @@ impl Input { } } -impl OutputMatch { +trait OutputMatchExt { + fn matches(&self, c: Connector, state: &State) -> bool; + + fn matches_<'a>( + &'a self, + c: Connector, + state: &'a State, + active: &mut AHashSet<&'a str>, + ) -> bool; +} + +impl OutputMatchExt for OutputMatch { fn matches(&self, c: Connector, state: &State) -> bool { if !c.connected() { return false; @@ -814,7 +869,11 @@ impl OutputMatch { } } -impl ConnectorMatch { +trait ConnectorMatchExt { + fn matches(&self, c: Connector) -> bool; +} + +impl ConnectorMatchExt for ConnectorMatch { fn matches(&self, c: Connector) -> bool { if !c.exists() { return false; @@ -833,13 +892,21 @@ impl ConnectorMatch { } } -impl ConfigConnector { +trait ConfigConnectorExt { + fn apply(&self, c: Connector); +} + +impl ConfigConnectorExt for ConfigConnector { fn apply(&self, c: Connector) { c.set_enabled(self.enabled); } } -impl Output { +trait OutputExt { + fn apply(&self, c: Connector); +} + +impl OutputExt for Output { fn apply(&self, c: Connector) { if self.x.is_some() || self.y.is_some() { let (old_x, old_y) = c.position(); @@ -1461,6 +1528,46 @@ fn load_config(initial_load: bool, auto_reload: bool, persistent: &Rc set_animation_style(AnimationStyle::PLAIN), + "multiphase" => set_animation_style(AnimationStyle::MULTIPHASE), + style_name => log::warn!("Unknown animation style: {style_name}"), + } + match config + .animations + .curve + .unwrap_or_else(|| AnimationCurveConfig::Preset("ease-out".to_string())) + { + AnimationCurveConfig::Preset(curve_name) => { + let curve = match curve_name.as_str() { + "linear" => Some(AnimationCurve::LINEAR), + "ease" => Some(AnimationCurve::EASE), + "ease-in" => Some(AnimationCurve::EASE_IN), + "ease-out" => Some(AnimationCurve::EASE_OUT), + "ease-in-out" => Some(AnimationCurve::EASE_IN_OUT), + _ => { + log::warn!("Unknown animation curve: {curve_name}"); + None + } + }; + if let Some(curve) = curve { + set_animation_curve(curve); + } + } + AnimationCurveConfig::CubicBezier([x1, y1, x2, y2]) => { + set_animation_cubic_bezier(x1, y1, x2, y2); + } + } if let Some(xwayland) = config.xwayland { if let Some(enabled) = xwayland.enabled { set_x_wayland_enabled(enabled); @@ -1657,6 +1796,8 @@ fn load_config(initial_load: bool, auto_reload: bool, persistent: &Rc Command { @@ -1723,9 +1867,6 @@ fn create_command(exec: &Exec) -> Command { for (k, v) in &exec.envs { command.env(k, v); } - if exec.privileged { - command.privileged(); - } if let Some(tag) = &exec.tag { command.tag(tag); } @@ -1762,5 +1903,3 @@ pub fn configure() { } load_config(true, false, &persistent); } - -config!(configure); diff --git a/toml-config/src/rules.rs b/crates/toml-config/src/rules.rs similarity index 98% rename from toml-config/src/rules.rs rename to crates/toml-config/src/rules.rs index b67a2cca..4b83eecd 100644 --- a/toml-config/src/rules.rs +++ b/crates/toml-config/src/rules.rs @@ -1,6 +1,6 @@ use { crate::{ - State, + ActionExt, State, config::{ClientMatch, ClientRule, GenericMatch, WindowMatch, WindowRule}, }, ahash::{AHashMap, AHashSet}, @@ -177,12 +177,6 @@ impl Rule for ClientRule { }); } } - if let Some(caps) = self.capabilities { - matcher.set_capabilities(caps); - } - if let Some(caps) = self.bounding_capabilities { - matcher.set_sandbox_bounding_capabilities(caps); - } } fn gen_matcher(m: Self::Matcher) -> Self::Criterion<'static> { diff --git a/toml-config/src/shortcuts.rs b/crates/toml-config/src/shortcuts.rs similarity index 99% rename from toml-config/src/shortcuts.rs rename to crates/toml-config/src/shortcuts.rs index 7156433c..325beb03 100644 --- a/toml-config/src/shortcuts.rs +++ b/crates/toml-config/src/shortcuts.rs @@ -1,6 +1,6 @@ use { crate::{ - State, + ActionExt, State, config::{Action, InputMode, Shortcut, SimpleCommand}, }, ahash::{AHashMap, AHashSet}, diff --git a/toml-config/toml-test b/crates/toml-config/toml-test similarity index 100% rename from toml-config/toml-test rename to crates/toml-config/toml-test diff --git a/crates/toml-parser/Cargo.toml b/crates/toml-parser/Cargo.toml new file mode 100644 index 00000000..8f1f423a --- /dev/null +++ b/crates/toml-parser/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "jay-toml" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "Internal TOML parser used by Jay" +repository = "https://github.com/mahkoh/jay" + +[dependencies] +bstr = { version = "1.9.1", default-features = false } +indexmap = "2.2.5" +thiserror = "2.0.11" + +[dev-dependencies] +serde_json = "1.0.114" +walkdir = "2.5.0" diff --git a/toml-config/src/toml.rs b/crates/toml-parser/src/lib.rs similarity index 51% rename from toml-config/src/toml.rs rename to crates/toml-parser/src/lib.rs index 8c2ce876..045b62d1 100644 --- a/toml-config/src/toml.rs +++ b/crates/toml-parser/src/lib.rs @@ -1,6 +1,11 @@ +mod spanned_ext; #[cfg(test)] mod tests; mod toml_lexer; pub mod toml_parser; pub mod toml_span; pub mod toml_value; +mod value_ext; +pub mod value_parser; + +pub use spanned_ext::SpannedErrorExt; diff --git a/toml-config/src/config/spanned.rs b/crates/toml-parser/src/spanned_ext.rs similarity index 93% rename from toml-config/src/config/spanned.rs rename to crates/toml-parser/src/spanned_ext.rs index a88282c7..40886a0a 100644 --- a/toml-config/src/config/spanned.rs +++ b/crates/toml-parser/src/spanned_ext.rs @@ -1,6 +1,7 @@ use crate::{ - config::parser::{ParseResult, Parser}, - toml::{toml_span::Spanned, toml_value::Value}, + toml_span::Spanned, + toml_value::Value, + value_parser::{ParseResult, Parser}, }; impl Spanned<&Value> { diff --git a/toml-config/src/toml/tests.rs b/crates/toml-parser/src/tests.rs similarity index 77% rename from toml-config/src/toml/tests.rs rename to crates/toml-parser/src/tests.rs index 0cb76e36..4f62d112 100644 --- a/toml-config/src/toml/tests.rs +++ b/crates/toml-parser/src/tests.rs @@ -1,15 +1,11 @@ use { crate::{ - config::error::SpannedError, - toml::{ - toml_parser::{ErrorHandler, ParserError, parse}, - toml_span::{Span, Spanned, SpannedExt}, - toml_value::Value, - }, + toml_parser::{ErrorHandler, ParserError, parse}, + toml_span::{Span, Spanned, SpannedExt}, + toml_value::Value, }, bstr::{BStr, ByteSlice}, std::{ - convert::Infallible, os::unix::ffi::OsStrExt, panic::{AssertUnwindSafe, catch_unwind}, str::FromStr, @@ -21,7 +17,15 @@ use { fn test() { let mut have_failures = false; let mut num = 0; - for path in WalkDir::new("./toml-test/tests/valid") { + let tests = std::path::Path::new(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../toml-config/toml-test/tests/valid" + )); + if !tests.exists() { + eprintln!("skipping TOML conformance tests because fixtures are not present"); + return; + } + for path in WalkDir::new(tests) { let path = path.unwrap(); if let Some(prefix) = path.path().as_os_str().as_bytes().strip_suffix(b".toml") { num += 1; @@ -45,11 +49,11 @@ fn run_test(prefix: &BStr) -> bool { let json: serde_json::Value = serde_json::from_str(&json).unwrap(); let json_as_toml = json_to_value(json); - let toml = match parse(toml.as_bytes(), &NoErrorHandler(prefix, &toml)) { + let toml = match parse(toml.as_bytes(), &NoErrorHandler(prefix)) { Ok(t) => t, Err(e) => { eprintln!("toml could not be parsed in test {}", prefix); - NoErrorHandler(prefix, &toml).handle(e); + NoErrorHandler(prefix).handle(e); return true; } }; @@ -95,18 +99,14 @@ fn json_to_value(json: serde_json::Value) -> Spanned { val.spanned(span) } -struct NoErrorHandler<'a>(&'a BStr, &'a [u8]); +struct NoErrorHandler<'a>(&'a BStr); impl<'a> ErrorHandler for NoErrorHandler<'a> { fn handle(&self, err: Spanned) { eprintln!( "{}: An error occurred during validation: {}", self.0, - SpannedError { - input: self.1.into(), - span: err.span, - cause: Some(err.value), - } + FormatError(err.span, Some(err.value)), ); } @@ -114,20 +114,23 @@ impl<'a> ErrorHandler for NoErrorHandler<'a> { eprintln!( "{}: Redefinition: {}", self.0, - SpannedError { - input: self.1.into(), - span: err.span, - cause: Some(err.value), - } + FormatError(err.span, Some(err.value)), ); eprintln!( "{}: Previous: {}", self.0, - SpannedError { - input: self.1.into(), - span: prev, - cause: None::, - } + FormatError(prev, None), ); } } + +struct FormatError(Span, Option); + +impl std::fmt::Display for FormatError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(cause) = &self.1 { + write!(f, "{cause}: ")?; + } + write!(f, "span {}..{}", self.0.lo, self.0.hi) + } +} diff --git a/toml-config/src/toml/toml_lexer.rs b/crates/toml-parser/src/toml_lexer.rs similarity index 99% rename from toml-config/src/toml/toml_lexer.rs rename to crates/toml-parser/src/toml_lexer.rs index 7e3d90b8..d59d5f43 100644 --- a/toml-config/src/toml/toml_lexer.rs +++ b/crates/toml-parser/src/toml_lexer.rs @@ -1,4 +1,4 @@ -use crate::toml::toml_span::{Span, Spanned, SpannedExt}; +use crate::toml_span::{Span, Spanned, SpannedExt}; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Token<'a> { diff --git a/toml-config/src/toml/toml_parser.rs b/crates/toml-parser/src/toml_parser.rs similarity index 99% rename from toml-config/src/toml/toml_parser.rs rename to crates/toml-parser/src/toml_parser.rs index 0ce75eba..5020d425 100644 --- a/toml-config/src/toml/toml_parser.rs +++ b/crates/toml-parser/src/toml_parser.rs @@ -1,5 +1,5 @@ use { - crate::toml::{ + crate::{ toml_lexer::{Lexer, Token}, toml_span::{Span, Spanned, SpannedExt}, toml_value::Value, diff --git a/toml-config/src/toml/toml_span.rs b/crates/toml-parser/src/toml_span.rs similarity index 100% rename from toml-config/src/toml/toml_span.rs rename to crates/toml-parser/src/toml_span.rs diff --git a/toml-config/src/toml/toml_value.rs b/crates/toml-parser/src/toml_value.rs similarity index 97% rename from toml-config/src/toml/toml_value.rs rename to crates/toml-parser/src/toml_value.rs index c93f942e..af290efc 100644 --- a/toml-config/src/toml/toml_value.rs +++ b/crates/toml-parser/src/toml_value.rs @@ -1,5 +1,5 @@ use { - crate::toml::toml_span::Spanned, + crate::toml_span::Spanned, indexmap::IndexMap, std::{ cmp::Ordering, diff --git a/toml-config/src/config/value.rs b/crates/toml-parser/src/value_ext.rs similarity index 85% rename from toml-config/src/config/value.rs rename to crates/toml-parser/src/value_ext.rs index 0a80b0c6..db061de5 100644 --- a/toml-config/src/config/value.rs +++ b/crates/toml-parser/src/value_ext.rs @@ -1,6 +1,7 @@ use crate::{ - config::parser::{ParseResult, Parser}, - toml::{toml_span::Span, toml_value::Value}, + toml_span::Span, + toml_value::Value, + value_parser::{ParseResult, Parser}, }; impl Value { diff --git a/toml-config/src/config/parser.rs b/crates/toml-parser/src/value_parser.rs similarity index 99% rename from toml-config/src/config/parser.rs rename to crates/toml-parser/src/value_parser.rs index 53bc3e91..a7003db8 100644 --- a/toml-config/src/config/parser.rs +++ b/crates/toml-parser/src/value_parser.rs @@ -1,5 +1,5 @@ use { - crate::toml::{ + crate::{ toml_span::{Span, Spanned, SpannedExt}, toml_value::Value, }, diff --git a/toml-spec/Cargo.toml b/crates/toml-spec/Cargo.toml similarity index 79% rename from toml-spec/Cargo.toml rename to crates/toml-spec/Cargo.toml index ec3a8426..3c62a8c1 100644 --- a/toml-spec/Cargo.toml +++ b/crates/toml-spec/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "toml-spec" -version = "0.1.0" -edition = "2024" +version.workspace = true +edition.workspace = true +license.workspace = true [dependencies] serde = { version = "1.0.197", features = ["derive"] } diff --git a/toml-spec/spec/spec.generated.json b/crates/toml-spec/spec/spec.generated.json similarity index 94% rename from toml-spec/spec/spec.generated.json rename to crates/toml-spec/spec/spec.generated.json index 930ad697..61312cf6 100644 --- a/toml-spec/spec/spec.generated.json +++ b/crates/toml-spec/spec/spec.generated.json @@ -162,6 +162,54 @@ "name" ] }, + { + "description": "Sends the currently focused window to a scratchpad and hides it.\n\nA scratchpad can hold any number of windows. If `name` is omitted, the\ndefault scratchpad is used.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-shift-minus = { type = \"send-to-scratchpad\", name = \"terminal\" }\n ```\n", + "type": "object", + "properties": { + "type": { + "const": "send-to-scratchpad" + }, + "name": { + "type": "string", + "description": "The name of the scratchpad." + } + }, + "required": [ + "type" + ] + }, + { + "description": "Toggles a scratchpad.\n\nIf the scratchpad has a visible window, that window is hidden. Otherwise, the\nmost recently hidden window in the scratchpad is shown on the current workspace.\nOnly one window of a scratchpad is shown at a time, and scratchpad windows are\nalways shown floating. If `name` is omitted, the default scratchpad is used.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-minus = { type = \"toggle-scratchpad\", name = \"terminal\" }\n ```\n", + "type": "object", + "properties": { + "type": { + "const": "toggle-scratchpad" + }, + "name": { + "type": "string", + "description": "The name of the scratchpad." + } + }, + "required": [ + "type" + ] + }, + { + "description": "Cycles through the windows of a scratchpad, one at a time.\n\nWith no window shown, the first window is brought up. Each further invocation\nhides the current window and shows the next; after the last window the\nscratchpad is hidden again. Scratchpad windows are always shown floating.\nIf `name` is omitted, the default scratchpad is used.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-minus = { type = \"cycle-scratchpad\", name = \"terminal\" }\n ```\n", + "type": "object", + "properties": { + "type": { + "const": "cycle-scratchpad" + }, + "name": { + "type": "string", + "description": "The name of the scratchpad." + } + }, + "required": [ + "type" + ] + }, { "description": "Moves a workspace to a different output.\n\n- Example 1: Move a specific workspace to a named output\n\n ```toml\n [shortcuts]\n alt-F1 = { type = \"move-to-output\", workspace = \"1\", output.name = \"right\" }\n ```\n\n- Example 2: Move the current workspace to a named output\n\n ```toml\n [shortcuts]\n alt-F1 = { type = \"move-to-output\", output.name = \"right\" }\n ```\n\n- Example 3: Move the current workspace to the output on the right (directional)\n\n ```toml\n [shortcuts]\n \"logo+ctrl+shift+Right\" = { type = \"move-to-output\", direction = \"right\" }\n \"logo+ctrl+shift+Left\" = { type = \"move-to-output\", direction = \"left\" }\n \"logo+ctrl+shift+Up\" = { type = \"move-to-output\", direction = \"up\" }\n \"logo+ctrl+shift+Down\" = { type = \"move-to-output\", direction = \"down\" }\n ```\n", "type": "object", @@ -641,6 +689,61 @@ } ] }, + "AnimationCurve": { + "description": "Describes a window animation curve.\n", + "anyOf": [ + { + "type": "string", + "description": "One of the supported curve presets.\n", + "enum": [ + "linear", + "ease", + "ease-in", + "ease-out", + "ease-in-out" + ] + }, + { + "type": "array", + "description": "A custom CSS-style cubic-bezier curve as four numbers:\n`x1`, `y1`, `x2`, and `y2`.\n\nThe implicit endpoints are `(0, 0)` and `(1, 1)`. `x1` and `x2` must\nbe between `0` and `1`.\n", + "items": { + "type": "number", + "description": "" + } + } + ] + }, + "AnimationStyle": { + "type": "string", + "description": "Describes a tiled window movement animation style.\n", + "enum": [ + "plain", + "multiphase" + ] + }, + "Animations": { + "description": "Describes window animation settings.\n\n- Example:\n\n ```toml\n [animations]\n enabled = true\n duration-ms = 160\n style = \"multiphase\"\n curve = [0.25, 0.1, 0.25, 1.0]\n ```\n", + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enables or disables window animations.\n\nThe default is `false`.\n" + }, + "duration-ms": { + "type": "integer", + "description": "Sets the animation duration in milliseconds.\n\nThe default is `160`.\n" + }, + "style": { + "description": "Sets the animation style used for tiled window movement animations.\n\nThe default is `multiphase`.\n", + "$ref": "#/$defs/AnimationStyle" + }, + "curve": { + "description": "Sets the animation curve.\n\nThe default is `ease-out`.\n", + "$ref": "#/$defs/AnimationCurve" + } + }, + "required": [] + }, "BarPosition": { "type": "string", "description": "The position of the bar.", @@ -699,42 +802,6 @@ "clickfinger" ] }, - "ClientCapabilities": { - "description": "A mask of client capabilities.\n", - "anyOf": [ - { - "type": "string", - "description": "A named mask.", - "enum": [ - "none", - "all", - "data-control", - "virtual-keyboard", - "foreign-toplevel-list", - "idle-notifier", - "session-lock", - "layer-shell", - "screencopy", - "seat-manager", - "drm-lease", - "input-method", - "workspace-manager", - "foreign-toplevel-manager", - "head-manager", - "gamma-control-manager", - "virtual-pointer" - ] - }, - { - "type": "array", - "description": "An array of masks that are OR'd.", - "items": { - "description": "", - "$ref": "#/$defs/ClientCapabilities" - } - } - ] - }, "ClientMatch": { "description": "Criteria for matching clients.\n\nIf no fields are set, all clients are matched. If multiple fields are set, all fields\nmust match the client.\n", "type": "object", @@ -875,14 +942,6 @@ "latch": { "description": "An action to execute when a client no longer matches the criteria.", "$ref": "#/$defs/Action" - }, - "capabilities": { - "description": "Sets the capabilities granted to clients matching this matcher.\n\nIf multiple matchers match a client, the capabilities are added.\n\nIf no matcher matches a client, it is granted the default capabilities depending\non whether it's sandboxed or not. If it is not sandboxed, it is granted the\ncapabilities `layer-shell` and `drm-lease`. Otherwise it is granted the\ncapability `drm-lease`.\n\nRegardless of any capabilities set through this function, the capabilities of the\nclient can never exceed its bounding capabilities.\n", - "$ref": "#/$defs/ClientCapabilities" - }, - "sandbox-bounding-capabilities": { - "description": "Sets the upper capability bounds for clients in sandboxes created by this client.\n\nIf multiple matchers match a client, the capabilities are added.\n\nIf no matcher matches a client, the bounding capabilities for sandboxes depend on\nwhether the client is itself sandboxed. If it is sandboxed, the bounding\ncapabilities are the effective capabilities of the client. Otherwise the bounding\ncapabilities are all capabilities.\n\nRegardless of any capabilities set through this function, the capabilities set\nthrough this function can never exceed the client's bounding capabilities.\n", - "$ref": "#/$defs/ClientCapabilities" } }, "required": [] @@ -1085,6 +1144,10 @@ "description": "Configures the ui-drag settings.\n\n- Example:\n\n ```toml\n ui-drag = { enabled = false, threshold = 20 }\n ```\n", "$ref": "#/$defs/UiDrag" }, + "animations": { + "description": "Configures window animations.\n\nAnimations are disabled by default.\n\n- Example:\n\n ```toml\n [animations]\n enabled = true\n duration-ms = 160\n style = \"multiphase\"\n curve = \"ease-out\"\n ```\n", + "$ref": "#/$defs/Animations" + }, "xwayland": { "description": "Configures the Xwayland settings.\n\n- Example:\n\n ```toml\n xwayland = { scaling-mode = \"downscaled\" }\n ```\n", "$ref": "#/$defs/Xwayland" @@ -1150,6 +1213,10 @@ "type": "boolean", "description": "Configures whether middle-click pasting is enabled.\n\nChanging this has no effect on running applications.\n\nThe default is `true`.\n" }, + "autotile": { + "type": "boolean", + "description": "Configures whether autotiling is enabled by default.\n\nWhen enabled, newly mapped tiled windows alternate their split\norientation automatically. This can also be toggled at runtime via the\n`enable-autotile`, `disable-autotile`, and `toggle-autotile` actions.\n\nThe default is `false`.\n" + }, "modes": { "description": "Configures the input modes.\n\nModes can be used to define shortcuts that are only active when the mode is\nactive.\n\n- Example\n\n ```toml\n [modes.\"navigation\".shortcuts]\n w = \"focus-up\"\n a = \"focus-left\"\n s = \"focus-down\"\n d = \"focus-right\"\n r = \"focus-above\"\n f = \"focus-below\"\n q = \"focus-prev\"\n e = \"focus-next\"\n ```\n\nModes can be activated with the `push-mode` and `latch-mode` actions.\n", "type": "object", @@ -1177,6 +1244,14 @@ "egui": { "description": "Sets the egui settings of the compositor.\n", "$ref": "#/$defs/Egui" + }, + "scratchpads": { + "type": "array", + "description": "An array of pre-configured scratchpads.\n\nEach entry launches a program when the graphics are first initialized and\nimmediately parks its window in the named scratchpad. The window is captured\nvia a unique tag attached to the spawned process, so other windows of the\nsame application are never affected.\n\nUse a `toggle-scratchpad` or `cycle-scratchpad` action to bring the windows\nup; they are always shown floating.\n\n- Example:\n\n ```toml\n [[scratchpads]]\n name = \"term\"\n exec = \"foot\"\n\n [[scratchpads]]\n name = \"notes\"\n exec = [\"obsidian\"]\n ```\n", + "items": { + "description": "", + "$ref": "#/$defs/Scratchpad" + } } }, "required": [] @@ -1365,7 +1440,7 @@ ] }, "Exec": { - "description": "Describes how to execute a program.\n\n- Example 1:\n\n ```toml\n [shortcuts]\n ctrl-a = { type = \"exec\", exec = \"alacritty\" }\n ```\n\n- Example 2:\n\n ```toml\n [shortcuts]\n ctrl-a = { type = \"exec\", exec = [\"notify-send\", \"hello world\"] }\n ```\n\n- Example 3:\n\n ```toml\n [shortcuts]\n ctrl-a = { type = \"exec\", exec = { prog = \"notify-send\", args = [\"hello world\"], env.WAYLAND_DISPLAY = \"2\" } }\n ```\n\n- Example 4:\n\n ```toml\n [shortcuts]\n ctrl-a = { type = \"exec\", exec = { shell = \"grim - | wl-copy\", privileged = true } }\n ```\n", + "description": "Describes how to execute a program.\n\n- Example 1:\n\n ```toml\n [shortcuts]\n ctrl-a = { type = \"exec\", exec = \"alacritty\" }\n ```\n\n- Example 2:\n\n ```toml\n [shortcuts]\n ctrl-a = { type = \"exec\", exec = [\"notify-send\", \"hello world\"] }\n ```\n\n- Example 3:\n\n ```toml\n [shortcuts]\n ctrl-a = { type = \"exec\", exec = { prog = \"notify-send\", args = [\"hello world\"], env.WAYLAND_DISPLAY = \"2\" } }\n ```\n", "anyOf": [ { "type": "string", @@ -1380,7 +1455,7 @@ } }, { - "description": "The name, arguments, and environment variables of the executable to execute.\n\nExactly one of the `prog` or `shell` fields must be specified.\n\n- Example 1:\n\n ```toml\n [shortcuts]\n ctrl-a = { type = \"exec\", exec = { prog = \"notify-send\", args = [\"hello world\"], env.WAYLAND_DISPLAY = \"2\" } }\n ```\n\n- Example 2:\n\n ```toml\n [shortcuts]\n ctrl-a = { type = \"exec\", exec = { shell = \"grim - | wl-copy\", privileged = true } }\n ```\n", + "description": "The name, arguments, and environment variables of the executable to execute.\n\nExactly one of the `prog` or `shell` fields must be specified.\n\n- Example 1:\n\n ```toml\n [shortcuts]\n ctrl-a = { type = \"exec\", exec = { prog = \"notify-send\", args = [\"hello world\"], env.WAYLAND_DISPLAY = \"2\" } }\n ```\n", "type": "object", "properties": { "prog": { @@ -1407,10 +1482,6 @@ "description": "" } }, - "privileged": { - "type": "boolean", - "description": "If `true`, the executable gets access to privileged wayland protocols.\n\nThe default is `false`.\n" - }, "tag": { "type": "string", "description": "Specifies a tag to apply to all spawned wayland connections.\n" @@ -1772,7 +1843,7 @@ "properties": { "enable-socket": { "type": "boolean", - "description": "Enables or disables the unauthenticated libei socket.\n\nEven if the socket is disabled, application can still request access via the portal.\n\nThe default is `false`.\n" + "description": "Enables or disables the unauthenticated libei socket.\n\nThe default is `false`.\n" } }, "required": [] @@ -1991,6 +2062,23 @@ }, "required": [] }, + "Scratchpad": { + "description": "A pre-configured scratchpad whose program is launched at startup and parked\nin the scratchpad.\n\n- Example:\n\n ```toml\n [[scratchpads]]\n name = \"term\"\n exec = \"foot\"\n ```\n", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the scratchpad that the spawned window is parked in." + }, + "exec": { + "description": "The program to launch when the graphics are first initialized.\n\nIf omitted, no program is launched and the scratchpad is only created on\ndemand by `send-to-scratchpad`.\n", + "$ref": "#/$defs/Exec" + } + }, + "required": [ + "name" + ] + }, "SimpleActionName": { "type": "string", "description": "The name of a `simple` Action.\n\nWhen used inside a window rule, the following actions apply to the matched window\ninstead fo the focused window:\n\n- `move-left`\n- `move-down`\n- `move-up`\n- `move-right`\n- `toggle-fullscreen`\n- `enter-fullscreen`\n- `exit-fullscreen`\n- `close`\n- `toggle-floating`\n- `float`\n- `tile`\n- `toggle-float-pinned`\n- `pin-float`\n- `unpin-float`\n\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-q = \"quit\"\n ```\n", @@ -2009,9 +2097,15 @@ "make-group-tab", "change-group-opposite", "toggle-tab", + "enable-autotile", + "disable-autotile", + "toggle-autotile", "toggle-fullscreen", "enter-fullscreen", "exit-fullscreen", + "send-to-scratchpad", + "toggle-scratchpad", + "cycle-scratchpad", "focus-parent", "close", "disable-pointer-constraint", @@ -2020,7 +2114,6 @@ "tile", "quit", "reload-config-toml", - "reload-config-so", "consume", "forward", "none", diff --git a/toml-spec/spec/spec.generated.md b/crates/toml-spec/spec/spec.generated.md similarity index 94% rename from toml-spec/spec/spec.generated.md rename to crates/toml-spec/spec/spec.generated.md index 43e9f20d..27a25895 100644 --- a/toml-spec/spec/spec.generated.md +++ b/crates/toml-spec/spec/spec.generated.md @@ -101,16 +101,16 @@ This table is a tagged union. The variant is determined by the `type` field. It A simple action that takes no arguments. These are usually written as plain strings instead. - + - Example 1: - + ```toml [shortcuts] alt-q = { type = "simple", cmd = "quit" } ``` - + - Example 2: - + ```toml [shortcuts] alt-q = "quit" @@ -129,19 +129,19 @@ This table is a tagged union. The variant is determined by the `type` field. It A named action that was defined via the top-level `actions` table or a `define-action` action. These are usually written as plain strings with a `$` prefix. - + - Example 1: - + ```toml [actions] my-action = "quit" - + [shortcuts] alt-q = { type = "named", name = "my-action" } ``` - + - Example 2: - + ```toml [shortcuts] alt-q = [ @@ -162,16 +162,16 @@ This table is a tagged union. The variant is determined by the `type` field. It A list of actions to execute in sequence. These are usually written as plain arrays instead. - + - Example 1: - + ```toml [shortcuts] alt-q = { type = "multi", actions = ["quit", "quit"] } ``` - + - Example 2: - + ```toml [shortcuts] alt-q = ["quit", "quit"] @@ -188,9 +188,9 @@ This table is a tagged union. The variant is determined by the `type` field. It - `exec`: Executes a program. - + - Example: - + ```toml [shortcuts] ctrl-a = { type = "exec", exec = "alacritty" } @@ -208,9 +208,9 @@ This table is a tagged union. The variant is determined by the `type` field. It - `switch-to-vt`: Switches to a virtual terminal. - + - Example: - + ```toml [shortcuts] ctrl-alt-F1 = { type = "switch-to-vt", num = 1 } @@ -231,16 +231,16 @@ This table is a tagged union. The variant is determined by the `type` field. It - `show-workspace`: Switches to a workspace. - + - Example 1: - + ```toml [shortcuts] alt-F1 = { type = "show-workspace", name = "1" } ``` - + - Example 2: - + ```toml [shortcuts] alt-F1 = { type = "show-workspace", name = "1", output.name = "left" } @@ -258,10 +258,10 @@ This table is a tagged union. The variant is determined by the `type` field. It The output to show a newly created workspace on. This has no effect on workspaces that already exist. - + If this is not set, then a new workspace is shown on the output that contains the cursor. - + If multiple outputs match, the workspace is shown on the first matching output. @@ -270,9 +270,9 @@ This table is a tagged union. The variant is determined by the `type` field. It - `move-to-workspace`: Moves the currently focused window to a workspace. - + - Example: - + ```toml [shortcuts] alt-F1 = { type = "move-to-workspace", name = "1" } @@ -286,26 +286,96 @@ This table is a tagged union. The variant is determined by the `type` field. It The value of this field should be a string. +- `send-to-scratchpad`: + + Sends the currently focused window to a scratchpad and hides it. + + A scratchpad can hold any number of windows. If `name` is omitted, the + default scratchpad is used. + + - Example: + + ```toml + [shortcuts] + alt-shift-minus = { type = "send-to-scratchpad", name = "terminal" } + ``` + + The table has the following fields: + + - `name` (optional): + + The name of the scratchpad. + + The value of this field should be a string. + +- `toggle-scratchpad`: + + Toggles a scratchpad. + + If the scratchpad has a visible window, that window is hidden. Otherwise, the + most recently hidden window in the scratchpad is shown on the current workspace. + Only one window of a scratchpad is shown at a time, and scratchpad windows are + always shown floating. If `name` is omitted, the default scratchpad is used. + + - Example: + + ```toml + [shortcuts] + alt-minus = { type = "toggle-scratchpad", name = "terminal" } + ``` + + The table has the following fields: + + - `name` (optional): + + The name of the scratchpad. + + The value of this field should be a string. + +- `cycle-scratchpad`: + + Cycles through the windows of a scratchpad, one at a time. + + With no window shown, the first window is brought up. Each further invocation + hides the current window and shows the next; after the last window the + scratchpad is hidden again. Scratchpad windows are always shown floating. + If `name` is omitted, the default scratchpad is used. + + - Example: + + ```toml + [shortcuts] + alt-minus = { type = "cycle-scratchpad", name = "terminal" } + ``` + + The table has the following fields: + + - `name` (optional): + + The name of the scratchpad. + + The value of this field should be a string. + - `move-to-output`: Moves a workspace to a different output. - + - Example 1: Move a specific workspace to a named output - + ```toml [shortcuts] alt-F1 = { type = "move-to-output", workspace = "1", output.name = "right" } ``` - + - Example 2: Move the current workspace to a named output - + ```toml [shortcuts] alt-F1 = { type = "move-to-output", output.name = "right" } ``` - + - Example 3: Move the current workspace to the output on the right (directional) - + ```toml [shortcuts] "logo+ctrl+shift+Right" = { type = "move-to-output", direction = "right" } @@ -319,7 +389,7 @@ This table is a tagged union. The variant is determined by the `type` field. It - `workspace` (optional): The name of the workspace. - + If this is omitted, the currently active workspace is moved. The value of this field should be a string. @@ -327,10 +397,10 @@ This table is a tagged union. The variant is determined by the `type` field. It - `output` (optional): The output to move to. - + If multiple outputs match, the workspace is moved to the first matching output. - + Either `output` or `direction` must be specified, but not both. The value of this field should be a [OutputMatch](#types-OutputMatch). @@ -338,11 +408,11 @@ This table is a tagged union. The variant is determined by the `type` field. It - `direction` (optional): The direction to search for the next output. - + Finds the closest output in the specified direction based on center-to-center distance, with preference for outputs better aligned with the movement axis. - + Either `output` or `direction` must be specified, but not both. The value of this field should be a [Direction](#types-Direction). @@ -350,9 +420,9 @@ This table is a tagged union. The variant is determined by the `type` field. It - `configure-connector`: Applies a configuration to connectors. - + - Example: - + ```toml [shortcuts] alt-j = { type = "configure-connector", connector = { match.name = "eDP-1", enabled = false } } @@ -370,14 +440,14 @@ This table is a tagged union. The variant is determined by the `type` field. It - `configure-input`: Applies a configuration to input devices. - + - Example: - + ```toml [shortcuts] alt-l = { type = "configure-input", input = { match.tag = "mouse", left-handed = true } } alt-r = { type = "configure-input", input = { match.tag = "mouse", left-handed = false } } - + [[inputs]] tag = "mouse" match.is-pointer = true @@ -394,9 +464,9 @@ This table is a tagged union. The variant is determined by the `type` field. It - `configure-idle`: Configures the idle timeout. - + - Example: - + ```toml [shortcuts] alt-l = { type = "configure-idle", idle.minutes = 0 } @@ -414,14 +484,14 @@ This table is a tagged union. The variant is determined by the `type` field. It - `configure-output`: Applies a configuration to input devices. - + - Example: - + ```toml [shortcuts] alt-l = { type = "configure-output", output = { match.name = "right", transform = "none" } } alt-r = { type = "configure-output", output = { match.name = "right", transform = "rotate-90" } } - + [[outputs]] name = "right" match.serial-number = "33K03894SL0" @@ -438,9 +508,9 @@ This table is a tagged union. The variant is determined by the `type` field. It - `set-env`: Sets environment variables for all programs started afterwards. - + - Example: - + ```toml [shortcuts] alt-l = { type = "set-env", env.GTK_THEME = "Adwaita:dark" } @@ -457,9 +527,9 @@ This table is a tagged union. The variant is determined by the `type` field. It - `unset-env`: Unsets environment variables for all programs started afterwards. - + - Example: - + ```toml [shortcuts] alt-l = { type = "unset-env", env = ["Adwaita:dark"] } @@ -476,18 +546,18 @@ This table is a tagged union. The variant is determined by the `type` field. It - `set-keymap`: Sets the keymap. - + - Example: - + ```toml [shortcuts] alt-j = { type = "set-keymap", keymap.name = "laptop" } alt-k = { type = "set-keymap", keymap.name = "external" } - + [[keymaps]] name = "laptop" path = "./laptop-keymap.xkb" - + [[keymaps]] name = "external" path = "./external-keymap.xkb" @@ -504,9 +574,9 @@ This table is a tagged union. The variant is determined by the `type` field. It - `set-repeat-rate`: Sets the keyboard repeat rate. - + - Example: - + ```toml [shortcuts] alt-x = { type = "set-repeat-rate", rate = { rate = 25, delay = 250 } } @@ -523,9 +593,9 @@ This table is a tagged union. The variant is determined by the `type` field. It - `set-status`: Sets the status command. - + - Example: - + ```toml [shortcuts] alt-j = { type = "set-status", status = { exec = "i3status" } } @@ -536,7 +606,7 @@ This table is a tagged union. The variant is determined by the `type` field. It - `status` (optional): The status setting. - + Omitting this causes the status to be reset to empty. The value of this field should be a [Status](#types-Status). @@ -544,9 +614,9 @@ This table is a tagged union. The variant is determined by the `type` field. It - `set-theme`: Sets the theme. - + - Example: - + ```toml [shortcuts] alt-j = { type = "set-theme", theme.bg-color = "#ff0000" } @@ -563,9 +633,9 @@ This table is a tagged union. The variant is determined by the `type` field. It - `set-log-level`: Sets the log level of the compositor. - + - Example: - + ```toml [shortcuts] alt-j = { type = "set-log-level", level = "debug" } @@ -582,11 +652,11 @@ This table is a tagged union. The variant is determined by the `type` field. It - `set-gfx-api`: Sets the graphics API used by new DRM devices. - + Setting this after the compositor has started usually has no effect. - + - Example: - + ```toml [shortcuts] alt-j = { type = "set-gfx-api", api = "Vulan" } @@ -603,9 +673,9 @@ This table is a tagged union. The variant is determined by the `type` field. It - `configure-direct-scanout`: Configure whether the compositor attempts direct scanout of client surfaces. - + - Example: - + ```toml [shortcuts] alt-j = { type = "configure-direct-scanout", enabled = false } @@ -622,13 +692,13 @@ This table is a tagged union. The variant is determined by the `type` field. It - `configure-drm-device`: Applies a configuration to DRM devices. - + - Example: - + ```toml [shortcuts] alt-j = { type = "configure-drm-device", dev = { match.name = "integrated", gfx-api = "Vulkan" } } - + [[drm-devices]] name = "integrated" match.syspath = "/sys/devices/pci0000:00/0000:00:08.1/0000:14:00.0" @@ -645,16 +715,16 @@ This table is a tagged union. The variant is determined by the `type` field. It - `set-render-device`: Sets the render device used for compositing. - + Changing this after the compositor has started might cause client windows to become invisible until they are resized. - + - Example: - + ```toml [shortcuts] alt-j = { type = "set-render-device", dev.name = "integrated" } - + [[drm-devices]] name = "integrated" match.syspath = "/sys/devices/pci0000:00/0000:00:08.1/0000:14:00.0" @@ -665,7 +735,7 @@ This table is a tagged union. The variant is determined by the `type` field. It - `dev` (required): The rule to find the device. - + The first matching device is used. The value of this field should be a [DrmDeviceMatch](#types-DrmDeviceMatch). @@ -674,14 +744,14 @@ This table is a tagged union. The variant is determined by the `type` field. It Defines a name for an action. Usually you would define these by using the top-level `actions` table. This action can be used to re-define actions. - + - Example: - + ```toml [actions] a1 = "quit" a2 = "$a1" - + [shortcuts] alt-q = [ { type = "define-action", name = "a2", action = [] }, @@ -718,20 +788,20 @@ This table is a tagged union. The variant is determined by the `type` field. It - `create-mark`: Creates a mark for the currently focused window. - + - Example 1: - + This example interactively selects a key that identifies the mark. - + ```toml [shortcuts] alt-x = { type = "create-mark" } ``` - + - Example 2: - + This example hard-codes a key. - + ```toml [shortcuts] alt-x = { type = "create-mark", id.key = "a" } @@ -742,7 +812,7 @@ This table is a tagged union. The variant is determined by the `type` field. It - `id` (optional): The identifier of the mark. - + If this field is omitted, the next pressed key identifies the mark. The value of this field should be a [MarkId](#types-MarkId). @@ -750,20 +820,20 @@ This table is a tagged union. The variant is determined by the `type` field. It - `jump-to-mark`: Moves the keyboard focus to a window identified by a mark. - + - Example 1: - + This example interactively selects a key that identifies the mark. - + ```toml [shortcuts] alt-x = { type = "jump-to-mark" } ``` - + - Example 2: - + This example hard-codes a key. - + ```toml [shortcuts] alt-x = { type = "jump-to-mark", id.key = "a" } @@ -774,7 +844,7 @@ This table is a tagged union. The variant is determined by the `type` field. It - `id` (optional): The identifier of the mark. - + If this field is omitted, the next pressed key identifies the mark. The value of this field should be a [MarkId](#types-MarkId). @@ -782,7 +852,7 @@ This table is a tagged union. The variant is determined by the `type` field. It - `copy-mark`: Copies a mark. - + If the `src` id identifies a mark before this function is called, the `dst` id will identify the same mark afterwards. @@ -804,9 +874,9 @@ This table is a tagged union. The variant is determined by the `type` field. It Pushes an input mode on top of the input-mode stack. The mode can be popped with the `pop-mode` action. - + - Example: - + ```toml [shortcuts] alt-x = { type = "push-mode", name = "navigation" } @@ -824,9 +894,9 @@ This table is a tagged union. The variant is determined by the `type` field. It Temporarily pushes an input mode on top of the input-mode stack. The new mode will automatically be popped when the next shortcut is invoked. - + - Example: - + ```toml [shortcuts] alt-x = { type = "latch-mode", name = "navigation" } @@ -843,25 +913,25 @@ This table is a tagged union. The variant is determined by the `type` field. It - `create-virtual-output`: Creates a virtual output. - + This is a no-op if a virtual output with that name already exists. - + The virtual output has the connector name `VO-{name}` and the serial number `{name}`. - + A newly created connector is initially disabled. When a connector is destroyed and later recreated, its previous state is restored. - + - Example: - + ```toml [shortcuts] alt-x = { type = "create-virtual-output", name = "abcd" } - + [[connectors]] match.name = "VO-abcd" enabled = true - + [[outputs]] match.connector = "VO-abcd" mode = { width = 1920, height = 1080, refresh-rate = 120.0 } @@ -878,7 +948,7 @@ This table is a tagged union. The variant is determined by the `type` field. It - `remove-virtual-output`: Removes a virtual output. - + This is a no-op if no virtual output with that name exists. The table has the following fields: @@ -892,16 +962,16 @@ This table is a tagged union. The variant is determined by the `type` field. It - `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 } @@ -942,6 +1012,126 @@ This table is a tagged union. The variant is determined by the `type` field. It The numbers should be integers. + +### `AnimationCurve` + +Describes a window animation curve. + +Values of this type should have one of the following forms: + +#### A string + +One of the supported curve presets. + +The string should have one of the following values: + +- `linear`: + + No easing. + +- `ease`: + + The CSS `ease` curve. + +- `ease-in`: + + The CSS `ease-in` curve. + +- `ease-out`: + + The CSS `ease-out` curve. + +- `ease-in-out`: + + The CSS `ease-in-out` curve. + + +#### An array + +A custom CSS-style cubic-bezier curve as four numbers: +`x1`, `y1`, `x2`, and `y2`. + +The implicit endpoints are `(0, 0)` and `(1, 1)`. `x1` and `x2` must +be between `0` and `1`. + +Each element of this array should be a number. + + + +### `AnimationStyle` + +Describes a tiled window movement animation style. + +Values of this type should be strings. + +The string should have one of the following values: + +- `plain`: + + Uses a single interpolated movement from each window's current visual + rectangle to its destination rectangle. + +- `multiphase`: + + Uses the no-overlap multiphase planner for tiled window movement when a + supported plan exists. + + + + +### `Animations` + +Describes window animation settings. + +- Example: + + ```toml + [animations] + enabled = true + duration-ms = 160 + style = "multiphase" + curve = [0.25, 0.1, 0.25, 1.0] + ``` + +Values of this type should be tables. + +The table has the following fields: + +- `enabled` (optional): + + Enables or disables window animations. + + The default is `false`. + + The value of this field should be a boolean. + +- `duration-ms` (optional): + + Sets the animation duration in milliseconds. + + The default is `160`. + + The value of this field should be a number. + + The numbers should be integers. + +- `style` (optional): + + Sets the animation style used for tiled window movement animations. + + The default is `multiphase`. + + The value of this field should be a [AnimationStyle](#types-AnimationStyle). + +- `curve` (optional): + + Sets the animation curve. + + The default is `ease-out`. + + The value of this field should be a [AnimationCurve](#types-AnimationCurve). + + ### `BarPosition` @@ -996,12 +1186,12 @@ The string should have one of the following values: - `default`: The default brightness setting. - + The behavior depends on the EOTF: - + - `default`: The maximum brightness of the output. - `PQ`: 203 cd/m^2 - + When used with the default transfer function, the default brightness is anchored at 80 cd/m^2. That is, setting this to 40 cd/m^2 makes everything appear half as bright as normal and creates 50% HDR headroom. @@ -1072,98 +1262,6 @@ The string should have one of the following values: - -### `ClientCapabilities` - -A mask of client capabilities. - -Values of this type should have one of the following forms: - -#### A string - -A named mask. - -The string should have one of the following values: - -- `none`: - - No capabilities. - -- `all`: - - The mask containing all capabilities. - -- `data-control`: - - Grants access to the `ext_data_control_manager_v1` and - `zwlr_data_control_manager_v1` globals. - -- `virtual-keyboard`: - - Grants access to the `zwp_virtual_keyboard_manager_v1` global. - -- `foreign-toplevel-list`: - - Grants access to the `ext_foreign_toplevel_list_v1` global. - -- `idle-notifier`: - - Grants access to the `ext_idle_notifier_v1` global. - -- `session-lock`: - - Grants access to the `ext_session_lock_manager_v1` global. - -- `layer-shell`: - - Grants access to the `zwlr_layer_shell_v1` global. - -- `screencopy`: - - Grants access to the `ext_image_copy_capture_manager_v1` and - `zwlr_screencopy_manager_v1` globals. - -- `seat-manager`: - - Grants access to the `ext_transient_seat_manager_v1` global. - -- `drm-lease`: - - Grants access to the `wp_drm_lease_device_v1` global. - -- `input-method`: - - Grants access to the `zwp_input_method_manager_v2` global. - -- `workspace-manager`: - - Grants access to the `ext_workspace_manager_v1` global. - -- `foreign-toplevel-manager`: - - Grants access to the `zwlr_foreign_toplevel_manager_v1` global. - -- `head-manager`: - - Grants access to the `jay_head_manager_v1` and `zwlr_output_manager_v1` - globals. - -- `gamma-control-manager`: - - Grants access to the `zwlr_gamma_control_manager_v1` global. - -- `virtual-pointer`: - - Grants access to the `zwlr_virtual_pointer_manager_v1` global. - - -#### An array - -An array of masks that are OR'd. - -Each element of this array should be a [ClientCapabilities](#types-ClientCapabilities). - - ### `ClientMatch` @@ -1179,14 +1277,14 @@ The table has the following fields: - `name` (optional): Matches if the client rule with this name matches. - + - Example: - + ```toml [[clients]] name = "spotify" match.sandbox-app-id = "com.spotify.Client" - + # Matches the same clients as the previous rule. [[clients]] match.name = "spotify" @@ -1197,9 +1295,9 @@ The table has the following fields: - `not` (optional): Matches if the contained criteria don't match. - + - Example: - + ```toml [[clients]] name = "not-spotify" @@ -1211,9 +1309,9 @@ The table has the following fields: - `all` (optional): Matches if all of the contained criteria match. - + - Example: - + ```toml [[clients]] match.all = [ @@ -1227,9 +1325,9 @@ The table has the following fields: - `any` (optional): Matches if any of the contained criteria match. - + - Example: - + ```toml [[clients]] match.any = [ @@ -1243,9 +1341,9 @@ The table has the following fields: - `exactly` (optional): Matches if a specific number of contained criteria match. - + - Example: - + ```toml # Matches any client that is either steam or sandboxed by flatpak but not both. [[clients]] @@ -1261,9 +1359,9 @@ The table has the following fields: - `sandboxed` (optional): Matches if the client is/isn't sandboxed. - + - Example: - + ```toml [[clients]] match.sandboxed = true @@ -1274,9 +1372,9 @@ The table has the following fields: - `sandbox-engine` (optional): Matches the engine name of the client's sandbox verbatim. - + - Example: - + ```toml [[clients]] match.sandbox-engine = "org.flatpak" @@ -1287,9 +1385,9 @@ The table has the following fields: - `sandbox-engine-regex` (optional): Matches the engine name of the client's sandbox with a regular expression. - + - Example: - + ```toml [[clients]] match.sandbox-engine = "flatpak" @@ -1300,9 +1398,9 @@ The table has the following fields: - `sandbox-app-id` (optional): Matches the app id of the client's sandbox verbatim. - + - Example: - + ```toml [[clients]] match.sandbox-app-id = "com.spotify.Client" @@ -1313,9 +1411,9 @@ The table has the following fields: - `sandbox-app-id-regex` (optional): Matches the app id of the client's sandbox with a regular expression. - + - Example: - + ```toml [[clients]] match.sandbox-app-id-regex = "(?i)spotify" @@ -1428,16 +1526,16 @@ The table has the following fields: - `name` (optional): The name of this rule. - + This name can be referenced in other rules. - + - Example - + ```toml [[clients]] name = "spotify" match.sandbox-app-id = "com.spotify.Client" - + [[clients]] match.name = "spotify" action = "kill-client" @@ -1463,38 +1561,6 @@ The table has the following fields: The value of this field should be a [Action](#types-Action). -- `capabilities` (optional): - - Sets the capabilities granted to clients matching this matcher. - - If multiple matchers match a client, the capabilities are added. - - If no matcher matches a client, it is granted the default capabilities depending - on whether it's sandboxed or not. If it is not sandboxed, it is granted the - capabilities `layer-shell` and `drm-lease`. Otherwise it is granted the - capability `drm-lease`. - - Regardless of any capabilities set through this function, the capabilities of the - client can never exceed its bounding capabilities. - - The value of this field should be a [ClientCapabilities](#types-ClientCapabilities). - -- `sandbox-bounding-capabilities` (optional): - - Sets the upper capability bounds for clients in sandboxes created by this client. - - If multiple matchers match a client, the capabilities are added. - - If no matcher matches a client, the bounding capabilities for sandboxes depend on - whether the client is itself sandboxed. If it is sandboxed, the bounding - capabilities are the effective capabilities of the client. Otherwise the bounding - capabilities are all capabilities. - - Regardless of any capabilities set through this function, the capabilities set - through this function can never exceed the client's bounding capabilities. - - The value of this field should be a [ClientCapabilities](#types-ClientCapabilities). - ### `Color` @@ -1530,9 +1596,9 @@ The table has the following fields: - `enabled` (optional): Whether the color management protocol is enabled. - + This has no effect on running applications. - + The default is `false`. The value of this field should be a boolean. @@ -1577,23 +1643,23 @@ The table has the following fields: - `mod-mask` (optional): The mod mask to apply to this shortcut. - + Should be a string containing modifiers concatenated by `-`. See the description of `Config.shortcuts` for more details. - + If this field is omitted, all modifiers are included in the mask. - + - Example: - + To raise the volume whenever the XF86AudioRaiseVolume key is pressed regardless of any modifiers except `alt`: - + ```toml [complex-shortcuts.XF86AudioRaiseVolume] mod-mask = "alt" action = { type = "exec", exec = ["pactl", "set-sink-volume", "0", "+10%"] } ``` - + Set `mod-mask = ""` to ignore all modifiers. The value of this field should be a string. @@ -1601,7 +1667,7 @@ The table has the following fields: - `action` (optional): The action to execute. - + Omitting this is the same as setting it to `"none"`. The value of this field should be a [Action](#types-Action). @@ -1609,20 +1675,20 @@ The table has the following fields: - `latch` (optional): An action to execute when the key is released. - + This registers an action to be executed when the key triggering the shortcut is released. The active modifiers are ignored for this purpose. - + - Example: - + To mute audio while the key is pressed: - + ```toml [complex-shortcuts.alt-x] action = { type = "exec", exec = ["pactl", "set-sink-mute", "0", "1"] } latch = { type = "exec", exec = ["pactl", "set-sink-mute", "0", "0"] } ``` - + Audio will be un-muted once `x` key is released, regardless of any other keys that are pressed at the time. @@ -1694,9 +1760,9 @@ The table has the following fields: - `keymap` (optional): The keymap to use. - + - Example: - + ```toml keymap = """ xkb_keymap { @@ -1713,9 +1779,9 @@ The table has the following fields: - `repeat-rate` (optional): The keyboard repeat rate. - + - Example: - + ```toml repeat-rate = { rate = 25, delay = 250 } ``` @@ -1725,28 +1791,28 @@ The table has the following fields: - `shortcuts` (optional): The compositor shortcuts. - + The keys should be in the following format: - + ``` (MOD-)*KEYSYM ``` - + `MOD` should be one of `shift`, `lock`, `ctrl`, `mod1`, `mod2`, `mod3`, `mod4`, `mod5`, `caps`, `alt`, `num`, `logo`, or `release`. - + Using the `release` modifier causes the shortcut to trigger when the key is released. - + `KEYSYM` should be the name of a keysym. The authorative location for these names is [1] with the `XKB_KEY_` prefix removed. - + The keysym should be the unmodified keysym. E.g. `shift-q` not `shift-Q`. - + [1]: https://github.com/xkbcommon/libxkbcommon/blob/master/include/xkbcommon/xkbcommon-keysyms.h - + - Example: - + ```toml [shortcuts] alt-q = "quit" @@ -1757,11 +1823,11 @@ The table has the following fields: - `complex-shortcuts` (optional): Complex compositor shortcuts. - + The keys should have the same format as in the `shortcuts` table. - + - Example: - + ```toml [complex-shortcuts.XF86AudioRaiseVolume] mod-mask = "alt" @@ -1773,11 +1839,11 @@ The table has the following fields: - `on-graphics-initialized` (optional): An action to execute when the graphics have been initialized for the first time. - + This is a good place to start graphical applications. - + - Example: - + ```toml on-graphics-initialized = { type = "exec", exec = "mako" } ``` @@ -1787,9 +1853,9 @@ The table has the following fields: - `status` (optional): The status program that will be used for the status text. - + - Example: - + ```toml [status] format = "i3bar" @@ -1801,24 +1867,24 @@ The table has the following fields: - `outputs` (optional): An array of output configurations. - + This can be used to configure outputs and create named outputs that can be referred to in actions. - + The configurations defined here will only be applied the first time matching outputs are connected to the compositor after the compositor has started. If you want change the configuration afterwards, use `jay randr` or a `configure-output` action. - + - Example: - + ```toml [[outputs]] name = "left" match.serial-number = "33K03894SL0" x = 0 y = 0 - + [[outputs]] name = "right" match.serial-number = "ETW1M02062SL0" @@ -1831,15 +1897,15 @@ The table has the following fields: - `connectors` (optional): An array of connector configurations. - + This can be used to configure connectors. - + The configurations defined here will only be applied when the connector is first discovered by the compositor. This usually never happens after the compositor has started unless you attach an external graphics card. - + - Example: - + ```toml [[connectors]] name = "eDP-1" @@ -1851,7 +1917,7 @@ The table has the following fields: - `workspace-capture` (optional): Configures whether newly created workspaces can be captured. - + The default is `true`. The value of this field should be a boolean. @@ -1859,9 +1925,9 @@ The table has the following fields: - `env` (optional): Defines environment variables that will be set for all applications. - + - Example: - + ```toml [env] GTK_THEME = "Adwaita:dark" @@ -1872,10 +1938,10 @@ The table has the following fields: - `on-startup` (optional): An action to execute as early as possible when the compositor starts. - + At this point, graphics have not yet been initialized. You should not use this to start graphical applications. See `on-graphics-initialized`. - + This setting has no effect during configuration reloads. The value of this field should be a [Action](#types-Action). @@ -1883,23 +1949,23 @@ The table has the following fields: - `keymaps` (optional): Defines named keymaps. - + These keymaps can be used to easily switch between keymaps for different keyboards. - + - Example: - + ```toml keymap.name = "laptop" - + [shortcuts] alt-j = { type = "set-keymap", keymap.name = "laptop" } alt-k = { type = "set-keymap", keymap.name = "external" } - + [[keymaps]] name = "laptop" path = "./laptop-keymap.xkb" - + [[keymaps]] name = "external" path = "./external-keymap.xkb" @@ -1910,12 +1976,12 @@ The table has the following fields: - `log-level` (optional): Sets the log level of the compositor. - + This setting cannot be changed by re-loading the configuration. Use `jay set-log-level` instead. - + - Example: - + ```toml log-level = "debug" ``` @@ -1925,11 +1991,11 @@ The table has the following fields: - `clean-logs-older-than` (optional): If specified on startup, deletes Jay's log files older than the specified time period. - + Possible keys in the table are `days` and `weeks`. - + - Example: - + ```toml clean-logs-older-than.days = 7 ``` @@ -1945,13 +2011,13 @@ The table has the following fields: - `gfx-api` (optional): Sets the graphics API used for newly discovered DRM devices. - + Changing this setting after the compositor has started usually has no effect unless you attach an external graphics card. Use `jay randr` to change the API used by individual devices at runtime. - + - Example: - + ```toml gfx-api = "Vulkan" ``` @@ -1961,21 +2027,21 @@ The table has the following fields: - `drm-devices` (optional): Names and configures DRM devices. - + These settings are only applied to devices discovered after the configuration has been loaded. Therefore changing these settings usually has no effect at runtime unless you attach an external graphics card. You can use `jay randr` or a `configure-drm-device` Action to change these settings at runtime. - + - Example: - + ```toml render-device.name = "dedicated" - + [[drm-devices]] name = "dedicated" match = { pci-vendor = 0x1002, pci-model = 0x73ff } - + [[drm-devices]] name = "integrated" match = { pci-vendor = 0x1002, pci-model = 0x164e } @@ -1993,9 +2059,9 @@ The table has the following fields: - `explicit-sync` (optional): Configures whether the compositor supports explicit sync. - + This cannot be changed after the compositor has started. - + The default is `true`. The value of this field should be a boolean. @@ -2003,14 +2069,14 @@ The table has the following fields: - `render-device` (optional): Selects the device to use for rendering in a system with multiple GPUs. - + The first device that matches will be used. - + - Example: - + ```toml render-device.name = "dedicated" - + [[drm-devices]] name = "dedicated" match = { pci-vendor = 0x1002, pci-model = 0x73ff } @@ -2021,16 +2087,16 @@ The table has the following fields: - `inputs` (optional): Names and configures input devices. - + These settings are only applied to devices connected after the configuration has been loaded. You can apply setting without re-connecting the device by using `jay input` or a `configure-input` Action. - + - Example: - + ```toml render-device.name = "dedicated" - + [[inputs]] match.is-pointer = true left-handed = true @@ -2043,9 +2109,9 @@ The table has the following fields: - `on-idle` (optional): An action to execute when the compositor becomes idle. - + - Example: - + ```toml on-idle = { type = "exec", exec = "lock" } ``` @@ -2055,12 +2121,12 @@ The table has the following fields: - `idle` (optional): The configuration of the idle timeout. - + Changing thise field after compositor startup has no effect. Use `jay idle` or a `configure-idle` action to change the idle timeout at runtime. - + - Example: - + ```toml idle.minutes = 10 ``` @@ -2071,7 +2137,7 @@ The table has the following fields: Configures whether moving the mouse over a window automatically moves the keyboard focus to that window. - + The default is `true`. The value of this field should be a boolean. @@ -2080,18 +2146,18 @@ The table has the following fields: Configures whether the mouse cursor is automatically centered on the active window when focus changes via keyboard commands. - + When enabled, the cursor will be automatically positioned to the center of the active window when focus changes through keyboard commands such as `focus-left`, `focus-right`, `show-workspace`, etc. - + The default is `false`. - + This option is unstable due to various issues. It is not subject to the usual semver guarantees. - + - Example: - + ```toml unstable-mouse-follows-focus = true ``` @@ -2101,12 +2167,12 @@ The table has the following fields: - `window-management-key` (optional): Configures a key that will enable window management mode while pressed. - + In window management mode, floating windows can be moved by pressing the left mouse button and all windows can be resize by pressing the right mouse button. - + - Example: - + ```toml window-management-key = "Alt_L" ``` @@ -2116,13 +2182,13 @@ The table has the following fields: - `vrr` (optional): Configures the default VRR settings. - + This can be overwritten for individual outputs. - + By default, the VRR mode is `never` and the cursor refresh rate is unbounded. - + - Example: - + ```toml vrr = { mode = "always", cursor-hz = 90 } ``` @@ -2132,13 +2198,13 @@ The table has the following fields: - `tearing` (optional): Configures the default tearing settings. - + This can be overwritten for individual outputs. - + By default, the tearing mode is `variant3`. - + - Example: - + ```toml tearing.mode = "never" ``` @@ -2148,9 +2214,9 @@ The table has the following fields: - `libei` (optional): Configures the libei settings. - + - Example: - + ```toml libei.enable-socket = true ``` @@ -2160,21 +2226,39 @@ The table has the following fields: - `ui-drag` (optional): Configures the ui-drag settings. - + - Example: - + ```toml ui-drag = { enabled = false, threshold = 20 } ``` The value of this field should be a [UiDrag](#types-UiDrag). +- `animations` (optional): + + Configures window animations. + + Animations are disabled by default. + + - Example: + + ```toml + [animations] + enabled = true + duration-ms = 160 + style = "multiphase" + curve = "ease-out" + ``` + + The value of this field should be a [Animations](#types-Animations). + - `xwayland` (optional): Configures the Xwayland settings. - + - Example: - + ```toml xwayland = { scaling-mode = "downscaled" } ``` @@ -2184,9 +2268,9 @@ The table has the following fields: - `color-management` (optional): Configures the color-management settings. - + - Example: - + ```toml [color-management] enabled = true @@ -2197,9 +2281,9 @@ The table has the following fields: - `float` (optional): Configures the settings of floating windows. - + - Example: - + ```toml [float] show-pin-icon = true @@ -2210,12 +2294,12 @@ The table has the following fields: - `actions` (optional): Named actions. - + Named actions can be used everywhere an action can be used. This can be used to avoid repeating the same action multiple times. - + - Example: - + ```toml actions.switch-to-1 = [ { type = "show-workspace", name = "1" }, @@ -2230,7 +2314,7 @@ The table has the following fields: { type = "define-action", name = "switch-to-next", action = "$switch-to-1" }, ] actions.switch-to-next = "$switch-to-1" - + [shortcuts] alt-x = "$switch-to-next" ``` @@ -2252,11 +2336,11 @@ The table has the following fields: - `clients` (optional): An array of client rules. - + These rules can be used to give names to clients and to manipulate them. - + - Example: - + ```toml [[clients]] name = "spotify" @@ -2268,11 +2352,11 @@ The table has the following fields: - `windows` (optional): An array of window rules. - + These rules can be used to give names to windows and to manipulate them. - + - Example: - + ```toml [[windows]] name = "spotify" @@ -2285,19 +2369,19 @@ The table has the following fields: - `pointer-revert-key` (optional): Sets the keysym that can be used to revert the pointer to the default state. - + Pressing this key cancels any grabs, drags, selections, etc. - + The default is `Escape`. Setting this to `NoSymbol` effectively disables this functionality. - + The value of the string should be the name of a keysym. The authoritative location for these names is [1] with the `XKB_KEY_` prefix removed. - + [1]: https://github.com/xkbcommon/libxkbcommon/blob/master/include/xkbcommon/xkbcommon-keysyms.h - + - Example: - + ```toml pointer-revert-key = "NoSymbol" ``` @@ -2307,7 +2391,7 @@ The table has the following fields: - `use-hardware-cursor` (optional): Configures whether the default seat uses hardware cursors. - + The default is `true`. The value of this field should be a boolean. @@ -2315,7 +2399,7 @@ The table has the following fields: - `show-bar` (optional): Configures whether the built-in bar is shown. - + The default is `true`. The value of this field should be a boolean. @@ -2323,7 +2407,7 @@ The table has the following fields: - `show-titles` (optional): Configures whether title bars on windows are shown. - + The default is `true`. The value of this field should be a boolean. @@ -2331,9 +2415,9 @@ The table has the following fields: - `focus-history` (optional): Configures the focus-history settings. - + - Example: - + ```toml [focus-history] only-visible: true @@ -2345,22 +2429,34 @@ The table has the following fields: - `middle-click-paste` (optional): Configures whether middle-click pasting is enabled. - + Changing this has no effect on running applications. - + The default is `true`. The value of this field should be a boolean. +- `autotile` (optional): + + Configures whether autotiling is enabled by default. + + When enabled, newly mapped tiled windows alternate their split + orientation automatically. This can also be toggled at runtime via the + `enable-autotile`, `disable-autotile`, and `toggle-autotile` actions. + + The default is `false`. + + The value of this field should be a boolean. + - `modes` (optional): Configures the input modes. - + Modes can be used to define shortcuts that are only active when the mode is active. - + - Example - + ```toml [modes."navigation".shortcuts] w = "focus-up" @@ -2372,7 +2468,7 @@ The table has the following fields: q = "focus-prev" e = "focus-next" ``` - + Modes can be activated with the `push-mode` and `latch-mode` actions. The value of this field should be a table whose values are [InputModes](#types-InputMode). @@ -2380,11 +2476,11 @@ The table has the following fields: - `workspace-display-order` (optional): Configures the order of workspaces displayed. - + The default is `manual`. - + - Example: - + ```toml workspace-display-order = "sorted" ``` @@ -2395,19 +2491,19 @@ The table has the following fields: Configures whether the compositor automatically reloads the configuration when the config file changes on disk. - + When enabled, the compositor uses inotify to watch the config file and its parent directories for changes. Changes are debounced with a 400ms delay to avoid redundant reloads from rapid successive writes. If the file contents have not changed, the reload is skipped. - + Setting this to `false` and will stop the file watcher. Removing this key entirely leaves the watcher state unchanged. - + The default is `false`. - + - Example: - + ```toml auto-reload = true ``` @@ -2417,14 +2513,14 @@ The table has the following fields: - `simple-im` (optional): Configures the simple, XCompose based input method. - - By default, the input method is enabled. - + + By default, the input method is enabled. + Even if the input method is enabled, it will only be used if there is no running external IM. - + - Example: - + ```toml [simple-im] enabled = false @@ -2435,11 +2531,11 @@ The table has the following fields: - `fallback-output-mode` (optional): Sets the fallback output mode. - + The default is `cursor`. - + - Example: - + ```toml fallback-output-mode = "focus" ``` @@ -2452,6 +2548,32 @@ The table has the following fields: The value of this field should be a [Egui](#types-Egui). +- `scratchpads` (optional): + + An array of pre-configured scratchpads. + + Each entry launches a program when the graphics are first initialized and + immediately parks its window in the named scratchpad. The window is captured + via a unique tag attached to the spawned process, so other windows of the + same application are never affected. + + Use a `toggle-scratchpad` or `cycle-scratchpad` action to bring the windows + up; they are always shown floating. + + - Example: + + ```toml + [[scratchpads]] + name = "term" + exec = "foot" + + [[scratchpads]] + name = "notes" + exec = ["obsidian"] + ``` + + The value of this field should be an array of [Scratchpads](#types-Scratchpad). + ### `Connector` @@ -2514,7 +2636,7 @@ The table has the following fields: - `name` (optional): The name of the connector. - + These values are not necessarily stable. You can find out the value by running `jay randr`. @@ -2609,7 +2731,7 @@ The table has the following fields: - `name` (optional): Assigns a name to the rule in the `match` field. - + This only has an effect when used in the top-level `drm-devices` array. The value of this field should be a string. @@ -2635,10 +2757,10 @@ The table has the following fields: - `flip-margin-ms` (optional): If specified, sets the flip margin of this device. - + This is duration between the compositor initiating a page flip and the output's vblank event. This determines the minimum input latency. The default is 1.5 ms. - + Note that if the margin is too small, the compositor will dynamically increase it. The value of this field should be a number. @@ -2676,19 +2798,19 @@ The table has the following fields: - `name` (optional): The name of another DrmDeviceMatch rule. - + For this rule to match, the referenced rule must match. The name of the rule should have been defined in the top-level `drm-devices` array. - + This can be used to easily refer to DRM devices. - + - Example: - + ```toml [shortcuts] alt-v = { type = "configure-drm-device", dev = { match.name = "dedicated", gfx-api = "Vulkan" } } alt-o = { type = "configure-drm-device", dev = { match.name = "dedicated", gfx-api = "OpenGl" } } - + [[drm-devices]] name = "dedicated" match = { pci-vendor = 0x1002, pci-model = 0x73ff } @@ -2699,14 +2821,14 @@ The table has the following fields: - `syspath` (optional): The syspath of the device. - + This is useful if you have multiple copies of the same device installed so that the PCI numbers are not unique. - + The values are usually stable unless you re-configure your hardware. - + - Example: - + ```toml [[drm-devices]] name = "integrated" @@ -2718,11 +2840,11 @@ The table has the following fields: - `devnode` (optional): The devnode of the device. - + The values are usually not-stable across PC restarts. - + - Example: - + ```toml [[drm-devices]] name = "integrated" @@ -2734,9 +2856,9 @@ The table has the following fields: - `vendor` (optional): The name of the vendor. - + - Example: - + ```toml [[drm-devices]] name = "integrated" @@ -2748,9 +2870,9 @@ The table has the following fields: - `model` (optional): The name of the model. - + - Example: - + ```toml [[drm-devices]] name = "integrated" @@ -2762,9 +2884,9 @@ The table has the following fields: - `pci-vendor` (optional): The PCI number of the vendor. - + - Example: - + ```toml [[drm-devices]] name = "integrated" @@ -2778,9 +2900,9 @@ The table has the following fields: - `pci-model` (optional): The PCI number of the model. - + - Example: - + ```toml [[drm-devices]] name = "integrated" @@ -2804,7 +2926,7 @@ The table has the following fields: - `proportional-fonts` (optional): The list of proportional fonts. - + The default is `["sans-serif", "Noto Sans", "Noto Color Emoji"]`. The value of this field should be an array of strings. @@ -2812,7 +2934,7 @@ The table has the following fields: - `monospace-fonts` (optional): The list of monospace fonts. - + The default is `["monospace", "Noto Sans Mono", "Noto Color Emoji"]`. The value of this field should be an array of strings. @@ -2863,13 +2985,6 @@ Describes how to execute a program. ctrl-a = { type = "exec", exec = { prog = "notify-send", args = ["hello world"], env.WAYLAND_DISPLAY = "2" } } ``` -- Example 4: - - ```toml - [shortcuts] - ctrl-a = { type = "exec", exec = { shell = "grim - | wl-copy", privileged = true } } - ``` - Values of this type should have one of the following forms: #### A string @@ -2909,13 +3024,6 @@ Exactly one of the `prog` or `shell` fields must be specified. ctrl-a = { type = "exec", exec = { prog = "notify-send", args = ["hello world"], env.WAYLAND_DISPLAY = "2" } } ``` -- Example 2: - - ```toml - [shortcuts] - ctrl-a = { type = "exec", exec = { shell = "grim - | wl-copy", privileged = true } } - ``` - The table has the following fields: - `prog` (optional): @@ -2934,7 +3042,7 @@ The table has the following fields: - `args` (optional): The arguments to pass to the executable. - + This field must not be specified if a shell command is used. The value of this field should be an array of strings. @@ -2945,14 +3053,6 @@ The table has the following fields: The value of this field should be a table whose values are strings. -- `privileged` (optional): - - If `true`, the executable gets access to privileged wayland protocols. - - The default is `false`. - - The value of this field should be a boolean. - - `tag` (optional): Specifies a tag to apply to all spawned wayland connections. @@ -3001,7 +3101,7 @@ The table has the following fields: - `show-pin-icon` (optional): Sets whether floating windows always show a pin icon. - + The default is `false`. The value of this field should be a boolean. @@ -3027,9 +3127,9 @@ The table has the following fields: - `only-visible` (optional): Sets whether the focus history only moves to windows that are already visible. - + If this is false, then the window will be made visible before focusing it. - + The default is `false`. The value of this field should be a boolean. @@ -3037,7 +3137,7 @@ The table has the following fields: - `same-workspace` (optional): Sets whether the focus history only moves to windows on the same workspace. - + The default is `false`. The value of this field should be a boolean. @@ -3164,7 +3264,7 @@ The string should have one of the following values: - `Vulkan`: The Vulkan API. - + Note that this API has the following restriction: If any of the DRM devices in the system use Vulkan, then all devices must support DRM format modifiers. This is usually the case but not for AMD devices older than RX 5xxx. @@ -3250,11 +3350,11 @@ The table has the following fields: - `grace-period` (optional): The grace period after the timeout expires. - + During the grace period, the screen goes black but the outputs are not yet disabled and the `on-idle` action does not yet run. This is a visual indicator that the system will soon get idle. - + The default is 5 seconds. The value of this field should be a [GracePeriod](#types-GracePeriod). @@ -3280,7 +3380,7 @@ The table has the following fields: - `tag` (optional): Assigns a name to the rule in the `match` field. - + This only has an effect when used in the top-level `inputs` array. The value of this field should be a string. @@ -3294,7 +3394,7 @@ The table has the following fields: - `accel-profile` (optional): The acceleration profile to use. - + See the libinput documentation for more details. The value of this field should be a [AccelProfile](#types-AccelProfile). @@ -3302,9 +3402,9 @@ The table has the following fields: - `accel-speed` (optional): The acceleration speed to use. - + Values should be in the range -1 to 1. - + See the libinput documentation for more details. The value of this field should be a number. @@ -3312,7 +3412,7 @@ The table has the following fields: - `tap-enabled` (optional): Whether tap is enabled for this device. - + See the libinput documentation for more details. The value of this field should be a boolean. @@ -3320,7 +3420,7 @@ The table has the following fields: - `tap-drag-enabled` (optional): Whether tap drag is enabled for this device. - + See the libinput documentation for more details. The value of this field should be a boolean. @@ -3328,7 +3428,7 @@ The table has the following fields: - `tap-drag-lock-enabled` (optional): Whether tap drag lock is enabled for this device. - + See the libinput documentation for more details. The value of this field should be a boolean. @@ -3336,7 +3436,7 @@ The table has the following fields: - `left-handed` (optional): Whether the device is left handed. - + See the libinput documentation for more details. The value of this field should be a boolean. @@ -3344,7 +3444,7 @@ The table has the following fields: - `natural-scrolling` (optional): Whether the device uses natural scrolling. - + See the libinput documentation for more details. The value of this field should be a boolean. @@ -3352,7 +3452,7 @@ The table has the following fields: - `middle-button-emulation` (optional): Converts a simultaneous left and right button click into a middle button click. - + See the libinput documentation for more details. The value of this field should be a boolean. @@ -3360,7 +3460,7 @@ The table has the following fields: - `click-method` (optional): Defines how button events are triggered on a clickable touchpad. - + See the libinput documentation for more details. The value of this field should be a [ClickMethod](#types-ClickMethod). @@ -3375,9 +3475,9 @@ The table has the following fields: A transformation matrix to apply to each motion event of this device. The matrix should be 2x2. - + - Example: To slow down the mouse to 35% of normal speed: - + ```toml [[inputs]] match.is-pointer = true @@ -3389,11 +3489,11 @@ The table has the following fields: - `keymap` (optional): The keymap to use for this device. - + This overrides the global keymap. The keymap becomes active when a key is pressed. - + - Example: - + ```toml [[inputs]] match.name = "ZSA Technology Labs Inc ErgoDox EZ" @@ -3405,11 +3505,11 @@ The table has the following fields: - `on-lid-closed` (optional): An action to execute when the laptop lid is closed. - + This should only be used in the top-level inputs array. - + - Example: - + ```toml [[inputs]] match.name = "" @@ -3422,11 +3522,11 @@ The table has the following fields: - `on-lid-opened` (optional): An action to execute when the laptop lid is opened. - + This should only be used in the top-level inputs array. - + - Example: - + ```toml [[inputs]] match.name = "" @@ -3439,7 +3539,7 @@ The table has the following fields: - `on-converted-to-laptop` (optional): An action to execute when the convertible device is converted to a laptop. - + This should only be used in the top-level inputs array. The value of this field should be a [Action](#types-Action). @@ -3447,7 +3547,7 @@ The table has the following fields: - `on-converted-to-tablet` (optional): An action to execute when the convertible device is converted to a tablet. - + This should only be used in the top-level inputs array. The value of this field should be a [Action](#types-Action). @@ -3455,11 +3555,11 @@ The table has the following fields: - `output` (optional): Maps this input device to an output. - + This is used to map touch screen and graphics tablets to outputs. - + - Example: - + ```toml [[inputs]] match.name = "Wacom Bamboo Comic 2FG Pen" @@ -3471,15 +3571,15 @@ The table has the following fields: - `remove-mapping` (optional): Removes the mapping of from this device to an output. - + This should only be used within `configure-input` actions. - + - Example: - + ```toml [shortcuts] alt-x = { type = "configure-input", input = { match.tag = "wacom", remove-mapping = true } } - + [[inputs]] tag = "wacom" match.name = "Wacom Bamboo Comic 2FG Pen" @@ -3491,11 +3591,11 @@ The table has the following fields: - `calibration-matrix` (optional): The calibration matrix of the device. This matrix should be 2x3. - + See the libinput documentation for more details. - + - Example: To flip the device 90 degrees: - + ```toml [[inputs]] calibration-matrix = [[0, 1, 0], [-1, 0, 1]] @@ -3536,19 +3636,19 @@ The table has the following fields: - `tag` (optional): The tag of another InputMatch rule. - + For this rule to match, the referenced rule must match. The name of the rule should have been defined in the top-level `inputs` array. - + This can be used to easily refer to input devices. - + - Example: - + ```toml [shortcuts] alt-l = { type = "configure-input", input = { match.tag = "mouse", left-handed = true } } alt-r = { type = "configure-input", input = { match.tag = "mouse", left-handed = false } } - + [[inputs]] tag = "mouse" match.is-pointer = true @@ -3559,11 +3659,11 @@ The table has the following fields: - `name` (optional): The libinput name of the device. - + You can find out the name of the devices by running `jay input`. - + - Example: - + ```toml [[inputs]] match.name = "Logitech G300s Optical Gaming Mouse" @@ -3575,14 +3675,14 @@ The table has the following fields: - `syspath` (optional): The syspath of the device. - + This is useful if you have multiple copies of the same device installed so that the name is not unique. - + The values are usually stable unless you re-configure your hardware. - + - Example: - + ```toml [[inputs]] match.syspath = "/sys/devices/pci0000:00/0000:00:08.1/0000:14:00.4/usb5/5-1/5-1.1/5-1.1.2/5-1.1.2:1.0" @@ -3594,11 +3694,11 @@ The table has the following fields: - `devnode` (optional): The devnode of the device. - + The values are usually not-stable across PC restarts. - + - Example: - + ```toml [[inputs]] match.devnode = "/dev/input/event4" @@ -3610,9 +3710,9 @@ The table has the following fields: - `is-keyboard` (optional): Whether the devices has been identified as a keyboard. - + - Example: - + ```toml [[inputs]] match.is-keyboard = false @@ -3624,9 +3724,9 @@ The table has the following fields: - `is-pointer` (optional): Whether the devices has been identified as a pointer. - + - Example: - + ```toml [[inputs]] match.is-pointer = false @@ -3638,9 +3738,9 @@ The table has the following fields: - `is-touch` (optional): Whether the devices has been identified as a touch device. - + - Example: - + ```toml [[inputs]] match.is-touch = true @@ -3652,9 +3752,9 @@ The table has the following fields: - `is-tablet-tool` (optional): Whether the devices has been identified as a tablet tool. - + - Example: - + ```toml [[inputs]] match.is-tablet-tool = true @@ -3666,9 +3766,9 @@ The table has the following fields: - `is-tablet-pad` (optional): Whether the devices has been identified as a tablet pad. - + - Example: - + ```toml [[inputs]] match.is-tablet-tool = true @@ -3680,9 +3780,9 @@ The table has the following fields: - `is-gesture` (optional): Whether the devices has been identified as a gesture device. - + - Example: - + ```toml [[inputs]] match.is-gesture = true @@ -3693,9 +3793,9 @@ The table has the following fields: - `is-switch` (optional): Whether the devices has been identified as a switch. - + - Example: - + ```toml [[inputs]] match.is-switch = true @@ -3732,10 +3832,10 @@ The table has the following fields: - `parent` (optional): The parent of this input mode. - + This mode inherits all shortcuts from this parent. If this field is not set, then it inherits the shortcuts from the top-level shortcuts. - + Note that you can disable a shortcut by explicitly assigning it the action `none`. The value of this field should be a string. @@ -3743,7 +3843,7 @@ The table has the following fields: - `shortcuts` (optional): The shortcuts of this mode. - + See the same field in the top-level `Config` object for a description. The value of this field should be a table whose values are [Actions](#types-Action). @@ -3751,7 +3851,7 @@ The table has the following fields: - `complex-shortcuts` (optional): The complex shortcuts of this mode. - + See the same field in the top-level `Config` object for a description. The value of this field should be a table whose values are [ComplexShortcuts](#types-ComplexShortcut). @@ -3810,10 +3910,10 @@ The table has the following fields: - `name` (optional): Defines a keymap name or references a defined keymap. - + If the value is set in the top-level `keymaps` array, it defines a named keymap. - + Otherwise it references a named keymap that should have been defined in the `keymaps` array. @@ -3822,7 +3922,7 @@ The table has the following fields: - `map` (optional): Defines a keymap by its XKB representation. - + For each keymap defined in the top-level `keymaps` array, exactly one of `map`, `path`, and `rmlvo` has to be defined. @@ -3831,10 +3931,10 @@ The table has the following fields: - `path` (optional): Loads a keymap's XKB representation from a file. - + If the path is relative, it will be interpreted relative to the Jay config directory. - + For each keymap defined in the top-level `keymaps` array, exactly one of `map`, `path`, and `rmlvo` has to be defined. @@ -3843,7 +3943,7 @@ The table has the following fields: - `rmlvo` (optional): Creates a keymap from RMLVO names. - + For each keymap defined in the top-level `keymaps` array, exactly one of `map`, `path`, and `rmlvo` has to be defined. @@ -3868,9 +3968,7 @@ The table has the following fields: - `enable-socket` (optional): Enables or disables the unauthenticated libei socket. - - Even if the socket is disabled, application can still request access via the portal. - + The default is `false`. The value of this field should be a boolean. @@ -3928,10 +4026,10 @@ The table has the following fields: - `key` (optional): Identifies a mark by a key press. - + The names of the keys can be found in [1] with the `KEY_` prefix removed. The key names must be written all lowercase. - + [1]: https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h The value of this field should be a string. @@ -4026,7 +4124,7 @@ The table has the following fields: - `name` (optional): Assigns a name to the rule in the `match` field. - + This only has an effect when used in the top-level `outputs` array. The value of this field should be a string. @@ -4074,7 +4172,7 @@ The table has the following fields: - `mode` (optional): The mode of the output. - + If the refresh rate is not specified, the first mode with the specified width and height is used. @@ -4083,11 +4181,11 @@ The table has the following fields: - `vrr` (optional): Configures the VRR settings of this output. - + By default, the VRR mode is `never` and the cursor refresh rate is unbounded. - + - Example: - + ```toml [[outputs]] match.serial-number = "33K03894SL0" @@ -4099,11 +4197,11 @@ The table has the following fields: - `tearing` (optional): Configures the tearing settings of this output. - + By default, the tearing mode is `variant3`. - + - Example: - + ```toml [[outputs]] match.serial-number = "33K03894SL0" @@ -4115,11 +4213,11 @@ The table has the following fields: - `format` (optional): Configures the framebuffer format of this output. - + By default, the format is `xrgb8888`. - + - Example: - + ```toml [[outputs]] match.serial-number = "33K03894SL0" @@ -4143,7 +4241,7 @@ The table has the following fields: - `brightness` (optional): The brightness of the output. - + This setting has no effect unless the vulkan renderer is used. The value of this field should be a [Brightness](#types-Brightness). @@ -4151,7 +4249,7 @@ The table has the following fields: - `blend-space` (optional): The blend space of the output. - + The default is `srgb`. The value of this field should be a [BlendSpace](#types-BlendSpace). @@ -4159,20 +4257,20 @@ The table has the following fields: - `use-native-gamut` (optional): Configures whether the display primaries are used. - + By default, Jay pretends that the display uses sRGB primaries. This is also how most other systems behave. In reality, most displays use a much larger gamut. For example, they advertise that they support 95% of the DCI-P3 gamut. If the display is interpreting colors in their native gamut, then colors will appear more saturated than their specification. - + If this is set to `true`, Jay assumes that the display uses the primaries advertised in its EDID. This might produce more accurate colors while also allowing color-managed applications to use the full gamut of the display. - + This setting has no effect when the display is explicitly operating in a wide color space. - + The default is `false`. The value of this field should be a boolean. @@ -4212,19 +4310,19 @@ The table has the following fields: - `name` (optional): The name of another OutputMatch rule. - + For this rule to match, the referenced rule must match. The name of the rule should have been defined in the top-level `outputs` array. - + This can be used to easily refer to outputs. - + - Example: - + ```toml [shortcuts] alt-l = { type = "configure-output", output = { match.name = "right", transform = "none" } } alt-r = { type = "configure-output", output = { match.name = "right", transform = "rotate-90" } } - + [[outputs]] name = "right" match.serial-number = "33K03894SL0" @@ -4235,11 +4333,11 @@ The table has the following fields: - `connector` (optional): The name of the connector the output is connected to. - + You can find out the name of the connector by running `jay randr`. - + - Example: - + ```toml [[outputs]] match.connector = "DP-1" @@ -4251,11 +4349,11 @@ The table has the following fields: - `serial-number` (optional): The serial number of the output. - + You can find out the serial number by running `jay randr`. - + - Example: - + ```toml [[outputs]] match.serial-number = "33K03894SL0" @@ -4267,11 +4365,11 @@ The table has the following fields: - `manufacturer` (optional): The manufacturer of the output. - + You can find out the manufacturer by running `jay randr`. - + - Example: - + ```toml [[outputs]] match.manufacturer = "BNQ" @@ -4283,11 +4381,11 @@ The table has the following fields: - `model` (optional): The model of the output. - + You can find out the model by running `jay randr`. - + - Example: - + ```toml [[outputs]] match.model = "BenQ GW2480" @@ -4385,6 +4483,40 @@ The table has the following fields: The value of this field should be a string. + +### `Scratchpad` + +A pre-configured scratchpad whose program is launched at startup and parked +in the scratchpad. + +- Example: + + ```toml + [[scratchpads]] + name = "term" + exec = "foot" + ``` + +Values of this type should be tables. + +The table has the following fields: + +- `name` (required): + + The name of the scratchpad that the spawned window is parked in. + + The value of this field should be a string. + +- `exec` (optional): + + The program to launch when the graphics are first initialized. + + If omitted, no program is launched and the scratchpad is only created on + demand by `send-to-scratchpad`. + + The value of this field should be a [Exec](#types-Exec). + + ### `SimpleActionName` @@ -4476,6 +4608,18 @@ The string should have one of the following values: Toggles the current group between tabbed and split mode. +- `enable-autotile`: + + Enables alternating split orientation for newly tiled windows. + +- `disable-autotile`: + + Disables alternating split orientation for newly tiled windows. + +- `toggle-autotile`: + + Toggles alternating split orientation for newly tiled windows. + - `toggle-fullscreen`: Toggle the currently focused window between fullscreen and windowed. @@ -4488,6 +4632,18 @@ The string should have one of the following values: Makes the currently focused window windowed. +- `send-to-scratchpad`: + + Sends the currently focused window to the default scratchpad. + +- `toggle-scratchpad`: + + Toggles the default scratchpad. + +- `cycle-scratchpad`: + + Cycles through the windows of the default scratchpad. + - `focus-parent`: Focus the parent of the currently focused window. @@ -4500,7 +4656,7 @@ The string should have one of the following values: Disable the currently active pointer constraint, allowing you to move the pointer outside the window. - + The constraint will be re-enabled when the pointer re-enters the window. - `toggle-floating`: @@ -4523,41 +4679,37 @@ The string should have one of the following values: Reload the `config.toml`. -- `reload-config-so`: - - Reload the `config.so`. - - `consume`: Consume the current key event. Don't forward it to the focused application. - + This action only has an effect in shortcuts. - + Key-press events events that trigger shortcuts are consumed by default. Key-release events events that trigger shortcuts are forwarded by default. - + Note that consuming key-release events can cause keys to get stuck in the focused application. - + See the `forward` action to achieve the opposite effect. - `forward`: Forward the current key event to the focused application. - + See the `consume` action for more details. - `none`: Perform no action. - + As a special case, if this is the action of a shortcut, the shortcut will be unbound. This can be used in modes to unbind a key. - `enable-window-management`: Enables window management mode. - + In window management mode, floating windows can be moved by pressing the left mouse button and all windows can be resize by pressing the right mouse button. @@ -4568,7 +4720,7 @@ The string should have one of the following values: - `enable-float-above-fullscreen`: Enables floating windows showing above fullscreen windows. - + By default, floating windows are hidden below fullscreen windows. - `disable-float-above-fullscreen`: @@ -4582,7 +4734,7 @@ The string should have one of the following values: - `pin-float`: Pins the currently focused floating window. - + If a floating window is pinned, it will stay visible even when switching to a different workspace. @@ -4597,7 +4749,7 @@ The string should have one of the following values: - `kill-client`: Kills a client. - + Within a window rule, it applies to the client of the window. Within a client rule it applies to the matched client. Has no effect otherwise. @@ -4648,13 +4800,13 @@ The string should have one of the following values: - `create-mark`: Interactively creates a mark. - + The next pressed key becomes the identifier for the mark. - `jump-to-mark`: Interactively jumps to a mark. - + The next pressed key identifies the mark to jump to. - `clear-modes`: @@ -4668,7 +4820,7 @@ The string should have one of the following values: - `enable-simple-im`: Enables the simple, XCompose based input method. - + Even if the input method is enabled, it will only be used if there is no running external IM. @@ -4683,13 +4835,13 @@ The string should have one of the following values: - `reload-simple-im`: Reloads the simple, XCompose based input method. - + This is useful if you change the XCompose files after starting the compositor. - `enable-unicode-input`: Enables Unicode input in the simple, XCompose based input method. - + This has no effect if the simple IM is not currently active. - `warp-mouse-to-focus`: @@ -4717,7 +4869,7 @@ The table has the following fields: - `enabled` (optional): Whether the input method is enabled. - + Even if the input method is enabled, it will only be used if there is no running external IM. @@ -4756,7 +4908,7 @@ The table has the following fields: - `i3bar-separator` (optional): The separator to be used between i3bar components. - + The default is ` | `. The value of this field should be a string. @@ -5083,7 +5235,7 @@ The table has the following fields: - `enabled` (optional): Enables or disables dragging of tiles and workspaces. - + The default is `true`. The value of this field should be a boolean. @@ -5091,7 +5243,7 @@ The table has the following fields: - `threshold` (optional): Sets the distance at which ui dragging starts. - + The default is `10`. The value of this field should be a number. @@ -5123,7 +5275,7 @@ The table has the following fields: - `cursor-hz` (optional): The VRR cursor refresh rate. - + Limits the rate at which cursor movement forces a screen update when VRR is active. @@ -5232,14 +5384,14 @@ The table has the following fields: - `name` (optional): Matches if the window rule with this name matches. - + - Example: - + ```toml [[windows]] name = "spotify" match.title-regex = "Spotify" - + # Matches the same windows as the previous rule. [[windows]] match.name = "spotify" @@ -5250,9 +5402,9 @@ The table has the following fields: - `not` (optional): Matches if the contained criteria don't match. - + - Example: - + ```toml [[windows]] name = "not-spotify" @@ -5264,9 +5416,9 @@ The table has the following fields: - `all` (optional): Matches if all of the contained criteria match. - + - Example: - + ```toml [[windows]] match.all = [ @@ -5280,9 +5432,9 @@ The table has the following fields: - `any` (optional): Matches if any of the contained criteria match. - + - Example: - + ```toml [[windows]] match.any = [ @@ -5296,9 +5448,9 @@ The table has the following fields: - `exactly` (optional): Matches if a specific number of contained criteria match. - + - Example: - + ```toml # Matches any window that is either Alacritty or on workspace 3 but not both. [[windows]] @@ -5380,7 +5532,7 @@ The table has the following fields: - `just-mapped` (optional): Matches if the window has/hasn't just been mapped. - + This is true for one iteration of the compositor's main loop immediately after the window has been mapped. @@ -5487,16 +5639,16 @@ The table has the following fields: - `name` (optional): The name of this rule. - + This name can be referenced in other rules. - + - Example - + ```toml [[windows]] name = "spotify" match.title-regex = "Spotify" - + [[windows]] match.name = "spotify" action = "enter-fullscreen" @@ -5525,7 +5677,7 @@ The table has the following fields: - `auto-focus` (optional): Whether newly mapped windows that match this rule get the keyboard focus. - + If a window matches any rule for which this is false, the window will not be automatically focused. @@ -5620,21 +5772,21 @@ The string should have one of the following values: - `default`: The default mode. - + Currently this means that windows are rendered at the lowest scale and then upscaled if necessary. - `downscaled`: Windows are rendered at the highest integer scale and then downscaled. - + This has significant performance implications unless the window is running on the output with the highest scale and that scale is an integer scale. - + For example, on a 3840x2160 output with a 1.5 scale, a fullscreen window will be rendered at 3840x2160 * 2 / 1.5 = 5120x2880 pixels and then downscaled to 3840x2160. This overhead gets worse the lower the scale of the output is. - + Additionally, this mode requires the X window to scale its contents itself. In the example above, you might achieve this by setting the environment variable `GDK_SCALE=2`. @@ -5659,7 +5811,7 @@ The table has the following fields: - `enabled` (optional): Enables or disables XWayland. - + The default is `true`. The value of this field should be a boolean. @@ -5669,5 +5821,3 @@ The table has the following fields: The scaling mode of X windows. The value of this field should be a [XScalingMode](#types-XScalingMode). - - diff --git a/toml-spec/spec/spec.yaml b/crates/toml-spec/spec/spec.yaml similarity index 94% rename from toml-spec/spec/spec.yaml rename to crates/toml-spec/spec/spec.yaml index aa6789da..6c3b96dc 100644 --- a/toml-spec/spec/spec.yaml +++ b/crates/toml-spec/spec/spec.yaml @@ -345,6 +345,64 @@ Action: description: The name of the workspace. required: true kind: string + send-to-scratchpad: + description: | + Sends the currently focused window to a scratchpad and hides it. + + A scratchpad can hold any number of windows. If `name` is omitted, the + default scratchpad is used. + + - Example: + + ```toml + [shortcuts] + alt-shift-minus = { type = "send-to-scratchpad", name = "terminal" } + ``` + fields: + name: + description: The name of the scratchpad. + required: false + kind: string + toggle-scratchpad: + description: | + Toggles a scratchpad. + + If the scratchpad has a visible window, that window is hidden. Otherwise, the + most recently hidden window in the scratchpad is shown on the current workspace. + Only one window of a scratchpad is shown at a time, and scratchpad windows are + always shown floating. If `name` is omitted, the default scratchpad is used. + + - Example: + + ```toml + [shortcuts] + alt-minus = { type = "toggle-scratchpad", name = "terminal" } + ``` + fields: + name: + description: The name of the scratchpad. + required: false + kind: string + cycle-scratchpad: + description: | + Cycles through the windows of a scratchpad, one at a time. + + With no window shown, the first window is brought up. Each further invocation + hides the current window and shows the next; after the last window the + scratchpad is hidden again. Scratchpad windows are always shown floating. + If `name` is omitted, the default scratchpad is used. + + - Example: + + ```toml + [shortcuts] + alt-minus = { type = "cycle-scratchpad", name = "terminal" } + ``` + fields: + name: + description: The name of the scratchpad. + required: false + kind: string move-to-output: description: | Moves a workspace to a different output. @@ -915,13 +973,6 @@ Exec: [shortcuts] ctrl-a = { type = "exec", exec = { prog = "notify-send", args = ["hello world"], env.WAYLAND_DISPLAY = "2" } } ``` - - - Example 4: - - ```toml - [shortcuts] - ctrl-a = { type = "exec", exec = { shell = "grim - | wl-copy", privileged = true } } - ``` kind: variable variants: - kind: string @@ -959,12 +1010,6 @@ Exec: ctrl-a = { type = "exec", exec = { prog = "notify-send", args = ["hello world"], env.WAYLAND_DISPLAY = "2" } } ``` - - Example 2: - - ```toml - [shortcuts] - ctrl-a = { type = "exec", exec = { shell = "grim - | wl-copy", privileged = true } } - ``` fields: prog: kind: string @@ -991,13 +1036,6 @@ Exec: values: kind: string description: The environment variables to pass to the executable. - privileged: - kind: boolean - required: false - description: | - If `true`, the executable gets access to privileged wayland protocols. - - The default is `false`. tag: kind: string required: false @@ -1064,12 +1102,24 @@ SimpleActionName: description: Toggles the current group's direction. - value: toggle-tab description: Toggles the current group between tabbed and split mode. + - value: enable-autotile + description: Enables alternating split orientation for newly tiled windows. + - value: disable-autotile + description: Disables alternating split orientation for newly tiled windows. + - value: toggle-autotile + description: Toggles alternating split orientation for newly tiled windows. - value: toggle-fullscreen description: Toggle the currently focused window between fullscreen and windowed. - value: enter-fullscreen description: Makes the currently focused window fullscreen. - value: exit-fullscreen description: Makes the currently focused window windowed. + - value: send-to-scratchpad + description: Sends the currently focused window to the default scratchpad. + - value: toggle-scratchpad + description: Toggles the default scratchpad. + - value: cycle-scratchpad + description: Cycles through the windows of the default scratchpad. - value: focus-parent description: Focus the parent of the currently focused window. - value: close @@ -1090,8 +1140,6 @@ SimpleActionName: description: Terminate the compositor. - value: reload-config-toml description: Reload the `config.toml`. - - value: reload-config-so - description: Reload the `config.so`. - value: consume description: | Consume the current key event. Don't forward it to the focused application. @@ -2942,6 +2990,23 @@ Config: ```toml ui-drag = { enabled = false, threshold = 20 } ``` + animations: + ref: Animations + required: false + description: | + Configures window animations. + + Animations are disabled by default. + + - Example: + + ```toml + [animations] + enabled = true + duration-ms = 160 + style = "multiphase" + curve = "ease-out" + ``` xwayland: ref: Xwayland required: false @@ -3112,10 +3177,21 @@ Config: required: false description: | Configures whether middle-click pasting is enabled. - + Changing this has no effect on running applications. The default is `true`. + autotile: + kind: boolean + required: false + description: | + Configures whether autotiling is enabled by default. + + When enabled, newly mapped tiled windows alternate their split + orientation automatically. This can also be toggled at runtime via the + `enable-autotile`, `disable-autotile`, and `toggle-autotile` actions. + + The default is `false`. modes: kind: map values: @@ -3212,6 +3288,61 @@ Config: required: false description: | Sets the egui settings of the compositor. + scratchpads: + kind: array + items: + ref: Scratchpad + required: false + description: | + An array of pre-configured scratchpads. + + Each entry launches a program when the graphics are first initialized and + immediately parks its window in the named scratchpad. The window is captured + via a unique tag attached to the spawned process, so other windows of the + same application are never affected. + + Use a `toggle-scratchpad` or `cycle-scratchpad` action to bring the windows + up; they are always shown floating. + + - Example: + + ```toml + [[scratchpads]] + name = "term" + exec = "foot" + + [[scratchpads]] + name = "notes" + exec = ["obsidian"] + ``` + + +Scratchpad: + kind: table + description: | + A pre-configured scratchpad whose program is launched at startup and parked + in the scratchpad. + + - Example: + + ```toml + [[scratchpads]] + name = "term" + exec = "foot" + ``` + fields: + name: + kind: string + required: true + description: The name of the scratchpad that the spawned window is parked in. + exec: + ref: Exec + required: false + description: | + The program to launch when the graphics are first initialized. + + If omitted, no program is launched and the scratchpad is only created on + demand by `send-to-scratchpad`. Idle: @@ -3549,8 +3680,6 @@ Libei: description: | Enables or disables the unauthenticated libei socket. - Even if the socket is disabled, application can still request access via the portal. - The default is `false`. @@ -3655,6 +3784,97 @@ UiDrag: The default is `10`. +Animations: + kind: table + description: | + Describes window animation settings. + + - Example: + + ```toml + [animations] + enabled = true + duration-ms = 160 + style = "multiphase" + curve = [0.25, 0.1, 0.25, 1.0] + ``` + fields: + enabled: + kind: boolean + required: false + description: | + Enables or disables window animations. + + The default is `false`. + duration-ms: + kind: number + integer_only: true + required: false + description: | + Sets the animation duration in milliseconds. + + The default is `160`. + style: + ref: AnimationStyle + required: false + description: | + Sets the animation style used for tiled window movement animations. + + The default is `multiphase`. + curve: + ref: AnimationCurve + required: false + description: | + Sets the animation curve. + + The default is `ease-out`. + + +AnimationStyle: + kind: string + description: | + Describes a tiled window movement animation style. + values: + - value: plain + description: | + Uses a single interpolated movement from each window's current visual + rectangle to its destination rectangle. + - value: multiphase + description: | + Uses the no-overlap multiphase planner for tiled window movement when a + supported plan exists. + + +AnimationCurve: + kind: variable + description: | + Describes a window animation curve. + variants: + - kind: string + description: | + One of the supported curve presets. + values: + - value: linear + description: No easing. + - value: ease + description: The CSS `ease` curve. + - value: ease-in + description: The CSS `ease-in` curve. + - value: ease-out + description: The CSS `ease-out` curve. + - value: ease-in-out + description: The CSS `ease-in-out` curve. + - kind: array + items: + kind: number + description: | + A custom CSS-style cubic-bezier curve as four numbers: + `x1`, `y1`, `x2`, and `y2`. + + The implicit endpoints are `(0, 0)` and `(1, 1)`. `x1` and `x2` must + be between `0` and `1`. + + Xwayland: kind: table description: | @@ -3840,36 +4060,6 @@ ClientRule: ref: Action required: false description: An action to execute when a client no longer matches the criteria. - capabilities: - ref: ClientCapabilities - required: false - description: | - Sets the capabilities granted to clients matching this matcher. - - If multiple matchers match a client, the capabilities are added. - - If no matcher matches a client, it is granted the default capabilities depending - on whether it's sandboxed or not. If it is not sandboxed, it is granted the - capabilities `layer-shell` and `drm-lease`. Otherwise it is granted the - capability `drm-lease`. - - Regardless of any capabilities set through this function, the capabilities of the - client can never exceed its bounding capabilities. - sandbox-bounding-capabilities: - ref: ClientCapabilities - required: false - description: | - Sets the upper capability bounds for clients in sandboxes created by this client. - - If multiple matchers match a client, the capabilities are added. - - If no matcher matches a client, the bounding capabilities for sandboxes depend on - whether the client is itself sandboxed. If it is sandboxed, the bounding - capabilities are the effective capabilities of the client. Otherwise the bounding - capabilities are all capabilities. - - Regardless of any capabilities set through this function, the capabilities set - through this function can never exceed the client's bounding capabilities. ClientMatch: @@ -4552,72 +4742,6 @@ Direction: description: The down direction. -ClientCapabilities: - description: | - A mask of client capabilities. - kind: variable - variants: - - kind: string - description: A named mask. - values: - - value: none - description: No capabilities. - - value: all - description: The mask containing all capabilities. - - value: data-control - description: | - Grants access to the `ext_data_control_manager_v1` and - `zwlr_data_control_manager_v1` globals. - - value: virtual-keyboard - description: | - Grants access to the `zwp_virtual_keyboard_manager_v1` global. - - value: foreign-toplevel-list - description: | - Grants access to the `ext_foreign_toplevel_list_v1` global. - - value: idle-notifier - description: | - Grants access to the `ext_idle_notifier_v1` global. - - value: session-lock - description: | - Grants access to the `ext_session_lock_manager_v1` global. - - value: layer-shell - description: | - Grants access to the `zwlr_layer_shell_v1` global. - - value: screencopy - description: | - Grants access to the `ext_image_copy_capture_manager_v1` and - `zwlr_screencopy_manager_v1` globals. - - value: seat-manager - description: | - Grants access to the `ext_transient_seat_manager_v1` global. - - value: drm-lease - description: | - Grants access to the `wp_drm_lease_device_v1` global. - - value: input-method - description: | - Grants access to the `zwp_input_method_manager_v2` global. - - value: workspace-manager - description: | - Grants access to the `ext_workspace_manager_v1` global. - - value: foreign-toplevel-manager - description: | - Grants access to the `zwlr_foreign_toplevel_manager_v1` global. - - value: head-manager - description: | - Grants access to the `jay_head_manager_v1` and `zwlr_output_manager_v1` - globals. - - value: gamma-control-manager - description: | - Grants access to the `zwlr_gamma_control_manager_v1` global. - - value: virtual-pointer - description: | - Grants access to the `zwlr_virtual_pointer_manager_v1` global. - - kind: array - description: An array of masks that are OR'd. - items: - ref: ClientCapabilities - - SimpleIm: kind: table description: | diff --git a/toml-spec/spec/template.md b/crates/toml-spec/spec/template.md similarity index 100% rename from toml-spec/spec/template.md rename to crates/toml-spec/spec/template.md diff --git a/toml-spec/src/json_schema.rs b/crates/toml-spec/src/json_schema.rs similarity index 100% rename from toml-spec/src/json_schema.rs rename to crates/toml-spec/src/json_schema.rs diff --git a/toml-spec/src/main.rs b/crates/toml-spec/src/main.rs similarity index 100% rename from toml-spec/src/main.rs rename to crates/toml-spec/src/main.rs diff --git a/toml-spec/src/markdown.rs b/crates/toml-spec/src/markdown.rs similarity index 100% rename from toml-spec/src/markdown.rs rename to crates/toml-spec/src/markdown.rs diff --git a/toml-spec/src/types.rs b/crates/toml-spec/src/types.rs similarity index 100% rename from toml-spec/src/types.rs rename to crates/toml-spec/src/types.rs diff --git a/crates/tracy/Cargo.toml b/crates/tracy/Cargo.toml new file mode 100644 index 00000000..3bc2704b --- /dev/null +++ b/crates/tracy/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "jay-tracy" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +ahash = { version = "0.8.7", optional = true } +parking_lot = { version = "0.12.1", optional = true } +rustc-demangle = { version = "0.1.24", optional = true } +tracy-client-sys = { version = "0.24.1", features = ["ondemand", "manual-lifetime", "debuginfod", "demangle"], optional = true } + +[features] +tracy = ["dep:ahash", "dep:parking_lot", "dep:rustc-demangle", "dep:tracy-client-sys"] diff --git a/src/tracy.rs b/crates/tracy/src/lib.rs similarity index 100% rename from src/tracy.rs rename to crates/tracy/src/lib.rs diff --git a/src/tracy/tracy_impl.rs b/crates/tracy/src/tracy_impl.rs similarity index 94% rename from src/tracy/tracy_impl.rs rename to crates/tracy/src/tracy_impl.rs index 5b5d0b51..43b8d4ce 100644 --- a/src/tracy/tracy_impl.rs +++ b/crates/tracy/src/tracy_impl.rs @@ -66,9 +66,10 @@ impl ZoneName { } } +#[macro_export] macro_rules! create_zone_name { ($($tt:tt)*) => { - crate::tracy::ZoneName::__get(&format!($($tt)*)) + $crate::ZoneName::__get(&format!($($tt)*)) }; } @@ -85,6 +86,7 @@ impl Drop for RunningZone { } } +#[macro_export] macro_rules! dynamic_raii_zone { ($name:expr) => {{ let name: ZoneName = $name; @@ -92,16 +94,18 @@ macro_rules! dynamic_raii_zone { }}; } +#[macro_export] macro_rules! dynamic_zone { ($name:expr) => { let _zone = dynamic_raii_zone!($name); }; } +#[macro_export] macro_rules! raii_zone { ($($tt:tt)*) => { { - static CACHE: std::sync::LazyLock = std::sync::LazyLock::new(|| { + static CACHE: std::sync::LazyLock<$crate::ZoneName> = std::sync::LazyLock::new(|| { create_zone_name!($($tt)*) }); CACHE.__enter() @@ -109,6 +113,7 @@ macro_rules! raii_zone { }; } +#[macro_export] macro_rules! zone { ($($tt:tt)*) => { let _zone = raii_zone!($($tt)*); @@ -147,6 +152,7 @@ impl FrameName { } } +#[macro_export] macro_rules! raii_frame { ($name:expr) => {{ let name: FrameName = $name; @@ -154,6 +160,7 @@ macro_rules! raii_frame { }}; } +#[macro_export] macro_rules! frame { ($name:expr) => { let _frame = raii_frame!($name); diff --git a/src/tracy/tracy_noop.rs b/crates/tracy/src/tracy_noop.rs similarity index 82% rename from src/tracy/tracy_noop.rs rename to crates/tracy/src/tracy_noop.rs index da88c2b6..04b3ccec 100644 --- a/src/tracy/tracy_noop.rs +++ b/crates/tracy/src/tracy_noop.rs @@ -12,36 +12,43 @@ impl FrameName { } } +#[macro_export] macro_rules! create_zone_name { ($($tt:tt)*) => { - crate::tracy::ZoneName + $crate::ZoneName }; } +#[macro_export] macro_rules! dynamic_raii_zone { ($name:expr) => {}; } +#[macro_export] macro_rules! dynamic_zone { ($name:expr) => {}; } +#[macro_export] macro_rules! raii_zone { ($($tt:tt)*) => { () }; } +#[macro_export] macro_rules! zone { ($($tt:tt)*) => {}; } +#[macro_export] macro_rules! raii_frame { ($name:expr) => { () }; } +#[macro_export] macro_rules! frame { ($name:expr) => {}; } diff --git a/crates/tree-types/Cargo.toml b/crates/tree-types/Cargo.toml new file mode 100644 index 00000000..e761d5db --- /dev/null +++ b/crates/tree-types/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "jay-tree-types" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +jay-config = { path = "../jay-config" } +jay-utils = { path = "../utils" } + +linearize = { version = "0.1.3", features = ["derive"] } diff --git a/crates/tree-types/src/lib.rs b/crates/tree-types/src/lib.rs new file mode 100644 index 00000000..67fab81d --- /dev/null +++ b/crates/tree-types/src/lib.rs @@ -0,0 +1,269 @@ +use { + jay_config::{ + Direction as ConfigDirection, + video::Transform as ConfigTransform, + window::TileState as ConfigTileState, + workspace::WorkspaceDisplayOrder as ConfigWorkspaceDisplayOrder, + }, + jay_utils::static_text::StaticText, + linearize::{Linearize, LinearizeExt}, +}; + +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default, Linearize)] +pub enum WorkspaceDisplayOrder { + #[default] + Manual, + Sorted, +} + +impl From for WorkspaceDisplayOrder { + fn from(value: ConfigWorkspaceDisplayOrder) -> Self { + match value { + ConfigWorkspaceDisplayOrder::Manual => WorkspaceDisplayOrder::Manual, + ConfigWorkspaceDisplayOrder::Sorted => WorkspaceDisplayOrder::Sorted, + } + } +} + +impl Into for WorkspaceDisplayOrder { + fn into(self) -> ConfigWorkspaceDisplayOrder { + match self { + WorkspaceDisplayOrder::Manual => ConfigWorkspaceDisplayOrder::Manual, + WorkspaceDisplayOrder::Sorted => ConfigWorkspaceDisplayOrder::Sorted, + } + } +} + +impl StaticText for WorkspaceDisplayOrder { + fn text(&self) -> &'static str { + match self { + WorkspaceDisplayOrder::Manual => "Manual", + WorkspaceDisplayOrder::Sorted => "Sorted", + } + } +} + +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default, Linearize)] +pub enum Transform { + #[default] + None, + Rotate90, + Rotate180, + Rotate270, + Flip, + FlipRotate90, + FlipRotate180, + FlipRotate270, +} + +impl StaticText for Transform { + fn text(&self) -> &'static str { + match self { + Transform::None => "none", + Transform::Rotate90 => "rotate-90", + Transform::Rotate180 => "rotate-180", + Transform::Rotate270 => "rotate-270", + Transform::Flip => "flip", + Transform::FlipRotate90 => "flip-rotate-90", + Transform::FlipRotate180 => "flip-rotate-180", + Transform::FlipRotate270 => "flip-rotate-270", + } + } +} + +impl From for Transform { + fn from(value: ConfigTransform) -> Self { + match value { + ConfigTransform::None => Transform::None, + ConfigTransform::Rotate90 => Transform::Rotate90, + ConfigTransform::Rotate180 => Transform::Rotate180, + ConfigTransform::Rotate270 => Transform::Rotate270, + ConfigTransform::Flip => Transform::Flip, + ConfigTransform::FlipRotate90 => Transform::FlipRotate90, + ConfigTransform::FlipRotate180 => Transform::FlipRotate180, + ConfigTransform::FlipRotate270 => Transform::FlipRotate270, + } + } +} + +impl Into for Transform { + fn into(self) -> ConfigTransform { + match self { + Transform::None => ConfigTransform::None, + Transform::Rotate90 => ConfigTransform::Rotate90, + Transform::Rotate180 => ConfigTransform::Rotate180, + Transform::Rotate270 => ConfigTransform::Rotate270, + Transform::Flip => ConfigTransform::Flip, + Transform::FlipRotate90 => ConfigTransform::FlipRotate90, + Transform::FlipRotate180 => ConfigTransform::FlipRotate180, + Transform::FlipRotate270 => ConfigTransform::FlipRotate270, + } + } +} + +const TF_NORMAL: i32 = 0; +const TF_90: i32 = 1; +const TF_180: i32 = 2; +const TF_270: i32 = 3; +const TF_FLIPPED: i32 = 4; +const TF_FLIPPED_90: i32 = 5; +const TF_FLIPPED_180: i32 = 6; +const TF_FLIPPED_270: i32 = 7; + +impl Transform { + pub fn maybe_swap(self, (left, right): (T, T)) -> (T, T) { + match self { + Self::None | Self::Rotate180 | Self::Flip | Self::FlipRotate180 => (left, right), + Self::Rotate90 | Self::Rotate270 | Self::FlipRotate90 | Self::FlipRotate270 => { + (right, left) + } + } + } + + pub fn to_wl(self) -> i32 { + match self { + Self::None => TF_NORMAL, + Self::Rotate90 => TF_90, + Self::Rotate180 => TF_180, + Self::Rotate270 => TF_270, + Self::Flip => TF_FLIPPED, + Self::FlipRotate90 => TF_FLIPPED_90, + Self::FlipRotate180 => TF_FLIPPED_180, + Self::FlipRotate270 => TF_FLIPPED_270, + } + } + + pub fn from_wl(wl: i32) -> Option { + let tf = match wl { + TF_NORMAL => Self::None, + TF_90 => Self::Rotate90, + TF_180 => Self::Rotate180, + TF_270 => Self::Rotate270, + TF_FLIPPED => Self::Flip, + TF_FLIPPED_90 => Self::FlipRotate90, + TF_FLIPPED_180 => Self::FlipRotate180, + TF_FLIPPED_270 => Self::FlipRotate270, + _ => return None, + }; + Some(tf) + } + + pub fn apply_point(self, width: i32, height: i32, (x, y): (i32, i32)) -> (i32, i32) { + match self { + Self::None => (x, y), + Self::Rotate90 => (y, height - x), + Self::Rotate180 => (width - x, height - y), + Self::Rotate270 => (width - y, x), + Self::Flip => (width - x, y), + Self::FlipRotate90 => (y, x), + Self::FlipRotate180 => (x, height - y), + Self::FlipRotate270 => (width - y, height - x), + } + } + + pub fn inverse(self) -> Self { + match self { + Self::Rotate90 => Self::Rotate270, + Self::Rotate270 => Self::Rotate90, + _ => self, + } + } +} + +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Linearize)] +pub enum TileState { + Tiled, + Floating, +} + +impl TryFrom for TileState { + type Error = (); + + fn try_from(value: ConfigTileState) -> Result { + let v = match value { + ConfigTileState::Tiled => TileState::Tiled, + ConfigTileState::Floating => TileState::Floating, + _ => return Err(()), + }; + Ok(v) + } +} + +impl Into for TileState { + fn into(self) -> ConfigTileState { + match self { + TileState::Tiled => ConfigTileState::Tiled, + TileState::Floating => ConfigTileState::Floating, + } + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Direction { + Unspecified, + Left, + Down, + Up, + Right, +} + +impl From for Direction { + fn from(d: ConfigDirection) -> Self { + match d { + ConfigDirection::Left => Self::Left, + ConfigDirection::Down => Self::Down, + ConfigDirection::Up => Self::Up, + ConfigDirection::Right => Self::Right, + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum FindTreeResult { + AcceptsInput, + Other, +} + +impl FindTreeResult { + pub fn accepts_input(self) -> bool { + self == Self::AcceptsInput + } +} + +#[derive(Copy, Clone)] +pub enum FindTreeUsecase { + None, + SelectToplevel, + SelectToplevelOrPopup, + SelectWorkspace, +} + +#[derive(Copy, Clone, Linearize, Eq, PartialEq, Debug)] +pub enum NodeLayer { + Display, + Layer0, + Layer1, + Output, + Workspace, + Tiled, + Fullscreen, + Stacked, + Layer2, + Layer3, + StackedAboveLayers, + Lock, + InputMethod, +} + +impl NodeLayer { + pub fn prev(self) -> Self { + if self == NodeLayer::Display { + return NodeLayer::InputMethod; + } + Self::from_linear(self.linearize() - 1).unwrap_or(NodeLayer::InputMethod) + } + + pub fn next(self) -> Self { + Self::from_linear(self.linearize() + 1).unwrap_or(NodeLayer::Display) + } +} diff --git a/crates/udmabuf/Cargo.toml b/crates/udmabuf/Cargo.toml new file mode 100644 index 00000000..c92fd8f4 --- /dev/null +++ b/crates/udmabuf/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "jay-udmabuf" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +jay-allocator = { path = "../allocator" } +jay-formats = { path = "../formats" } +jay-utils = { path = "../utils" } +jay-video-types = { path = "../video-types" } + +log = "0.4.20" +thiserror = "2.0.11" +uapi = "0.2.13" diff --git a/src/udmabuf.rs b/crates/udmabuf/src/lib.rs similarity index 92% rename from src/udmabuf.rs rename to crates/udmabuf/src/lib.rs index 96acf0ac..2e90536c 100644 --- a/src/udmabuf.rs +++ b/crates/udmabuf/src/lib.rs @@ -1,20 +1,19 @@ use { - crate::{ - allocator::{Allocator, AllocatorError, BufferObject, BufferUsage, MappedBuffer}, - format::Format, - utils::{ - clonecell::CloneCell, - compat::IoctlNumber, - errorfmt::ErrorFmt, - once::Once, - oserror::{OsError, OsErrorExt, OsErrorExt2}, - page_size::page_size, - }, - video::{ - LINEAR_MODIFIER, LINEAR_STRIDE_ALIGN, Modifier, - dmabuf::{DmaBuf, DmaBufIds, DmaBufPlane, PlaneVec}, - drm::Drm, - }, + jay_allocator::{ + Allocator, AllocatorDrm, AllocatorError, BufferObject, BufferUsage, MappedBuffer, + }, + jay_formats::Format, + jay_utils::{ + clonecell::CloneCell, + compat::IoctlNumber, + errorfmt::ErrorFmt, + once::Once, + oserror::{OsError, OsErrorExt, OsErrorExt2}, + page_size::page_size, + }, + jay_video_types::{ + LINEAR_MODIFIER, LINEAR_STRIDE_ALIGN, Modifier, + dmabuf::{DmaBuf, DmaBufIds, DmaBufPlane, PlaneVec}, }, std::{ptr, rc::Rc}, thiserror::Error, @@ -78,7 +77,7 @@ impl UdmabufHolder { self.logged.exec(|| { log::warn!("Unable to open /dev/udmabuf: {}", ErrorFmt(&e)); }); - if not_matches!(e, UdmabufError::Open(OsError(c::EPERM))) { + if !matches!(e, UdmabufError::Open(OsError(c::EPERM))) { self.udmabuf.set(Some(None)); } None @@ -169,7 +168,7 @@ impl Udmabuf { } impl Allocator for Udmabuf { - fn drm(&self) -> Option<&Drm> { + fn drm(&self) -> Option<&dyn AllocatorDrm> { None } diff --git a/crates/units/Cargo.toml b/crates/units/Cargo.toml new file mode 100644 index 00000000..54f7cf0f --- /dev/null +++ b/crates/units/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "jay-units" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "Shared unit types for Jay" +repository = "https://github.com/mahkoh/jay" diff --git a/src/fixed.rs b/crates/units/src/fixed.rs similarity index 100% rename from src/fixed.rs rename to crates/units/src/fixed.rs diff --git a/crates/units/src/lib.rs b/crates/units/src/lib.rs new file mode 100644 index 00000000..3ddbd029 --- /dev/null +++ b/crates/units/src/lib.rs @@ -0,0 +1,4 @@ +pub mod fixed; +pub mod scale; + +pub use {fixed::Fixed, scale::Scale}; diff --git a/src/scale.rs b/crates/units/src/scale.rs similarity index 100% rename from src/scale.rs rename to crates/units/src/scale.rs diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml new file mode 100644 index 00000000..352013f0 --- /dev/null +++ b/crates/utils/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "jay-utils" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +jay-config = { path = "../jay-config" } + +ahash = "0.8.7" +arrayvec = "0.7.4" +bstr = { version = "1.9.0", default-features = false, features = ["std"] } +cfg-if = "1.0.0" +isnt = "0.2.0" +linearize = { version = "0.1.3", features = ["derive"] } +log = { version = "0.4.20", features = ["std"] } +parking_lot = "0.12.1" +rand = "0.10.0" +serde = { version = "1.0.196", features = ["derive"] } +smallvec = { version = "1.11.1", features = ["const_generics", "const_new", "union"] } +thiserror = "2.0.11" +uapi = "0.2.13" + +[features] +it = [] +rc_tracking = [] diff --git a/src/utils/array.rs b/crates/utils/src/array.rs similarity index 100% rename from src/utils/array.rs rename to crates/utils/src/array.rs diff --git a/src/utils/array_to_tuple.rs b/crates/utils/src/array_to_tuple.rs similarity index 100% rename from src/utils/array_to_tuple.rs rename to crates/utils/src/array_to_tuple.rs diff --git a/src/utils/asyncevent.rs b/crates/utils/src/asyncevent.rs similarity index 97% rename from src/utils/asyncevent.rs rename to crates/utils/src/asyncevent.rs index c575f630..438480f9 100644 --- a/src/utils/asyncevent.rs +++ b/crates/utils/src/asyncevent.rs @@ -1,5 +1,5 @@ use { - crate::utils::numcell::NumCell, + crate::numcell::NumCell, std::{ cell::Cell, fmt::{Debug, Formatter}, diff --git a/src/utils/atomic_enum.rs b/crates/utils/src/atomic_enum.rs similarity index 100% rename from src/utils/atomic_enum.rs rename to crates/utils/src/atomic_enum.rs diff --git a/src/utils/binary_search_map.rs b/crates/utils/src/binary_search_map.rs similarity index 99% rename from src/utils/binary_search_map.rs rename to crates/utils/src/binary_search_map.rs index e6f2865a..b4588a0d 100644 --- a/src/utils/binary_search_map.rs +++ b/crates/utils/src/binary_search_map.rs @@ -1,5 +1,5 @@ use { - crate::utils::ptr_ext::{MutPtrExt, PtrExt}, + crate::ptr_ext::{MutPtrExt, PtrExt}, smallvec::SmallVec, std::{ fmt::{Debug, Formatter}, diff --git a/src/utils/bitfield.rs b/crates/utils/src/bitfield.rs similarity index 100% rename from src/utils/bitfield.rs rename to crates/utils/src/bitfield.rs diff --git a/src/utils/bitflags.rs b/crates/utils/src/bitflags.rs similarity index 100% rename from src/utils/bitflags.rs rename to crates/utils/src/bitflags.rs diff --git a/src/utils/buf.rs b/crates/utils/src/buf.rs similarity index 98% rename from src/utils/buf.rs rename to crates/utils/src/buf.rs index b1d87b9d..bd5f5b77 100644 --- a/src/utils/buf.rs +++ b/crates/utils/src/buf.rs @@ -1,5 +1,5 @@ use { - crate::utils::{numcell::NumCell, ptr_ext::PtrExt}, + crate::{numcell::NumCell, ptr_ext::PtrExt}, std::{ alloc::Layout, cmp, @@ -145,7 +145,6 @@ impl Buf { unsafe { self.storage.as_ptr().add(self.range.start as _) } } - #[expect(dead_code)] pub fn write_fmt(&mut self, args: Arguments) -> Result { let cap = self.len(); let mut buf = self.deref_mut(); diff --git a/src/utils/cell_ext.rs b/crates/utils/src/cell_ext.rs similarity index 83% rename from src/utils/cell_ext.rs rename to crates/utils/src/cell_ext.rs index 074c5083..e525a58b 100644 --- a/src/utils/cell_ext.rs +++ b/crates/utils/src/cell_ext.rs @@ -1,4 +1,4 @@ -use {crate::utils::ptr_ext::PtrExt, std::cell::Cell}; +use {crate::ptr_ext::PtrExt, std::cell::Cell}; pub trait CellExt { fn is_some(&self) -> bool; diff --git a/src/utils/clonecell.rs b/crates/utils/src/clonecell.rs similarity index 90% rename from src/utils/clonecell.rs rename to crates/utils/src/clonecell.rs index aebb5c72..8f5a8f11 100644 --- a/src/utils/clonecell.rs +++ b/crates/utils/src/clonecell.rs @@ -1,11 +1,5 @@ use { - crate::{ - tree::NodeId, - utils::{ - linkedlist::NodeRef, - ptr_ext::{MutPtrExt, PtrExt}, - }, - }, + crate::ptr_ext::{MutPtrExt, PtrExt}, jay_config::{keyboard::mods::Modifiers, window::Window}, std::{ cell::UnsafeCell, @@ -89,8 +83,6 @@ unsafe impl UnsafeCellCloneSafe for Rc {} unsafe impl UnsafeCellCloneSafe for Weak {} unsafe impl UnsafeCellCloneSafe for Arc {} -unsafe impl UnsafeCellCloneSafe for NodeRef {} - unsafe impl UnsafeCellCloneSafe for () {} unsafe impl UnsafeCellCloneSafe for u64 {} unsafe impl UnsafeCellCloneSafe for i32 {} @@ -101,6 +93,6 @@ unsafe impl UnsafeCellCloneSafe unsafe impl UnsafeCellCloneSafe for Modifiers {} -unsafe impl UnsafeCellCloneSafe for NodeId {} - unsafe impl UnsafeCellCloneSafe for Window {} + +unsafe impl UnsafeCellCloneSafe for crate::linkedlist::NodeRef {} diff --git a/src/utils/compat.rs b/crates/utils/src/compat.rs similarity index 100% rename from src/utils/compat.rs rename to crates/utils/src/compat.rs diff --git a/src/utils/copyhashmap.rs b/crates/utils/src/copyhashmap.rs similarity index 99% rename from src/utils/copyhashmap.rs rename to crates/utils/src/copyhashmap.rs index d534e1db..cbb2d7c9 100644 --- a/src/utils/copyhashmap.rs +++ b/crates/utils/src/copyhashmap.rs @@ -1,5 +1,5 @@ use { - crate::utils::{ + crate::{ clonecell::UnsafeCellCloneSafe, ptr_ext::{MutPtrExt, PtrExt}, }, diff --git a/src/utils/double_buffered.rs b/crates/utils/src/double_buffered.rs similarity index 100% rename from src/utils/double_buffered.rs rename to crates/utils/src/double_buffered.rs diff --git a/src/utils/errorfmt.rs b/crates/utils/src/errorfmt.rs similarity index 100% rename from src/utils/errorfmt.rs rename to crates/utils/src/errorfmt.rs diff --git a/src/utils/event_listener.rs b/crates/utils/src/event_listener.rs similarity index 86% rename from src/utils/event_listener.rs rename to crates/utils/src/event_listener.rs index 95820b95..454f0dcd 100644 --- a/src/utils/event_listener.rs +++ b/crates/utils/src/event_listener.rs @@ -1,10 +1,7 @@ use { crate::{ - state::State, - utils::{ - linkedlist::{LinkedList, LinkedListIter, LinkedNode}, - queue::AsyncQueue, - }, + linkedlist::{LinkedList, LinkedListIter, LinkedNode}, + queue::AsyncQueue, }, std::{ cell::Cell, @@ -13,15 +10,7 @@ use { }, }; -pub async fn handle_lazy_event_sources(state: Rc) { - handle_lazy_event_sources_of(&state.lazy_event_sources).await; -} - -pub async fn handle_post_layout_event_sources(state: Rc) { - handle_lazy_event_sources_of(&state.post_layout_event_sources).await; -} - -async fn handle_lazy_event_sources_of(sources: &LazyEventSources) { +pub async fn handle_lazy_event_sources_of(sources: &LazyEventSources) { loop { let source = sources.queue.pop().await; source.queued.set(false); diff --git a/src/utils/fdcloser.rs b/crates/utils/src/fdcloser.rs similarity index 100% rename from src/utils/fdcloser.rs rename to crates/utils/src/fdcloser.rs diff --git a/src/utils/free_list.rs b/crates/utils/src/free_list.rs similarity index 94% rename from src/utils/free_list.rs rename to crates/utils/src/free_list.rs index 63d17529..aade5555 100644 --- a/src/utils/free_list.rs +++ b/crates/utils/src/free_list.rs @@ -2,7 +2,7 @@ mod tests; use { - crate::utils::ptr_ext::MutPtrExt, + crate::ptr_ext::MutPtrExt, std::{ array, cell::UnsafeCell, @@ -41,7 +41,6 @@ impl FreeList { unsafe { self.levels.get().deref_mut() } } - #[cfg_attr(not(test), expect(dead_code))] pub fn release(&self, n: T) where T: Into, @@ -59,7 +58,6 @@ impl FreeList { } } - #[cfg_attr(not(test), expect(dead_code))] pub fn acquire(&self) -> T where u32: Into, diff --git a/src/utils/free_list/tests.rs b/crates/utils/src/free_list/tests.rs similarity index 95% rename from src/utils/free_list/tests.rs rename to crates/utils/src/free_list/tests.rs index a8c4eae5..7a581cb8 100644 --- a/src/utils/free_list/tests.rs +++ b/crates/utils/src/free_list/tests.rs @@ -1,4 +1,4 @@ -use crate::utils::free_list::FreeList; +use crate::free_list::FreeList; #[test] fn test() { diff --git a/src/utils/geometric_decay.rs b/crates/utils/src/geometric_decay.rs similarity index 100% rename from src/utils/geometric_decay.rs rename to crates/utils/src/geometric_decay.rs diff --git a/src/utils/hash_map_ext.rs b/crates/utils/src/hash_map_ext.rs similarity index 100% rename from src/utils/hash_map_ext.rs rename to crates/utils/src/hash_map_ext.rs diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs new file mode 100644 index 00000000..d5937af8 --- /dev/null +++ b/crates/utils/src/lib.rs @@ -0,0 +1,58 @@ +pub mod array; +pub mod array_to_tuple; +pub mod asyncevent; +pub mod atomic_enum; +pub mod binary_search_map; +pub mod bitfield; +pub mod bitflags; +pub mod buf; +pub mod cell_ext; +pub mod clonecell; +pub mod compat; +pub mod copyhashmap; +pub mod double_buffered; +pub mod errorfmt; +pub mod event_listener; +pub mod fdcloser; +pub mod free_list; +pub mod geometric_decay; +pub mod hash_map_ext; +pub mod linkedlist; +pub mod log_on_drop; +pub mod mmap; +pub mod nice; +pub mod nonblock; +pub mod num_cpus; +pub mod numcell; +pub mod on_change; +pub mod on_drop_event; +pub mod once; +pub mod opaque; +pub mod opaque_cell; +pub mod opt; +pub mod option_ext; +pub mod ordered_float; +pub mod oserror; +pub mod page_size; +pub mod pid_info; +pub mod pidfd_send_signal; +pub mod pipe; +pub mod process_name; +pub mod ptr_ext; +pub mod queue; +pub mod rc_eq; +pub mod refcounted; +pub mod smallmap; +pub mod stack; +pub mod static_text; +pub mod string_ext; +pub mod syncqueue; +pub mod threshold_counter; +pub mod tri; +pub mod unlink_on_drop; +pub mod vec_ext; +pub mod vecdeque_ext; +pub mod vecset; +pub mod vecstorage; +pub mod windows; +pub mod xrd; diff --git a/crates/utils/src/linkedlist.rs b/crates/utils/src/linkedlist.rs new file mode 100644 index 00000000..e14b1f5c --- /dev/null +++ b/crates/utils/src/linkedlist.rs @@ -0,0 +1,414 @@ +use { + crate::numcell::NumCell, + std::{ + cell::Cell, + fmt::{Debug, Formatter}, + mem, + ops::Deref, + ptr::NonNull, + }, +}; + +const LINKED_NODE_REF_COUNT: usize = !(!0 >> 1); + +pub struct LinkedList { + root: LinkedNode, +} + +impl Debug for LinkedList { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_list().entries(self.iter()).finish() + } +} + +impl Default for LinkedList { + fn default() -> Self { + Self::new() + } +} + +impl LinkedList { + pub fn new() -> Self { + Self { + root: LinkedNode::new(None), + } + } + + pub fn append_all(&self, other: &LinkedList) { + if other.is_empty() || self.root.data == other.root.data { + return; + } + unsafe { + let o_root = other.root.data; + let o_first = o_root.as_ref().next.get(); + let o_last = o_root.as_ref().prev.get(); + let s_first = self.root.data; + let s_last = s_first.as_ref().prev.get(); + o_first.as_ref().prev.set(s_last); + s_last.as_ref().next.set(o_first); + o_last.as_ref().next.set(s_first); + s_first.as_ref().prev.set(o_last); + o_root.as_ref().next.set(o_root); + o_root.as_ref().prev.set(o_root); + } + } + + fn endpoint(&self, ep: NonNull>) -> Option> { + unsafe { + if ep != self.root.data { + ep.as_ref().rc.fetch_add(1); + Some(NodeRef { data: ep }) + } else { + None + } + } + } + + pub fn is_empty(&self) -> bool { + self.last().is_none() + } + + pub fn is_not_empty(&self) -> bool { + !self.is_empty() + } + + pub fn last(&self) -> Option> { + unsafe { self.endpoint(self.root.data.as_ref().prev.get()) } + } + + pub fn first(&self) -> Option> { + unsafe { self.endpoint(self.root.data.as_ref().next.get()) } + } + + pub fn add_last(&self, t: T) -> LinkedNode { + self.root.prepend(t) + } + + pub fn add_first(&self, t: T) -> LinkedNode { + self.root.append(t) + } + + pub fn add_last_existing(&self, t: &NodeRef) { + self.root.prepend_existing(t) + } + + pub fn add_first_existing(&self, t: &NodeRef) { + self.root.append_existing(t) + } + + pub fn rotate_last(&self, t: &NodeRef) { + unsafe { + let root = self.root.data.as_ref(); + root.prev.get().as_ref().next.set(root.next.get()); + root.next.get().as_ref().prev.set(root.prev.get()); + root.prev.set(t.data); + root.next.set(t.data.as_ref().next.get()); + t.data.as_ref().next.get().as_ref().prev.set(self.root.data); + t.data.as_ref().next.set(self.root.data); + } + } + + pub fn iter(&self) -> LinkedListIter { + unsafe { + let root = self.root.data.as_ref(); + root.rc.fetch_add(1); + root.next.get().as_ref().rc.fetch_add(1); + LinkedListIter { + root: self.root.data, + next: root.next.get(), + } + } + } + + pub fn rev_iter(&self) -> RevLinkedListIter { + unsafe { + let root = self.root.data.as_ref(); + root.rc.fetch_add(1); + root.prev.get().as_ref().rc.fetch_add(1); + RevLinkedListIter { + root: self.root.data, + next: root.prev.get(), + } + } + } +} + +pub struct LinkedListIter { + root: NonNull>, + next: NonNull>, +} + +impl Iterator for LinkedListIter { + type Item = NodeRef; + + fn next(&mut self) -> Option { + if self.root == self.next { + return None; + } + unsafe { + let old_next = self.next; + self.next = old_next.as_ref().next.get(); + self.next.as_ref().rc.fetch_add(1); + Some(NodeRef { data: old_next }) + } + } +} + +impl Drop for LinkedListIter { + fn drop(&mut self) { + unsafe { + dec_ref_count(self.root, 1); + dec_ref_count(self.next, 1); + } + } +} + +pub struct RevLinkedListIter { + root: NonNull>, + next: NonNull>, +} + +impl Iterator for RevLinkedListIter { + type Item = NodeRef; + + fn next(&mut self) -> Option { + if self.root == self.next { + return None; + } + unsafe { + let old_next = self.next; + self.next = old_next.as_ref().prev.get(); + self.next.as_ref().rc.fetch_add(1); + Some(NodeRef { data: old_next }) + } + } +} + +impl Drop for RevLinkedListIter { + fn drop(&mut self) { + unsafe { + dec_ref_count(self.root, 1); + dec_ref_count(self.next, 1); + } + } +} + +#[repr(transparent)] +#[must_use] +pub struct LinkedNode { + data: NonNull>, +} + +impl Debug for LinkedNode { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + unsafe { self.data.as_ref().data.as_ref().unwrap_unchecked().fmt(f) } + } +} + +impl Deref for LinkedNode { + type Target = NodeRef; + + fn deref(&self) -> &Self::Target { + unsafe { mem::transmute(self) } + } +} + +#[repr(transparent)] +pub struct NodeRef { + data: NonNull>, +} + +impl Debug for NodeRef { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + unsafe { self.data.as_ref().data.as_ref().unwrap_unchecked().fmt(f) } + } +} + +impl Deref for NodeRef { + type Target = T; + + fn deref(&self) -> &Self::Target { + unsafe { self.data.as_ref().data.as_ref().unwrap_unchecked() } + } +} + +impl Drop for NodeRef { + fn drop(&mut self) { + unsafe { + dec_ref_count(self.data, 1); + } + } +} + +impl Clone for NodeRef { + fn clone(&self) -> Self { + unsafe { + self.data.as_ref().rc.fetch_add(1); + Self { data: self.data } + } + } +} + +impl NodeRef { + pub fn prepend(&self, t: T) -> LinkedNode { + unsafe { prepend(self.data, t) } + } + + pub fn append(&self, t: T) -> LinkedNode { + unsafe { append(self.data, t) } + } + + pub fn prepend_existing(&self, t: &NodeRef) { + unsafe { prepend_existing(self.data, t) } + } + + pub fn append_existing(&self, t: &NodeRef) { + unsafe { append_existing(self.data, t) } + } + + fn peer(&self, peer: F) -> Option> + where + F: FnOnce(&NodeData) -> &Cell>>, + { + unsafe { + let data = self.data.as_ref(); + let other = peer(data).get(); + if other.as_ref().data.is_some() { + other.as_ref().rc.fetch_add(1); + Some(NodeRef { data: other }) + } else { + None + } + } + } + + pub fn prev(&self) -> Option> { + self.peer(|d| &d.prev) + } + + pub fn next(&self) -> Option> { + self.peer(|d| &d.next) + } + + pub fn detach(&self) { + unsafe { + let data = self.data.as_ref(); + data.prev.get().as_ref().next.set(data.next.get()); + data.next.get().as_ref().prev.set(data.prev.get()); + data.prev.set(self.data); + data.next.set(self.data); + } + } +} + +struct NodeData { + rc: NumCell, + prev: Cell>>, + next: Cell>>, + data: Option, +} + +unsafe fn dec_ref_count(slf: NonNull>, n: usize) { + unsafe { + if slf.as_ref().rc.fetch_sub(n) == n { + drop(Box::from_raw(slf.as_ptr())); + } + } +} + +impl Drop for LinkedNode { + fn drop(&mut self) { + unsafe { + self.detach(); + dec_ref_count(self.data, LINKED_NODE_REF_COUNT); + } + } +} + +impl LinkedNode { + fn new(t: Option) -> Self { + let node = Box::leak(Box::new(NodeData { + rc: NumCell::new(LINKED_NODE_REF_COUNT), + prev: Cell::new(NonNull::dangling()), + next: Cell::new(NonNull::dangling()), + data: t, + })); + let ptr = NonNull::from(&mut *node); + node.prev.set(ptr); + node.next.set(ptr); + LinkedNode { data: node.into() } + } + + pub fn detached(t: T) -> Self { + Self::new(Some(t)) + } + + pub fn to_ref(&self) -> NodeRef { + unsafe { + self.data.as_ref().rc.fetch_add(1); + NodeRef { data: self.data } + } + } +} + +unsafe fn prepend_existing(data: NonNull>, t: &NodeRef) { + unsafe { + let dref = data.as_ref(); + let tref = t.data.as_ref(); + if tref.rc.get() < LINKED_NODE_REF_COUNT { + log::error!("Trying to prepend a node whose linked node has already been dropped"); + return; + } + t.detach(); + tref.prev.set(dref.prev.get()); + tref.next.set(data); + dref.prev.get().as_ref().next.set(t.data); + dref.prev.set(t.data); + } +} + +unsafe fn prepend(data: NonNull>, t: T) -> LinkedNode { + unsafe { + let dref = data.as_ref(); + let node = NonNull::new_unchecked(Box::into_raw(Box::new(NodeData { + rc: NumCell::new(LINKED_NODE_REF_COUNT), + prev: Cell::new(dref.prev.get()), + next: Cell::new(data), + data: Some(t), + }))); + dref.prev.get().as_ref().next.set(node); + dref.prev.set(node); + LinkedNode { data: node } + } +} + +unsafe fn append_existing(data: NonNull>, t: &NodeRef) { + unsafe { + let dref = data.as_ref(); + let tref = t.data.as_ref(); + if tref.rc.get() < LINKED_NODE_REF_COUNT { + log::error!("Trying to append a node whose linked node has already been dropped"); + return; + } + t.detach(); + tref.prev.set(data); + tref.next.set(dref.next.get()); + dref.next.get().as_ref().prev.set(t.data); + dref.next.set(t.data); + } +} + +unsafe fn append(data: NonNull>, t: T) -> LinkedNode { + unsafe { + let dref = data.as_ref(); + let node = NonNull::new_unchecked(Box::into_raw(Box::new(NodeData { + rc: NumCell::new(LINKED_NODE_REF_COUNT), + prev: Cell::new(data), + next: Cell::new(dref.next.get()), + data: Some(t), + }))); + dref.next.get().as_ref().prev.set(node); + dref.next.set(node); + LinkedNode { data: node } + } +} diff --git a/src/utils/log_on_drop.rs b/crates/utils/src/log_on_drop.rs similarity index 86% rename from src/utils/log_on_drop.rs rename to crates/utils/src/log_on_drop.rs index 204a82b8..ea3abfac 100644 --- a/src/utils/log_on_drop.rs +++ b/crates/utils/src/log_on_drop.rs @@ -1,4 +1,3 @@ -#[expect(dead_code)] pub struct LogOnDrop(pub &'static str); impl Drop for LogOnDrop { diff --git a/src/utils/mmap.rs b/crates/utils/src/mmap.rs similarity index 92% rename from src/utils/mmap.rs rename to crates/utils/src/mmap.rs index eeae5af7..ea9afe0e 100644 --- a/src/utils/mmap.rs +++ b/crates/utils/src/mmap.rs @@ -1,5 +1,5 @@ use { - crate::utils::{oserror::OsError, ptr_ext::PtrExt}, + crate::{oserror::OsError, ptr_ext::PtrExt}, std::ptr, uapi::c, }; diff --git a/src/utils/nice.rs b/crates/utils/src/nice.rs similarity index 66% rename from src/utils/nice.rs rename to crates/utils/src/nice.rs index d7d4b5a8..c79f2a39 100644 --- a/src/utils/nice.rs +++ b/crates/utils/src/nice.rs @@ -1,5 +1,4 @@ use { - crate::{compositor::config_dir, config::have_config_so}, c::sched_setscheduler, std::{ env, mem, @@ -16,9 +15,6 @@ pub fn elevate_scheduler() { if env::var(JAY_NO_REALTIME).as_deref().unwrap_or_default() == "1" { return; } - if have_config_so(config_dir().as_deref()) && dont_allow_realtime_config_so() { - return; - } let mut param = unsafe { mem::zeroed::() }; param.sched_priority = 1; let res = unsafe { sched_setscheduler(0, SCHED_RR | SCHED_RESET_ON_FORK, ¶m) }; @@ -30,11 +26,3 @@ pub fn elevate_scheduler() { pub fn did_elevate_scheduler() -> bool { DID_ELEVATE_SCHEDULER.load(Relaxed) } - -fn dont_allow_realtime_config_so() -> bool { - option_env!(jay_allow_realtime_config_so!()).unwrap_or_default() != "1" -} - -pub fn dont_allow_config_so() -> bool { - did_elevate_scheduler() && dont_allow_realtime_config_so() -} diff --git a/src/utils/nonblock.rs b/crates/utils/src/nonblock.rs similarity index 88% rename from src/utils/nonblock.rs rename to crates/utils/src/nonblock.rs index 0c3f0752..4c20f037 100644 --- a/src/utils/nonblock.rs +++ b/crates/utils/src/nonblock.rs @@ -1,5 +1,5 @@ use { - crate::utils::oserror::{OsError, OsErrorExt}, + crate::oserror::{OsError, OsErrorExt}, uapi::c, }; diff --git a/src/utils/num_cpus.rs b/crates/utils/src/num_cpus.rs similarity index 83% rename from src/utils/num_cpus.rs rename to crates/utils/src/num_cpus.rs index 2e589942..b32f9279 100644 --- a/src/utils/num_cpus.rs +++ b/crates/utils/src/num_cpus.rs @@ -1,10 +1,9 @@ use { - crate::utils::oserror::{OsError, OsErrorExt}, + crate::oserror::{OsError, OsErrorExt}, smallvec::{SmallVec, smallvec_inline}, uapi::c, }; -#[cfg_attr(not(feature = "it"), expect(dead_code))] pub fn num_cpus() -> Result { let mut buf: SmallVec<[usize; 32]> = smallvec_inline![0; 32]; loop { diff --git a/src/utils/numcell.rs b/crates/utils/src/numcell.rs similarity index 100% rename from src/utils/numcell.rs rename to crates/utils/src/numcell.rs diff --git a/src/utils/on_change.rs b/crates/utils/src/on_change.rs similarity index 93% rename from src/utils/on_change.rs rename to crates/utils/src/on_change.rs index e0427a7b..e0de0d7d 100644 --- a/src/utils/on_change.rs +++ b/crates/utils/src/on_change.rs @@ -1,5 +1,5 @@ use { - crate::utils::{clonecell::CloneCell, syncqueue::SyncQueue}, + crate::{clonecell::CloneCell, syncqueue::SyncQueue}, std::{ fmt::{Debug, Formatter}, rc::Rc, diff --git a/src/utils/on_drop_event.rs b/crates/utils/src/on_drop_event.rs similarity index 81% rename from src/utils/on_drop_event.rs rename to crates/utils/src/on_drop_event.rs index e3ceea04..e81242f9 100644 --- a/src/utils/on_drop_event.rs +++ b/crates/utils/src/on_drop_event.rs @@ -1,4 +1,4 @@ -use {crate::utils::asyncevent::AsyncEvent, std::rc::Rc}; +use {crate::asyncevent::AsyncEvent, std::rc::Rc}; #[derive(Default)] pub struct OnDropEvent { diff --git a/src/utils/once.rs b/crates/utils/src/once.rs similarity index 100% rename from src/utils/once.rs rename to crates/utils/src/once.rs diff --git a/src/utils/opaque.rs b/crates/utils/src/opaque.rs similarity index 99% rename from src/utils/opaque.rs rename to crates/utils/src/opaque.rs index 2f2c58a4..550c459a 100644 --- a/src/utils/opaque.rs +++ b/crates/utils/src/opaque.rs @@ -1,5 +1,5 @@ use { - crate::utils::array, + crate::array, arrayvec::ArrayString, rand::{RngExt, rng}, serde::{Deserialize, Deserializer, Serialize, Serializer, de}, diff --git a/src/utils/opaque/tests.rs b/crates/utils/src/opaque/tests.rs similarity index 80% rename from src/utils/opaque/tests.rs rename to crates/utils/src/opaque/tests.rs index b9a8aa59..10a13150 100644 --- a/src/utils/opaque/tests.rs +++ b/crates/utils/src/opaque/tests.rs @@ -1,5 +1,5 @@ use { - crate::utils::opaque::{Opaque, opaque}, + crate::opaque::{Opaque, opaque}, std::str::FromStr, }; diff --git a/src/utils/opaque_cell.rs b/crates/utils/src/opaque_cell.rs similarity index 100% rename from src/utils/opaque_cell.rs rename to crates/utils/src/opaque_cell.rs diff --git a/src/utils/opt.rs b/crates/utils/src/opt.rs similarity index 88% rename from src/utils/opt.rs rename to crates/utils/src/opt.rs index 86b4387e..6ab327f0 100644 --- a/src/utils/opt.rs +++ b/crates/utils/src/opt.rs @@ -1,4 +1,4 @@ -use {crate::utils::clonecell::CloneCell, std::rc::Rc}; +use {crate::clonecell::CloneCell, std::rc::Rc}; pub struct Opt { t: CloneCell>>, diff --git a/src/utils/option_ext.rs b/crates/utils/src/option_ext.rs similarity index 100% rename from src/utils/option_ext.rs rename to crates/utils/src/option_ext.rs diff --git a/src/utils/ordered_float.rs b/crates/utils/src/ordered_float.rs similarity index 100% rename from src/utils/ordered_float.rs rename to crates/utils/src/ordered_float.rs diff --git a/src/utils/oserror.rs b/crates/utils/src/oserror.rs similarity index 100% rename from src/utils/oserror.rs rename to crates/utils/src/oserror.rs diff --git a/src/utils/page_size.rs b/crates/utils/src/page_size.rs similarity index 100% rename from src/utils/page_size.rs rename to crates/utils/src/page_size.rs diff --git a/src/utils/pid_info.rs b/crates/utils/src/pid_info.rs similarity index 96% rename from src/utils/pid_info.rs rename to crates/utils/src/pid_info.rs index 11f121ba..6a31d3bc 100644 --- a/src/utils/pid_info.rs +++ b/crates/utils/src/pid_info.rs @@ -1,5 +1,5 @@ use { - crate::utils::{errorfmt::ErrorFmt, oserror::OsErrorExt}, + crate::{errorfmt::ErrorFmt, oserror::OsErrorExt}, bstr::ByteSlice, std::os::unix::ffi::OsStrExt, uapi::{OwnedFd, c}, diff --git a/src/utils/pidfd_send_signal.rs b/crates/utils/src/pidfd_send_signal.rs similarity index 90% rename from src/utils/pidfd_send_signal.rs rename to crates/utils/src/pidfd_send_signal.rs index e856bf2a..adb8d6e4 100644 --- a/src/utils/pidfd_send_signal.rs +++ b/crates/utils/src/pidfd_send_signal.rs @@ -1,5 +1,5 @@ use { - crate::utils::oserror::{OsError, OsErrorExt}, + crate::oserror::{OsError, OsErrorExt}, c::{c_int, syscall}, std::{ptr, rc::Rc}, uapi::{ diff --git a/src/utils/pipe.rs b/crates/utils/src/pipe.rs similarity index 93% rename from src/utils/pipe.rs rename to crates/utils/src/pipe.rs index 9d07d2ef..7eb28aab 100644 --- a/src/utils/pipe.rs +++ b/crates/utils/src/pipe.rs @@ -1,5 +1,5 @@ use { - crate::utils::oserror::{OsError, OsErrorExt}, + crate::oserror::{OsError, OsErrorExt}, uapi::{OwnedFd, c, pipe2}, }; diff --git a/src/utils/process_name.rs b/crates/utils/src/process_name.rs similarity index 100% rename from src/utils/process_name.rs rename to crates/utils/src/process_name.rs diff --git a/src/utils/ptr_ext.rs b/crates/utils/src/ptr_ext.rs similarity index 100% rename from src/utils/ptr_ext.rs rename to crates/utils/src/ptr_ext.rs diff --git a/src/utils/queue.rs b/crates/utils/src/queue.rs similarity index 98% rename from src/utils/queue.rs rename to crates/utils/src/queue.rs index c39a28d5..35d1d0c6 100644 --- a/src/utils/queue.rs +++ b/crates/utils/src/queue.rs @@ -1,5 +1,5 @@ use { - crate::utils::ptr_ext::{MutPtrExt, PtrExt}, + crate::ptr_ext::{MutPtrExt, PtrExt}, std::{ cell::{Cell, UnsafeCell}, collections::VecDeque, diff --git a/src/utils/rc_eq.rs b/crates/utils/src/rc_eq.rs similarity index 100% rename from src/utils/rc_eq.rs rename to crates/utils/src/rc_eq.rs diff --git a/src/utils/refcounted.rs b/crates/utils/src/refcounted.rs similarity index 97% rename from src/utils/refcounted.rs rename to crates/utils/src/refcounted.rs index a5d6fef1..b3742254 100644 --- a/src/utils/refcounted.rs +++ b/crates/utils/src/refcounted.rs @@ -1,5 +1,5 @@ use { - crate::utils::ptr_ext::{MutPtrExt, PtrExt}, + crate::ptr_ext::{MutPtrExt, PtrExt}, std::{cell::UnsafeCell, mem, ops::Deref}, }; diff --git a/src/utils/smallmap.rs b/crates/utils/src/smallmap.rs similarity index 99% rename from src/utils/smallmap.rs rename to crates/utils/src/smallmap.rs index cd2f155a..944bb518 100644 --- a/src/utils/smallmap.rs +++ b/crates/utils/src/smallmap.rs @@ -1,5 +1,5 @@ use { - crate::utils::{ + crate::{ clonecell::UnsafeCellCloneSafe, ptr_ext::{MutPtrExt, PtrExt}, }, diff --git a/src/utils/stack.rs b/crates/utils/src/stack.rs similarity index 93% rename from src/utils/stack.rs rename to crates/utils/src/stack.rs index 0ff7ee6f..6cc4a6a7 100644 --- a/src/utils/stack.rs +++ b/crates/utils/src/stack.rs @@ -1,5 +1,5 @@ use { - crate::utils::{ + crate::{ clonecell::UnsafeCellCloneSafe, ptr_ext::{MutPtrExt, PtrExt}, }, @@ -43,7 +43,6 @@ impl Stack { unsafe { mem::take(self.vec.get().deref_mut()) } } - #[cfg_attr(not(test), expect(dead_code))] pub fn len(&self) -> usize { unsafe { self.vec.get().deref().len() } } diff --git a/src/utils/static_text.rs b/crates/utils/src/static_text.rs similarity index 100% rename from src/utils/static_text.rs rename to crates/utils/src/static_text.rs diff --git a/src/utils/string_ext.rs b/crates/utils/src/string_ext.rs similarity index 100% rename from src/utils/string_ext.rs rename to crates/utils/src/string_ext.rs diff --git a/src/utils/syncqueue.rs b/crates/utils/src/syncqueue.rs similarity index 97% rename from src/utils/syncqueue.rs rename to crates/utils/src/syncqueue.rs index d8bc938f..661059d7 100644 --- a/src/utils/syncqueue.rs +++ b/crates/utils/src/syncqueue.rs @@ -1,5 +1,5 @@ use { - crate::utils::ptr_ext::MutPtrExt, + crate::ptr_ext::MutPtrExt, std::{cell::UnsafeCell, collections::VecDeque, mem}, }; diff --git a/src/utils/threshold_counter.rs b/crates/utils/src/threshold_counter.rs similarity index 92% rename from src/utils/threshold_counter.rs rename to crates/utils/src/threshold_counter.rs index fc227f4a..133dcf5e 100644 --- a/src/utils/threshold_counter.rs +++ b/crates/utils/src/threshold_counter.rs @@ -1,4 +1,4 @@ -use crate::utils::numcell::NumCell; +use crate::numcell::NumCell; #[derive(Default)] pub struct ThresholdCounter { diff --git a/src/utils/tri.rs b/crates/utils/src/tri.rs similarity index 97% rename from src/utils/tri.rs rename to crates/utils/src/tri.rs index 02d8cdb6..1384c0ad 100644 --- a/src/utils/tri.rs +++ b/crates/utils/src/tri.rs @@ -10,7 +10,6 @@ pub trait Try: Sized { where F: FnOnce() -> Result<(), Self>; - #[expect(dead_code)] fn tria(f: F) -> Tria where F: Future>; diff --git a/src/utils/unlink_on_drop.rs b/crates/utils/src/unlink_on_drop.rs similarity index 100% rename from src/utils/unlink_on_drop.rs rename to crates/utils/src/unlink_on_drop.rs diff --git a/src/utils/vec_ext.rs b/crates/utils/src/vec_ext.rs similarity index 100% rename from src/utils/vec_ext.rs rename to crates/utils/src/vec_ext.rs diff --git a/src/utils/vecdeque_ext.rs b/crates/utils/src/vecdeque_ext.rs similarity index 100% rename from src/utils/vecdeque_ext.rs rename to crates/utils/src/vecdeque_ext.rs diff --git a/src/utils/vecset.rs b/crates/utils/src/vecset.rs similarity index 100% rename from src/utils/vecset.rs rename to crates/utils/src/vecset.rs diff --git a/src/utils/vecstorage.rs b/crates/utils/src/vecstorage.rs similarity index 62% rename from src/utils/vecstorage.rs rename to crates/utils/src/vecstorage.rs index 751638e6..99191372 100644 --- a/src/utils/vecstorage.rs +++ b/crates/utils/src/vecstorage.rs @@ -3,6 +3,36 @@ use std::{ ops::{Deref, DerefMut}, }; +macro_rules! assert_size_eq { + ($t:ty, $u:ty) => {{ + struct AssertEqSize(std::marker::PhantomData, std::marker::PhantomData); + impl AssertEqSize { + const VAL: usize = { + if std::mem::size_of::() != std::mem::size_of::() { + panic!("Types have different size"); + } + 1 + }; + } + let _ = AssertEqSize::<$t, $u>::VAL; + }}; +} + +macro_rules! assert_align_eq { + ($t:ty, $u:ty) => {{ + struct AssertEqAlign(std::marker::PhantomData, std::marker::PhantomData); + impl AssertEqAlign { + const VAL: usize = { + if std::mem::align_of::() != std::mem::align_of::() { + panic!("Types have different alignment"); + } + 1 + }; + } + let _ = AssertEqAlign::<$t, $u>::VAL; + }}; +} + pub struct VecStorage { ptr: *mut T, cap: usize, diff --git a/src/utils/windows.rs b/crates/utils/src/windows.rs similarity index 92% rename from src/utils/windows.rs rename to crates/utils/src/windows.rs index 123a6dcd..09caafa8 100644 --- a/src/utils/windows.rs +++ b/crates/utils/src/windows.rs @@ -1,4 +1,4 @@ -use crate::utils::ptr_ext::PtrExt; +use crate::ptr_ext::PtrExt; pub trait WindowsExt { type Windows<'a, const N: usize>: Iterator @@ -6,7 +6,6 @@ pub trait WindowsExt { Self: 'a, T: 'a; - #[cfg_attr(not(feature = "rc_tracking"), expect(dead_code))] fn array_windows_ext<'a, const N: usize>(&'a self) -> Self::Windows<'a, N>; fn array_chunks_ext<'a, const N: usize>(&'a self) -> &'a [[T; N]]; } diff --git a/src/utils/xrd.rs b/crates/utils/src/xrd.rs similarity index 100% rename from src/utils/xrd.rs rename to crates/utils/src/xrd.rs diff --git a/crates/video-types/Cargo.toml b/crates/video-types/Cargo.toml new file mode 100644 index 00000000..ce0ba360 --- /dev/null +++ b/crates/video-types/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "jay-video-types" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +jay-formats = { path = "../formats" } +jay-utils = { path = "../utils" } + +arrayvec = "0.7.4" +uapi = "0.2.13" diff --git a/src/video/dmabuf.rs b/crates/video-types/src/dmabuf.rs similarity index 82% rename from src/video/dmabuf.rs rename to crates/video-types/src/dmabuf.rs index e19def66..a096b8b3 100644 --- a/src/video/dmabuf.rs +++ b/crates/video-types/src/dmabuf.rs @@ -1,10 +1,8 @@ use { - crate::{ - format::Format, - utils::{compat::IoctlNumber, oserror::OsError}, - video::{LINEAR_MODIFIER, Modifier}, - }, + crate::{LINEAR_MODIFIER, Modifier}, arrayvec::ArrayVec, + jay_formats::Format, + jay_utils::{compat::IoctlNumber, numcell::NumCell, oserror::OsError}, std::{cell::OnceCell, rc::Rc, sync::OnceLock}, uapi::{ _IOW, _IOWR, OwnedFd, @@ -20,7 +18,45 @@ pub struct DmaBufPlane { pub fd: Rc, } -linear_ids!(DmaBufIds, DmaBufId); +#[derive(Debug)] +pub struct DmaBufIds { + next: NumCell, +} + +impl Default for DmaBufIds { + fn default() -> Self { + Self { + next: NumCell::new(1), + } + } +} + +impl DmaBufIds { + pub fn next(&self) -> DmaBufId { + DmaBufId(self.next.fetch_add(1)) + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct DmaBufId(u32); + +impl DmaBufId { + #[allow(dead_code)] + pub fn raw(&self) -> u32 { + self.0 + } + + #[allow(dead_code)] + pub fn from_raw(id: u32) -> Self { + Self(id) + } +} + +impl std::fmt::Display for DmaBufId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } +} #[derive(Debug, Clone)] pub struct DmaBuf { diff --git a/crates/video-types/src/drm.rs b/crates/video-types/src/drm.rs new file mode 100644 index 00000000..26a5ea83 --- /dev/null +++ b/crates/video-types/src/drm.rs @@ -0,0 +1,48 @@ +pub const DRM_MODE_OBJECT_CRTC: u32 = 0xcccccccc; +pub const DRM_MODE_OBJECT_CONNECTOR: u32 = 0xc0c0c0c0; +pub const DRM_MODE_OBJECT_ENCODER: u32 = 0xe0e0e0e0; +pub const DRM_MODE_OBJECT_PROPERTY: u32 = 0xb0b0b0b0; +pub const DRM_MODE_OBJECT_FB: u32 = 0xfbfbfbfb; +pub const DRM_MODE_OBJECT_BLOB: u32 = 0xbbbbbbbb; +pub const DRM_MODE_OBJECT_PLANE: u32 = 0xeeeeeeee; + +pub trait DrmObject { + const TYPE: u32; + const NONE: Self; + fn id(&self) -> u32; + fn is_some(&self) -> bool; + fn is_none(&self) -> bool; +} + +macro_rules! drm_obj { + ($name:ident, $ty:expr) => { + #[repr(transparent)] + #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default, Ord, PartialOrd)] + pub struct $name(pub u32); + + impl DrmObject for $name { + const TYPE: u32 = $ty; + const NONE: Self = Self(0); + + fn id(&self) -> u32 { + self.0 + } + + fn is_some(&self) -> bool { + self.0 != 0 + } + + fn is_none(&self) -> bool { + self.0 == 0 + } + } + }; +} + +drm_obj!(DrmCrtc, DRM_MODE_OBJECT_CRTC); +drm_obj!(DrmConnector, DRM_MODE_OBJECT_CONNECTOR); +drm_obj!(DrmEncoder, DRM_MODE_OBJECT_ENCODER); +drm_obj!(DrmProperty, DRM_MODE_OBJECT_PROPERTY); +drm_obj!(DrmFb, DRM_MODE_OBJECT_FB); +drm_obj!(DrmBlob, DRM_MODE_OBJECT_BLOB); +drm_obj!(DrmPlane, DRM_MODE_OBJECT_PLANE); diff --git a/crates/video-types/src/lib.rs b/crates/video-types/src/lib.rs new file mode 100644 index 00000000..c01c353c --- /dev/null +++ b/crates/video-types/src/lib.rs @@ -0,0 +1,10 @@ +pub mod drm; +pub mod dmabuf; + +pub type Modifier = u64; + +pub const INVALID_MODIFIER: Modifier = 0x00ff_ffff_ffff_ffff; +pub const LINEAR_MODIFIER: Modifier = 0; + +// This is required by AMD and therefore everyone else uses this too. +pub const LINEAR_STRIDE_ALIGN: u64 = 256; diff --git a/crates/wheel/Cargo.toml b/crates/wheel/Cargo.toml new file mode 100644 index 00000000..bc24696f --- /dev/null +++ b/crates/wheel/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "jay-wheel" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +jay-async-engine = { path = "../async-engine" } +jay-io-uring = { path = "../io-uring" } +jay-time = { path = "../time" } +jay-utils = { path = "../utils" } + +log = { version = "0.4.20", features = ["std"] } +thiserror = "2.0.11" +uapi = "0.2.13" diff --git a/src/wheel.rs b/crates/wheel/src/lib.rs similarity index 94% rename from src/wheel.rs rename to crates/wheel/src/lib.rs index c0d10c97..360d20c7 100644 --- a/src/wheel.rs +++ b/crates/wheel/src/lib.rs @@ -1,17 +1,15 @@ use { - crate::{ - async_engine::{AsyncEngine, SpawnedFuture}, - io_uring::{IoUring, IoUringError}, - time::Time, - utils::{ - buf::TypedBuf, - copyhashmap::CopyHashMap, - errorfmt::ErrorFmt, - hash_map_ext::HashMapExt, - numcell::NumCell, - oserror::{OsError, OsErrorExt, OsErrorExt2}, - stack::Stack, - }, + jay_async_engine::{AsyncEngine, SpawnedFuture}, + jay_io_uring::{IoUring, IoUringError}, + jay_time::Time, + jay_utils::{ + buf::TypedBuf, + copyhashmap::CopyHashMap, + errorfmt::ErrorFmt, + hash_map_ext::HashMapExt, + numcell::NumCell, + oserror::{OsError, OsErrorExt, OsErrorExt2}, + stack::Stack, }, std::{ cell::{Cell, RefCell}, diff --git a/crates/wire-buf/Cargo.toml b/crates/wire-buf/Cargo.toml new file mode 100644 index 00000000..b908a044 --- /dev/null +++ b/crates/wire-buf/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "jay-wire-buf" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +jay-io-uring = { path = "../io-uring" } +jay-time = { path = "../time" } +jay-units = { path = "../units" } +jay-utils = { path = "../utils" } +jay-wire-types = { path = "../wire-types" } + +bstr = { version = "1.9.0", default-features = false, features = ["std"] } +smallvec = { version = "1.11.1", features = ["const_generics", "const_new", "union"] } +thiserror = "2.0.11" +uapi = "0.2.13" diff --git a/src/utils/buffd/buf_in.rs b/crates/wire-buf/src/buf_in.rs similarity index 94% rename from src/utils/buffd/buf_in.rs rename to crates/wire-buf/src/buf_in.rs index d4c06295..568d73b2 100644 --- a/src/utils/buffd/buf_in.rs +++ b/crates/wire-buf/src/buf_in.rs @@ -1,11 +1,7 @@ use { - crate::{ - io_uring::IoUring, - utils::{ - buf::Buf, - buffd::{BUF_SIZE, BufFdError, MAX_IN_FD}, - }, - }, + crate::{BUF_SIZE, BufFdError, MAX_IN_FD}, + jay_io_uring::IoUring, + jay_utils::buf::Buf, smallvec::SmallVec, std::{collections::VecDeque, mem::MaybeUninit, rc::Rc}, uapi::{OwnedFd, Pod}, diff --git a/src/utils/buffd/buf_out.rs b/crates/wire-buf/src/buf_out.rs similarity index 95% rename from src/utils/buffd/buf_out.rs rename to crates/wire-buf/src/buf_out.rs index b28e5dc9..1bed2d95 100644 --- a/src/utils/buffd/buf_out.rs +++ b/crates/wire-buf/src/buf_out.rs @@ -1,13 +1,8 @@ use { - crate::{ - io_uring::{IoUring, IoUringError}, - time::Time, - utils::{ - buf::Buf, - buffd::{BUF_SIZE, BufFdError}, - oserror::OsError, - }, - }, + crate::{BUF_SIZE, BufFdError}, + jay_io_uring::{IoUring, IoUringError}, + jay_time::Time, + jay_utils::{buf::Buf, oserror::OsError}, std::{ collections::VecDeque, mem::{self}, diff --git a/src/utils/buffd/ei_formatter.rs b/crates/wire-buf/src/ei_formatter.rs similarity index 94% rename from src/utils/buffd/ei_formatter.rs rename to crates/wire-buf/src/ei_formatter.rs index af71f627..0652101c 100644 --- a/src/utils/buffd/ei_formatter.rs +++ b/crates/wire-buf/src/ei_formatter.rs @@ -1,8 +1,6 @@ use { - crate::{ - ei::ei_object::EiObjectId, - utils::buffd::buf_out::{MsgFds, OUT_BUF_SIZE, OutBuffer, OutBufferMeta}, - }, + crate::buf_out::{MsgFds, OUT_BUF_SIZE, OutBuffer, OutBufferMeta}, + jay_wire_types::EiObjectId, std::{mem, rc::Rc}, uapi::OwnedFd, }; @@ -42,7 +40,6 @@ impl<'a> EiMsgFormatter<'a> { self } - #[expect(dead_code)] pub fn long(&mut self, int: i64) -> &mut Self { self.write(uapi::as_bytes(&int)); self diff --git a/src/utils/buffd/ei_parser.rs b/crates/wire-buf/src/ei_parser.rs similarity index 97% rename from src/utils/buffd/ei_parser.rs rename to crates/wire-buf/src/ei_parser.rs index 361e6523..9ed0eae2 100644 --- a/src/utils/buffd/ei_parser.rs +++ b/crates/wire-buf/src/ei_parser.rs @@ -1,5 +1,6 @@ use { - crate::{ei::ei_object::EiObjectId, utils::buffd::BufFdIn}, + crate::BufFdIn, + jay_wire_types::EiObjectId, std::{ptr, rc::Rc}, thiserror::Error, uapi::OwnedFd, diff --git a/src/utils/buffd/formatter.rs b/crates/wire-buf/src/formatter.rs similarity index 94% rename from src/utils/buffd/formatter.rs rename to crates/wire-buf/src/formatter.rs index 2ad83618..cde9642f 100644 --- a/src/utils/buffd/formatter.rs +++ b/crates/wire-buf/src/formatter.rs @@ -1,9 +1,7 @@ use { - crate::{ - fixed::Fixed, - object::ObjectId, - utils::buffd::buf_out::{MsgFds, OUT_BUF_SIZE, OutBuffer, OutBufferMeta}, - }, + crate::buf_out::{MsgFds, OUT_BUF_SIZE, OutBuffer, OutBufferMeta}, + jay_units::Fixed, + jay_wire_types::ObjectId, std::{mem, rc::Rc}, uapi::{OwnedFd, Packed}, }; @@ -48,13 +46,11 @@ impl<'a> MsgFormatter<'a> { self } - #[expect(dead_code)] pub fn u64(&mut self, int: u64) -> &mut Self { self.uint((int >> 32) as u32); self.uint(int as u32) } - #[expect(dead_code)] pub fn u64_rev(&mut self, int: u64) -> &mut Self { self.uint(int as u32); self.uint((int >> 32) as u32) @@ -96,7 +92,6 @@ impl<'a> MsgFormatter<'a> { self.object(obj).uint(event) } - #[expect(dead_code)] pub fn array)>(&mut self, f: F) -> &mut Self { let pos = self.meta.write_pos; self.uint(0); diff --git a/src/utils/buffd.rs b/crates/wire-buf/src/lib.rs similarity index 95% rename from src/utils/buffd.rs rename to crates/wire-buf/src/lib.rs index bbb8a67e..3166180c 100644 --- a/src/utils/buffd.rs +++ b/crates/wire-buf/src/lib.rs @@ -1,4 +1,4 @@ -use {crate::io_uring::IoUringError, thiserror::Error}; +use {jay_io_uring::IoUringError, thiserror::Error}; pub use { buf_in::BufFdIn, buf_out::{BufFdOut, OutBuffer, OutBufferSwapchain}, diff --git a/src/utils/buffd/parser.rs b/crates/wire-buf/src/parser.rs similarity index 97% rename from src/utils/buffd/parser.rs rename to crates/wire-buf/src/parser.rs index 7ae74118..cc5b0f3c 100644 --- a/src/utils/buffd/parser.rs +++ b/crates/wire-buf/src/parser.rs @@ -1,6 +1,7 @@ use { - crate::{fixed::Fixed, globals::GlobalName, object::ObjectId}, bstr::{BStr, ByteSlice}, + jay_units::Fixed, + jay_wire_types::{GlobalName, ObjectId}, std::{collections::VecDeque, ptr, rc::Rc}, thiserror::Error, uapi::{OwnedFd, Pod}, @@ -55,14 +56,12 @@ impl<'a, 'b> MsgParser<'a, 'b> { self.int().map(|i| i as u32) } - #[expect(dead_code)] pub fn u64(&mut self) -> Result { let hi = self.uint()?; let lo = self.uint()?; Ok(((hi as u64) << 32) | lo as u64) } - #[expect(dead_code)] pub fn u64_rev(&mut self) -> Result { let lo = self.uint()?; let hi = self.uint()?; @@ -76,7 +75,6 @@ impl<'a, 'b> MsgParser<'a, 'b> { self.int().map(|i| ObjectId::from_raw(i as u32).into()) } - #[expect(dead_code)] pub fn global(&mut self) -> Result { self.int().map(|i| GlobalName::from_raw(i as u32)) } diff --git a/src/utils/buffd/wl_buf_in.rs b/crates/wire-buf/src/wl_buf_in.rs similarity index 95% rename from src/utils/buffd/wl_buf_in.rs rename to crates/wire-buf/src/wl_buf_in.rs index 8bca5956..9a1e5914 100644 --- a/src/utils/buffd/wl_buf_in.rs +++ b/crates/wire-buf/src/wl_buf_in.rs @@ -1,12 +1,8 @@ use { - crate::{ - io_uring::IoUring, - object::ObjectId, - utils::{ - buf::Buf, - buffd::{BufFdError, MAX_IN_FD}, - }, - }, + crate::{BufFdError, MAX_IN_FD}, + jay_io_uring::IoUring, + jay_utils::buf::Buf, + jay_wire_types::ObjectId, std::{collections::VecDeque, ptr, rc::Rc, slice}, uapi::OwnedFd, }; diff --git a/wire-to-xml/Cargo.toml b/crates/wire-to-xml/Cargo.toml similarity index 66% rename from wire-to-xml/Cargo.toml rename to crates/wire-to-xml/Cargo.toml index d8c8c2cb..0e594b6d 100644 --- a/wire-to-xml/Cargo.toml +++ b/crates/wire-to-xml/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "wire-to-xml" -version = "0.1.0" -edition = "2024" +version.workspace = true +edition.workspace = true +license.workspace = true [dependencies] anyhow = "1.0.79" diff --git a/wire-to-xml/src/main.rs b/crates/wire-to-xml/src/main.rs similarity index 99% rename from wire-to-xml/src/main.rs rename to crates/wire-to-xml/src/main.rs index 5341e137..6a73075e 100644 --- a/wire-to-xml/src/main.rs +++ b/crates/wire-to-xml/src/main.rs @@ -21,7 +21,7 @@ use { std::{io, os::unix::ffi::OsStrExt, path::PathBuf}, }; -#[path = "../../build/wire/parser.rs"] +#[path = "../../../build/wire/parser.rs"] #[allow(dead_code)] mod parser; diff --git a/crates/wire-types/Cargo.toml b/crates/wire-types/Cargo.toml new file mode 100644 index 00000000..c51716f4 --- /dev/null +++ b/crates/wire-types/Cargo.toml @@ -0,0 +1,5 @@ +[package] +name = "jay-wire-types" +version.workspace = true +edition.workspace = true +license.workspace = true diff --git a/crates/wire-types/src/lib.rs b/crates/wire-types/src/lib.rs new file mode 100644 index 00000000..11434980 --- /dev/null +++ b/crates/wire-types/src/lib.rs @@ -0,0 +1,68 @@ +use std::fmt::{Display, Formatter, LowerHex}; + +#[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)] +pub struct ObjectId(u32); + +impl ObjectId { + pub const NONE: Self = ObjectId(0); + + pub fn from_raw(raw: u32) -> Self { + Self(raw) + } + + pub fn raw(self) -> u32 { + self.0 + } +} + +impl Display for ObjectId { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.0, f) + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct GlobalName(u32); + +impl GlobalName { + pub fn from_raw(id: u32) -> Self { + Self(id) + } + + pub fn raw(self) -> u32 { + self.0 + } +} + +impl Display for GlobalName { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.0, f) + } +} + +#[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)] +pub struct EiObjectId(u64); + +impl EiObjectId { + pub const NONE: Self = EiObjectId(0); + + pub fn from_raw(raw: u64) -> Self { + Self(raw) + } + + pub fn raw(self) -> u64 { + self.0 + } +} + +impl Display for EiObjectId { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.0, f) + } +} + +impl LowerHex for EiObjectId { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + LowerHex::fmt(&self.0, f) + } +} diff --git a/crates/xcon/Cargo.toml b/crates/xcon/Cargo.toml new file mode 100644 index 00000000..a2087f50 --- /dev/null +++ b/crates/xcon/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "jay-xcon" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +jay-bufio = { path = "../bufio" } +jay-io-uring = { path = "../io-uring" } +jay-utils = { path = "../utils" } + +bstr = { version = "1.9.0", default-features = false, features = ["std"] } +log = { version = "0.4.20", features = ["std"] } +thiserror = "2.0.11" +uapi = "0.2.13" diff --git a/src/xcon/consts.rs b/crates/xcon/src/consts.rs similarity index 100% rename from src/xcon/consts.rs rename to crates/xcon/src/consts.rs diff --git a/src/xcon/formatter.rs b/crates/xcon/src/formatter.rs similarity index 97% rename from src/xcon/formatter.rs rename to crates/xcon/src/formatter.rs index e1fcf21f..d284c23b 100644 --- a/src/xcon/formatter.rs +++ b/crates/xcon/src/formatter.rs @@ -1,5 +1,6 @@ use { - crate::{utils::buf::DynamicBuf, xcon::Message}, + crate::Message, + jay_utils::buf::DynamicBuf, std::rc::Rc, uapi::{AssertPacked, OwnedFd, Packed}, }; diff --git a/crates/xcon/src/lib.rs b/crates/xcon/src/lib.rs new file mode 100644 index 00000000..09db05d6 --- /dev/null +++ b/crates/xcon/src/lib.rs @@ -0,0 +1,95 @@ +pub use { + formatter::Formatter, + parser::Parser, + wire_type::{Message, Request, SendEvent, XEvent}, +}; +use { + bstr::BString, + jay_bufio::BufIoError, + jay_io_uring::IoUringError, + jay_utils::oserror::OsError, + std::rc::Rc, + thiserror::Error, +}; + +pub mod consts; +mod formatter; +mod parser; +mod wire_type; +pub mod xauthority; + +#[derive(Debug, Error)] +pub enum XconError { + #[error("Unexpected EOF")] + UnexpectedEof, + #[error("Buffer slice is not properly aligned")] + UnalignedSlice, + #[error("Neither XAUTHORITY nor HOME is set")] + HomeNotSet, + #[error("Could not read Xauthority file")] + ReadXAuthority(#[source] std::io::Error), + #[error("Display field in Xauthority could not be parsed")] + InvalidAuthorityDisplay, + #[error("The DISPLAY is not set")] + DisplayNotSet, + #[error("DISPLAY contains an invalid value")] + InvalidDisplayFormat, + #[error("Could not create a unix socket")] + CreateSocket(#[source] OsError), + #[error("Could not connect to Xserver")] + ConnectSocket(#[source] IoUringError), + #[error("Could not retrive the hostname")] + Hostname(#[source] OsError), + #[error("Server did not send enough fds")] + NotEnoughFds, + #[error("Server rejected our connection attempt: {0}")] + Connect(BString), + #[error("Server requires additional authentication: {0}")] + Authenticate(BString), + #[error(transparent)] + BufIoError(#[from] BufIoError), + #[error("The server did not send a reply to a request")] + MissingReply, + #[error("The server did not send fds with a reply")] + MissingFds, + #[error("The server sent a message with an excessive size")] + ExcessiveMessageSize, + #[error(transparent)] + XconError(Rc), + #[error("The server does not support the `{0}` extension")] + ExtensionUnavailable(&'static str), + #[error("The server returned error {0}")] + CoreError(u8), + #[error("The extension `{0}` returned error {1}")] + ExtensionError(&'static str, u8), + #[error("The connection to the server has already been closed")] + Dead, + #[error("Could not query the `{0}` extension")] + QueryExtension(BString, #[source] Box), + #[error("All available xids have been used")] + XidExhausted, + #[error("Enum contains an unknown variant")] + UnknownEnumVariant, + #[error("Could not query the render pict formats")] + QueryPictFormats(#[source] Box), + #[error("The server does not support the picture format for cursors")] + CursorFormatNotSupported, + #[error("Could not create a pixmap")] + CreatePixmap(#[source] Box), + #[error("Could not create a graphics context")] + CreateGc(#[source] Box), + #[error("Could not upload an image")] + PutImage(#[source] Box), + #[error("Could not create a picture")] + CreatePicture(#[source] Box), + #[error("Could not create a cursor")] + CreateCursor(#[source] Box), + #[error("Property has an invalid type")] + InvalidPropertyType, + #[error("Property has an invalid format. Expected: {0}; Actual: {1}")] + InvalidPropertyFormat(u8, u8), + #[error("Length of the property data is not a multiple of its format")] + IrregularPropertyLength, + #[error("The property is not set")] + PropertyUnavailable, +} diff --git a/src/xcon/parser.rs b/crates/xcon/src/parser.rs similarity index 97% rename from src/xcon/parser.rs rename to crates/xcon/src/parser.rs index a8d103f0..d68d47e9 100644 --- a/src/xcon/parser.rs +++ b/crates/xcon/src/parser.rs @@ -1,9 +1,7 @@ use { - crate::{ - utils::ptr_ext::PtrExt, - xcon::{XconError, wire_type::Message}, - }, + crate::{Message, XconError}, bstr::{BStr, ByteSlice}, + jay_utils::ptr_ext::PtrExt, std::{borrow::Cow, rc::Rc}, uapi::{OwnedFd, Pod}, }; diff --git a/src/xcon/wire_type.rs b/crates/xcon/src/wire_type.rs similarity index 97% rename from src/xcon/wire_type.rs rename to crates/xcon/src/wire_type.rs index a3dbbfa5..7e932360 100644 --- a/src/xcon/wire_type.rs +++ b/crates/xcon/src/wire_type.rs @@ -1,5 +1,5 @@ use { - crate::xcon::{XconError, formatter::Formatter, parser::Parser}, + crate::{Formatter, Parser, XconError}, bstr::{BStr, ByteSlice}, std::{borrow::Cow, fmt::Debug, rc::Rc}, uapi::OwnedFd, @@ -118,7 +118,7 @@ unsafe impl<'a> Message<'a> for Rc { } #[derive(Debug, Clone)] -pub(super) struct SendEvent { +pub struct SendEvent { pub propagate: u8, pub destination: u32, pub event_mask: u32, diff --git a/src/xcon/xauthority.rs b/crates/xcon/src/xauthority.rs similarity index 98% rename from src/xcon/xauthority.rs rename to crates/xcon/src/xauthority.rs index 09827d30..aeff63aa 100644 --- a/src/xcon/xauthority.rs +++ b/crates/xcon/src/xauthority.rs @@ -1,5 +1,5 @@ use { - crate::xcon::XconError, + crate::XconError, bstr::{BString, ByteSlice}, std::{fs::File, io::Read}, }; diff --git a/xml-to-wire/Cargo.toml b/crates/xml-to-wire/Cargo.toml similarity index 54% rename from xml-to-wire/Cargo.toml rename to crates/xml-to-wire/Cargo.toml index b2f95ab7..302bcdd0 100644 --- a/xml-to-wire/Cargo.toml +++ b/crates/xml-to-wire/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "xml-to-wire" -version = "0.1.0" -edition = "2024" +version.workspace = true +edition.workspace = true +license.workspace = true [dependencies] quick-xml = "0.39.0" diff --git a/xml-to-wire/src/ast.rs b/crates/xml-to-wire/src/ast.rs similarity index 100% rename from xml-to-wire/src/ast.rs rename to crates/xml-to-wire/src/ast.rs diff --git a/xml-to-wire/src/builder.rs b/crates/xml-to-wire/src/builder.rs similarity index 100% rename from xml-to-wire/src/builder.rs rename to crates/xml-to-wire/src/builder.rs diff --git a/xml-to-wire/src/main.rs b/crates/xml-to-wire/src/main.rs similarity index 100% rename from xml-to-wire/src/main.rs rename to crates/xml-to-wire/src/main.rs diff --git a/xml-to-wire/src/parser.rs b/crates/xml-to-wire/src/parser.rs similarity index 100% rename from xml-to-wire/src/parser.rs rename to crates/xml-to-wire/src/parser.rs diff --git a/etc/jay-portals.conf b/etc/jay-portals.conf index 607e1691..661fb70f 100644 --- a/etc/jay-portals.conf +++ b/etc/jay-portals.conf @@ -3,4 +3,4 @@ default=gtk org.freedesktop.impl.portal.ScreenCast=jay org.freedesktop.impl.portal.RemoteDesktop=jay org.freedesktop.impl.portal.Inhibit=none -org.freedesktop.impl.portal.FileChooser=gtk4 +org.freedesktop.impl.portal.FileChooser=gtk diff --git a/jay-config/src/_private.rs b/jay-config/src/_private.rs deleted file mode 100644 index facb7c17..00000000 --- a/jay-config/src/_private.rs +++ /dev/null @@ -1,134 +0,0 @@ -pub mod client; -pub mod ipc; -mod logging; - -use { - crate::{ - Workspace, - client::ClientMatcher, - input::Seat, - video::Mode, - window::{ContentType, WindowMatcher, WindowType}, - }, - bincode::Options, - serde::{Deserialize, Serialize}, - std::marker::PhantomData, -}; - -pub const VERSION: u32 = 1; - -#[repr(C)] -pub struct ConfigEntry { - pub version: u32, - pub init: unsafe extern "C" fn( - srv_data: *const u8, - srv_unref: unsafe extern "C" fn(data: *const u8), - srv_handler: unsafe extern "C" fn(data: *const u8, msg: *const u8, size: usize), - msg: *const u8, - size: usize, - ) -> *const u8, - pub unref: unsafe extern "C" fn(data: *const u8), - pub handle_msg: unsafe extern "C" fn(data: *const u8, msg: *const u8, size: usize), -} - -pub struct ConfigEntryGen { - _phantom: PhantomData, -} - -impl ConfigEntryGen {} - -pub fn bincode_ops() -> impl Options { - bincode::DefaultOptions::new() - .with_fixint_encoding() - .with_little_endian() - .with_no_limit() -} - -pub trait Config { - extern "C" fn configure(); -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct WireMode { - pub width: i32, - pub height: i32, - pub refresh_millihz: u32, -} - -impl WireMode { - pub fn to_mode(self) -> Mode { - Mode { - width: self.width, - height: self.height, - refresh_millihz: self.refresh_millihz, - } - } -} - -#[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct PollableId(pub u64); - -pub const DEFAULT_SEAT_NAME: &str = "default"; - -#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)] -pub enum GenericCriterionIpc { - Matcher(T), - Not(T), - List { list: Vec, all: bool }, - Exactly { list: Vec, num: usize }, -} - -#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)] -pub enum ClientCriterionIpc { - Generic(GenericCriterionIpc), - String { - string: String, - field: ClientCriterionStringField, - regex: bool, - }, - Sandboxed, - Uid(i32), - Pid(i32), - IsXwayland, -} - -#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)] -pub enum ClientCriterionStringField { - SandboxEngine, - SandboxAppId, - SandboxInstanceId, - Comm, - Exe, - Tag, -} - -#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)] -pub enum WindowCriterionIpc { - Generic(GenericCriterionIpc), - String { - string: String, - field: WindowCriterionStringField, - regex: bool, - }, - Types(WindowType), - Client(ClientMatcher), - Floating, - Visible, - Urgent, - SeatFocus(Seat), - Fullscreen, - JustMapped, - Workspace(Workspace), - ContentTypes(ContentType), -} - -#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)] -pub enum WindowCriterionStringField { - Title, - AppId, - Tag, - XClass, - XInstance, - XRole, - Workspace, -} diff --git a/src/acceptor.rs b/src/acceptor.rs index d221d32c..4bd6dd2e 100644 --- a/src/acceptor.rs +++ b/src/acceptor.rs @@ -1,8 +1,8 @@ +use jay_async_engine::SpawnedFuture; use { crate::{ - async_engine::SpawnedFuture, - client::ClientCaps, - security_context_acceptor::AcceptorMetadata, + + client::ClientMetadata, state::State, utils::{ errorfmt::ErrorFmt, @@ -46,63 +46,49 @@ struct AllocatedSocket { name: String, // /run/user/1000/wayland-x path: Ustring, - insecure: Rc, + socket: Rc, // /run/user/1000/wayland-x.lock lock_path: Ustring, _lock_fd: OwnedFd, - // /run/user/1000/wayland-x.jay - secure_path: Ustring, - secure: Rc, } impl Drop for AllocatedSocket { fn drop(&mut self) { let _ = uapi::unlink(&self.path); let _ = uapi::unlink(&self.lock_path); - let _ = uapi::unlink(&self.secure_path); } } -fn bind_socket( - insecure: &Rc, - secure: &Rc, - xrd: &str, - id: u32, -) -> Result { +fn bind_socket(socket: &Rc, xrd: &str, id: u32) -> Result { let mut addr: c::sockaddr_un = uapi::pod_zeroed(); addr.sun_family = c::AF_UNIX as _; let name = format!("wayland-{}", id); let path = format_ustr!("{}/{}", xrd, name); - let jay_path = format_ustr!("{}.jay", path.display()); let lock_path = format_ustr!("{}.lock", path.display()); - if jay_path.len() + 1 > addr.sun_path.len() { + if path.len() + 1 > addr.sun_path.len() { return Err(AcceptorError::XrdTooLong(xrd.to_string())); } let lock_fd = uapi::open(&*lock_path, c::O_CREAT | c::O_CLOEXEC | c::O_RDWR, 0o644) .map_os_err(AcceptorError::OpenLockFile)?; uapi::flock(lock_fd.raw(), c::LOCK_EX | c::LOCK_NB).map_os_err(AcceptorError::LockLockFile)?; - for (name, fd) in [(&path, insecure), (&jay_path, secure)] { - match uapi::lstat(name).to_os_error() { - Ok(_) => { - log::info!("Unlinking {}", name.display()); - let _ = uapi::unlink(name); - } - Err(OsError(c::ENOENT)) => {} - Err(e) => return Err(AcceptorError::SocketStat(e)), + match uapi::lstat(&path).to_os_error() { + Ok(_) => { + log::info!("Unlinking {}", path.display()); + let _ = uapi::unlink(&path); } - let sun_path = uapi::as_bytes_mut(&mut addr.sun_path[..]); - sun_path[..name.len()].copy_from_slice(name.as_bytes()); - sun_path[name.len()] = 0; - uapi::bind(fd.raw(), &addr).map_os_err(AcceptorError::BindFailed)?; + Err(OsError(c::ENOENT)) => {} + Err(e) => return Err(AcceptorError::SocketStat(e)), } + let sun_path = uapi::as_bytes_mut(&mut addr.sun_path[..]); + sun_path[..path.len()].copy_from_slice(path.as_bytes()); + sun_path[path.len()] = 0; + uapi::bind(socket.raw(), &addr).map_os_err(AcceptorError::BindFailed)?; Ok(AllocatedSocket { name, path, - insecure: insecure.clone(), + socket: socket.clone(), lock_path, _lock_fd: lock_fd, - secure_path: jay_path, - secure: secure.clone(), }) } @@ -111,17 +97,11 @@ fn allocate_socket() -> Result { Some(d) => d, _ => return Err(AcceptorError::XrdNotSet), }; - let mut fds = [None, None]; - for fd in &mut fds { - let socket = uapi::socket(c::AF_UNIX, c::SOCK_STREAM | c::SOCK_CLOEXEC, 0) - .map(Rc::new) - .map_os_err(AcceptorError::SocketFailed)?; - *fd = Some(socket); - } - let unsecure = fds[0].take().unwrap(); - let secure = fds[1].take().unwrap(); + let socket = uapi::socket(c::AF_UNIX, c::SOCK_STREAM | c::SOCK_CLOEXEC, 0) + .map(Rc::new) + .map_os_err(AcceptorError::SocketFailed)?; for i in 1..1000 { - match bind_socket(&unsecure, &secure, &xrd, i) { + match bind_socket(&socket, &xrd, i) { Ok(s) => return Ok(s), Err(e) => { log::warn!("Cannot use the wayland-{} socket: {}", i, ErrorFmt(e)); @@ -137,19 +117,12 @@ impl Acceptor { ) -> Result<(Rc, Vec>), AcceptorError> { let socket = allocate_socket()?; log::info!("bound to socket {}", socket.path.display()); - for fd in [&socket.secure, &socket.insecure] { - uapi::listen(fd.raw(), 4096).map_os_err(AcceptorError::ListenFailed)?; - } + uapi::listen(socket.socket.raw(), 4096).map_os_err(AcceptorError::ListenFailed)?; let acc = Rc::new(Acceptor { socket }); let futures = vec![ - state.eng.spawn( - "secure acceptor", - accept(acc.socket.secure.clone(), state.clone(), true), - ), - state.eng.spawn( - "insecure acceptor", - accept(acc.socket.insecure.clone(), state.clone(), false), - ), + state + .eng + .spawn("client acceptor", accept(acc.socket.socket.clone(), state.clone())), ]; state.acceptor.set(Some(acc.clone())); Ok((acc, futures)) @@ -160,16 +133,13 @@ impl Acceptor { } #[cfg_attr(not(feature = "it"), expect(dead_code))] - pub fn secure_path(&self) -> &Ustr { - self.socket.secure_path.as_ustr() + pub fn socket_path(&self) -> &Ustr { + self.socket.path.as_ustr() } } -async fn accept(fd: Rc, state: Rc, secure: bool) { - let metadata = Rc::new(AcceptorMetadata { - secure, - ..Default::default() - }); +async fn accept(fd: Rc, state: Rc) { + let metadata = Rc::new(ClientMetadata::default()); loop { let fd = match state.ring.accept(&fd, c::SOCK_CLOEXEC).await { Ok(fd) => fd, @@ -181,7 +151,7 @@ async fn accept(fd: Rc, state: Rc, secure: bool) { let id = state.clients.id(); if let Err(e) = state .clients - .spawn(id, &state, fd, ClientCaps::all(), false, &metadata) + .spawn(id, &state, fd, &metadata) { log::error!("Could not spawn a client: {}", ErrorFmt(e)); break; diff --git a/src/allocator.rs b/src/allocator.rs index 91ec23d0..d8d14aef 100644 --- a/src/allocator.rs +++ b/src/allocator.rs @@ -1,57 +1,15 @@ -use { - crate::{ - format::Format, - video::{ - Modifier, - dmabuf::{DmaBuf, DmaBufIds}, - drm::Drm, - }, - }, - std::{error::Error, rc::Rc}, - thiserror::Error, -}; +pub use jay_allocator::*; -#[derive(Debug, Error)] -#[error(transparent)] -pub struct AllocatorError(#[from] pub Box); +use crate::video::drm::Drm; -bitflags! { - BufferUsage: u32; - BO_USE_SCANOUT, - BO_USE_CURSOR, - BO_USE_RENDERING, - BO_USE_WRITE, - BO_USE_LINEAR, - BO_USE_PROTECTED, -} - -pub trait Allocator { - fn drm(&self) -> Option<&Drm>; - fn create_bo( - &self, - dma_buf_ids: &DmaBufIds, - width: i32, - height: i32, - format: &'static Format, - modifiers: &[Modifier], - usage: BufferUsage, - ) -> Result, AllocatorError>; - fn import_dmabuf( - &self, - dmabuf: &DmaBuf, - usage: BufferUsage, - ) -> Result, AllocatorError>; -} - -pub trait BufferObject { - fn dmabuf(&self) -> &DmaBuf; - fn map_read(self: Rc) -> Result, AllocatorError>; - fn map_write(self: Rc) -> Result, AllocatorError>; -} - -pub trait MappedBuffer { - unsafe fn data(&self) -> &[u8]; - #[cfg_attr(not(feature = "it"), expect(dead_code))] - fn data_ptr(&self) -> *mut u8; - fn stride(&self) -> i32; +impl AllocatorDrm for Drm { + fn dev(&self) -> uapi::c::dev_t { + self.dev() + } + + fn dup_render_fd(&self) -> Result, AllocatorError> { + self.dup_render() + .map(|drm| drm.fd().clone()) + .map_err(|e| AllocatorError(Box::new(e))) + } } diff --git a/src/animation.rs b/src/animation.rs new file mode 100644 index 00000000..b4e29a02 --- /dev/null +++ b/src/animation.rs @@ -0,0 +1,1078 @@ +use jay_theme::Color; +use jay_geometry::Rect; +use { + crate::{ + cmm::{cmm_description::ColorDescription, cmm_render_intent::RenderIntent}, + gfx_api::{GfxTexture, SampleRect}, + ifs::wl_surface::{SurfaceBuffer, WlSurface}, + + state::State, + + tree::{LatchListener, NodeId, OutputNode}, + utils::{clonecell::CloneCell, event_listener::EventListener}, + }, + ahash::AHashMap, + std::{ + cell::{Cell, RefCell}, + collections::VecDeque, + rc::{Rc, Weak}, + }, +}; + +pub mod multiphase; + +pub use jay_layout_animation::{AnimationCurve, AnimationStyle}; + +const DEFAULT_DURATION_MS: u32 = 160; + +pub struct AnimationState { + pub enabled: Cell, + pub duration_ms: Cell, + pub curve: Cell, + pub style: Cell, + windows: RefCell>, + phased: RefCell>, + exits: RefCell>, + tick: CloneCell>>, +} + +pub struct RetainedToplevel { + pub offset: (i32, i32), + pub surface: RetainedSurface, +} + +pub struct RetainedSurface { + pub offset: (i32, i32), + pub size: (i32, i32), + pub content: RetainedContent, + pub below: Vec, + pub above: Vec, +} + +pub enum RetainedContent { + Texture { + texture: Rc, + buffer: Rc, + source: SampleRect, + alpha: Option, + color_description: Rc, + render_intent: RenderIntent, + alpha_mode: crate::gfx_api::AlphaMode, + opaque: bool, + }, + Color { + color: Color, + alpha: Option, + color_description: Rc, + render_intent: RenderIntent, + }, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum RetainedExitLayer { + Tiled, + Floating, +} + +pub struct RetainedExitFrame { + pub rect: Rect, + pub retained: Rc, + pub frame_inset: i32, + pub source_body_size: (i32, i32), + pub active: bool, + pub layer: RetainedExitLayer, +} + +impl RetainedToplevel { + pub fn capture_surface(surface: &WlSurface, offset: (i32, i32)) -> Option> { + Some(Rc::new(Self { + offset, + surface: RetainedSurface::capture(surface, (0, 0))?, + })) + } +} + +impl RetainedSurface { + fn capture(surface: &WlSurface, offset: (i32, i32)) -> Option { + let buffer = surface.buffer.get()?; + buffer.buffer.buf.update_texture_or_log(surface, true); + let size = surface.buffer_abs_pos.get().size(); + let source = *surface.buffer_points_norm.borrow(); + let color_description = surface.color_description(); + let render_intent = surface.render_intent(); + let alpha_mode = surface.alpha_mode(); + let alpha = surface.alpha(); + let content = match buffer.buffer.buf.get_texture(surface) { + Some(texture) => RetainedContent::Texture { + opaque: surface.opaque(), + texture, + buffer, + source, + alpha, + color_description, + render_intent, + alpha_mode, + }, + None => { + let color = buffer.buffer.buf.color?; + RetainedContent::Color { + color: Color::from_u32( + color_description.eotf, + alpha_mode, + color[0], + color[1], + color[2], + color[3], + ), + alpha, + color_description, + render_intent, + } + } + }; + let mut below = vec![]; + let mut above = vec![]; + if let Some(children) = surface.children.borrow().as_deref() { + for child in children.below.iter() { + if child.pending.get() { + continue; + } + let pos = child.sub_surface.position.get(); + if let Some(surface) = Self::capture(&child.sub_surface.surface, pos) { + below.push(surface); + } + } + for child in children.above.iter() { + if child.pending.get() { + continue; + } + let pos = child.sub_surface.position.get(); + if let Some(surface) = Self::capture(&child.sub_surface.surface, pos) { + above.push(surface); + } + } + } + Some(Self { + offset, + size, + content, + below, + above, + }) + } +} + +impl Default for AnimationState { + fn default() -> Self { + Self { + enabled: Cell::new(false), + duration_ms: Cell::new(DEFAULT_DURATION_MS), + curve: Cell::new(AnimationCurve::from_config(3)), + style: Cell::new(AnimationStyle::Multiphase), + windows: Default::default(), + phased: Default::default(), + exits: Default::default(), + tick: Default::default(), + } + } +} + +impl AnimationState { + pub fn clear(&self) { + self.windows.borrow_mut().clear(); + self.phased.borrow_mut().clear(); + self.exits.borrow_mut().clear(); + if let Some(tick) = self.tick.take() { + tick.detach(); + } + } + + pub fn set_target( + &self, + node_id: NodeId, + old: Rect, + new: Rect, + _retained: Option>, + now_nsec: u64, + duration_ms: u32, + curve: AnimationCurve, + ) -> bool { + if old == new || new.is_empty() || duration_ms == 0 { + self.windows.borrow_mut().remove(&node_id); + self.phased.borrow_mut().remove(&node_id); + return false; + } + let duration_nsec = duration_ms as u64 * 1_000_000; + let mut from = old; + { + let phased = self.phased.borrow(); + if let Some(anim) = phased.get(&node_id) { + if anim.final_rect == new { + return false; + } + from = anim.rect_at(now_nsec); + } + } + { + let windows = self.windows.borrow(); + if let Some(anim) = windows.get(&node_id) { + if anim.to == new { + return false; + } + from = anim.rect_at(now_nsec); + } + } + if from == new { + self.windows.borrow_mut().remove(&node_id); + self.phased.borrow_mut().remove(&node_id); + return false; + } + self.phased.borrow_mut().remove(&node_id); + self.windows.borrow_mut().insert( + node_id, + WindowAnimation { + from, + to: new, + start_nsec: now_nsec, + duration_nsec, + curve, + last_damage: from, + retained: None, + }, + ); + true + } + + pub fn set_phased_target( + &self, + node_id: NodeId, + phases: Vec<(Rect, Rect)>, + _retained: Option>, + now_nsec: u64, + duration_ms: u32, + curve: AnimationCurve, + ) -> bool { + if phases.is_empty() || duration_ms == 0 { + return false; + } + let Some((from, _)) = phases.first().copied() else { + return false; + }; + let Some((_, final_rect)) = phases.last().copied() else { + return false; + }; + if from.is_empty() || final_rect.is_empty() || from == final_rect { + return false; + } + let segments: Vec<_> = phases + .into_iter() + .map(|(from, to)| PhasedSegment { from, to }) + .collect(); + let mut route_edges = route_edges_from_segments(&segments); + if let Some(anim) = self.phased.borrow().get(&node_id) + && !anim.done(now_nsec) + { + for &(from, to) in &anim.route_edges { + push_unique_route_edge(&mut route_edges, from, to); + } + } + self.windows.borrow_mut().remove(&node_id); + self.phased.borrow_mut().insert( + node_id, + PhasedWindowAnimation { + segments, + start_nsec: now_nsec, + duration_nsec: duration_ms as u64 * 1_000_000, + curve, + last_damage: from, + final_rect, + route_edges, + retained: None, + }, + ); + true + } + + pub fn set_spawn_in( + &self, + node_id: NodeId, + target: Rect, + retained: Option>, + now_nsec: u64, + duration_ms: u32, + curve: AnimationCurve, + ) -> bool { + let start = spawn_in_start_rect(target); + self.set_target( + node_id, + start, + target, + retained, + now_nsec, + duration_ms, + curve, + ) + } + + pub fn set_spawn_out( + &self, + from: Rect, + frame_inset: i32, + retained: Rc, + active: bool, + layer: RetainedExitLayer, + now_nsec: u64, + duration_ms: u32, + curve: AnimationCurve, + ) -> bool { + if from.is_empty() || duration_ms == 0 { + return false; + } + let to = spawn_in_start_rect(from); + if to == from { + return false; + } + let source_body_size = body_size_for_frame(from, frame_inset); + if source_body_size.0 <= 0 || source_body_size.1 <= 0 { + return false; + } + self.exits.borrow_mut().push(ExitAnimation { + from, + to, + start_nsec: now_nsec, + duration_nsec: duration_ms as u64 * 1_000_000, + curve, + last_damage: from, + retained, + frame_inset, + source_body_size, + active, + layer, + }); + true + } + + pub fn visual_rect(&self, node_id: NodeId, layout: Rect, now_nsec: u64) -> Rect { + let phased = self.phased.borrow(); + if let Some(anim) = phased.get(&node_id) + && !anim.done(now_nsec) + { + return anim.rect_at(now_nsec); + } + drop(phased); + let windows = self.windows.borrow(); + match windows.get(&node_id) { + Some(anim) if !anim.done(now_nsec) => anim.rect_at(now_nsec), + _ => layout, + } + } + + pub fn retained_snapshot( + &self, + node_id: NodeId, + now_nsec: u64, + ) -> Option> { + let phased = self.phased.borrow(); + if let Some(anim) = phased.get(&node_id) + && !anim.done(now_nsec) + { + return anim.retained.clone(); + } + drop(phased); + let windows = self.windows.borrow(); + match windows.get(&node_id) { + Some(anim) if !anim.done(now_nsec) => anim.retained.clone(), + _ => None, + } + } + + pub fn phased_route_to( + &self, + node_id: NodeId, + target: Rect, + now_nsec: u64, + ) -> Option> { + let phased = self.phased.borrow(); + let anim = phased.get(&node_id)?; + if anim.done(now_nsec) { + return None; + } + anim.route_to(target, now_nsec) + } + + pub fn exit_frames(&self, now_nsec: u64) -> Vec { + self.exits + .borrow() + .iter() + .filter(|exit| !exit.done(now_nsec)) + .map(|exit| RetainedExitFrame { + rect: exit.rect_at(now_nsec), + retained: exit.retained.clone(), + frame_inset: exit.frame_inset, + source_body_size: exit.source_body_size, + active: exit.active, + layer: exit.layer, + }) + .collect() + } + + fn damage_active(&self, state: &State, now_nsec: u64) -> bool { + let mut damages = vec![]; + let mut any_active = false; + { + let mut windows = self.windows.borrow_mut(); + windows.retain(|_, anim| { + let current = anim.rect_at(now_nsec); + let damage = anim.last_damage.union(current).union(anim.to); + damages.push(expand_damage_rect( + damage, + state.theme.sizes.border_width.get().max(0), + )); + anim.last_damage = current; + let active = !anim.done(now_nsec); + any_active |= active; + active + }); + self.phased.borrow_mut().retain(|_, anim| { + let current = anim.rect_at(now_nsec); + let damage = anim.last_damage.union(current).union(anim.final_rect); + damages.push(expand_damage_rect( + damage, + state.theme.sizes.border_width.get().max(0), + )); + anim.last_damage = current; + let active = !anim.done(now_nsec); + any_active |= active; + active + }); + self.exits.borrow_mut().retain_mut(|exit| { + let current = exit.rect_at(now_nsec); + let damage = exit.last_damage.union(current).union(exit.to); + damages.push(expand_damage_rect( + damage, + state.theme.sizes.border_width.get().max(0), + )); + exit.last_damage = current; + let active = !exit.done(now_nsec); + any_active |= active; + active + }); + } + for damage in damages { + state.damage(damage); + } + any_active + } + + pub(crate) fn tick_is_active(&self) -> bool { + self.tick.is_some() + } + + pub(crate) fn set_tick(&self, tick: Rc) { + self.tick.set(Some(tick)); + } + + pub(crate) fn clear_tick(&self) { + self.tick.take(); + } +} + +struct WindowAnimation { + from: Rect, + to: Rect, + start_nsec: u64, + duration_nsec: u64, + curve: AnimationCurve, + last_damage: Rect, + retained: Option>, +} + +impl WindowAnimation { + fn done(&self, now_nsec: u64) -> bool { + now_nsec.saturating_sub(self.start_nsec) >= self.duration_nsec + } + + fn rect_at(&self, now_nsec: u64) -> Rect { + if self.duration_nsec == 0 { + return self.to; + } + let elapsed = now_nsec.saturating_sub(self.start_nsec); + let t = (elapsed as f64 / self.duration_nsec as f64).clamp(0.0, 1.0); + let t = self.curve.sample(t); + lerp_rect(self.from, self.to, t) + } +} + +struct PhasedWindowAnimation { + segments: Vec, + start_nsec: u64, + duration_nsec: u64, + curve: AnimationCurve, + last_damage: Rect, + final_rect: Rect, + route_edges: Vec<(Rect, Rect)>, + retained: Option>, +} + +struct PhasedSegment { + from: Rect, + to: Rect, +} + +impl PhasedWindowAnimation { + fn done(&self, now_nsec: u64) -> bool { + let total_duration = self + .duration_nsec + .saturating_mul(self.segments.len() as u64); + now_nsec.saturating_sub(self.start_nsec) >= total_duration + } + + fn rect_at(&self, now_nsec: u64) -> Rect { + if self.duration_nsec == 0 { + return self.final_rect; + } + let elapsed = now_nsec.saturating_sub(self.start_nsec); + let phase = (elapsed / self.duration_nsec) as usize; + let Some(segment) = self.segments.get(phase) else { + return self.final_rect; + }; + let phase_elapsed = elapsed % self.duration_nsec; + let t = (phase_elapsed as f64 / self.duration_nsec as f64).clamp(0.0, 1.0); + let t = self.curve.sample(t); + lerp_rect(segment.from, segment.to, t) + } + + fn phase_at(&self, now_nsec: u64) -> Option { + if self.duration_nsec == 0 || self.segments.is_empty() { + return None; + } + let elapsed = now_nsec.saturating_sub(self.start_nsec); + let phase = (elapsed / self.duration_nsec) as usize; + (phase < self.segments.len()).then_some(phase) + } + + fn route_to(&self, target: Rect, now_nsec: u64) -> Option> { + let phase = self.phase_at(now_nsec)?; + let current = self.rect_at(now_nsec); + if current == target { + return Some(vec![]); + } + let segment = self.segments.get(phase)?; + route_through_edges(current, target, segment.from, segment.to, &self.route_edges) + } +} + +fn route_edges_from_segments(segments: &[PhasedSegment]) -> Vec<(Rect, Rect)> { + let mut edges = vec![]; + for segment in segments { + push_unique_route_edge(&mut edges, segment.from, segment.to); + } + edges +} + +fn push_unique_route_edge(edges: &mut Vec<(Rect, Rect)>, from: Rect, to: Rect) { + if from == to { + return; + } + if edges + .iter() + .any(|&(a, b)| (a == from && b == to) || (a == to && b == from)) + { + return; + } + edges.push((from, to)); +} + +fn route_through_edges( + current: Rect, + target: Rect, + current_from: Rect, + current_to: Rect, + known_edges: &[(Rect, Rect)], +) -> Option> { + let mut edges = known_edges.to_vec(); + push_unique_route_edge(&mut edges, current, current_from); + push_unique_route_edge(&mut edges, current, current_to); + rect_graph_route(current, target, &edges) +} + +fn rect_graph_route( + start: Rect, + target: Rect, + edges: &[(Rect, Rect)], +) -> Option> { + let mut nodes = vec![]; + let mut adjacency: Vec> = vec![]; + let start_idx = rect_graph_node(&mut nodes, &mut adjacency, start); + let target_idx = rect_graph_node(&mut nodes, &mut adjacency, target); + for &(from, to) in edges { + let from_idx = rect_graph_node(&mut nodes, &mut adjacency, from); + let to_idx = rect_graph_node(&mut nodes, &mut adjacency, to); + if !adjacency[from_idx].contains(&to_idx) { + adjacency[from_idx].push(to_idx); + } + if !adjacency[to_idx].contains(&from_idx) { + adjacency[to_idx].push(from_idx); + } + } + + let mut previous = vec![None; nodes.len()]; + let mut queue = VecDeque::from([start_idx]); + previous[start_idx] = Some(start_idx); + while let Some(idx) = queue.pop_front() { + if idx == target_idx { + break; + } + for &next in &adjacency[idx] { + if previous[next].is_none() { + previous[next] = Some(idx); + queue.push_back(next); + } + } + } + previous[target_idx]?; + + let mut reversed_nodes = vec![target_idx]; + let mut idx = target_idx; + while idx != start_idx { + idx = previous[idx]?; + reversed_nodes.push(idx); + } + reversed_nodes.reverse(); + + let mut route = vec![]; + for pair in reversed_nodes.windows(2) { + push_non_empty_segment(&mut route, nodes[pair[0]], nodes[pair[1]]); + } + Some(route) +} + +fn rect_graph_node(nodes: &mut Vec, adjacency: &mut Vec>, rect: Rect) -> usize { + if let Some(idx) = nodes.iter().position(|&node| node == rect) { + return idx; + } + let idx = nodes.len(); + nodes.push(rect); + adjacency.push(vec![]); + idx +} + +fn push_non_empty_segment(route: &mut Vec<(Rect, Rect)>, from: Rect, to: Rect) { + if from != to { + route.push((from, to)); + } +} + +struct ExitAnimation { + from: Rect, + to: Rect, + start_nsec: u64, + duration_nsec: u64, + curve: AnimationCurve, + last_damage: Rect, + retained: Rc, + frame_inset: i32, + source_body_size: (i32, i32), + active: bool, + layer: RetainedExitLayer, +} + +impl ExitAnimation { + fn done(&self, now_nsec: u64) -> bool { + now_nsec.saturating_sub(self.start_nsec) >= self.duration_nsec + } + + fn rect_at(&self, now_nsec: u64) -> Rect { + if self.duration_nsec == 0 { + return self.to; + } + let elapsed = now_nsec.saturating_sub(self.start_nsec); + let t = (elapsed as f64 / self.duration_nsec as f64).clamp(0.0, 1.0); + let t = self.curve.sample(t); + lerp_rect(self.from, self.to, t) + } +} + +pub struct AnimationTick { + state: Weak, + slf: Weak, + latch_listeners: RefCell>>, +} + +impl AnimationTick { + pub fn new(state: &Rc, slf: &Weak) -> Self { + let slf: Weak = slf.clone(); + Self { + state: Rc::downgrade(state), + slf, + latch_listeners: Default::default(), + } + } + + pub fn attach(&self, output: &OutputNode) { + let listener = EventListener::new(self.slf.clone()); + listener.attach(&output.latch_event); + self.latch_listeners.borrow_mut().push(listener); + } + + pub fn detach(&self) { + for listener in self.latch_listeners.borrow_mut().drain(..) { + listener.detach(); + } + } +} + +impl LatchListener for AnimationTick { + fn after_latch(self: Rc, _on: &OutputNode, _tearing: bool) { + let Some(state) = self.state.upgrade() else { + self.detach(); + return; + }; + let active = state.animations.damage_active(&state, state.now_nsec()); + if !active { + self.detach(); + state.animations.clear_tick(); + } + } +} + +pub(crate) fn spawn_in_start_rect(target: Rect) -> Rect { + let (cx, cy) = target.center(); + Rect::new_empty(cx, cy) +} + +fn body_size_for_frame(rect: Rect, frame_inset: i32) -> (i32, i32) { + ( + rect.width().saturating_sub(2 * frame_inset), + rect.height().saturating_sub(2 * frame_inset), + ) +} + +fn lerp_rect(from: Rect, to: Rect, t: f64) -> Rect { + fn lerp(from: i32, to: i32, t: f64) -> i32 { + (from as f64 + (to as f64 - from as f64) * t).round() as i32 + } + Rect::new_saturating( + lerp(from.x1(), to.x1(), t), + lerp(from.y1(), to.y1(), t), + lerp(from.x2(), to.x2(), t), + lerp(from.y2(), to.y2(), t), + ) +} + +pub(crate) fn expand_damage_rect(rect: Rect, width: i32) -> Rect { + Rect::new_saturating( + rect.x1().saturating_sub(width), + rect.y1().saturating_sub(width), + rect.x2().saturating_add(width), + rect.y2().saturating_add(width), + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::cmm::cmm_manager::ColorManager; + + fn retained_for_tests() -> Rc { + let color_manager = ColorManager::new(); + Rc::new(RetainedToplevel { + offset: (0, 0), + surface: RetainedSurface { + offset: (0, 0), + size: (100, 100), + content: RetainedContent::Color { + color: Color::SOLID_BLACK, + alpha: None, + color_description: color_manager.srgb_gamma22().clone(), + render_intent: RenderIntent::Perceptual, + }, + below: vec![], + above: vec![], + }, + }) + } + + #[test] + fn linear_rect_interpolation_is_symmetric() { + let a = Rect::new_sized_saturating(0, 0, 100, 100); + let b = Rect::new_sized_saturating(100, 40, 200, 80); + assert_eq!(lerp_rect(a, b, 0.25), lerp_rect(b, a, 0.75)); + } + + #[test] + fn custom_cubic_bezier_curve_is_prepared() { + let curve = AnimationCurve::from_cubic_bezier(0.0, 0.0, 1.0, 1.0).unwrap(); + assert_eq!(curve.sample(0.0), 0.0); + assert_eq!(curve.sample(1.0), 1.0); + assert!((curve.sample(0.5) - 0.5).abs() < 0.001); + + let ease_out = AnimationCurve::from_cubic_bezier(0.0, 0.0, 0.58, 1.0).unwrap(); + let mid = ease_out.sample(0.5); + assert!(mid > 0.5); + assert!(mid < 1.0); + } + + #[test] + fn invalid_custom_cubic_bezier_curve_is_rejected() { + assert!(AnimationCurve::from_cubic_bezier(-0.1, 0.0, 0.58, 1.0).is_none()); + assert!(AnimationCurve::from_cubic_bezier(0.0, 0.0, 1.1, 1.0).is_none()); + assert!(AnimationCurve::from_cubic_bezier(0.0, f32::NAN, 0.58, 1.0).is_none()); + } + + #[test] + fn spawn_out_frames_use_configured_curve_and_expire() { + let state = AnimationState::default(); + let retained = retained_for_tests(); + let from = Rect::new_sized_saturating(10, 20, 100, 80); + let to = spawn_in_start_rect(from); + let curve = AnimationCurve::from_config(3); + assert!(state.set_spawn_out( + from, + 2, + retained.clone(), + true, + RetainedExitLayer::Floating, + 0, + 160, + curve + )); + + let start = state.exit_frames(0); + assert_eq!(start.len(), 1); + assert_eq!(start[0].rect, from); + assert_eq!(start[0].source_body_size, (96, 76)); + assert!(start[0].active); + assert_eq!(start[0].layer, RetainedExitLayer::Floating); + assert!(Rc::ptr_eq(&start[0].retained, &retained)); + + let middle = state.exit_frames(80_000_000); + assert_eq!(middle.len(), 1); + assert_eq!(middle[0].rect, lerp_rect(from, to, curve.sample(0.5))); + assert_ne!(middle[0].rect, lerp_rect(from, to, 0.5)); + assert!(state.exit_frames(160_000_000).is_empty()); + } + + #[test] + fn normal_window_animations_do_not_retain_content() { + let state = AnimationState::default(); + let id = NodeId(1); + let from = Rect::new_sized_saturating(0, 0, 100, 100); + let to = Rect::new_sized_saturating(100, 0, 100, 100); + assert!(state.set_target( + id, + from, + to, + Some(retained_for_tests()), + 0, + 160, + AnimationCurve::Linear + )); + + assert!(state.retained_snapshot(id, 80_000_000).is_none()); + } + + #[test] + fn phased_window_animations_do_not_retain_content() { + let state = AnimationState::default(); + let id = NodeId(1); + let a = Rect::new_sized_saturating(0, 0, 100, 100); + let b = Rect::new_sized_saturating(0, 0, 100, 50); + let c = Rect::new_sized_saturating(100, 0, 100, 50); + assert!(state.set_phased_target( + id, + vec![(a, b), (b, c)], + Some(retained_for_tests()), + 0, + 100, + AnimationCurve::Linear + )); + + assert!(state.retained_snapshot(id, 50_000_000).is_none()); + } + + #[test] + fn phased_animation_uses_full_duration_per_phase() { + let state = AnimationState::default(); + let id = NodeId(1); + let a = Rect::new_sized_saturating(0, 0, 100, 100); + let b = Rect::new_sized_saturating(0, 0, 100, 50); + let c = Rect::new_sized_saturating(100, 0, 100, 50); + assert!(state.set_phased_target( + id, + vec![(a, b), (b, c)], + None, + 0, + 100, + AnimationCurve::Linear + )); + assert_eq!(state.visual_rect(id, c, 0), a); + assert_eq!(state.visual_rect(id, c, 50_000_000), lerp_rect(a, b, 0.5)); + assert_eq!(state.visual_rect(id, c, 100_000_000), b); + assert_eq!(state.visual_rect(id, c, 150_000_000), lerp_rect(b, c, 0.5)); + assert_eq!(state.visual_rect(id, c, 200_000_000), c); + } + + #[test] + fn phased_route_reverses_to_existing_endpoint() { + let state = AnimationState::default(); + let id = NodeId(1); + let a = Rect::new_sized_saturating(0, 0, 100, 100); + let b = Rect::new_sized_saturating(0, 0, 100, 50); + let c = Rect::new_sized_saturating(100, 0, 100, 50); + assert!(state.set_phased_target( + id, + vec![(a, b), (b, c)], + None, + 0, + 100, + AnimationCurve::Linear + )); + + let current = lerp_rect(b, c, 0.5); + assert_eq!( + state.phased_route_to(id, a, 150_000_000).unwrap(), + vec![(current, b), (b, a)] + ); + } + + #[test] + fn phased_route_continues_to_existing_endpoint() { + let state = AnimationState::default(); + let id = NodeId(1); + let a = Rect::new_sized_saturating(0, 0, 100, 100); + let b = Rect::new_sized_saturating(0, 0, 100, 50); + let c = Rect::new_sized_saturating(100, 0, 100, 50); + assert!(state.set_phased_target( + id, + vec![(a, b), (b, c)], + None, + 0, + 100, + AnimationCurve::Linear + )); + + let current = lerp_rect(a, b, 0.5); + assert_eq!( + state.phased_route_to(id, c, 50_000_000).unwrap(), + vec![(current, b), (b, c)] + ); + } + + #[test] + fn phased_route_remembers_original_path_after_retarget() { + let state = AnimationState::default(); + let id = NodeId(1); + let a = Rect::new_sized_saturating(0, 0, 100, 100); + let b = Rect::new_sized_saturating(0, 0, 100, 50); + let c = Rect::new_sized_saturating(100, 0, 100, 50); + assert!(state.set_phased_target( + id, + vec![(a, b), (b, c)], + None, + 0, + 100, + AnimationCurve::Linear + )); + + let current = lerp_rect(b, c, 0.5); + let reverse = state.phased_route_to(id, a, 150_000_000).unwrap(); + assert_eq!(reverse, vec![(current, b), (b, a)]); + assert!(state.set_phased_target( + id, + reverse, + None, + 150_000_000, + 100, + AnimationCurve::Linear + )); + + let current = lerp_rect(current, b, 0.5); + assert_eq!( + state.phased_route_to(id, c, 200_000_000).unwrap(), + vec![(current, b), (b, c)] + ); + } + + #[test] + fn linear_retarget_interrupts_phased_animation_from_current_rect() { + let state = AnimationState::default(); + let id = NodeId(1); + let a = Rect::new_sized_saturating(0, 0, 100, 100); + let b = Rect::new_sized_saturating(0, 0, 100, 50); + let c = Rect::new_sized_saturating(100, 0, 100, 50); + let d = Rect::new_sized_saturating(100, 100, 100, 50); + assert!(state.set_phased_target( + id, + vec![(a, b), (b, c)], + None, + 0, + 100, + AnimationCurve::Linear + )); + let current = lerp_rect(a, b, 0.5); + assert!(state.set_target(id, a, d, None, 50_000_000, 100, AnimationCurve::Linear)); + assert_eq!(state.visual_rect(id, d, 50_000_000), current); + assert_eq!( + state.visual_rect(id, d, 100_000_000), + lerp_rect(current, d, 0.5) + ); + } + + #[test] + fn unchanged_target_does_not_restart() { + let state = AnimationState::default(); + let id = NodeId(1); + let a = Rect::new_sized_saturating(0, 0, 100, 100); + let b = Rect::new_sized_saturating(100, 0, 100, 100); + assert!(state.set_target(id, a, b, None, 0, 160, AnimationCurve::Linear)); + assert!(!state.set_target(id, a, b, None, 80_000_000, 160, AnimationCurve::Linear)); + assert_eq!( + state.visual_rect(id, b, 80_000_000), + Rect::new_sized_saturating(50, 0, 100, 100) + ); + } + + #[test] + fn changed_target_restarts_from_current_visual_rect() { + let state = AnimationState::default(); + let id = NodeId(1); + let a = Rect::new_sized_saturating(0, 0, 100, 100); + let b = Rect::new_sized_saturating(100, 0, 100, 100); + let c = Rect::new_sized_saturating(200, 0, 100, 100); + assert!(state.set_target(id, a, b, None, 0, 160, AnimationCurve::Linear)); + assert!(state.set_target(id, a, c, None, 80_000_000, 160, AnimationCurve::Linear)); + assert_eq!( + state.visual_rect(id, c, 80_000_000), + Rect::new_sized_saturating(50, 0, 100, 100) + ); + assert_eq!( + state.visual_rect(id, c, 160_000_000), + Rect::new_sized_saturating(125, 0, 100, 100) + ); + } + + #[test] + fn spawn_in_start_rect_is_centered_and_empty() { + let target = Rect::new_sized_saturating(10, 20, 100, 50); + assert_eq!(spawn_in_start_rect(target), Rect::new_empty(60, 45)); + } + + #[test] + fn spawn_in_uses_configured_curve() { + let state = AnimationState::default(); + let id = NodeId(1); + let target = Rect::new_sized_saturating(10, 20, 100, 50); + let curve = AnimationCurve::from_config(3); + assert!(state.set_spawn_in(id, target, None, 0, 160, curve)); + assert_eq!( + state.visual_rect(id, target, 80_000_000), + lerp_rect(spawn_in_start_rect(target), target, curve.sample(0.5)) + ); + assert_ne!( + state.visual_rect(id, target, 80_000_000), + Rect::new_sized_saturating(35, 33, 50, 25) + ); + } +} diff --git a/src/animation/multiphase.rs b/src/animation/multiphase.rs new file mode 100644 index 00000000..64879f72 --- /dev/null +++ b/src/animation/multiphase.rs @@ -0,0 +1,27 @@ +pub use jay_layout_animation::*; + +use crate::tree::NodeId as TreeNodeId; + +impl From for NodeId { + fn from(value: TreeNodeId) -> Self { + Self(value.0) + } +} + +impl From for TreeNodeId { + fn from(value: NodeId) -> Self { + Self(value.0) + } +} + +impl PartialEq for NodeId { + fn eq(&self, other: &TreeNodeId) -> bool { + self.0 == other.0 + } +} + +impl PartialEq for TreeNodeId { + fn eq(&self, other: &NodeId) -> bool { + self.0 == other.0 + } +} diff --git a/src/backend.rs b/src/backend.rs index f30582ad..e83ecad5 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -1,51 +1,45 @@ +use jay_async_engine::SpawnedFuture; use { crate::{ - async_engine::SpawnedFuture, + backend::transaction::{ BackendConnectorTransaction, BackendConnectorTransactionError, BackendConnectorTransactionType, BackendConnectorTransactionTypeDyn, }, - cmm::cmm_primaries::Primaries, drm_feedback::DrmFeedback, - fixed::Fixed, format::Format, gfx_api::{FdSync, GfxApi, GfxFramebuffer}, - ifs::{ - wl_output::OutputId, - wl_seat::{ - tablet::{ - PadButtonState, TabletInit, TabletPadId, TabletPadInit, TabletRingEventSource, - TabletStripEventSource, TabletToolChanges, TabletToolId, TabletToolInit, - ToolButtonState, - }, - wl_pointer::{CONTINUOUS, FINGER, HORIZONTAL_SCROLL, VERTICAL_SCROLL, WHEEL}, - }, - }, - libinput::consts::DeviceCapability, - utils::static_text::StaticText, - video::drm::{ - ConnectorType, DRM_MODE_COLORIMETRY_BT2020_RGB, DRM_MODE_COLORIMETRY_DEFAULT, - DrmConnector, DrmError, DrmVersion, HDMI_EOTF_SMPTE_ST2084, - HDMI_EOTF_TRADITIONAL_GAMMA_SDR, - }, + video::drm::{ConnectorType, DrmError, DrmVersion}, }, - jay_config::input::SwitchEvent, - linearize::Linearize, + jay_video_types::drm::DrmConnector, std::{ any::Any, error::Error, fmt::{Debug, Display, Formatter}, - hash::Hash, rc::Rc, }, - uapi::{OwnedFd, Packed, Pod, c}, + uapi::{OwnedFd, c}, }; pub mod transaction; -linear_ids!(ConnectorIds, ConnectorId); -linear_ids!(InputDeviceIds, InputDeviceId); -linear_ids!(DrmDeviceIds, DrmDeviceId); +pub use jay_output_types::{ + BackendColorSpace, BackendConnectorState, BackendConnectorStateSerial, + BackendConnectorStateSerials, BackendEotfs, BackendGammaLut, BackendGammaLutElement, + BackendLuminance, CONCAP_CONNECTOR, CONCAP_MODE_SETTING, CONCAP_PHYSICAL_DISPLAY, + ConnectorCaps, ConnectorId, ConnectorIds, DrmDeviceId, DrmDeviceIds, Mode, MonitorInfo, + OutputId, +}; + +pub use jay_input_types::{ + AXIS_120, AxisSource, ButtonState, InputDeviceAccelProfile, InputDeviceCapability, + InputDeviceClickMethod, InputDeviceGroupId, InputDeviceGroupIds, InputDeviceId, + InputDeviceIds, InputEvent, KeyState, Leds, PadButtonState, ScrollAxis, SwitchEvent, TabletId, + TabletIds, TabletInit, TabletPadGroupInit, TabletPadId, TabletPadIds, TabletPadInit, + TabletRingEventSource, TabletStripEventSource, TabletTool2dChange, TabletToolCapability, + TabletToolChanges, TabletToolId, TabletToolIds, TabletToolInit, TabletToolPositionChange, + TabletToolType, TabletToolWheelChange, ToolButtonState, TransformMatrix, +}; pub trait Backend: Any { fn run(self: Rc) -> SpawnedFuture>>; @@ -70,54 +64,6 @@ pub trait Backend: Any { } } -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)] -pub struct Mode { - pub width: i32, - pub height: i32, - pub refresh_rate_millihz: u32, -} - -impl Mode { - pub fn refresh_nsec(&self) -> u64 { - match self.refresh_rate_millihz { - 0 => u64::MAX, - n => 1_000_000_000_000 / (n as u64), - } - } - - pub fn size(&self) -> (i32, i32) { - (self.width, self.height) - } -} - -impl Display for Mode { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}x{}@{}", - self.width, - self.height, - self.refresh_rate_millihz as f64 / 1000.0, - ) - } -} - -#[derive(Clone, Debug)] -pub struct MonitorInfo { - pub modes: Option>, - pub output_id: Rc, - pub width_mm: i32, - pub height_mm: i32, - pub non_desktop: bool, - pub non_desktop_effective: bool, - pub vrr_capable: bool, - pub eotfs: Vec, - pub color_spaces: Vec, - pub primaries: Primaries, - pub luminance: Option, - pub state: BackendConnectorState, -} - #[derive(Copy, Clone, Debug)] pub struct ConnectorKernelId { pub ty: ConnectorType, @@ -130,13 +76,6 @@ impl Display for ConnectorKernelId { } } -bitflags! { - ConnectorCaps: u32; - CONCAP_CONNECTOR, - CONCAP_MODE_SETTING, - CONCAP_PHYSICAL_DISPLAY, -} - pub trait Connector: Any { fn id(&self) -> ConnectorId; fn kernel_id(&self) -> ConnectorKernelId; @@ -203,10 +142,6 @@ pub trait HardwareCursor: Debug { fn damage(&self); } -pub type TransformMatrix = [[f64; 2]; 2]; - -linear_ids!(InputDeviceGroupIds, InputDeviceGroupId, usize); - pub trait InputDevice { fn id(&self) -> InputDeviceId; fn removed(&self) -> bool; @@ -276,78 +211,6 @@ pub trait InputDevice { } } -#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Linearize)] -pub enum InputDeviceCapability { - Keyboard, - Pointer, - Touch, - TabletTool, - TabletPad, - Gesture, - Switch, -} - -impl StaticText for InputDeviceCapability { - fn text(&self) -> &'static str { - match self { - InputDeviceCapability::Keyboard => "keyboard", - InputDeviceCapability::Pointer => "pointer", - InputDeviceCapability::Touch => "touch", - InputDeviceCapability::TabletTool => "tablet tool", - InputDeviceCapability::TabletPad => "tablet pad", - InputDeviceCapability::Gesture => "gesture", - InputDeviceCapability::Switch => "switch", - } - } -} - -impl InputDeviceCapability { - pub fn to_libinput(self) -> DeviceCapability { - use crate::libinput::consts::*; - match self { - InputDeviceCapability::Keyboard => LIBINPUT_DEVICE_CAP_KEYBOARD, - InputDeviceCapability::Pointer => LIBINPUT_DEVICE_CAP_POINTER, - InputDeviceCapability::Touch => LIBINPUT_DEVICE_CAP_TOUCH, - InputDeviceCapability::TabletTool => LIBINPUT_DEVICE_CAP_TABLET_TOOL, - InputDeviceCapability::TabletPad => LIBINPUT_DEVICE_CAP_TABLET_PAD, - InputDeviceCapability::Gesture => LIBINPUT_DEVICE_CAP_GESTURE, - InputDeviceCapability::Switch => LIBINPUT_DEVICE_CAP_SWITCH, - } - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Linearize)] -pub enum InputDeviceAccelProfile { - Flat, - Adaptive, -} - -impl StaticText for InputDeviceAccelProfile { - fn text(&self) -> &'static str { - match self { - InputDeviceAccelProfile::Flat => "Flat", - InputDeviceAccelProfile::Adaptive => "Adaptive", - } - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Linearize)] -pub enum InputDeviceClickMethod { - None, - ButtonAreas, - Clickfinger, -} - -impl StaticText for InputDeviceClickMethod { - fn text(&self) -> &'static str { - match self { - InputDeviceClickMethod::None => "none", - InputDeviceClickMethod::ButtonAreas => "button-areas", - InputDeviceClickMethod::Clickfinger => "clickfinger", - } - } -} - pub enum BackendEvent { NewDrmDevice(Rc), NewConnector(Rc), @@ -355,216 +218,6 @@ pub enum BackendEvent { DevicesEnumerated, } -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum KeyState { - Released, - Pressed, - Repeated, -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum ButtonState { - Released, - Pressed, -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq, Linearize)] -pub enum ScrollAxis { - Horizontal = HORIZONTAL_SCROLL as _, - Vertical = VERTICAL_SCROLL as _, -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum AxisSource { - Wheel = WHEEL as _, - Finger = FINGER as _, - Continuous = CONTINUOUS as _, -} - -pub const AXIS_120: i32 = 120; - -bitflags! { - Leds: u32; - LED_NUM_LOCK, - LED_CAPS_LOCK, - LED_SCROLL_LOCK, - LED_COMPOSE, - LED_KANA, -} - -#[derive(Debug)] -pub enum InputEvent { - Key { - time_usec: u64, - key: u32, - state: KeyState, - }, - ConnectorPosition { - time_usec: u64, - connector: ConnectorId, - x: Fixed, - y: Fixed, - }, - Motion { - time_usec: u64, - dx: Fixed, - dy: Fixed, - dx_unaccelerated: Fixed, - dy_unaccelerated: Fixed, - }, - MotionAbsolute { - time_usec: u64, - x_normed: f32, - y_normed: f32, - }, - Button { - time_usec: u64, - button: u32, - state: ButtonState, - }, - - AxisPx { - dist: Fixed, - axis: ScrollAxis, - inverted: bool, - }, - AxisSource { - source: AxisSource, - }, - AxisStop { - axis: ScrollAxis, - }, - Axis120 { - dist: i32, - axis: ScrollAxis, - inverted: bool, - }, - AxisFrame { - time_usec: u64, - }, - SwipeBegin { - time_usec: u64, - finger_count: u32, - }, - SwipeUpdate { - time_usec: u64, - dx: Fixed, - dy: Fixed, - dx_unaccelerated: Fixed, - dy_unaccelerated: Fixed, - }, - SwipeEnd { - time_usec: u64, - cancelled: bool, - }, - PinchBegin { - time_usec: u64, - finger_count: u32, - }, - PinchUpdate { - time_usec: u64, - dx: Fixed, - dy: Fixed, - dx_unaccelerated: Fixed, - dy_unaccelerated: Fixed, - scale: Fixed, - rotation: Fixed, - }, - PinchEnd { - time_usec: u64, - cancelled: bool, - }, - HoldBegin { - time_usec: u64, - finger_count: u32, - }, - HoldEnd { - time_usec: u64, - cancelled: bool, - }, - - SwitchEvent { - time_usec: u64, - event: SwitchEvent, - }, - - TabletToolAdded { - time_usec: u64, - init: Box, - }, - TabletToolChanged { - time_usec: u64, - id: TabletToolId, - changes: Box, - }, - TabletToolButton { - time_usec: u64, - id: TabletToolId, - button: u32, - state: ToolButtonState, - }, - TabletToolRemoved { - time_usec: u64, - id: TabletToolId, - }, - - TabletPadButton { - time_usec: u64, - id: TabletPadId, - button: u32, - state: PadButtonState, - }, - TabletPadModeSwitch { - time_usec: u64, - pad: TabletPadId, - group: u32, - mode: u32, - }, - TabletPadRing { - time_usec: u64, - pad: TabletPadId, - ring: u32, - source: Option, - angle: Option, - }, - TabletPadStrip { - time_usec: u64, - pad: TabletPadId, - strip: u32, - source: Option, - position: Option, - }, - TabletPadDial { - time_usec: u64, - pad: TabletPadId, - dial: u32, - value120: i32, - }, - TouchDown { - time_usec: u64, - id: i32, - x_normed: Fixed, - y_normed: Fixed, - }, - TouchUp { - time_usec: u64, - id: i32, - }, - TouchMotion { - time_usec: u64, - id: i32, - x_normed: Fixed, - y_normed: Fixed, - }, - TouchCancel { - time_usec: u64, - id: i32, - }, - TouchFrame { - time_usec: u64, - }, -} - pub enum DrmEvent { #[expect(dead_code)] Removed, @@ -608,113 +261,3 @@ pub trait BackendDrmLease { pub trait BackendDrmLessee { fn created(&self, lease: Rc); } - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Linearize)] -pub enum BackendEotfs { - #[default] - Default, - Pq, -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Linearize)] -pub enum BackendColorSpace { - #[default] - Default, - Bt2020, -} - -#[derive(Copy, Clone, Debug)] -pub struct BackendLuminance { - pub min: f64, - pub max: f64, - pub max_fall: f64, -} - -impl BackendEotfs { - pub fn to_drm(self) -> u8 { - match self { - BackendEotfs::Default => HDMI_EOTF_TRADITIONAL_GAMMA_SDR, - BackendEotfs::Pq => HDMI_EOTF_SMPTE_ST2084, - } - } - - pub const fn name(self) -> &'static str { - match self { - BackendEotfs::Default => "default", - BackendEotfs::Pq => "pq", - } - } -} - -impl BackendColorSpace { - pub fn to_drm(self) -> u64 { - match self { - BackendColorSpace::Default => DRM_MODE_COLORIMETRY_DEFAULT, - BackendColorSpace::Bt2020 => DRM_MODE_COLORIMETRY_BT2020_RGB, - } - } - - pub const fn name(self) -> &'static str { - match self { - BackendColorSpace::Default => "default", - BackendColorSpace::Bt2020 => "bt2020", - } - } -} - -// kernel: struct drm_color_lut -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)] -#[repr(C)] -pub struct BackendGammaLutElement { - pub red: u16, - pub green: u16, - pub blue: u16, - pub reserved: u16, -} - -unsafe impl Pod for BackendGammaLutElement {} -unsafe impl Packed for BackendGammaLutElement {} - -#[derive(Debug, Eq)] -pub struct BackendGammaLut { - id: [u8; 32], - pub gamma_lut: Vec, -} - -impl BackendGammaLut { - pub fn new(mut gamma_lut: Vec) -> Self { - for element in &mut gamma_lut { - element.reserved = 0; - } - let gamma_lut_bytes = uapi::as_bytes(&gamma_lut as &[_]); - let id = *blake3::hash(gamma_lut_bytes).as_bytes(); - Self { id, gamma_lut } - } -} - -impl PartialEq for BackendGammaLut { - fn eq(&self, other: &Self) -> bool { - self.id == other.id - } -} - -linear_ids!( - BackendConnectorStateSerials, - BackendConnectorStateSerial, - u64 -); - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct BackendConnectorState { - pub serial: BackendConnectorStateSerial, - pub enabled: bool, - pub active: bool, - pub mode: Mode, - pub non_desktop_override: Option, - pub vrr: bool, - pub tearing: bool, - pub format: &'static Format, - pub color_space: BackendColorSpace, - pub eotf: BackendEotfs, - pub gamma_lut: Option>, -} diff --git a/src/backends/dummy.rs b/src/backends/dummy.rs index 1ac3be15..07499141 100644 --- a/src/backends/dummy.rs +++ b/src/backends/dummy.rs @@ -1,6 +1,7 @@ +use jay_async_engine::SpawnedFuture; use { crate::{ - async_engine::SpawnedFuture, + backend::{ self, Backend, BackendConnectorState, BackendConnectorStateSerial, Connector, ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, diff --git a/src/backends/headless.rs b/src/backends/headless.rs index 74134481..a0450031 100644 --- a/src/backends/headless.rs +++ b/src/backends/headless.rs @@ -1,12 +1,14 @@ +use jay_io_uring::IoUringError; +use jay_async_engine::SpawnedFuture; use { crate::{ - async_engine::SpawnedFuture, + backend::{Backend, BackendDrmDevice, BackendEvent, DrmDeviceId, DrmEvent}, backends::headless::HeadlessBackendError::{ CreateDrm, GetDrmNodes, MonitorFdFailed, MonitorFdReadable, NoDrmNodes, OpenDrmNode, }, gfx_api::{GfxApi, GfxContext}, - io_uring::IoUringError, + state::State, udev::{Udev, UdevDevice, UdevError, UdevMonitor}, utils::{ diff --git a/src/backends/metal.rs b/src/backends/metal.rs index 3bdc7422..6a97e546 100644 --- a/src/backends/metal.rs +++ b/src/backends/metal.rs @@ -5,12 +5,14 @@ mod present; mod transaction; mod video; +use jay_async_engine::SpawnedFuture; use { crate::{ - async_engine::SpawnedFuture, + backend::{ Backend, ButtonState, InputDevice, InputDeviceAccelProfile, InputDeviceCapability, InputDeviceClickMethod, InputDeviceGroupId, InputDeviceId, InputEvent, KeyState, Leds, + OutputId, TabletId, TabletInit, TabletPadGroupInit, TabletPadId, TabletPadInit, TransformMatrix, transaction::BackendConnectorTransactionError, }, backends::metal::{ @@ -23,12 +25,6 @@ use { dbus::{DbusError, SignalHandler}, drm_feedback::DrmFeedback, gfx_api::{GfxError, SyncFile}, - ifs::{ - wl_output::OutputId, - wl_seat::tablet::{ - TabletId, TabletInit, TabletPadGroupInit, TabletPadId, TabletPadInit, - }, - }, libinput::{ LibInput, LibInputAdapter, LibInputError, consts::{ @@ -548,7 +544,7 @@ impl InputDevice for MetalInputDevice { } fn has_capability(&self, cap: InputDeviceCapability) -> bool { - let li = cap.to_libinput(); + let li = crate::libinput::device_capability(cap); match self.inputdev.get() { Some(dev) => dev.device().has_cap(li), _ => false, diff --git a/src/backends/metal/allocator.rs b/src/backends/metal/allocator.rs index 956de322..b0d19b41 100644 --- a/src/backends/metal/allocator.rs +++ b/src/backends/metal/allocator.rs @@ -1,3 +1,5 @@ +use jay_udmabuf::{Udmabuf, UdmabufError}; +use jay_geometry::{DamageQueue, Rect, Region}; use { crate::{ allocator::BufferObject, @@ -14,8 +16,8 @@ use { AcquireSync, FdSync, GfxBlendBuffer, GfxError, GfxFormat, GfxFramebuffer, GfxTexture, GfxWriteModifier, ReleaseSync, needs_render_usage, }, - rect::{DamageQueue, Rect, Region}, - udmabuf::{Udmabuf, UdmabufError}, + + utils::{errorfmt::ErrorFmt, rc_eq::rc_eq}, video::{ LINEAR_MODIFIER, Modifier, diff --git a/src/backends/metal/input.rs b/src/backends/metal/input.rs index baa05259..a298ac52 100644 --- a/src/backends/metal/input.rs +++ b/src/backends/metal/input.rs @@ -1,13 +1,13 @@ +use jay_units::fixed::Fixed; use { crate::{ - backend::{AxisSource, ButtonState, InputEvent, KeyState, ScrollAxis}, - backends::metal::MetalBackend, - fixed::Fixed, - ifs::wl_seat::tablet::{ - PadButtonState, TabletRingEventSource, TabletStripEventSource, TabletTool2dChange, + backend::{ + AxisSource, ButtonState, InputEvent, KeyState, PadButtonState, ScrollAxis, SwitchEvent, + TabletRingEventSource, TabletStripEventSource, TabletTool2dChange, TabletToolCapability, TabletToolChanges, TabletToolId, TabletToolInit, TabletToolPositionChange, TabletToolType, TabletToolWheelChange, ToolButtonState, }, + backends::metal::MetalBackend, libinput::{ consts::{ LIBINPUT_BUTTON_STATE_PRESSED, LIBINPUT_BUTTON_STATE_RELEASED, @@ -25,7 +25,6 @@ use { }, utils::{bitflags::BitflagsExt, errorfmt::ErrorFmt}, }, - jay_config::input::SwitchEvent, std::rc::Rc, uapi::c, }; diff --git a/src/backends/metal/present.rs b/src/backends/metal/present.rs index 88024bfb..2ccf2d87 100644 --- a/src/backends/metal/present.rs +++ b/src/backends/metal/present.rs @@ -1,3 +1,5 @@ +use jay_time::Time; +use jay_geometry::Region; use { crate::{ backend::{BackendDrmDevice, Connector}, @@ -13,9 +15,9 @@ use { SyncFile, create_render_pass, }, ifs::wl_output::BlendSpace, - rect::Region, - time::Time, - tracy::FrameName, + + + jay_tracy::FrameName, tree::OutputNode, utils::{errorfmt::ErrorFmt, oserror::OsError}, video::{ diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 64dc1c59..efd447b7 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -1,12 +1,40 @@ +mod copy_device; +mod discovery; +mod hardware_cursor; +mod lease; +mod model; +mod properties; + +#[allow(unused_imports)] +pub use { + copy_device::CopyDeviceHolder, + hardware_cursor::{MetalHardwareCursor, MetalHardwareCursorChange}, + lease::{MetalLease, MetalLeaseData}, + model::{ + ConnectorDisplayData, ConnectorFutures, FrontState, HandleEvents, MetalConnector, + MetalCrtc, MetalDrmDevice, MetalDrmDeviceData, MetalEncoder, MetalLeaseId, MetalLeaseIds, + MetalPlane, MetalRenderContext, PendingDrmDevice, PersistentDisplayData, PlaneFormat, + PlaneType, + }, + properties::{DefaultProperty, TypedProperty}, +}; + +use { + discovery::{ + create_connector, create_connector_display_data, create_crtc, create_encoder, create_plane, + get_connectors, + }, + properties::collect_untyped_properties, +}; + use { crate::{ - async_engine::{Phase, SpawnedFuture}, backend::{ - BackendColorSpace, BackendConnectorState, BackendDrmDevice, BackendDrmLease, - BackendDrmLessee, BackendEotfs, BackendEvent, BackendGammaLut, BackendGammaLutElement, - BackendLuminance, CONCAP_CONNECTOR, CONCAP_MODE_SETTING, CONCAP_PHYSICAL_DISPLAY, + BackendColorSpace, BackendConnectorState, BackendDrmDevice, BackendDrmLessee, + BackendEotfs, BackendEvent, BackendGammaLut, BackendGammaLutElement, + CONCAP_CONNECTOR, CONCAP_MODE_SETTING, CONCAP_PHYSICAL_DISPLAY, Connector, ConnectorCaps, ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, - HardwareCursor, HardwareCursorUpdate, Mode, MonitorInfo, + MonitorInfo, transaction::{ BackendConnectorTransaction, BackendConnectorTransactionError, BackendConnectorTransactionType, BackendConnectorTransactionTypeDyn, @@ -14,134 +42,41 @@ use { }, backends::metal::{ MetalBackend, MetalError, - allocator::RenderBuffer, - present::{ - DEFAULT_POST_COMMIT_MARGIN, DEFAULT_PRE_COMMIT_MARGIN, DirectScanoutCache, - POST_COMMIT_MARGIN_DELTA, PresentFb, - }, - transaction::{DrmConnectorState, DrmCrtcState, DrmPlaneState, MetalDeviceTransaction}, + present::{DEFAULT_POST_COMMIT_MARGIN, POST_COMMIT_MARGIN_DELTA}, + transaction::MetalDeviceTransaction, }, - cmm::{cmm_description::ColorDescription, cmm_primaries::Primaries}, - copy_device::{CopyDevice, CopyDeviceRegistry}, drm_feedback::DrmFeedback, - edid::{CtaDataBlock, Descriptor, EdidExtension}, - format::{Format, XRGB8888}, - gfx_api::{FdSync, GfxApi, GfxContext, GfxFramebuffer}, - ifs::{ - wl_output::OutputId, - wp_presentation_feedback::{KIND_HW_COMPLETION, KIND_VSYNC, KIND_ZERO_COPY}, - }, - state::State, + format::XRGB8888, + gfx_api::GfxApi, + ifs::wp_presentation_feedback::{KIND_HW_COMPLETION, KIND_VSYNC, KIND_ZERO_COPY}, tree::OutputNode, udev::UdevDevice, utils::{ - asyncevent::AsyncEvent, binary_search_map::BinarySearchMap, bitflags::BitflagsExt, - cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, - geometric_decay::GeometricDecay, numcell::NumCell, on_change::OnChange, - opaque_cell::OpaqueCell, ordered_float::F64, oserror::OsError, + cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap, + errorfmt::ErrorFmt, oserror::OsError, }, video::{ - INVALID_MODIFIER, Modifier, - dmabuf::DmaBufId, drm::{ - ConnectorStatus, ConnectorType, DRM_CLIENT_CAP_ATOMIC, DrmBlob, DrmCardResources, - DrmConnector, DrmCrtc, DrmEncoder, DrmError, DrmEvent, DrmFb, DrmLease, DrmMaster, - DrmModeInfo, DrmObject, DrmPlane, DrmProperty, DrmPropertyDefinition, - DrmPropertyType, DrmVersion, HDMI_EOTF_TRADITIONAL_GAMMA_SDR, drm_mode_modeinfo, - hdr_output_metadata, + ConnectorStatus, DRM_CLIENT_CAP_ATOMIC, DrmBlob, DrmCardResources, DrmConnector, + DrmCrtc, DrmError, DrmEvent, DrmFb, DrmMaster, DrmObject, DrmProperty, + DrmVersion, drm_mode_modeinfo, hdr_output_metadata, }, gbm::GbmDevice, }, }, ahash::{AHashMap, AHashSet}, - bstr::{BString, ByteSlice}, - indexmap::{IndexSet, indexset}, + bstr::ByteSlice, isnt::std_1::collections::IsntHashMapExt, std::{ - cell::{Cell, OnceCell, RefCell}, + cell::Cell, collections::hash_map::Entry, - ffi::CString, - fmt::{Debug, Formatter}, mem, ops::DerefMut, rc::Rc, }, - uapi::{ - OwnedFd, - c::{self, dev_t}, - }, + uapi::c::{self, dev_t}, }; -pub struct PendingDrmDevice { - pub id: DrmDeviceId, - pub devnum: c::dev_t, - pub devnode: CString, -} - -#[derive(Debug)] -pub struct MetalRenderContext { - pub dev_id: DrmDeviceId, - pub gfx: Rc, - pub gbm: Rc, - pub devnode: CString, - pub copy_device: Rc, -} - -pub struct CopyDeviceHolder { - pub registry: Rc, - pub devnum: dev_t, - pub dev: OnceCell>>, -} - -impl Debug for CopyDeviceHolder { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("CopyDeviceHolder").finish_non_exhaustive() - } -} - -pub struct MetalDrmDevice { - pub backend: Rc, - pub id: DrmDeviceId, - pub devnum: c::dev_t, - pub devnode: CString, - pub master: Rc, - pub supports_kms: bool, - pub crtcs: AHashMap>, - pub encoders: AHashMap>, - pub planes: AHashMap>, - pub cursor_width: u64, - pub cursor_height: u64, - pub supports_async_commit: bool, - pub gbm: Rc, - pub handle_events: HandleEvents, - pub ctx: CloneCell>, - pub copy_device: Rc, - pub on_change: OnChange, - pub direct_scanout_enabled: Cell>, - pub is_nvidia: bool, - pub _is_amd: bool, - pub lease_ids: MetalLeaseIds, - pub leases: CopyHashMap, - pub leases_to_break: CopyHashMap, - pub paused: Cell, - pub min_post_commit_margin: Cell, -} - -impl Debug for MetalDrmDevice { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("MetalDrmDevice").finish_non_exhaustive() - } -} - -impl MetalDrmDevice { - pub fn is_render_device(&self) -> bool { - if let Some(ctx) = self.backend.ctx.get() { - return ctx.dev_id == self.id; - } - false - } -} - impl BackendDrmDevice for MetalDrmDevice { fn id(&self) -> DrmDeviceId { self.id @@ -323,312 +258,6 @@ impl BackendDrmDevice for MetalDrmDevice { } } -pub struct HandleEvents { - pub handle_events: Cell>>, -} - -impl Debug for HandleEvents { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("HandleEvents").finish_non_exhaustive() - } -} - -#[derive(Debug)] -pub struct MetalDrmDeviceData { - pub dev: Rc, - pub connectors: CopyHashMap>, - pub futures: CopyHashMap, -} - -#[derive(Debug)] -pub struct PersistentDisplayData { - pub state: RefCell, -} - -#[derive(Debug)] -pub struct DefaultProperty { - pub name: &'static str, - pub prop: DrmProperty, - pub value: u64, -} - -#[derive(Debug)] -pub struct ConnectorDisplayData { - pub crtc_id: DrmProperty, - pub crtcs: BinarySearchMap, 8>, - pub first_mode: Mode, - pub modes: Vec, - pub persistent: Rc, - pub refresh: u32, - pub non_desktop: bool, - pub non_desktop_effective: bool, - pub vrr_capable: bool, - pub _vrr_refresh_max_nsec: u64, - pub default_properties: Vec, - pub untyped_properties: AHashMap, - - pub connector_id: ConnectorKernelId, - pub output_id: Rc, - - pub connection: ConnectorStatus, - pub mm_width: u32, - pub mm_height: u32, - pub _subpixel: u32, - - pub supports_bt2020: bool, - pub supports_pq: bool, - pub primaries: Primaries, - pub luminance: Option, - - pub colorspace: Option, - pub hdr_metadata: Option, - pub drm_state: DrmConnectorState, -} - -impl ConnectorDisplayData { - fn update_refresh(&mut self, dev: &MetalDrmDevice) { - self.refresh = 0; - if self.drm_state.crtc_id.is_none() { - return; - } - let Some(crtc) = dev.crtcs.get(&self.drm_state.crtc_id) else { - return; - }; - let drm_state = &*crtc.drm_state.borrow(); - let Some(mode) = &drm_state.mode else { - return; - }; - let refresh_rate_mhz = mode.refresh_rate_millihz(); - if refresh_rate_mhz != 0 { - self.refresh = (1_000_000_000_000u64 / refresh_rate_mhz as u64) as u32; - } - } - - fn update_non_desktop_effective(&mut self) { - let state = &*self.persistent.state.borrow(); - self.non_desktop_effective = - !state.enabled || state.non_desktop_override.unwrap_or(self.non_desktop); - } - - pub fn update_cached_fields(&mut self, dev: &MetalDrmDevice) { - self.update_refresh(dev); - self.update_non_desktop_effective(); - } -} - -linear_ids!(MetalLeaseIds, MetalLeaseId, u64); - -pub struct MetalLeaseData { - pub lease: DrmLease, - pub _lessee: Rc, - pub connectors: Vec>, - pub crtcs: Vec>, - pub planes: Vec>, - pub revoked: Cell, -} - -impl MetalLeaseData { - fn try_revoke(&self) -> bool { - if self.revoked.get() { - return true; - } - let res = self.lease.try_revoke(); - if res { - self.revoked.set(res); - for c in &self.connectors { - c.lease.take(); - if let Err(e) = c.update_properties() { - log::error!("Could not update connector properties: {}", ErrorFmt(e)); - } - } - for c in &self.crtcs { - c.lease.take(); - if let Err(e) = c.update_properties() { - log::error!("Could not update crtc properties: {}", ErrorFmt(e)); - } - } - for p in &self.planes { - p.lease.take(); - if let Err(e) = p.update_properties() { - log::error!("Could not update plane properties: {}", ErrorFmt(e)); - } - } - } - res - } -} - -pub struct MetalLease { - dev: Rc, - id: MetalLeaseId, - fd: Rc, -} - -impl Drop for MetalLease { - fn drop(&mut self) { - if let Some(lease) = self.dev.leases.remove(&self.id) { - if !self.dev.paused.get() { - for c in &lease.connectors { - match c.frontend_state.get() { - FrontState::Removed - | FrontState::Disconnected - | FrontState::Connected { .. } => {} - FrontState::Unavailable => { - c.send_event(ConnectorEvent::Available); - } - } - } - } - if !lease.try_revoke() { - self.dev.leases_to_break.set(self.id, lease); - } - } - } -} - -impl BackendDrmLease for MetalLease { - fn fd(&self) -> &Rc { - &self.fd - } -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum FrontState { - Removed, - Disconnected, - Connected { non_desktop: bool }, - Unavailable, -} - -pub struct MetalConnector { - pub id: DrmConnector, - pub kernel_id: Cell, - pub master: Rc, - pub state: Rc, - - pub dev: Rc, - pub backend: Rc, - - pub connector_id: ConnectorId, - - pub buffers: CloneCell>>, - pub color_description: CloneCell>, - - pub lease: Cell>, - - pub buffers_idle: Cell, - pub crtc_idle: Cell, - pub has_damage: NumCell, - pub cursor_changed: Cell, - pub cursor_damage: Cell, - pub next_vblank_nsec: Cell, - - pub display: RefCell, - - pub frontend_state: Cell, - - pub primary_plane: CloneCell>>, - pub cursor_plane: CloneCell>>, - - pub crtc: CloneCell>>, - - pub on_change: OnChange, - - pub present_trigger: AsyncEvent, - - pub cursor_x: Cell, - pub cursor_y: Cell, - pub cursor_enabled: Cell, - pub cursor_buffers: CloneCell>>, - pub cursor_swap_buffer: Cell, - pub cursor_sync: CloneCell>, - - pub drm_feedback: CloneCell>>, - pub scanout_buffers: RefCell>, - pub active_framebuffer: RefCell>, - pub next_framebuffer: OpaqueCell>, - pub direct_scanout_active: Cell, - - pub version: NumCell, - pub expected_sequence: Cell>, - pub pre_commit_margin: Cell, - pub pre_commit_margin_decay: GeometricDecay, - pub post_commit_margin: Cell, - pub post_commit_margin_decay: GeometricDecay, - pub vblank_miss_sec: Cell, - pub vblank_miss_this_sec: NumCell, - pub presentation_is_sync: Cell, - pub presentation_is_zero_copy: Cell, -} - -impl Debug for MetalConnector { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("MetalConnnector").finish_non_exhaustive() - } -} - -pub struct MetalHardwareCursor { - pub connector: Rc, -} - -pub struct MetalHardwareCursorChange<'a> { - pub cursor_swap_buffer: Option>, - pub cursor_enabled: bool, - pub cursor_x: i32, - pub cursor_y: i32, - pub cursor_buffer: &'a RenderBuffer, - pub cursor_size: (i32, i32), -} - -impl Debug for MetalHardwareCursor { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("MetalHardwareCursor") - .finish_non_exhaustive() - } -} - -impl HardwareCursor for MetalHardwareCursor { - fn damage(&self) { - self.connector.cursor_damage.set(true); - if self.connector.buffers_idle.get() && self.connector.crtc_idle.get() { - self.connector.schedule_present(); - } - } -} - -impl HardwareCursorUpdate for MetalHardwareCursorChange<'_> { - fn set_enabled(&mut self, enabled: bool) { - self.cursor_enabled = enabled; - } - - fn get_buffer(&self) -> Rc { - self.cursor_buffer.render.fb.clone() - } - - fn set_position(&mut self, x: i32, y: i32) { - self.cursor_x = x; - self.cursor_y = y; - } - - fn swap_buffer(&mut self, sync: Option) { - self.cursor_swap_buffer = Some(sync); - } - - fn size(&self) -> (i32, i32) { - self.cursor_size - } -} - -pub struct ConnectorFutures { - pub _present: SpawnedFuture<()>, -} - -impl Debug for ConnectorFutures { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ConnectorFutures").finish_non_exhaustive() - } -} - impl MetalConnector { pub fn send_connected(self: &Rc) { let dd = &*self.display.borrow(); @@ -921,842 +550,6 @@ impl Connector for MetalConnector { } } -pub struct MetalCrtc { - pub id: DrmCrtc, - pub idx: usize, - pub master: Rc, - pub default_properties: Vec, - pub untyped_properties: RefCell>, - - pub lease: Cell>, - - pub possible_planes: BinarySearchMap, 8>, - - pub connector: CloneCell>>, - pub pending_flip: CloneCell>>, - - pub active: DrmProperty, - pub mode_id: DrmProperty, - pub vrr_enabled: DrmProperty, - pub out_fence_ptr: DrmProperty, - pub gamma_lut: Option, - pub gamma_lut_size: Option, - pub drm_state: RefCell, - - pub sequence: Cell, - pub have_queued_sequence: Cell, - pub needs_vblank_emulation: Cell, -} - -impl Debug for MetalCrtc { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("MetalCrtc").finish_non_exhaustive() - } -} - -#[derive(Debug)] -pub struct MetalEncoder { - pub id: DrmEncoder, - pub crtcs: AHashMap>, -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum PlaneType { - Overlay, - Primary, - Cursor, -} - -#[derive(Debug)] -pub struct PlaneFormat { - pub format: &'static Format, - pub modifiers: IndexSet, -} - -pub struct MetalPlane { - pub id: DrmPlane, - pub master: Rc, - pub default_properties: Vec, - pub untyped_properties: RefCell>, - - pub ty: PlaneType, - - pub possible_crtcs: u32, - pub formats: AHashMap, - - pub lease: Cell>, - - pub mode_w: Cell, - pub mode_h: Cell, - - pub crtc_id: DrmProperty, - pub crtc_x: DrmProperty, - pub crtc_y: DrmProperty, - pub crtc_w: DrmProperty, - pub crtc_h: DrmProperty, - pub src_x: DrmProperty, - pub src_y: DrmProperty, - pub src_w: DrmProperty, - pub src_h: DrmProperty, - pub in_fence_fd: DrmProperty, - pub fb_id: DrmProperty, - - pub drm_state: RefCell, -} - -impl Debug for MetalPlane { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("MetalPlane").finish_non_exhaustive() - } -} - -fn get_connectors( - backend: &Rc, - dev: &Rc, - ids: &[DrmConnector], -) -> Result< - ( - CopyHashMap>, - CopyHashMap, - ), - DrmError, -> { - let connectors = CopyHashMap::new(); - let futures = CopyHashMap::new(); - for connector in ids { - match create_connector(backend, *connector, dev) { - Ok((con, fut)) => { - let id = con.id; - connectors.set(id, con); - futures.set(id, fut); - } - Err(e) => return Err(DrmError::CreateConnector(Box::new(e))), - } - } - Ok((connectors, futures)) -} - -#[derive(Copy, Clone)] -enum DefaultValue { - Fixed(u64), - Enum(&'static str), - Bitmask(&'static [&'static str]), - RangeMax, -} - -fn create_default_properties( - props: &CollectedProperties, - defaults: &[(&'static str, DefaultValue)], -) -> Vec { - let mut res = vec![]; - let mut defaults = defaults.iter(); - 'outer: loop { - let Some(&(name, def)) = defaults.next() else { - break; - }; - if let Some((definition, _)) = props.props.get(name.as_bytes().as_bstr()) { - let value = match def { - DefaultValue::Fixed(v) => v, - DefaultValue::Enum(e) => match &definition.ty { - DrmPropertyType::Enum { - values, - bitmask: false, - } => match values.iter().find(|v| v.name == e) { - None => continue, - Some(v) => v.value, - }, - _ => continue, - }, - DefaultValue::Bitmask(e) => match &definition.ty { - DrmPropertyType::Enum { - values, - bitmask: true, - } => { - let mut res = 0; - for &e in e { - match values.iter().find(|v| v.name == e) { - None => continue 'outer, - Some(v) => res |= 1 << v.value, - } - } - res - } - _ => continue, - }, - DefaultValue::RangeMax => match &definition.ty { - DrmPropertyType::Range { max, .. } => *max, - DrmPropertyType::SignedRange { max, .. } => *max as u64, - _ => continue, - }, - }; - res.push(DefaultProperty { - name, - prop: definition.id, - value, - }); - } - } - res -} - -fn create_connector( - backend: &Rc, - connector: DrmConnector, - dev: &Rc, -) -> Result<(Rc, ConnectorFutures), DrmError> { - let display = create_connector_display_data(connector, dev)?; - log::info!( - "Creating connector {} for device {}", - display.connector_id, - dev.devnode.as_bytes().as_bstr(), - ); - let slf = Rc::new(MetalConnector { - id: connector, - kernel_id: Cell::new(display.connector_id), - master: dev.master.clone(), - state: backend.state.clone(), - dev: dev.clone(), - backend: backend.clone(), - connector_id: backend.state.connector_ids.next(), - buffers: Default::default(), - color_description: CloneCell::new(backend.state.color_manager.srgb_gamma22().clone()), - lease: Cell::new(None), - buffers_idle: Cell::new(true), - crtc_idle: Cell::new(true), - has_damage: NumCell::new(1), - primary_plane: Default::default(), - cursor_plane: Default::default(), - crtc: Default::default(), - on_change: Default::default(), - present_trigger: Default::default(), - cursor_x: Cell::new(0), - cursor_y: Cell::new(0), - cursor_enabled: Cell::new(false), - cursor_buffers: Default::default(), - display: RefCell::new(display), - frontend_state: Cell::new(FrontState::Removed), - cursor_changed: Cell::new(false), - cursor_damage: Cell::new(false), - cursor_swap_buffer: Cell::new(false), - cursor_sync: Default::default(), - drm_feedback: Default::default(), - scanout_buffers: Default::default(), - active_framebuffer: Default::default(), - next_framebuffer: Default::default(), - direct_scanout_active: Cell::new(false), - next_vblank_nsec: Cell::new(0), - version: Default::default(), - expected_sequence: Default::default(), - pre_commit_margin_decay: GeometricDecay::new(0.5, DEFAULT_PRE_COMMIT_MARGIN), - pre_commit_margin: Cell::new(DEFAULT_PRE_COMMIT_MARGIN), - post_commit_margin_decay: GeometricDecay::new(0.1, dev.min_post_commit_margin.get()), - post_commit_margin: Cell::new(dev.min_post_commit_margin.get()), - vblank_miss_sec: Cell::new(0), - vblank_miss_this_sec: Default::default(), - presentation_is_sync: Cell::new(false), - presentation_is_zero_copy: Cell::new(false), - }); - let futures = ConnectorFutures { - _present: backend.state.eng.spawn2( - "present loop", - Phase::Present, - slf.clone().present_loop(), - ), - }; - Ok((slf, futures)) -} - -fn create_connector_display_data( - connector: DrmConnector, - dev: &Rc, -) -> Result { - let info = dev.master.get_connector_info(connector, true)?; - let mut crtcs = BinarySearchMap::new(); - for encoder in info.encoders { - if let Some(encoder) = dev.encoders.get(&encoder) { - for (_, crtc) in &encoder.crtcs { - crtcs.insert(crtc.id, crtc.clone()); - } - } - } - let props = collect_properties(&dev.master, connector)?; - let connection = ConnectorStatus::from_drm(info.connection); - let mut name = String::new(); - let mut manufacturer = String::new(); - let mut serial_number = String::new(); - let mut vrr_refresh_max_nsec = u64::MAX; - let connector_id = ConnectorKernelId { - ty: ConnectorType::from_drm(info.connector_type), - idx: info.connector_type_id, - }; - let mut supports_bt2020 = false; - let mut supports_pq = false; - let mut luminance = None; - let mut primaries = Primaries::SRGB; - 'fetch_edid: { - if connection != ConnectorStatus::Connected { - break 'fetch_edid; - } - let edid = match props.get("EDID") { - Ok(e) => e, - _ => { - log::warn!( - "Connector {} is connected but has no EDID blob", - connector_id, - ); - break 'fetch_edid; - } - }; - let blob = match dev.master.getblob_vec::(DrmBlob(edid.value as _)) { - Ok(b) => b, - Err(e) => { - log::error!( - "Could not fetch edid property of connector {}: {}", - connector_id, - ErrorFmt(e) - ); - break 'fetch_edid; - } - }; - let edid = match crate::edid::parse(&blob) { - Ok(e) => e, - Err(e) => { - log::error!( - "Could not parse edid property of connector {}: {}", - connector_id, - ErrorFmt(e) - ); - break 'fetch_edid; - } - }; - manufacturer = edid.base_block.id_manufacturer_name.to_string(); - for descriptor in edid.base_block.descriptors.iter().flatten() { - match descriptor { - Descriptor::DisplayProductSerialNumber(s) => { - serial_number.clone_from(s); - } - Descriptor::DisplayProductName(s) => { - name.clone_from(s); - } - _ => {} - } - } - if name.is_empty() { - log::warn!( - "The display attached to connector {} does not have a product name descriptor", - connector_id, - ); - } - if serial_number.is_empty() { - log::warn!( - "The display attached to connector {} does not have a serial number descriptor", - connector_id, - ); - serial_number = edid.base_block.id_serial_number.to_string(); - } - let min_vrr_hz = 'fetch_min_hz: { - for ext in &edid.extension_blocks { - if let EdidExtension::CtaV3(cta) = ext { - for data_block in &cta.data_blocks { - if let CtaDataBlock::VendorAmd(amd) = data_block { - break 'fetch_min_hz amd.minimum_refresh_hz as u64; - } - } - } - } - for desc in &edid.base_block.descriptors { - if let Some(desc) = desc - && let Descriptor::DisplayRangeLimitsAndAdditionalTiming(timings) = desc - { - break 'fetch_min_hz timings.vertical_field_rate_min as u64; - } - } - 0 - }; - if min_vrr_hz > 0 { - vrr_refresh_max_nsec = 1_000_000_000 / min_vrr_hz; - } - let cc = &edid.base_block.chromaticity_coordinates; - let map = |c: u16| F64(c as f64 / 1024.0); - primaries = Primaries { - r: (map(cc.red_x), map(cc.red_y)), - g: (map(cc.green_x), map(cc.green_y)), - b: (map(cc.blue_x), map(cc.blue_y)), - wp: (map(cc.white_x), map(cc.white_y)), - }; - for ext in &edid.extension_blocks { - if let EdidExtension::CtaV3(cta) = ext { - for data_block in &cta.data_blocks { - match data_block { - CtaDataBlock::Colorimetry(c) => { - if c.bt2020_rgb { - supports_bt2020 = true; - } - } - CtaDataBlock::StaticHdrMetadata(h) => { - if h.smpte_st_2084 { - supports_pq = true; - } - if let Some(max) = h.max_luminance { - luminance = Some(BackendLuminance { - min: h.min_luminance.unwrap_or(0.0), - max, - max_fall: h.max_luminance.unwrap_or(max), - }); - } - } - _ => {} - } - } - } - } - } - let output_id = OutputId::new(connector_id.to_string(), manufacturer, name, serial_number); - let first_mode = info - .modes - .first() - .cloned() - .map(|m| m.to_backend()) - .unwrap_or_default(); - let persistent = match dev.backend.persistent_display_data.get(&output_id) { - Some(ds) => { - if connection != ConnectorStatus::Disconnected { - log::info!("Reusing desired state for {:?}", output_id); - } - ds - } - None => { - let ds = Rc::new(PersistentDisplayData { - state: RefCell::new(BackendConnectorState { - serial: dev.backend.state.backend_connector_state_serials.next(), - enabled: true, - active: true, - mode: first_mode, - non_desktop_override: None, - vrr: false, - tearing: false, - format: XRGB8888, - color_space: Default::default(), - eotf: Default::default(), - gamma_lut: Default::default(), - }), - }); - dev.backend - .persistent_display_data - .set(output_id.clone(), ds.clone()); - ds - } - }; - let mut desired_state = persistent.state.borrow_mut(); - if desired_state.mode == Mode::default() { - desired_state.mode = first_mode; - } else if info - .modes - .iter() - .all(|m| m.to_backend() != desired_state.mode) - { - log::warn!("Discarding previously desired mode"); - desired_state.mode = first_mode; - } - let non_desktop = props.get("non-desktop")?.value != 0; - let vrr_capable = match props.get("vrr_capable") { - Ok(c) => c.value == 1, - Err(_) => false, - }; - if !vrr_capable && desired_state.vrr { - log::warn!("Connector has lost VRR capability"); - desired_state.vrr = false; - } - { - let viable = match desired_state.eotf { - BackendEotfs::Default => true, - BackendEotfs::Pq => supports_pq, - }; - if !viable { - log::warn!("Discarding previously desired EOTF"); - desired_state.eotf = BackendEotfs::Default; - } - } - { - let viable = match desired_state.color_space { - BackendColorSpace::Default => true, - BackendColorSpace::Bt2020 => supports_bt2020, - }; - if !viable { - log::warn!("Discarding previously desired color space"); - desired_state.color_space = BackendColorSpace::Default; - } - } - drop(desired_state); - let default_properties = create_default_properties( - &props, - &[ - ("Broadcast RGB", DefaultValue::Enum("Automatic")), - ("HDR_SOURCE_METADATA", DefaultValue::Fixed(0)), - ("Output format", DefaultValue::Enum("Default")), - ("WRITEBACK_FB_ID", DefaultValue::Fixed(0)), - ("WRITEBACK_OUT_FENCE_PTR", DefaultValue::Fixed(0)), - ("content type", DefaultValue::Enum("No Data")), - ("dither", DefaultValue::Enum("off")), - ("max bpc", DefaultValue::RangeMax), - ], - ); - let hdr_metadata_prop = props - .get("HDR_OUTPUT_METADATA") - .map(|p| p.map(|v| DrmBlob(v as _))) - .ok(); - let mut hdr_metadata = None; - let mut hdr_metadata_blob_id = DrmBlob::NONE; - if let Some(p) = &hdr_metadata_prop { - hdr_metadata_blob_id = p.value; - hdr_metadata = Some(hdr_output_metadata::from_eotf( - HDMI_EOTF_TRADITIONAL_GAMMA_SDR, - )); - if p.value.is_some() { - match dev.master.getblob::(p.value) { - Ok(m) => hdr_metadata = Some(m), - _ => { - log::debug!("Could not retrieve hdr output metadata"); - } - } - } - } - let colorspace_prop = props.get("Colorspace").ok(); - let crtc_id = props.get("CRTC_ID")?.map(|v| DrmCrtc(v as _)); - let drm_state = DrmConnectorState { - crtc_id: crtc_id.value, - color_space: colorspace_prop.map(|p| p.value), - hdr_metadata, - hdr_metadata_blob_id, - hdr_metadata_blob: None, - locked: true, - fb: DrmFb::NONE, - fb_idx: 0, - cursor_fb: DrmFb::NONE, - cursor_fb_idx: 0, - cursor_x: 0, - cursor_y: 0, - out_fd: None, - src_w: 0, - src_h: 0, - crtc_x: 0, - crtc_y: 0, - crtc_w: 0, - crtc_h: 0, - }; - Ok(ConnectorDisplayData { - crtc_id: props.get("CRTC_ID")?.id, - crtcs, - first_mode, - modes: info.modes, - persistent, - refresh: 0, - non_desktop, - non_desktop_effective: non_desktop, - vrr_capable, - _vrr_refresh_max_nsec: vrr_refresh_max_nsec, - default_properties, - untyped_properties: props.to_untyped(), - connection, - mm_width: info.mm_width, - mm_height: info.mm_height, - _subpixel: info.subpixel, - supports_bt2020, - supports_pq, - primaries, - luminance, - connector_id, - output_id, - colorspace: colorspace_prop.map(|p| p.id), - hdr_metadata: hdr_metadata_prop.map(|p| p.id), - drm_state, - }) -} - -fn create_encoder( - encoder: DrmEncoder, - master: &Rc, - crtcs: &AHashMap>, -) -> Result { - let info = master.get_encoder_info(encoder)?; - let mut possible = AHashMap::new(); - for crtc in crtcs.values() { - if info.possible_crtcs.contains(1 << crtc.idx) { - possible.insert(crtc.id, crtc.clone()); - } - } - Ok(MetalEncoder { - id: encoder, - crtcs: possible, - }) -} - -fn create_crtc( - crtc: DrmCrtc, - idx: usize, - master: &Rc, - planes: &AHashMap>, -) -> Result { - let mask = 1 << idx; - let mut possible_planes = BinarySearchMap::new(); - for plane in planes.values() { - if plane.possible_crtcs.contains(mask) { - possible_planes.insert(plane.id, plane.clone()); - } - } - let props = collect_properties(master, crtc)?; - let default_properties = create_default_properties( - &props, - &[ - ("AMD_CRTC_REGAMMA_TF", DefaultValue::Enum("Default")), - ("CTM", DefaultValue::Fixed(0)), - ("DEGAMMA_LUT", DefaultValue::Fixed(0)), - ("OUT_FENCE_PTR", DefaultValue::Fixed(0)), - ], - ); - let active = props.get("ACTIVE")?.map(|v| v == 1); - let mode_id = props.get("MODE_ID")?.map(|v| DrmBlob(v as u32)); - let vrr_enabled = props.get("VRR_ENABLED")?.map(|v| v == 1); - let out_fence_ptr = props.get("OUT_FENCE_PTR")?; - let gamma_lut = props - .get("GAMMA_LUT") - .ok() - .map(|v| v.map(|v| DrmBlob(v as u32))); - let mut gamma_lut_size = None; - if gamma_lut.is_some() { - gamma_lut_size = props.get("GAMMA_LUT_SIZE").ok().map(|v| v.value as u32); - } - let mut mode = None; - if mode_id.value.is_some() { - match master.getblob::(mode_id.value) { - Ok(m) => mode = Some(m.into()), - _ => { - log::debug!("Could not retrieve current mode of connector"); - } - } - } - let state = DrmCrtcState { - active: active.value, - mode, - mode_blob_id: mode_id.value, - mode_blob: None, - vrr_enabled: vrr_enabled.value, - assigned_connector: DrmConnector::NONE, - gamma_lut: None, - gamma_lut_blob_id: gamma_lut.map_or(DrmBlob::NONE, |v| v.value), - gamma_lut_blob: None, - }; - Ok(MetalCrtc { - id: crtc, - idx, - master: master.clone(), - default_properties, - untyped_properties: RefCell::new(props.to_untyped()), - lease: Cell::new(None), - possible_planes, - connector: Default::default(), - pending_flip: Default::default(), - drm_state: RefCell::new(state), - active: active.id, - mode_id: mode_id.id, - vrr_enabled: vrr_enabled.id, - out_fence_ptr: out_fence_ptr.id, - gamma_lut: gamma_lut.map(|v| v.id), - gamma_lut_size, - sequence: Cell::new(0), - have_queued_sequence: Cell::new(false), - needs_vblank_emulation: Cell::new(false), - }) -} - -fn create_plane(plane: DrmPlane, master: &Rc) -> Result { - let info = master.get_plane_info(plane)?; - let props = collect_properties(master, plane)?; - let mut formats = AHashMap::new(); - if let Some((_, v)) = props.props.get(b"IN_FORMATS".as_bstr()) { - for format in master.get_in_formats(*v as _)? { - if format.modifiers.is_empty() { - continue; - } - if let Some(f) = crate::format::formats().get(&format.format) { - formats.insert( - format.format, - PlaneFormat { - format: f, - modifiers: format.modifiers, - }, - ); - } - } - } else { - for format in info.format_types { - if let Some(f) = crate::format::formats().get(&format) { - formats.insert( - format, - PlaneFormat { - format: f, - modifiers: indexset![INVALID_MODIFIER], - }, - ); - } - } - } - let ty = match props.props.get(b"type".as_bstr()) { - Some((def, val)) => match &def.ty { - DrmPropertyType::Enum { values, .. } => 'ty: { - for v in values { - if v.value == *val { - match v.name.as_bytes() { - b"Overlay" => break 'ty PlaneType::Overlay, - b"Primary" => break 'ty PlaneType::Primary, - b"Cursor" => break 'ty PlaneType::Cursor, - _ => return Err(DrmError::UnknownPlaneType(v.name.to_owned())), - } - } - } - return Err(DrmError::InvalidPlaneType(*val)); - } - _ => return Err(DrmError::InvalidPlaneTypeProperty), - }, - _ => { - return Err(DrmError::MissingProperty( - "type".to_string().into_boxed_str(), - )); - } - }; - let default_properties = create_default_properties( - &props, - &[ - ("AMD_PLANE_BLEND_LUT", DefaultValue::Fixed(0)), - ("AMD_PLANE_BLEND_TF", DefaultValue::Enum("Default")), - ("AMD_PLANE_CTM", DefaultValue::Fixed(0)), - ("AMD_PLANE_DEGAMMA_LUT", DefaultValue::Fixed(0)), - ("AMD_PLANE_HDR_MULT", DefaultValue::Fixed(0)), - ("AMD_PLANE_LUT3D", DefaultValue::Fixed(0)), - ("AMD_PLANE_SHAPER_LUT", DefaultValue::Fixed(0)), - ("AMD_PLANE_SHAPER_TF", DefaultValue::Enum("Default")), - ("alpha", DefaultValue::RangeMax), - ("pixel blend mode", DefaultValue::Enum("Pre-multiplied")), - ("rotation", DefaultValue::Bitmask(&["rotate-0"])), - ], - ); - let fb_id = props.get("FB_ID")?.map(|v| DrmFb(v as _)); - let crtc_id = props.get("CRTC_ID")?.map(|v| DrmCrtc(v as _)); - let crtc_x = props.get("CRTC_X")?.map(|v| v as i32); - let crtc_y = props.get("CRTC_Y")?.map(|v| v as i32); - let crtc_w = props.get("CRTC_W")?.map(|v| v as i32); - let crtc_h = props.get("CRTC_H")?.map(|v| v as i32); - let src_x = props.get("SRC_X")?.map(|v| v as u32); - let src_y = props.get("SRC_Y")?.map(|v| v as u32); - let src_w = props.get("SRC_W")?.map(|v| v as u32); - let src_h = props.get("SRC_H")?.map(|v| v as u32); - let in_fence_fd = props.get("IN_FENCE_FD")?; - let state = DrmPlaneState { - fb_id: fb_id.value, - src_x: src_x.value, - src_y: src_y.value, - src_w: src_w.value, - src_h: src_h.value, - assigned_crtc: DrmCrtc::NONE, - crtc_id: crtc_id.value, - crtc_x: crtc_x.value, - crtc_y: crtc_y.value, - crtc_w: crtc_w.value, - crtc_h: crtc_h.value, - buffers: None, - }; - Ok(MetalPlane { - id: plane, - master: master.clone(), - default_properties, - untyped_properties: RefCell::new(props.to_untyped()), - ty, - possible_crtcs: info.possible_crtcs, - formats, - drm_state: RefCell::new(state), - fb_id: fb_id.id, - crtc_id: crtc_id.id, - crtc_x: crtc_x.id, - crtc_y: crtc_y.id, - crtc_w: crtc_w.id, - crtc_h: crtc_h.id, - src_x: src_x.id, - src_y: src_y.id, - src_w: src_w.id, - src_h: src_h.id, - in_fence_fd: in_fence_fd.id, - mode_w: Cell::new(0), - mode_h: Cell::new(0), - lease: Cell::new(None), - }) -} - -fn collect_properties( - master: &Rc, - t: T, -) -> Result { - let mut props = AHashMap::new(); - for prop in master.get_properties(t)? { - let def = master.get_property(prop.id)?; - props.insert(def.name.clone(), (def, prop.value)); - } - Ok(CollectedProperties { props }) -} - -fn collect_untyped_properties( - master: &Rc, - t: T, - props: &mut AHashMap, -) -> Result<(), DrmError> { - props.clear(); - for prop in master.get_properties(t)? { - props.insert(prop.id, prop.value); - } - Ok(()) -} - -struct CollectedProperties { - props: AHashMap, -} - -impl CollectedProperties { - fn get(&self, name: &str) -> Result, DrmError> { - match self.props.get(name.as_bytes().as_bstr()) { - Some((def, value)) => Ok(TypedProperty { - id: def.id, - value: *value, - }), - _ => Err(DrmError::MissingProperty(name.to_string().into_boxed_str())), - } - } - - fn to_untyped(&self) -> AHashMap { - let mut res = AHashMap::new(); - for (def, val) in self.props.values() { - res.insert(def.id, *val); - } - res - } -} - -#[derive(Copy, Clone, Debug)] -pub struct TypedProperty { - pub id: DrmProperty, - pub value: T, -} - -impl TypedProperty { - fn map(self, f: F) -> TypedProperty - where - F: FnOnce(T) -> U, - { - TypedProperty { - id: self.id, - value: f(self.value), - } - } -} - impl MetalBackend { pub fn check_render_context(&self, dev: &Rc) -> bool { let ctx = match self.ctx.get() { @@ -2694,23 +1487,3 @@ impl MetalBackend { connector.schedule_present(); } } - -impl CopyDeviceHolder { - pub fn get(&self) -> Option> { - self.dev - .get_or_init( - || match self.registry.get(self.devnum)?.create_device().map(Some) { - Ok(d) => d, - Err(e) => { - log::error!( - "Could not get copy device for {}: {}", - self.devnum, - ErrorFmt(e), - ); - None - } - }, - ) - .clone() - } -} diff --git a/src/backends/metal/video/copy_device.rs b/src/backends/metal/video/copy_device.rs new file mode 100644 index 00000000..29c0b3fe --- /dev/null +++ b/src/backends/metal/video/copy_device.rs @@ -0,0 +1,44 @@ +use { + crate::{ + copy_device::{CopyDevice, CopyDeviceRegistry}, + utils::errorfmt::ErrorFmt, + }, + std::{ + cell::OnceCell, + fmt::{Debug, Formatter}, + rc::Rc, + }, + uapi::c::dev_t, +}; + +pub struct CopyDeviceHolder { + pub registry: Rc, + pub devnum: dev_t, + pub dev: OnceCell>>, +} + +impl Debug for CopyDeviceHolder { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CopyDeviceHolder").finish_non_exhaustive() + } +} + +impl CopyDeviceHolder { + pub fn get(&self) -> Option> { + self.dev + .get_or_init( + || match self.registry.get(self.devnum)?.create_device().map(Some) { + Ok(d) => d, + Err(e) => { + log::error!( + "Could not get copy device for {}: {}", + self.devnum, + ErrorFmt(e), + ); + None + } + }, + ) + .clone() + } +} diff --git a/src/backends/metal/video/discovery.rs b/src/backends/metal/video/discovery.rs new file mode 100644 index 00000000..659e1454 --- /dev/null +++ b/src/backends/metal/video/discovery.rs @@ -0,0 +1,662 @@ +use jay_edid::{CtaDataBlock, Descriptor, EdidExtension}; +use jay_async_engine::Phase; +use { + super::{ + ConnectorDisplayData, ConnectorFutures, FrontState, MetalConnector, MetalCrtc, + MetalDrmDevice, MetalEncoder, MetalPlane, PersistentDisplayData, PlaneFormat, PlaneType, + properties::{DefaultValue, collect_properties, create_default_properties}, + }, + crate::{ + + backend::{ + BackendColorSpace, BackendConnectorState, BackendEotfs, BackendLuminance, + ConnectorKernelId, Mode, OutputId, + }, + backends::metal::{ + MetalBackend, + present::DEFAULT_PRE_COMMIT_MARGIN, + transaction::{DrmConnectorState, DrmCrtcState, DrmPlaneState}, + }, + cmm::cmm_primaries::Primaries, + + format::XRGB8888, + utils::{ + binary_search_map::BinarySearchMap, bitflags::BitflagsExt, clonecell::CloneCell, + copyhashmap::CopyHashMap, errorfmt::ErrorFmt, geometric_decay::GeometricDecay, + numcell::NumCell, ordered_float::F64, + }, + video::{ + INVALID_MODIFIER, + drm::{ + ConnectorStatus, ConnectorType, DrmBlob, DrmConnector, DrmCrtc, DrmEncoder, + DrmError, DrmFb, DrmMaster, DrmObject, DrmPlane, DrmPropertyType, + HDMI_EOTF_TRADITIONAL_GAMMA_SDR, drm_mode_modeinfo, hdr_output_metadata, + }, + }, + }, + ahash::AHashMap, + bstr::ByteSlice, + indexmap::indexset, + std::{ + cell::{Cell, RefCell}, + rc::Rc, + }, +}; + +pub(super) fn get_connectors( + backend: &Rc, + dev: &Rc, + ids: &[DrmConnector], +) -> Result< + ( + CopyHashMap>, + CopyHashMap, + ), + DrmError, +> { + let connectors = CopyHashMap::new(); + let futures = CopyHashMap::new(); + for connector in ids { + match create_connector(backend, *connector, dev) { + Ok((con, fut)) => { + let id = con.id; + connectors.set(id, con); + futures.set(id, fut); + } + Err(e) => return Err(DrmError::CreateConnector(Box::new(e))), + } + } + Ok((connectors, futures)) +} + +pub(super) fn create_connector( + backend: &Rc, + connector: DrmConnector, + dev: &Rc, +) -> Result<(Rc, ConnectorFutures), DrmError> { + let display = create_connector_display_data(connector, dev)?; + log::info!( + "Creating connector {} for device {}", + display.connector_id, + dev.devnode.as_bytes().as_bstr(), + ); + let slf = Rc::new(MetalConnector { + id: connector, + kernel_id: Cell::new(display.connector_id), + master: dev.master.clone(), + state: backend.state.clone(), + dev: dev.clone(), + backend: backend.clone(), + connector_id: backend.state.connector_ids.next(), + buffers: Default::default(), + color_description: CloneCell::new(backend.state.color_manager.srgb_gamma22().clone()), + lease: Cell::new(None), + buffers_idle: Cell::new(true), + crtc_idle: Cell::new(true), + has_damage: NumCell::new(1), + primary_plane: Default::default(), + cursor_plane: Default::default(), + crtc: Default::default(), + on_change: Default::default(), + present_trigger: Default::default(), + cursor_x: Cell::new(0), + cursor_y: Cell::new(0), + cursor_enabled: Cell::new(false), + cursor_buffers: Default::default(), + display: RefCell::new(display), + frontend_state: Cell::new(FrontState::Removed), + cursor_changed: Cell::new(false), + cursor_damage: Cell::new(false), + cursor_swap_buffer: Cell::new(false), + cursor_sync: Default::default(), + drm_feedback: Default::default(), + scanout_buffers: Default::default(), + active_framebuffer: Default::default(), + next_framebuffer: Default::default(), + direct_scanout_active: Cell::new(false), + next_vblank_nsec: Cell::new(0), + version: Default::default(), + expected_sequence: Default::default(), + pre_commit_margin_decay: GeometricDecay::new(0.5, DEFAULT_PRE_COMMIT_MARGIN), + pre_commit_margin: Cell::new(DEFAULT_PRE_COMMIT_MARGIN), + post_commit_margin_decay: GeometricDecay::new(0.1, dev.min_post_commit_margin.get()), + post_commit_margin: Cell::new(dev.min_post_commit_margin.get()), + vblank_miss_sec: Cell::new(0), + vblank_miss_this_sec: Default::default(), + presentation_is_sync: Cell::new(false), + presentation_is_zero_copy: Cell::new(false), + }); + let futures = ConnectorFutures { + _present: backend.state.eng.spawn2( + "present loop", + Phase::Present, + slf.clone().present_loop(), + ), + }; + Ok((slf, futures)) +} + +pub(super) fn create_connector_display_data( + connector: DrmConnector, + dev: &Rc, +) -> Result { + let info = dev.master.get_connector_info(connector, true)?; + let mut crtcs = BinarySearchMap::new(); + for encoder in info.encoders { + if let Some(encoder) = dev.encoders.get(&encoder) { + for (_, crtc) in &encoder.crtcs { + crtcs.insert(crtc.id, crtc.clone()); + } + } + } + let props = collect_properties(&dev.master, connector)?; + let connection = ConnectorStatus::from_drm(info.connection); + let mut name = String::new(); + let mut manufacturer = String::new(); + let mut serial_number = String::new(); + let mut vrr_refresh_max_nsec = u64::MAX; + let connector_id = ConnectorKernelId { + ty: ConnectorType::from_drm(info.connector_type), + idx: info.connector_type_id, + }; + let mut supports_bt2020 = false; + let mut supports_pq = false; + let mut luminance = None; + let mut primaries = Primaries::SRGB; + 'fetch_edid: { + if connection != ConnectorStatus::Connected { + break 'fetch_edid; + } + let edid = match props.get("EDID") { + Ok(e) => e, + _ => { + log::warn!( + "Connector {} is connected but has no EDID blob", + connector_id, + ); + break 'fetch_edid; + } + }; + let blob = match dev.master.getblob_vec::(DrmBlob(edid.value as _)) { + Ok(b) => b, + Err(e) => { + log::error!( + "Could not fetch edid property of connector {}: {}", + connector_id, + ErrorFmt(e) + ); + break 'fetch_edid; + } + }; + let edid = match jay_edid::parse(&blob) { + Ok(e) => e, + Err(e) => { + log::error!( + "Could not parse edid property of connector {}: {}", + connector_id, + ErrorFmt(e) + ); + break 'fetch_edid; + } + }; + manufacturer = edid.base_block.id_manufacturer_name.to_string(); + for descriptor in edid.base_block.descriptors.iter().flatten() { + match descriptor { + Descriptor::DisplayProductSerialNumber(s) => { + serial_number.clone_from(s); + } + Descriptor::DisplayProductName(s) => { + name.clone_from(s); + } + _ => {} + } + } + if name.is_empty() { + log::warn!( + "The display attached to connector {} does not have a product name descriptor", + connector_id, + ); + } + if serial_number.is_empty() { + log::warn!( + "The display attached to connector {} does not have a serial number descriptor", + connector_id, + ); + serial_number = edid.base_block.id_serial_number.to_string(); + } + let min_vrr_hz = 'fetch_min_hz: { + for ext in &edid.extension_blocks { + if let EdidExtension::CtaV3(cta) = ext { + for data_block in &cta.data_blocks { + if let CtaDataBlock::VendorAmd(amd) = data_block { + break 'fetch_min_hz amd.minimum_refresh_hz as u64; + } + } + } + } + for desc in &edid.base_block.descriptors { + if let Some(desc) = desc + && let Descriptor::DisplayRangeLimitsAndAdditionalTiming(timings) = desc + { + break 'fetch_min_hz timings.vertical_field_rate_min as u64; + } + } + 0 + }; + if min_vrr_hz > 0 { + vrr_refresh_max_nsec = 1_000_000_000 / min_vrr_hz; + } + let cc = &edid.base_block.chromaticity_coordinates; + let map = |c: u16| F64(c as f64 / 1024.0); + primaries = Primaries { + r: (map(cc.red_x), map(cc.red_y)), + g: (map(cc.green_x), map(cc.green_y)), + b: (map(cc.blue_x), map(cc.blue_y)), + wp: (map(cc.white_x), map(cc.white_y)), + }; + for ext in &edid.extension_blocks { + if let EdidExtension::CtaV3(cta) = ext { + for data_block in &cta.data_blocks { + match data_block { + CtaDataBlock::Colorimetry(c) => { + if c.bt2020_rgb { + supports_bt2020 = true; + } + } + CtaDataBlock::StaticHdrMetadata(h) => { + if h.smpte_st_2084 { + supports_pq = true; + } + if let Some(max) = h.max_luminance { + luminance = Some(BackendLuminance { + min: h.min_luminance.unwrap_or(0.0), + max, + max_fall: h.max_luminance.unwrap_or(max), + }); + } + } + _ => {} + } + } + } + } + } + let output_id = OutputId::new(connector_id.to_string(), manufacturer, name, serial_number); + let first_mode = info + .modes + .first() + .cloned() + .map(|m| m.to_backend()) + .unwrap_or_default(); + let persistent = match dev.backend.persistent_display_data.get(&output_id) { + Some(ds) => { + if connection != ConnectorStatus::Disconnected { + log::info!("Reusing desired state for {:?}", output_id); + } + ds + } + None => { + let ds = Rc::new(PersistentDisplayData { + state: RefCell::new(BackendConnectorState { + serial: dev.backend.state.backend_connector_state_serials.next(), + enabled: true, + active: true, + mode: first_mode, + non_desktop_override: None, + vrr: false, + tearing: false, + format: XRGB8888, + color_space: Default::default(), + eotf: Default::default(), + gamma_lut: Default::default(), + }), + }); + dev.backend + .persistent_display_data + .set(output_id.clone(), ds.clone()); + ds + } + }; + let mut desired_state = persistent.state.borrow_mut(); + if desired_state.mode == Mode::default() { + desired_state.mode = first_mode; + } else if info + .modes + .iter() + .all(|m| m.to_backend() != desired_state.mode) + { + log::warn!("Discarding previously desired mode"); + desired_state.mode = first_mode; + } + let non_desktop = props.get("non-desktop")?.value != 0; + let vrr_capable = match props.get("vrr_capable") { + Ok(c) => c.value == 1, + Err(_) => false, + }; + if !vrr_capable && desired_state.vrr { + log::warn!("Connector has lost VRR capability"); + desired_state.vrr = false; + } + { + let viable = match desired_state.eotf { + BackendEotfs::Default => true, + BackendEotfs::Pq => supports_pq, + }; + if !viable { + log::warn!("Discarding previously desired EOTF"); + desired_state.eotf = BackendEotfs::Default; + } + } + { + let viable = match desired_state.color_space { + BackendColorSpace::Default => true, + BackendColorSpace::Bt2020 => supports_bt2020, + }; + if !viable { + log::warn!("Discarding previously desired color space"); + desired_state.color_space = BackendColorSpace::Default; + } + } + drop(desired_state); + let default_properties = create_default_properties( + &props, + &[ + ("Broadcast RGB", DefaultValue::Enum("Automatic")), + ("HDR_SOURCE_METADATA", DefaultValue::Fixed(0)), + ("Output format", DefaultValue::Enum("Default")), + ("WRITEBACK_FB_ID", DefaultValue::Fixed(0)), + ("WRITEBACK_OUT_FENCE_PTR", DefaultValue::Fixed(0)), + ("content type", DefaultValue::Enum("No Data")), + ("dither", DefaultValue::Enum("off")), + ("max bpc", DefaultValue::RangeMax), + ], + ); + let hdr_metadata_prop = props + .get("HDR_OUTPUT_METADATA") + .map(|p| p.map(|v| DrmBlob(v as _))) + .ok(); + let mut hdr_metadata = None; + let mut hdr_metadata_blob_id = DrmBlob::NONE; + if let Some(p) = &hdr_metadata_prop { + hdr_metadata_blob_id = p.value; + hdr_metadata = Some(hdr_output_metadata::from_eotf( + HDMI_EOTF_TRADITIONAL_GAMMA_SDR, + )); + if p.value.is_some() { + match dev.master.getblob::(p.value) { + Ok(m) => hdr_metadata = Some(m), + _ => { + log::debug!("Could not retrieve hdr output metadata"); + } + } + } + } + let colorspace_prop = props.get("Colorspace").ok(); + let crtc_id = props.get("CRTC_ID")?.map(|v| DrmCrtc(v as _)); + let drm_state = DrmConnectorState { + crtc_id: crtc_id.value, + color_space: colorspace_prop.map(|p| p.value), + hdr_metadata, + hdr_metadata_blob_id, + hdr_metadata_blob: None, + locked: true, + fb: DrmFb::NONE, + fb_idx: 0, + cursor_fb: DrmFb::NONE, + cursor_fb_idx: 0, + cursor_x: 0, + cursor_y: 0, + out_fd: None, + src_w: 0, + src_h: 0, + crtc_x: 0, + crtc_y: 0, + crtc_w: 0, + crtc_h: 0, + }; + Ok(ConnectorDisplayData { + crtc_id: props.get("CRTC_ID")?.id, + crtcs, + first_mode, + modes: info.modes, + persistent, + refresh: 0, + non_desktop, + non_desktop_effective: non_desktop, + vrr_capable, + _vrr_refresh_max_nsec: vrr_refresh_max_nsec, + default_properties, + untyped_properties: props.to_untyped(), + connection, + mm_width: info.mm_width, + mm_height: info.mm_height, + _subpixel: info.subpixel, + supports_bt2020, + supports_pq, + primaries, + luminance, + connector_id, + output_id, + colorspace: colorspace_prop.map(|p| p.id), + hdr_metadata: hdr_metadata_prop.map(|p| p.id), + drm_state, + }) +} + +pub(super) fn create_encoder( + encoder: DrmEncoder, + master: &Rc, + crtcs: &AHashMap>, +) -> Result { + let info = master.get_encoder_info(encoder)?; + let mut possible = AHashMap::new(); + for crtc in crtcs.values() { + if info.possible_crtcs.contains(1 << crtc.idx) { + possible.insert(crtc.id, crtc.clone()); + } + } + Ok(MetalEncoder { + id: encoder, + crtcs: possible, + }) +} + +pub(super) fn create_crtc( + crtc: DrmCrtc, + idx: usize, + master: &Rc, + planes: &AHashMap>, +) -> Result { + let mask = 1 << idx; + let mut possible_planes = BinarySearchMap::new(); + for plane in planes.values() { + if plane.possible_crtcs.contains(mask) { + possible_planes.insert(plane.id, plane.clone()); + } + } + let props = collect_properties(master, crtc)?; + let default_properties = create_default_properties( + &props, + &[ + ("AMD_CRTC_REGAMMA_TF", DefaultValue::Enum("Default")), + ("CTM", DefaultValue::Fixed(0)), + ("DEGAMMA_LUT", DefaultValue::Fixed(0)), + ("OUT_FENCE_PTR", DefaultValue::Fixed(0)), + ], + ); + let active = props.get("ACTIVE")?.map(|v| v == 1); + let mode_id = props.get("MODE_ID")?.map(|v| DrmBlob(v as u32)); + let vrr_enabled = props.get("VRR_ENABLED")?.map(|v| v == 1); + let out_fence_ptr = props.get("OUT_FENCE_PTR")?; + let gamma_lut = props + .get("GAMMA_LUT") + .ok() + .map(|v| v.map(|v| DrmBlob(v as u32))); + let mut gamma_lut_size = None; + if gamma_lut.is_some() { + gamma_lut_size = props.get("GAMMA_LUT_SIZE").ok().map(|v| v.value as u32); + } + let mut mode = None; + if mode_id.value.is_some() { + match master.getblob::(mode_id.value) { + Ok(m) => mode = Some(m.into()), + _ => { + log::debug!("Could not retrieve current mode of connector"); + } + } + } + let state = DrmCrtcState { + active: active.value, + mode, + mode_blob_id: mode_id.value, + mode_blob: None, + vrr_enabled: vrr_enabled.value, + assigned_connector: DrmConnector::NONE, + gamma_lut: None, + gamma_lut_blob_id: gamma_lut.map_or(DrmBlob::NONE, |v| v.value), + gamma_lut_blob: None, + }; + Ok(MetalCrtc { + id: crtc, + idx, + master: master.clone(), + default_properties, + untyped_properties: RefCell::new(props.to_untyped()), + lease: Cell::new(None), + possible_planes, + connector: Default::default(), + pending_flip: Default::default(), + drm_state: RefCell::new(state), + active: active.id, + mode_id: mode_id.id, + vrr_enabled: vrr_enabled.id, + out_fence_ptr: out_fence_ptr.id, + gamma_lut: gamma_lut.map(|v| v.id), + gamma_lut_size, + sequence: Cell::new(0), + have_queued_sequence: Cell::new(false), + needs_vblank_emulation: Cell::new(false), + }) +} + +pub(super) fn create_plane(plane: DrmPlane, master: &Rc) -> Result { + let info = master.get_plane_info(plane)?; + let props = collect_properties(master, plane)?; + let mut formats = AHashMap::new(); + if let Some((_, v)) = props.props.get(b"IN_FORMATS".as_bstr()) { + for format in master.get_in_formats(*v as _)? { + if format.modifiers.is_empty() { + continue; + } + if let Some(f) = crate::format::formats().get(&format.format) { + formats.insert( + format.format, + PlaneFormat { + format: f, + modifiers: format.modifiers, + }, + ); + } + } + } else { + for format in info.format_types { + if let Some(f) = crate::format::formats().get(&format) { + formats.insert( + format, + PlaneFormat { + format: f, + modifiers: indexset![INVALID_MODIFIER], + }, + ); + } + } + } + let ty = match props.props.get(b"type".as_bstr()) { + Some((def, val)) => match &def.ty { + DrmPropertyType::Enum { values, .. } => 'ty: { + for v in values { + if v.value == *val { + match v.name.as_bytes() { + b"Overlay" => break 'ty PlaneType::Overlay, + b"Primary" => break 'ty PlaneType::Primary, + b"Cursor" => break 'ty PlaneType::Cursor, + _ => return Err(DrmError::UnknownPlaneType(v.name.to_owned())), + } + } + } + return Err(DrmError::InvalidPlaneType(*val)); + } + _ => return Err(DrmError::InvalidPlaneTypeProperty), + }, + _ => { + return Err(DrmError::MissingProperty( + "type".to_string().into_boxed_str(), + )); + } + }; + let default_properties = create_default_properties( + &props, + &[ + ("AMD_PLANE_BLEND_LUT", DefaultValue::Fixed(0)), + ("AMD_PLANE_BLEND_TF", DefaultValue::Enum("Default")), + ("AMD_PLANE_CTM", DefaultValue::Fixed(0)), + ("AMD_PLANE_DEGAMMA_LUT", DefaultValue::Fixed(0)), + ("AMD_PLANE_HDR_MULT", DefaultValue::Fixed(0)), + ("AMD_PLANE_LUT3D", DefaultValue::Fixed(0)), + ("AMD_PLANE_SHAPER_LUT", DefaultValue::Fixed(0)), + ("AMD_PLANE_SHAPER_TF", DefaultValue::Enum("Default")), + ("alpha", DefaultValue::RangeMax), + ("pixel blend mode", DefaultValue::Enum("Pre-multiplied")), + ("rotation", DefaultValue::Bitmask(&["rotate-0"])), + ], + ); + let fb_id = props.get("FB_ID")?.map(|v| DrmFb(v as _)); + let crtc_id = props.get("CRTC_ID")?.map(|v| DrmCrtc(v as _)); + let crtc_x = props.get("CRTC_X")?.map(|v| v as i32); + let crtc_y = props.get("CRTC_Y")?.map(|v| v as i32); + let crtc_w = props.get("CRTC_W")?.map(|v| v as i32); + let crtc_h = props.get("CRTC_H")?.map(|v| v as i32); + let src_x = props.get("SRC_X")?.map(|v| v as u32); + let src_y = props.get("SRC_Y")?.map(|v| v as u32); + let src_w = props.get("SRC_W")?.map(|v| v as u32); + let src_h = props.get("SRC_H")?.map(|v| v as u32); + let in_fence_fd = props.get("IN_FENCE_FD")?; + let state = DrmPlaneState { + fb_id: fb_id.value, + src_x: src_x.value, + src_y: src_y.value, + src_w: src_w.value, + src_h: src_h.value, + assigned_crtc: DrmCrtc::NONE, + crtc_id: crtc_id.value, + crtc_x: crtc_x.value, + crtc_y: crtc_y.value, + crtc_w: crtc_w.value, + crtc_h: crtc_h.value, + buffers: None, + }; + Ok(MetalPlane { + id: plane, + master: master.clone(), + default_properties, + untyped_properties: RefCell::new(props.to_untyped()), + ty, + possible_crtcs: info.possible_crtcs, + formats, + drm_state: RefCell::new(state), + fb_id: fb_id.id, + crtc_id: crtc_id.id, + crtc_x: crtc_x.id, + crtc_y: crtc_y.id, + crtc_w: crtc_w.id, + crtc_h: crtc_h.id, + src_x: src_x.id, + src_y: src_y.id, + src_w: src_w.id, + src_h: src_h.id, + in_fence_fd: in_fence_fd.id, + mode_w: Cell::new(0), + mode_h: Cell::new(0), + lease: Cell::new(None), + }) +} diff --git a/src/backends/metal/video/hardware_cursor.rs b/src/backends/metal/video/hardware_cursor.rs new file mode 100644 index 00000000..47dfb134 --- /dev/null +++ b/src/backends/metal/video/hardware_cursor.rs @@ -0,0 +1,64 @@ +use { + super::MetalConnector, + crate::{ + backend::{HardwareCursor, HardwareCursorUpdate}, + backends::metal::allocator::RenderBuffer, + gfx_api::{FdSync, GfxFramebuffer}, + }, + std::{ + fmt::{Debug, Formatter}, + rc::Rc, + }, +}; + +pub struct MetalHardwareCursor { + pub connector: Rc, +} + +pub struct MetalHardwareCursorChange<'a> { + pub cursor_swap_buffer: Option>, + pub cursor_enabled: bool, + pub cursor_x: i32, + pub cursor_y: i32, + pub cursor_buffer: &'a RenderBuffer, + pub cursor_size: (i32, i32), +} + +impl Debug for MetalHardwareCursor { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("MetalHardwareCursor") + .finish_non_exhaustive() + } +} + +impl HardwareCursor for MetalHardwareCursor { + fn damage(&self) { + self.connector.cursor_damage.set(true); + if self.connector.buffers_idle.get() && self.connector.crtc_idle.get() { + self.connector.schedule_present(); + } + } +} + +impl HardwareCursorUpdate for MetalHardwareCursorChange<'_> { + fn set_enabled(&mut self, enabled: bool) { + self.cursor_enabled = enabled; + } + + fn get_buffer(&self) -> Rc { + self.cursor_buffer.render.fb.clone() + } + + fn set_position(&mut self, x: i32, y: i32) { + self.cursor_x = x; + self.cursor_y = y; + } + + fn swap_buffer(&mut self, sync: Option) { + self.cursor_swap_buffer = Some(sync); + } + + fn size(&self) -> (i32, i32) { + self.cursor_size + } +} diff --git a/src/backends/metal/video/lease.rs b/src/backends/metal/video/lease.rs new file mode 100644 index 00000000..b681c0cc --- /dev/null +++ b/src/backends/metal/video/lease.rs @@ -0,0 +1,84 @@ +use { + super::{FrontState, MetalConnector, MetalCrtc, MetalDrmDevice, MetalLeaseId, MetalPlane}, + crate::{ + backend::{BackendDrmLease, BackendDrmLessee, ConnectorEvent}, + utils::errorfmt::ErrorFmt, + video::drm::DrmLease, + }, + std::{cell::Cell, rc::Rc}, + uapi::OwnedFd, +}; + +pub struct MetalLeaseData { + pub lease: DrmLease, + pub _lessee: Rc, + pub connectors: Vec>, + pub crtcs: Vec>, + pub planes: Vec>, + pub revoked: Cell, +} + +impl MetalLeaseData { + pub(super) fn try_revoke(&self) -> bool { + if self.revoked.get() { + return true; + } + let res = self.lease.try_revoke(); + if res { + self.revoked.set(res); + for c in &self.connectors { + c.lease.take(); + if let Err(e) = c.update_properties() { + log::error!("Could not update connector properties: {}", ErrorFmt(e)); + } + } + for c in &self.crtcs { + c.lease.take(); + if let Err(e) = c.update_properties() { + log::error!("Could not update crtc properties: {}", ErrorFmt(e)); + } + } + for p in &self.planes { + p.lease.take(); + if let Err(e) = p.update_properties() { + log::error!("Could not update plane properties: {}", ErrorFmt(e)); + } + } + } + res + } +} + +pub struct MetalLease { + pub(super) dev: Rc, + pub(super) id: MetalLeaseId, + pub(super) fd: Rc, +} + +impl Drop for MetalLease { + fn drop(&mut self) { + if let Some(lease) = self.dev.leases.remove(&self.id) { + if !self.dev.paused.get() { + for c in &lease.connectors { + match c.frontend_state.get() { + FrontState::Removed + | FrontState::Disconnected + | FrontState::Connected { .. } => {} + FrontState::Unavailable => { + c.send_event(ConnectorEvent::Available); + } + } + } + } + if !lease.try_revoke() { + self.dev.leases_to_break.set(self.id, lease); + } + } + } +} + +impl BackendDrmLease for MetalLease { + fn fd(&self) -> &Rc { + &self.fd + } +} diff --git a/src/backends/metal/video/model.rs b/src/backends/metal/video/model.rs new file mode 100644 index 00000000..90299602 --- /dev/null +++ b/src/backends/metal/video/model.rs @@ -0,0 +1,365 @@ +use jay_async_engine::SpawnedFuture; +use { + super::{copy_device::CopyDeviceHolder, lease::MetalLeaseData, properties::DefaultProperty}, + crate::{ + + backend::{ + BackendConnectorState, BackendLuminance, ConnectorEvent, ConnectorId, + ConnectorKernelId, DrmDeviceId, DrmEvent, Mode, OutputId, + }, + backends::metal::{ + MetalBackend, + allocator::RenderBuffer, + present::{DirectScanoutCache, PresentFb}, + transaction::{DrmConnectorState, DrmCrtcState, DrmPlaneState}, + }, + cmm::{cmm_description::ColorDescription, cmm_primaries::Primaries}, + drm_feedback::DrmFeedback, + format::Format, + gfx_api::{FdSync, GfxContext}, + state::State, + utils::{ + asyncevent::AsyncEvent, binary_search_map::BinarySearchMap, clonecell::CloneCell, + copyhashmap::CopyHashMap, geometric_decay::GeometricDecay, numcell::NumCell, + on_change::OnChange, opaque_cell::OpaqueCell, + }, + video::{ + Modifier, + dmabuf::DmaBufId, + drm::{ + ConnectorStatus, DrmConnector, DrmCrtc, DrmEncoder, DrmMaster, DrmModeInfo, + DrmObject, DrmPlane, DrmProperty, + }, + gbm::GbmDevice, + }, + }, + ahash::AHashMap, + indexmap::IndexSet, + std::{ + cell::{Cell, RefCell}, + ffi::CString, + fmt::{Debug, Formatter}, + rc::Rc, + }, + uapi::c, +}; + +pub struct PendingDrmDevice { + pub id: DrmDeviceId, + pub devnum: c::dev_t, + pub devnode: CString, +} + +#[derive(Debug)] +pub struct MetalRenderContext { + pub dev_id: DrmDeviceId, + pub gfx: Rc, + pub gbm: Rc, + pub devnode: CString, + pub copy_device: Rc, +} + +pub struct MetalDrmDevice { + pub backend: Rc, + pub id: DrmDeviceId, + pub devnum: c::dev_t, + pub devnode: CString, + pub master: Rc, + pub supports_kms: bool, + pub crtcs: AHashMap>, + pub encoders: AHashMap>, + pub planes: AHashMap>, + pub cursor_width: u64, + pub cursor_height: u64, + pub supports_async_commit: bool, + pub gbm: Rc, + pub handle_events: HandleEvents, + pub ctx: CloneCell>, + pub copy_device: Rc, + pub on_change: OnChange, + pub direct_scanout_enabled: Cell>, + pub is_nvidia: bool, + pub _is_amd: bool, + pub lease_ids: MetalLeaseIds, + pub leases: CopyHashMap, + pub leases_to_break: CopyHashMap, + pub paused: Cell, + pub min_post_commit_margin: Cell, +} + +impl Debug for MetalDrmDevice { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("MetalDrmDevice").finish_non_exhaustive() + } +} + +impl MetalDrmDevice { + pub fn is_render_device(&self) -> bool { + if let Some(ctx) = self.backend.ctx.get() { + return ctx.dev_id == self.id; + } + false + } +} + +pub struct HandleEvents { + pub handle_events: Cell>>, +} + +impl Debug for HandleEvents { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("HandleEvents").finish_non_exhaustive() + } +} + +#[derive(Debug)] +pub struct MetalDrmDeviceData { + pub dev: Rc, + pub connectors: CopyHashMap>, + pub futures: CopyHashMap, +} + +#[derive(Debug)] +pub struct PersistentDisplayData { + pub state: RefCell, +} + +#[derive(Debug)] +pub struct ConnectorDisplayData { + pub crtc_id: DrmProperty, + pub crtcs: BinarySearchMap, 8>, + pub first_mode: Mode, + pub modes: Vec, + pub persistent: Rc, + pub refresh: u32, + pub non_desktop: bool, + pub non_desktop_effective: bool, + pub vrr_capable: bool, + pub _vrr_refresh_max_nsec: u64, + pub default_properties: Vec, + pub untyped_properties: AHashMap, + + pub connector_id: ConnectorKernelId, + pub output_id: Rc, + + pub connection: ConnectorStatus, + pub mm_width: u32, + pub mm_height: u32, + pub _subpixel: u32, + + pub supports_bt2020: bool, + pub supports_pq: bool, + pub primaries: Primaries, + pub luminance: Option, + + pub colorspace: Option, + pub hdr_metadata: Option, + pub drm_state: DrmConnectorState, +} + +impl ConnectorDisplayData { + fn update_refresh(&mut self, dev: &MetalDrmDevice) { + self.refresh = 0; + if self.drm_state.crtc_id.is_none() { + return; + } + let Some(crtc) = dev.crtcs.get(&self.drm_state.crtc_id) else { + return; + }; + let drm_state = &*crtc.drm_state.borrow(); + let Some(mode) = &drm_state.mode else { + return; + }; + let refresh_rate_mhz = mode.refresh_rate_millihz(); + if refresh_rate_mhz != 0 { + self.refresh = (1_000_000_000_000u64 / refresh_rate_mhz as u64) as u32; + } + } + + fn update_non_desktop_effective(&mut self) { + let state = &*self.persistent.state.borrow(); + self.non_desktop_effective = + !state.enabled || state.non_desktop_override.unwrap_or(self.non_desktop); + } + + pub fn update_cached_fields(&mut self, dev: &MetalDrmDevice) { + self.update_refresh(dev); + self.update_non_desktop_effective(); + } +} + +linear_ids!(MetalLeaseIds, MetalLeaseId, u64); + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum FrontState { + Removed, + Disconnected, + Connected { non_desktop: bool }, + Unavailable, +} + +pub struct MetalConnector { + pub id: DrmConnector, + pub kernel_id: Cell, + pub master: Rc, + pub state: Rc, + + pub dev: Rc, + pub backend: Rc, + + pub connector_id: ConnectorId, + + pub buffers: CloneCell>>, + pub color_description: CloneCell>, + + pub lease: Cell>, + + pub buffers_idle: Cell, + pub crtc_idle: Cell, + pub has_damage: NumCell, + pub cursor_changed: Cell, + pub cursor_damage: Cell, + pub next_vblank_nsec: Cell, + + pub display: RefCell, + + pub frontend_state: Cell, + + pub primary_plane: CloneCell>>, + pub cursor_plane: CloneCell>>, + + pub crtc: CloneCell>>, + + pub on_change: OnChange, + + pub present_trigger: AsyncEvent, + + pub cursor_x: Cell, + pub cursor_y: Cell, + pub cursor_enabled: Cell, + pub cursor_buffers: CloneCell>>, + pub cursor_swap_buffer: Cell, + pub cursor_sync: CloneCell>, + + pub drm_feedback: CloneCell>>, + pub scanout_buffers: RefCell>, + pub active_framebuffer: RefCell>, + pub next_framebuffer: OpaqueCell>, + pub direct_scanout_active: Cell, + + pub version: NumCell, + pub expected_sequence: Cell>, + pub pre_commit_margin: Cell, + pub pre_commit_margin_decay: GeometricDecay, + pub post_commit_margin: Cell, + pub post_commit_margin_decay: GeometricDecay, + pub vblank_miss_sec: Cell, + pub vblank_miss_this_sec: NumCell, + pub presentation_is_sync: Cell, + pub presentation_is_zero_copy: Cell, +} + +impl Debug for MetalConnector { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("MetalConnnector").finish_non_exhaustive() + } +} + +pub struct ConnectorFutures { + pub _present: SpawnedFuture<()>, +} + +impl Debug for ConnectorFutures { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ConnectorFutures").finish_non_exhaustive() + } +} + +pub struct MetalCrtc { + pub id: DrmCrtc, + pub idx: usize, + pub master: Rc, + pub default_properties: Vec, + pub untyped_properties: RefCell>, + + pub lease: Cell>, + + pub possible_planes: BinarySearchMap, 8>, + + pub connector: CloneCell>>, + pub pending_flip: CloneCell>>, + + pub active: DrmProperty, + pub mode_id: DrmProperty, + pub vrr_enabled: DrmProperty, + pub out_fence_ptr: DrmProperty, + pub gamma_lut: Option, + pub gamma_lut_size: Option, + pub drm_state: RefCell, + + pub sequence: Cell, + pub have_queued_sequence: Cell, + pub needs_vblank_emulation: Cell, +} + +impl Debug for MetalCrtc { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("MetalCrtc").finish_non_exhaustive() + } +} + +#[derive(Debug)] +pub struct MetalEncoder { + pub id: DrmEncoder, + pub crtcs: AHashMap>, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum PlaneType { + Overlay, + Primary, + Cursor, +} + +#[derive(Debug)] +pub struct PlaneFormat { + pub format: &'static Format, + pub modifiers: IndexSet, +} + +pub struct MetalPlane { + pub id: DrmPlane, + pub master: Rc, + pub default_properties: Vec, + pub untyped_properties: RefCell>, + + pub ty: PlaneType, + + pub possible_crtcs: u32, + pub formats: AHashMap, + + pub lease: Cell>, + + pub mode_w: Cell, + pub mode_h: Cell, + + pub crtc_id: DrmProperty, + pub crtc_x: DrmProperty, + pub crtc_y: DrmProperty, + pub crtc_w: DrmProperty, + pub crtc_h: DrmProperty, + pub src_x: DrmProperty, + pub src_y: DrmProperty, + pub src_w: DrmProperty, + pub src_h: DrmProperty, + pub in_fence_fd: DrmProperty, + pub fb_id: DrmProperty, + + pub drm_state: RefCell, +} + +impl Debug for MetalPlane { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("MetalPlane").finish_non_exhaustive() + } +} diff --git a/src/backends/metal/video/properties.rs b/src/backends/metal/video/properties.rs new file mode 100644 index 00000000..bae74866 --- /dev/null +++ b/src/backends/metal/video/properties.rs @@ -0,0 +1,144 @@ +use { + crate::video::drm::{ + DrmError, DrmMaster, DrmObject, DrmProperty, DrmPropertyDefinition, DrmPropertyType, + }, + ahash::AHashMap, + bstr::{BString, ByteSlice}, + std::rc::Rc, +}; + +#[derive(Debug)] +pub struct DefaultProperty { + pub name: &'static str, + pub prop: DrmProperty, + pub value: u64, +} + +#[derive(Copy, Clone)] +pub(super) enum DefaultValue { + Fixed(u64), + Enum(&'static str), + Bitmask(&'static [&'static str]), + RangeMax, +} + +pub(super) fn create_default_properties( + props: &CollectedProperties, + defaults: &[(&'static str, DefaultValue)], +) -> Vec { + let mut res = vec![]; + let mut defaults = defaults.iter(); + 'outer: loop { + let Some(&(name, def)) = defaults.next() else { + break; + }; + if let Some((definition, _)) = props.props.get(name.as_bytes().as_bstr()) { + let value = match def { + DefaultValue::Fixed(v) => v, + DefaultValue::Enum(e) => match &definition.ty { + DrmPropertyType::Enum { + values, + bitmask: false, + } => match values.iter().find(|v| v.name == e) { + None => continue, + Some(v) => v.value, + }, + _ => continue, + }, + DefaultValue::Bitmask(e) => match &definition.ty { + DrmPropertyType::Enum { + values, + bitmask: true, + } => { + let mut res = 0; + for &e in e { + match values.iter().find(|v| v.name == e) { + None => continue 'outer, + Some(v) => res |= 1 << v.value, + } + } + res + } + _ => continue, + }, + DefaultValue::RangeMax => match &definition.ty { + DrmPropertyType::Range { max, .. } => *max, + DrmPropertyType::SignedRange { max, .. } => *max as u64, + _ => continue, + }, + }; + res.push(DefaultProperty { + name, + prop: definition.id, + value, + }); + } + } + res +} + +pub(super) fn collect_properties( + master: &Rc, + t: T, +) -> Result { + let mut props = AHashMap::new(); + for prop in master.get_properties(t)? { + let def = master.get_property(prop.id)?; + props.insert(def.name.clone(), (def, prop.value)); + } + Ok(CollectedProperties { props }) +} + +pub(super) fn collect_untyped_properties( + master: &Rc, + t: T, + props: &mut AHashMap, +) -> Result<(), DrmError> { + props.clear(); + for prop in master.get_properties(t)? { + props.insert(prop.id, prop.value); + } + Ok(()) +} + +pub(super) struct CollectedProperties { + pub(super) props: AHashMap, +} + +impl CollectedProperties { + pub(super) fn get(&self, name: &str) -> Result, DrmError> { + match self.props.get(name.as_bytes().as_bstr()) { + Some((def, value)) => Ok(TypedProperty { + id: def.id, + value: *value, + }), + _ => Err(DrmError::MissingProperty(name.to_string().into_boxed_str())), + } + } + + pub(super) fn to_untyped(&self) -> AHashMap { + let mut res = AHashMap::new(); + for (def, val) in self.props.values() { + res.insert(def.id, *val); + } + res + } +} + +#[derive(Copy, Clone, Debug)] +pub struct TypedProperty { + pub id: DrmProperty, + pub value: T, +} + +impl TypedProperty { + pub(super) fn map(self, f: F) -> TypedProperty + where + F: FnOnce(T) -> U, + { + TypedProperty { + id: self.id, + value: f(self.value), + } + } +} diff --git a/src/backends/x.rs b/src/backends/x.rs index a843f391..9cd85547 100644 --- a/src/backends/x.rs +++ b/src/backends/x.rs @@ -1,13 +1,16 @@ +use jay_time::Time; +use jay_async_engine::{Phase, SpawnedFuture}; +use jay_units::fixed::Fixed; use { crate::{ allocator::BufferObject, - async_engine::{Phase, SpawnedFuture}, + backend::{ AXIS_120, AxisSource, Backend, BackendConnectorState, BackendDrmDevice, BackendEvent, ButtonState, Connector, ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, DrmEvent, InputDevice, InputDeviceAccelProfile, InputDeviceCapability, InputDeviceClickMethod, InputDeviceId, InputEvent, KeyState, Mode, MonitorInfo, - ScrollAxis, TransformMatrix, + OutputId, ScrollAxis, TransformMatrix, transaction::{ BackendAppliedConnectorTransaction, BackendConnectorTransaction, BackendConnectorTransactionError, BackendConnectorTransactionType, @@ -15,14 +18,12 @@ use { }, }, cmm::cmm_primaries::Primaries, - fixed::Fixed, format::{Format, XRGB8888}, gfx_api::{ AcquireSync, GfxApi, GfxContext, GfxError, GfxFramebuffer, GfxTexture, ReleaseSync, }, - ifs::wl_output::OutputId, state::State, - time::Time, + utils::{ clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, queue::AsyncQueue, syncqueue::SyncQueue, diff --git a/src/cli.rs b/src/cli.rs index e3d8d74a..f641c157 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -3,6 +3,7 @@ mod color; mod color_management; mod config; mod damage_tracking; +mod dpms; mod duration; mod generate; mod idle; @@ -13,8 +14,6 @@ mod pid; mod quit; mod randr; mod reexec; -mod run_privileged; -mod run_tagged; pub mod screenshot; mod seat_test; mod set_log_level; @@ -23,20 +22,22 @@ mod unlock; mod version; mod xwayland; +use jay_pr_caps::drop_all_pr_caps; +use jay_logger::LogLevel; use { crate::{ cli::{ clients::ClientsArgs, color_management::ColorManagementArgs, config::ConfigArgs, damage_tracking::DamageTrackingArgs, idle::IdleCmd, input::InputArgs, - json::VERBOSE_JSON, randr::RandrArgs, reexec::ReexecArgs, run_tagged::RunTaggedArgs, - tree::TreeArgs, xwayland::XwaylandArgs, + json::VERBOSE_JSON, randr::RandrArgs, reexec::ReexecArgs, tree::TreeArgs, + xwayland::XwaylandArgs, }, - compositor::{LogLevel, start_compositor}, - format::{Format, ref_formats}, + compositor::start_compositor, + portal, - pr_caps::drop_all_pr_caps, + }, - clap::{Args, Parser, Subcommand, ValueEnum, ValueHint, builder::PossibleValue}, + clap::{Args, Parser, Subcommand, ValueEnum, ValueHint}, clap_complete::Shell, std::sync::atomic::Ordering::Relaxed, }; @@ -85,10 +86,8 @@ pub enum Cmd { Screenshot(ScreenshotArgs), /// Inspect/modify the idle (screensaver) settings. Idle(IdleArgs), - /// Run a privileged program. - RunPrivileged(RunPrivilegedArgs), - /// Run a program with a connection tag. - RunTagged(RunTaggedArgs), + /// Turn monitors on or off. + Dpms(DpmsArgs), /// Tests the events produced by a seat. SeatTest(SeatTestArgs), /// Run the desktop portal. @@ -132,10 +131,16 @@ pub struct IdleArgs { } #[derive(Args, Debug)] -pub struct RunPrivilegedArgs { - /// The program to run - #[clap(required = true, trailing_var_arg = true, value_hint = ValueHint::CommandWithArguments)] - pub program: Vec, +pub struct DpmsArgs { + /// Whether monitors should be on or off. + #[clap(value_enum)] + pub state: DpmsState, +} + +#[derive(ValueEnum, Debug, Copy, Clone, Eq, PartialEq)] +pub enum DpmsState { + On, + Off, } #[derive(ValueEnum, Debug, Copy, Clone, Hash, Default, PartialEq)] @@ -224,16 +229,6 @@ pub struct GenerateArgs { shell: Shell, } -impl ValueEnum for &'static Format { - fn value_variants<'a>() -> &'a [Self] { - ref_formats() - } - - fn to_possible_value(&self) -> Option { - Some(PossibleValue::new(self.name)) - } -} - pub fn main() { let cli = Jay::parse(); if not_matches!(cli.command, Cmd::Run(_)) { @@ -250,9 +245,8 @@ pub fn main() { Cmd::SetLogLevel(a) => set_log_level::main(cli.global, a), Cmd::Screenshot(a) => screenshot::main(cli.global, a), Cmd::Idle(a) => idle::main(cli.global, a), + Cmd::Dpms(a) => dpms::main(cli.global, a), Cmd::Unlock => unlock::main(cli.global), - Cmd::RunPrivileged(a) => run_privileged::main(cli.global, a), - Cmd::RunTagged(a) => run_tagged::main(cli.global, a), Cmd::SeatTest(a) => seat_test::main(cli.global, a), Cmd::Portal => portal::run_freestanding(cli.global), Cmd::Randr(a) => randr::main(cli.global, a), diff --git a/src/cli/clients.rs b/src/cli/clients.rs index 6af73f46..393c2ecd 100644 --- a/src/cli/clients.rs +++ b/src/cli/clients.rs @@ -167,7 +167,6 @@ pub struct Client { pub is_xwayland: bool, pub comm: Option, pub exe: Option, - pub tag: Option, } pub async fn handle_client_query( @@ -212,9 +211,6 @@ pub async fn handle_client_query( Exe::handle(tl, id, c.clone(), |c, event| { last!(c).exe = Some(event.exe.to_string()); }); - Tag::handle(tl, id, c.clone(), |c, event| { - last!(c).tag = Some(event.tag.to_string()); - }); tl.round_trip().await; mem::take(&mut *c.borrow_mut()) .into_iter() @@ -253,7 +249,6 @@ impl ClientPrinter<'_> { bol!(is_xwayland, "xwayland"); opt!(comm, "comm"); opt!(exe, "exe"); - opt!(tag, "tag"); } } @@ -269,6 +264,5 @@ pub fn make_json_client(client: &Client) -> JsonClient<'_> { is_xwayland: client.is_xwayland, comm: client.comm.as_deref(), exe: client.exe.as_deref(), - tag: client.tag.as_deref(), } } diff --git a/src/cli/color.rs b/src/cli/color.rs index 24136ecc..df60b29d 100644 --- a/src/cli/color.rs +++ b/src/cli/color.rs @@ -1,5 +1,6 @@ use { - crate::{theme::Color, utils::errorfmt::ErrorFmt}, + crate::utils::errorfmt::ErrorFmt, + jay_theme::Color, std::ops::Range, }; diff --git a/src/cli/config.rs b/src/cli/config.rs index b5de8def..64522a5f 100644 --- a/src/cli/config.rs +++ b/src/cli/config.rs @@ -1,8 +1,9 @@ +use jay_logger::Logger; use { crate::{ cli::{GlobalArgs, json::jsonl}, compositor::config_dir, - logger::Logger, + utils::errorfmt::ErrorFmt, }, clap::{Args, Subcommand}, diff --git a/src/cli/dpms.rs b/src/cli/dpms.rs new file mode 100644 index 00000000..ec8fe577 --- /dev/null +++ b/src/cli/dpms.rs @@ -0,0 +1,23 @@ +use { + crate::{ + cli::{DpmsArgs, DpmsState, GlobalArgs}, + tools::tool_client::{ToolClient, with_tool_client}, + wire::jay_compositor::SetDpms, + }, + std::rc::Rc, +}; + +pub fn main(global: GlobalArgs, args: DpmsArgs) { + with_tool_client(global.log_level, |tc| async move { + run(tc, args).await; + }); +} + +async fn run(tc: Rc, args: DpmsArgs) { + let comp = tc.jay_compositor().await; + tc.send(SetDpms { + self_id: comp, + active: (args.state == DpmsState::On) as u32, + }); + tc.round_trip().await; +} diff --git a/src/cli/json.rs b/src/cli/json.rs index f7369a54..66f02d3f 100644 --- a/src/cli/json.rs +++ b/src/cli/json.rs @@ -66,8 +66,6 @@ pub struct JsonClient<'a> { pub comm: Option<&'a str>, #[serde(skip_serializing_if = "is_none")] pub exe: Option<&'a str>, - #[serde(skip_serializing_if = "is_none")] - pub tag: Option<&'a str>, } #[derive(Serialize)] diff --git a/src/cli/randr.rs b/src/cli/randr.rs index d77591ae..ba56ff74 100644 --- a/src/cli/randr.rs +++ b/src/cli/randr.rs @@ -1,3 +1,5 @@ +use std::time::Duration; +use jay_units::scale::Scale; use { crate::{ backend::{BackendColorSpace, BackendEotfs}, @@ -11,7 +13,6 @@ use { cmm::cmm_primaries::Primaries, format::{Format, XRGB8888}, ifs::wl_output::BlendSpace, - scale::Scale, tools::tool_client::{Handle, ToolClient, with_tool_client}, tree::Transform, utils::{errorfmt::ErrorFmt, ordered_float::F64, static_text::StaticText}, @@ -29,7 +30,7 @@ use { fmt::{self, Display, Formatter}, rc::Rc, str::FromStr, - time::Duration, + }, thiserror::Error, }; diff --git a/src/cli/run_privileged.rs b/src/cli/run_privileged.rs deleted file mode 100644 index 7b1b01b9..00000000 --- a/src/cli/run_privileged.rs +++ /dev/null @@ -1,35 +0,0 @@ -use { - crate::{ - cli::{GlobalArgs, RunPrivilegedArgs}, - compositor::WAYLAND_DISPLAY, - logger::Logger, - utils::{errorfmt::ErrorFmt, oserror::OsErrorExt, xrd::xrd}, - }, - std::path::PathBuf, - uapi::UstrPtr, -}; - -pub fn main(global: GlobalArgs, args: RunPrivilegedArgs) { - Logger::install_stderr(global.log_level); - if let Some(xrd) = xrd() { - let mut wd = match std::env::var(WAYLAND_DISPLAY) { - Ok(v) => v, - _ => fatal!("{} is not set", WAYLAND_DISPLAY), - }; - wd.push_str(".jay"); - let mut path = PathBuf::from(xrd); - path.push(&wd); - if path.exists() { - unsafe { - std::env::set_var(WAYLAND_DISPLAY, &wd); - } - } - } - let mut argv = UstrPtr::new(); - for arg in &args.program { - argv.push(arg.as_str()); - } - let program = args.program[0].as_str(); - let res = uapi::execvp(program, &argv).to_os_error().unwrap_err(); - fatal!("Could not execute `{}`: {}", program, ErrorFmt(res)); -} diff --git a/src/cli/run_tagged.rs b/src/cli/run_tagged.rs deleted file mode 100644 index de7ba2c4..00000000 --- a/src/cli/run_tagged.rs +++ /dev/null @@ -1,70 +0,0 @@ -use { - crate::{ - cli::GlobalArgs, - compositor::WAYLAND_DISPLAY, - tools::tool_client::{Handle, ToolClient, with_tool_client}, - utils::{errorfmt::ErrorFmt, oserror::OsErrorExt}, - wire::{jay_acceptor_request, jay_compositor}, - }, - clap::{Args, ValueHint}, - std::{cell::Cell, env, rc::Rc}, - uapi::UstrPtr, -}; - -#[derive(Args, Debug)] -pub struct RunTaggedArgs { - /// Specifies a tag to apply to all spawned wayland connections. - tag: String, - /// The program to run. - #[clap(required = true, trailing_var_arg = true, value_hint = ValueHint::CommandWithArguments)] - pub program: Vec, -} - -pub fn main(global: GlobalArgs, run_tagged_args: RunTaggedArgs) { - with_tool_client(global.log_level, |tc| async move { - let run_tagged = Rc::new(RunTagged { tc: tc.clone() }); - run_tagged.run(run_tagged_args).await; - }); -} - -struct RunTagged { - tc: Rc, -} - -impl RunTagged { - async fn run(&self, args: RunTaggedArgs) { - let tc = &self.tc; - let comp = tc.jay_compositor().await; - let req = tc.id(); - tc.send(jay_compositor::GetTaggedAcceptor { - self_id: comp, - id: req, - tag: &args.tag, - }); - let res = Rc::new(Cell::new(None)); - jay_acceptor_request::Done::handle(&tc, req, res.clone(), |res, ev| { - res.set(Some(Ok(ev.name.to_owned()))); - }); - jay_acceptor_request::Failed::handle(&tc, req, res.clone(), |res, ev| { - res.set(Some(Err(ev.msg.to_owned()))); - }); - tc.round_trip().await; - match res.take().unwrap() { - Ok(n) => { - unsafe { - env::set_var(WAYLAND_DISPLAY, &n); - } - let mut argv = UstrPtr::new(); - for arg in &args.program { - argv.push(arg.as_str()); - } - let program = args.program[0].as_str(); - let res = uapi::execvp(program, &argv).to_os_error().unwrap_err(); - fatal!("Could not execute `{}`: {}", program, ErrorFmt(res)); - } - Err(msg) => { - fatal!("Could not create acceptor: {}", msg); - } - } - } -} diff --git a/src/cli/screenshot.rs b/src/cli/screenshot.rs index 7ffd5f63..c9d84469 100644 --- a/src/cli/screenshot.rs +++ b/src/cli/screenshot.rs @@ -1,12 +1,14 @@ +use jay_udmabuf::{Udmabuf, UdmabufError}; +use jay_eventfd_cache::EventfdCache; use { crate::{ allocator::{Allocator, AllocatorError, BufferUsage, MappedBuffer}, cli::{GlobalArgs, ScreenshotArgs, ScreenshotFormat}, - eventfd_cache::EventfdCache, + format::XRGB8888, gfx_apis, tools::tool_client::{Handle, ToolClient, with_tool_client}, - udmabuf::{Udmabuf, UdmabufError}, + utils::{errorfmt::ErrorFmt, queue::AsyncQueue, windows::WindowsExt}, video::{ dmabuf::{DmaBuf, DmaBufIds, DmaBufPlane, PlaneVec}, diff --git a/src/cli/seat_test.rs b/src/cli/seat_test.rs index c5b382b8..ca81c2a2 100644 --- a/src/cli/seat_test.rs +++ b/src/cli/seat_test.rs @@ -1,10 +1,10 @@ +use jay_units::fixed::Fixed; use { crate::{ cli::{ GlobalArgs, SeatTestArgs, json::{JsonAxisData, JsonSeatEvent, jsonl}, }, - fixed::Fixed, ifs::wl_seat::wl_pointer::{ CONTINUOUS, FINGER, HORIZONTAL_SCROLL, PendingScroll, VERTICAL_SCROLL, WHEEL, }, diff --git a/src/cli/tree.rs b/src/cli/tree.rs index 333ba0da..175e74cd 100644 --- a/src/cli/tree.rs +++ b/src/cli/tree.rs @@ -1,3 +1,4 @@ +use jay_geometry::Rect; use { crate::{ cli::{ @@ -10,7 +11,7 @@ use { TREE_TY_LOCK_SURFACE, TREE_TY_OUTPUT, TREE_TY_PLACEHOLDER, TREE_TY_WORKSPACE, TREE_TY_X_WINDOW, TREE_TY_XDG_POPUP, TREE_TY_XDG_TOPLEVEL, }, - rect::Rect, + tools::tool_client::{Handle, ToolClient, with_tool_client}, wire::{JayCompositorId, JayTreeQueryId, jay_client_query, jay_compositor, jay_tree_query}, }, diff --git a/src/client.rs b/src/client.rs index e21665ba..93cf4699 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,11 +1,9 @@ +use jay_async_engine::SpawnedFuture; use { crate::{ - async_engine::SpawnedFuture, + client::{error::LookupError, objects::Objects}, - criteria::{ - CritDestroyListener, CritMatcherId, - clm::{CL_CHANGED_DESTROYED, CL_CHANGED_NEW, ClMatcherChange}, - }, + criteria::clm::{CL_CHANGED_DESTROYED, CL_CHANGED_NEW, ClMatcherChange}, ifs::{ wl_display::WlDisplay, wl_registry::WlRegistry, @@ -14,7 +12,6 @@ use { }, leaks::Tracker, object::{Interface, Object, ObjectId, WL_DISPLAY_ID}, - security_context_acceptor::AcceptorMetadata, state::State, utils::{ asyncevent::AsyncEvent, @@ -25,11 +22,11 @@ use { pending_serial::PendingSerial, pid_info::{PidInfo, get_pid_info, get_socket_creds}, pidfd_send_signal::pidfd_send_signal, - static_text::StaticText, }, wire::WlRegistryId, }, ahash::AHashMap, + jay_criteria::{CritDestroyListener, CritMatcherId}, std::{ cell::{Cell, RefCell}, collections::VecDeque, @@ -50,56 +47,6 @@ mod error; mod objects; mod tasks; -bitflags! { - ClientCaps: u32; - CAP_DATA_CONTROL_MANAGER = 1 << 0, - CAP_VIRTUAL_KEYBOARD_MANAGER = 1 << 1, - CAP_FOREIGN_TOPLEVEL_LIST = 1 << 2, - CAP_IDLE_NOTIFIER = 1 << 3, - CAP_SESSION_LOCK_MANAGER = 1 << 4, - CAP_JAY_COMPOSITOR = 1 << 5, - CAP_LAYER_SHELL = 1 << 6, - CAP_SCREENCOPY_MANAGER = 1 << 7, - CAP_SEAT_MANAGER = 1 << 8, - CAP_DRM_LEASE = 1 << 9, - CAP_INPUT_METHOD = 1 << 10, - CAP_WORKSPACE = 1 << 11, - CAP_FOREIGN_TOPLEVEL_MANAGER = 1 << 12, - CAP_HEAD_MANAGER = 1 << 13, - CAP_GAMMA_CONTROL_MANAGER = 1 << 14, - CAP_VIRTUAL_POINTER_MANAGER = 1 << 15, - CAP_FOREIGN_TOPLEVEL_GEOMETRY_TRACKING = 1 << 16, -} - -impl StaticText for ClientCapsEnum { - fn text(&self) -> &'static str { - match self { - ClientCapsEnum::CAP_DATA_CONTROL_MANAGER => "data-control", - ClientCapsEnum::CAP_VIRTUAL_KEYBOARD_MANAGER => "virtual-keyboard", - ClientCapsEnum::CAP_FOREIGN_TOPLEVEL_LIST => "foreign-toplevel-list", - ClientCapsEnum::CAP_IDLE_NOTIFIER => "idle-notifier", - ClientCapsEnum::CAP_SESSION_LOCK_MANAGER => "session-lock", - ClientCapsEnum::CAP_JAY_COMPOSITOR => "jay-compositor", - ClientCapsEnum::CAP_LAYER_SHELL => "layer-shell", - ClientCapsEnum::CAP_SCREENCOPY_MANAGER => "screencopy", - ClientCapsEnum::CAP_SEAT_MANAGER => "seat-manager", - ClientCapsEnum::CAP_DRM_LEASE => "drm-lease", - ClientCapsEnum::CAP_INPUT_METHOD => "input-method", - ClientCapsEnum::CAP_WORKSPACE => "workspace-manager", - ClientCapsEnum::CAP_FOREIGN_TOPLEVEL_MANAGER => "foreign-toplevel-manager", - ClientCapsEnum::CAP_HEAD_MANAGER => "head-manager", - ClientCapsEnum::CAP_GAMMA_CONTROL_MANAGER => "gamma-control-manager", - ClientCapsEnum::CAP_VIRTUAL_POINTER_MANAGER => "virtual-pointer", - ClientCapsEnum::CAP_FOREIGN_TOPLEVEL_GEOMETRY_TRACKING => { - "foreign-toplevel-geometry-tracking" - } - } - } -} - -pub const CAPS_DEFAULT: ClientCaps = ClientCaps(CAP_LAYER_SHELL.0 | CAP_DRM_LEASE.0); -pub const CAPS_DEFAULT_SANDBOXED: ClientCaps = ClientCaps(CAP_DRM_LEASE.0); - #[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)] pub struct ClientId(u64); @@ -125,6 +72,15 @@ pub struct Clients { shutdown_clients: RefCell>, } +#[derive(Default)] +pub struct ClientMetadata { + pub sandboxed: bool, + pub sandbox_engine: Option, + pub app_id: Option, + pub instance_id: Option, + pub tag: Option, +} + impl Clients { pub fn new() -> Self { Self { @@ -156,24 +112,12 @@ impl Clients { id: ClientId, global: &Rc, socket: Rc, - bounding_caps: ClientCaps, - set_bounding_caps_for_children: bool, - acceptor: &Rc, + metadata: &Rc, ) -> Result<(), ClientError> { let Some((uid, pid)) = get_socket_creds(&socket) else { return Ok(()); }; - self.spawn2( - id, - global, - socket, - uid, - pid, - bounding_caps, - set_bounding_caps_for_children, - false, - acceptor, - )?; + self.spawn2(id, global, socket, uid, pid, false, metadata)?; Ok(()) } @@ -184,15 +128,9 @@ impl Clients { socket: Rc, uid: c::uid_t, pid: c::pid_t, - bounding_caps: ClientCaps, - set_bounding_caps_for_children: bool, is_xwayland: bool, - acceptor: &Rc, + metadata: &Rc, ) -> Result, ClientError> { - let effective_caps = match acceptor.sandboxed { - true => CAPS_DEFAULT_SANDBOXED, - false => CAPS_DEFAULT, - }; let data = Rc::new_cyclic(|slf| Client { id, state: global.clone(), @@ -204,8 +142,6 @@ impl Clients { shutdown: Default::default(), tracker: Default::default(), is_xwayland, - effective_caps: Cell::new(effective_caps & bounding_caps), - bounding_caps_for_children: Cell::new(bounding_caps), last_enter_serial: Default::default(), pid_info: get_pid_info(uid, pid), serials: Default::default(), @@ -223,13 +159,9 @@ impl Clients { focus_stealing_serial: Default::default(), changed_properties: Default::default(), destroyed: Default::default(), - acceptor: acceptor.clone(), + metadata: metadata.clone(), }); track!(data, data); - global.update_capabilities(&data, bounding_caps, set_bounding_caps_for_children); - if acceptor.secure || is_xwayland { - data.effective_caps.set(ClientCaps::all()); - } let display = Rc::new(WlDisplay::new(&data)); track!(data, display); data.objects.display.set(Some(display.clone())); @@ -239,13 +171,12 @@ impl Clients { data: data.clone(), }; log::info!( - "Client {} connected, pid: {}, uid: {}, fd: {}, comm: {:?}, caps: {:?}", + "Client {} connected, pid: {}, uid: {}, fd: {}, comm: {:?}", id, pid, uid, client.data.socket.raw(), data.pid_info.comm, - data.effective_caps.get(), ); client.data.property_changed(CL_CHANGED_NEW); self.clients.borrow_mut().insert(client.data.id, client); @@ -268,15 +199,13 @@ impl Clients { } } - pub fn broadcast(&self, required_caps: ClientCaps, xwayland_only: bool, mut f: B) + pub fn broadcast(&self, xwayland_only: bool, mut f: B) where B: FnMut(&Rc), { let clients = self.clients.borrow(); for client in clients.values() { - if client.data.effective_caps.get().contains(required_caps) - && (!xwayland_only || client.data.is_xwayland) - { + if !xwayland_only || client.data.is_xwayland { f(&client.data); } } @@ -336,8 +265,6 @@ pub struct Client { shutdown: AsyncEvent, pub tracker: Tracker, pub is_xwayland: bool, - pub effective_caps: Cell, - pub bounding_caps_for_children: Cell, pub last_enter_serial: Cell>, pub pid_info: PidInfo, pub serials: RefCell>, @@ -349,8 +276,8 @@ pub struct Client { pub wire_scale: Cell>, pub focus_stealing_serial: Cell>, pub changed_properties: Cell, - pub destroyed: CopyHashMap>>>, - pub acceptor: Rc, + pub destroyed: CopyHashMap>>, + pub metadata: Rc, } pub const NUM_CACHED_SERIAL_RANGES: usize = 64; diff --git a/src/client/objects.rs b/src/client/objects.rs index d4449f84..09550a9e 100644 --- a/src/client/objects.rs +++ b/src/client/objects.rs @@ -6,11 +6,7 @@ use { wp_image_description_reference_v1::WpImageDescriptionReferenceV1, wp_image_description_v1::WpImageDescriptionV1, }, - ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, - ext_image_capture_source_v1::ExtImageCaptureSourceV1, - ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1, - head_management::jay_head_error_v1::JayHeadErrorV1, - ipc::{ + data_transfer::{ data_control::{ ext_data_control_source_v1::ExtDataControlSourceV1, zwlr_data_control_source_v1::ZwlrDataControlSourceV1, @@ -18,6 +14,10 @@ use { wl_data_source::WlDataSource, zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, }, + ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, + ext_image_capture_source_v1::ExtImageCaptureSourceV1, + ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1, + head_management::jay_head_error_v1::JayHeadErrorV1, jay_output::JayOutput, jay_screencast::JayScreencast, jay_toplevel::JayToplevel, diff --git a/src/client/tasks.rs b/src/client/tasks.rs index fe51a9ea..5bbed202 100644 --- a/src/client/tasks.rs +++ b/src/client/tasks.rs @@ -1,6 +1,7 @@ +use jay_async_engine::Phase; use { crate::{ - async_engine::Phase, + client::{Client, ClientError}, utils::{ buffd::{BufFdOut, MsgParser, WlBufFdIn, WlMessage}, diff --git a/src/clientmem.rs b/src/clientmem.rs index 681713da..39eb4ef9 100644 --- a/src/clientmem.rs +++ b/src/clientmem.rs @@ -1,329 +1,8 @@ -use { - crate::{ - client::Client, - cpu_worker::{AsyncCpuWork, CpuJob, CpuWork, CpuWorker}, - gfx_api::{ShmMemory, ShmMemoryBacking}, - utils::{ - oserror::{OsError, OsErrorExt2}, - page_size::page_size, - vec_ext::VecExt, - }, - }, - std::{ - cell::Cell, - error::Error, - mem::{ManuallyDrop, MaybeUninit}, - ops::Deref, - ptr, - rc::Rc, - sync::atomic::{Ordering, compiler_fence}, - }, - thiserror::Error, - uapi::{ - OwnedFd, Pod, - c::{self, raise}, - ftruncate, - }, -}; +pub use jay_clientmem::*; -#[derive(Debug, Error)] -pub enum ClientMemError { - #[error("Could not install the sigbus handler")] - SigactionFailed(#[source] crate::utils::oserror::OsError), - #[error("A SIGBUS occurred while accessing mapped memory")] - Sigbus, - #[error("mmap failed")] - MmapFailed(#[source] crate::utils::oserror::OsError), - #[error("Length was not a multiple of the data element size")] - InvalidLength, -} - -pub struct ClientMem { - fd: ManuallyDrop>, - failed: Cell, - sigbus_impossible: bool, - data: *const [Cell], - cpu: Option>, -} - -#[derive(Clone)] -pub struct ClientMemOffset { - mem: Rc, - offset: usize, - data: *const [Cell], -} - -impl ClientMem { - pub fn new( - fd: &Rc, - len: usize, - read_only: bool, - client: Option<&Client>, - cpu: Option<&Rc>, - is_udmabuf: bool, - ) -> Result { - Self::new2(fd, len, read_only, client, cpu, c::MAP_SHARED, is_udmabuf) - } - - pub fn new_private( - fd: &Rc, - len: usize, - read_only: bool, - client: Option<&Client>, - cpu: Option<&Rc>, - ) -> Result { - Self::new2(fd, len, read_only, client, cpu, c::MAP_PRIVATE, false) - } - - fn new2( - fd: &Rc, - len: usize, - read_only: bool, - client: Option<&Client>, - cpu: Option<&Rc>, - flags: c::c_int, - is_udmabuf: bool, - ) -> Result { - let mut sigbus_impossible = is_udmabuf; - let mut real_size = None; - if !sigbus_impossible - && let Ok(seals) = uapi::fcntl_get_seals(fd.raw()) - && seals & c::F_SEAL_SHRINK != 0 - && let Ok(stat) = uapi::fstat(fd.raw()) - { - real_size = Some(stat.st_size as usize); - sigbus_impossible = stat.st_size as u64 >= len as u64; - } - if !sigbus_impossible && let Some(client) = client { - log::debug!( - "Client {} ({}) has created a shm buffer that might cause SIGBUS", - client.pid_info.comm, - client.id, - ); - } - let len = len.next_multiple_of(page_size()); - if let Some(real_size) = real_size - && real_size < len - { - let _ = ftruncate(fd.raw(), len as _); - } - let data = if len == 0 { - &mut [][..] - } else { - let prot = match read_only { - true => c::PROT_READ, - false => c::PROT_READ | c::PROT_WRITE, - }; - unsafe { - let data = c::mmap64(ptr::null_mut(), len, prot, flags, fd.raw(), 0); - if data == c::MAP_FAILED { - return Err(ClientMemError::MmapFailed(OsError::default())); - } - std::slice::from_raw_parts_mut(data as *mut Cell, len) - } - }; - Ok(Self { - fd: ManuallyDrop::new(fd.clone()), - failed: Cell::new(false), - sigbus_impossible, - data, - cpu: cpu.cloned(), - }) - } - - pub fn len(&self) -> usize { - self.data.len() - } - - pub fn offset(self: &Rc, offset: usize, len: usize) -> ClientMemOffset { - let mem = unsafe { &*self.data }; - ClientMemOffset { - mem: self.clone(), - offset, - data: &mem[offset..][..len], - } - } - - pub fn fd(&self) -> &Rc { - &self.fd - } - - pub fn is_sealed_memfd(&self) -> bool { - self.sigbus_impossible - } -} - -impl ClientMemOffset { - pub fn pool(&self) -> &ClientMem { - &self.mem - } - - pub fn offset(&self) -> usize { - self.offset - } - - #[expect(dead_code)] - pub fn ptr(&self) -> *const [Cell] { - self.data - } - - pub fn access]) -> T>(&self, f: F) -> Result { - unsafe { - if self.mem.sigbus_impossible { - return Ok(f(&*self.data)); - } - let mref = MemRef { - mem: &*self.mem, - outer: MEM.get(), - }; - MEM.set(&mref); - compiler_fence(Ordering::SeqCst); - let res = f(&*self.data); - MEM.set(mref.outer); - compiler_fence(Ordering::SeqCst); - match self.mem.failed.get() { - true => Err(ClientMemError::Sigbus), - _ => Ok(res), - } - } - } - - pub fn read(&self, dst: &mut Vec) -> Result<(), ClientMemError> { - if self.data.len().checked_rem(std::mem::size_of::()) != Some(0) { - return Err(ClientMemError::InvalidLength); - } - self.access(|v| { - let len_elements = v.len() / std::mem::size_of::(); - dst.reserve(len_elements); - let (_, unused) = dst.split_at_spare_mut_bytes_ext(); - unused[..v.len()].copy_from_slice(uapi::as_maybe_uninit_bytes(v)); - unsafe { - dst.set_len(dst.len() + len_elements); - } - }) - } -} - -impl Drop for ClientMem { - fn drop(&mut self) { - let fd = unsafe { ManuallyDrop::take(&mut self.fd) }; - if let Some(cpu) = &self.cpu { - let pending = cpu.submit(Box::new(CloseMemWork { - fd: Rc::try_unwrap(fd).ok(), - data: self.data, - })); - pending.detach(); - } else { - unsafe { - c::munmap(self.data as _, self.len()); - } - } - } -} - -struct MemRef { - mem: *const ClientMem, - outer: *const MemRef, -} - -thread_local! { - static MEM: Cell<*const MemRef> = const { Cell::new(ptr::null()) }; -} - -unsafe fn kill() -> ! { - unsafe { - c::signal(c::SIGBUS, c::SIG_DFL); - raise(c::SIGBUS); - } - unreachable!(); -} - -unsafe extern "C" fn sigbus(sig: i32, info: &c::siginfo_t, _ucontext: *mut c::c_void) { - unsafe { - assert_eq!(sig, c::SIGBUS); - let mut memr_ptr = MEM.get(); - while !memr_ptr.is_null() { - let memr = &*memr_ptr; - let mem = &*memr.mem; - let lo = mem.data as *mut u8 as usize; - let hi = lo + mem.len(); - let fault_addr = info.si_addr() as usize; - if fault_addr < lo || fault_addr >= hi { - memr_ptr = memr.outer; - continue; - } - let res = c::mmap64( - lo as _, - hi - lo, - c::PROT_WRITE | c::PROT_READ, - c::MAP_ANONYMOUS | c::MAP_PRIVATE | c::MAP_FIXED, - -1, - 0, - ); - if res == c::MAP_FAILED { - kill(); - } - mem.failed.set(true); - return; - } - kill(); - } -} - -pub fn init() -> Result<(), ClientMemError> { - unsafe { - let mut action: c::sigaction = MaybeUninit::zeroed().assume_init(); - action.sa_sigaction = - sigbus as unsafe extern "C" fn(i32, &c::siginfo_t, *mut c::c_void) as _; - action.sa_flags = c::SA_NODEFER | c::SA_SIGINFO; - let res = c::sigaction(c::SIGBUS, &action, ptr::null_mut()); - uapi::map_err!(res) - .map(drop) - .map_os_err(ClientMemError::SigactionFailed) - } -} - -struct CloseMemWork { - fd: Option, - data: *const [Cell], -} - -unsafe impl Send for CloseMemWork {} - -impl CpuJob for CloseMemWork { - fn work(&mut self) -> &mut dyn CpuWork { - self - } - - fn completed(self: Box) { - // nothing - } -} - -impl CpuWork for CloseMemWork { - fn run(&mut self) -> Option> { - zone!("CloseMemWork"); - self.fd.take(); - unsafe { - c::munmap(self.data as _, self.data.len()); - } - None - } -} - -impl ShmMemory for ClientMemOffset { - fn len(&self) -> usize { - self.data.len() - } - - fn safe_access(&self) -> ShmMemoryBacking { - match self.mem.is_sealed_memfd() { - true => ShmMemoryBacking::Ptr(self.data), - false => ShmMemoryBacking::Fd(self.mem.fd.deref().clone(), self.offset), - } - } - - fn access(&self, f: &mut dyn FnMut(&[Cell])) -> Result<(), Box> { - self.access(f).map_err(|e| e.into()) +pub fn client_mem_client(client: &crate::client::Client) -> ClientMemClient<'_> { + ClientMemClient { + comm: &client.pid_info.comm, + id: client.id.raw(), } } diff --git a/src/cmm.rs b/src/cmm.rs index 9c359c9d..7a67574d 100644 --- a/src/cmm.rs +++ b/src/cmm.rs @@ -1,9 +1,48 @@ -pub mod cmm_description; -pub mod cmm_eotf; -pub mod cmm_luminance; -pub mod cmm_manager; -pub mod cmm_primaries; -pub mod cmm_render_intent; -#[cfg(test)] -mod cmm_tests; -pub mod cmm_transform; +pub mod cmm_description { + pub use jay_cmm::cmm_description::*; +} + +pub mod cmm_eotf { + pub use jay_cmm::cmm_eotf::*; +} + +pub mod cmm_luminance { + pub use jay_cmm::cmm_luminance::*; +} + +pub mod cmm_manager { + pub use jay_cmm::cmm_manager::*; +} + +pub mod cmm_primaries { + pub use jay_cmm::cmm_primaries::*; +} + +pub mod cmm_render_intent { + use crate::{ + ifs::color_management::{ + ABSOLUTE_NO_ADAPTATION_SINCE, RENDER_INTENT_ABSOLUTE_NO_ADAPTATION, + RENDER_INTENT_PERCEPTUAL, RENDER_INTENT_RELATIVE, RENDER_INTENT_RELATIVE_BPC, + }, + object::Version, + }; + + pub use jay_cmm::cmm_render_intent::*; + + pub fn from_wayland(intent: u32, version: Version) -> Option { + let res = match intent { + RENDER_INTENT_PERCEPTUAL => RenderIntent::Perceptual, + RENDER_INTENT_RELATIVE => RenderIntent::Relative, + RENDER_INTENT_RELATIVE_BPC => RenderIntent::RelativeBpc, + RENDER_INTENT_ABSOLUTE_NO_ADAPTATION if version >= ABSOLUTE_NO_ADAPTATION_SINCE => { + RenderIntent::AbsoluteNoAdaptation + } + _ => return None, + }; + Some(res) + } +} + +pub mod cmm_transform { + pub use jay_cmm::cmm_transform::*; +} diff --git a/src/compositor.rs b/src/compositor.rs index 45d2a018..b2979886 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -1,9 +1,20 @@ +use jay_wheel::{Wheel, WheelError}; +use std::time::Duration; +use jay_sighand::{self as sighand, SighandError}; +use jay_pr_caps::{PrCapsThread, pr_caps}; +use jay_logger::Logger; +use jay_io_uring::{IoUring, IoUringError}; +use jay_eventfd_cache::EventfdCache; +use jay_cpu_worker::{CpuWorker, CpuWorkerError}; +use jay_async_engine::{AsyncEngine, Phase, SpawnedFuture}; +use jay_keyboard::KbvmContext; #[cfg(feature = "it")] use crate::it::test_backend::TestBackend; +use jay_units::scale::Scale; use { crate::{ acceptor::{Acceptor, AcceptorError}, - async_engine::{AsyncEngine, Phase, SpawnedFuture}, + backend::{Backend, Connector}, backends::{ dummy::{DummyBackend, DummyOutput}, @@ -15,9 +26,8 @@ use { cmm::{cmm_manager::ColorManager, cmm_primaries::Primaries}, config::ConfigProxy, copy_device::CopyDeviceRegistry, - cpu_worker::{CpuWorker, CpuWorkerError}, + criteria::{ - CritMatcherIds, clm::{ClMatcherManager, handle_cl_changes, handle_cl_leaf_events}, tlm::{ TlMatcherManager, handle_tl_changes, handle_tl_just_mapped, handle_tl_leaf_events, @@ -26,7 +36,7 @@ use { damage::{DamageVisualizer, visualize_damage}, dbus::Dbus, ei::ei_client::EiClients, - eventfd_cache::EventfdCache, + forker, format::XRGB8888, gfx_api::GfxApi, @@ -45,18 +55,16 @@ use { wlr_output_manager::wlr_output_manager_done, workspace_manager::workspace_manager_done, }, - io_uring::{IoUring, IoUringError}, - kbvm::KbvmContext, + leaks, - logger::Logger, - output_schedule::OutputSchedule, + + output_schedule::create_output_schedule, portal::{self, PortalStartup}, - pr_caps::{PrCapsThread, pr_caps}, - scale::Scale, - sighand::{self, SighandError}, + + state::{ConnectorData, IdleState, ScreenlockState, State, XWaylandState}, tasks::{self, handle_const_40hz_latch, idle}, - tracy::enable_profiler, + jay_tracy::enable_profiler, tree::{ DisplayNode, NodeIds, OutputNode, TearingMode, Transform, VrrMode, WorkspaceDisplayOrder, WorkspaceNode, container_layout, container_render_positions, @@ -78,19 +86,16 @@ use { rc_eq::RcEq, refcounted::RefCounted, run_toplevel::RunToplevel, - static_text::StaticText, tri::Try, }, version::VERSION, video::drm::wait_for_syncobj::WaitForSyncobj, - wheel::{Wheel, WheelError}, + }, ahash::AHashSet, - clap::ValueEnum, forker::ForkerProxy, - jay_config::{_private::DEFAULT_SEAT_NAME, logging::LogLevel as ConfigLogLevel}, - linearize::Linearize, - log::LevelFilter, + jay_config::protocol::DEFAULT_SEAT_NAME, + jay_criteria::CritMatcherIds, std::{ cell::{Cell, RefCell}, env, @@ -98,7 +103,7 @@ use { ops::Deref, rc::Rc, sync::Arc, - time::Duration, + }, thiserror::Error, uapi::c, @@ -240,6 +245,7 @@ fn start_compositor2( render_ctx_ever_initialized: Cell::new(false), cursors: Default::default(), wheel, + clients: Clients::new(), globals: Globals::new(), connector_ids: Default::default(), @@ -279,11 +285,14 @@ fn start_compositor2( change: Default::default(), timeout: Cell::new(Duration::from_secs(10 * 60)), grace_period: Cell::new(Duration::from_secs(5)), + key_press_enables_dpms: Cell::new(false), + mouse_move_enables_dpms: Cell::new(false), timeout_changed: Default::default(), inhibitors: Default::default(), inhibitors_changed: Default::default(), inhibited_idle_notifications: Default::default(), backend_idle: Cell::new(true), + dpms_off_by_command: Cell::new(false), in_grace_period: Cell::new(false), }, run_args, @@ -301,11 +310,11 @@ fn start_compositor2( display: Default::default(), }, acceptor: Default::default(), + tagged_acceptors: Default::default(), serial: Default::default(), idle_inhibitor_ids: Default::default(), run_toplevel, config_dir: explicit_config_dir.or_else(config_dir), - config_file_id: NumCell::new(1), tracker: Default::default(), data_offer_ids: Default::default(), data_source_ids: Default::default(), @@ -338,8 +347,6 @@ fn start_compositor2( explicit_sync_supported: Default::default(), keyboard_state_ids: Default::default(), physical_keyboard_ids: Default::default(), - security_context_acceptors: Default::default(), - tagged_acceptors: Default::default(), cursor_user_group_ids: Default::default(), cursor_user_ids: Default::default(), cursor_user_groups: Default::default(), @@ -358,8 +365,16 @@ fn start_compositor2( ei_clients: EiClients::new(), slow_ei_clients: Default::default(), cpu_worker, + ui_drag_enabled: Cell::new(true), ui_drag_threshold_squared: Cell::new(10), + animations: Default::default(), + layout_animations_requested: Default::default(), + layout_animations_active: Default::default(), + layout_animation_curve_override: Default::default(), + layout_animation_style_override: Default::default(), + layout_animation_batch: Default::default(), + suppress_animations_for_next_layout: Default::default(), toplevels: Default::default(), const_40hz_latch: Default::default(), tray_item_ids: Default::default(), @@ -391,11 +406,13 @@ fn start_compositor2( copy_device_registry: Rc::new(CopyDeviceRegistry::new(&ring, &engine, &eventfd_cache)), supports_presentation_feedback: Default::default(), eventfd_cache, + lazy_event_sources: Default::default(), post_layout_event_sources: Default::default(), bo_drop_queue: Rc::new(ObjectDropQueue::new(&ring)), virtual_outputs: Default::default(), clean_logs_older_than: Default::default(), + scratchpads: Default::default(), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); @@ -477,14 +494,7 @@ fn load_config( if for_test { return ConfigProxy::for_test(state); } - match ConfigProxy::from_config_dir(state) { - Ok(c) => c, - Err(e) => { - log::warn!("Could not load config.so: {}", ErrorFmt(e)); - log::warn!("Using default config"); - ConfigProxy::default(state) - } - } + ConfigProxy::default(state) } fn start_global_event_handlers(state: &Rc) -> Vec> { @@ -536,16 +546,6 @@ fn start_global_event_handlers(state: &Rc) -> Vec> { Phase::PostLayout, input_popup_positioning(state.clone()), ), - eng.spawn2( - "toplevel screencast present", - Phase::Present, - perform_toplevel_screencasts(state.clone()), - ), - eng.spawn2( - "screencast realloc", - Phase::PostLayout, - perform_screencast_realloc(state.clone()), - ), eng.spawn2( "visualize damage", Phase::PostLayout, @@ -593,6 +593,16 @@ fn start_global_event_handlers(state: &Rc) -> Vec> { Phase::PostLayout, handle_xdg_surface_configure_events(state.clone()), ), + eng.spawn2( + "toplevel screencast present", + Phase::Present, + perform_toplevel_screencasts(state.clone()), + ), + eng.spawn2( + "screencast realloc", + Phase::PostLayout, + perform_screencast_realloc(state.clone()), + ), eng.spawn( "lazy event sources", handle_lazy_event_sources(state.clone()), @@ -734,7 +744,7 @@ fn create_dummy_output(state: &Rc) { head_managers: HeadManagers::new(head_name, head_state), wlr_output_heads: Default::default(), }); - let schedule = Rc::new(OutputSchedule::new( + let schedule = Rc::new(create_output_schedule( state, &connector_data, &persistent_state, @@ -781,8 +791,8 @@ fn create_dummy_output(state: &Rc) { lock_surface: Default::default(), hardware_cursor: Default::default(), update_render_data_scheduled: Cell::new(false), - screencasts: Default::default(), hardware_cursor_needs_render: Cell::new(false), + screencasts: Default::default(), screencopies: Default::default(), title_visible: Cell::new(false), schedule, @@ -822,65 +832,3 @@ pub fn config_dir() -> Option { None } } - -#[derive(ValueEnum, Debug, Copy, Clone, Hash, Default, Eq, PartialEq, Linearize)] -pub enum LogLevel { - Trace, - Debug, - #[default] - Info, - Warn, - Error, - Off, -} - -impl Into for LogLevel { - fn into(self) -> LevelFilter { - match self { - LogLevel::Trace => LevelFilter::Trace, - LogLevel::Debug => LevelFilter::Debug, - LogLevel::Info => LevelFilter::Info, - LogLevel::Warn => LevelFilter::Warn, - LogLevel::Error => LevelFilter::Error, - LogLevel::Off => LevelFilter::Off, - } - } -} - -impl From for LogLevel { - fn from(value: LevelFilter) -> Self { - match value { - LevelFilter::Trace => LogLevel::Trace, - LevelFilter::Debug => LogLevel::Debug, - LevelFilter::Info => LogLevel::Info, - LevelFilter::Warn => LogLevel::Warn, - LevelFilter::Error => LogLevel::Error, - LevelFilter::Off => LogLevel::Off, - } - } -} - -impl StaticText for LogLevel { - fn text(&self) -> &'static str { - match self { - LogLevel::Off => "Off", - LogLevel::Error => "Error", - LogLevel::Warn => "Warn", - LogLevel::Info => "Info", - LogLevel::Debug => "Debug", - LogLevel::Trace => "Trace", - } - } -} - -impl From for LogLevel { - fn from(value: ConfigLogLevel) -> Self { - match value { - ConfigLogLevel::Trace => LogLevel::Trace, - ConfigLogLevel::Debug => LogLevel::Debug, - ConfigLogLevel::Info => LogLevel::Info, - ConfigLogLevel::Warn => LogLevel::Warn, - ConfigLogLevel::Error => LogLevel::Error, - } - } -} diff --git a/src/config.rs b/src/config.rs index 64ff595f..6994a92d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,52 +5,29 @@ use crate::it::test_config::TEST_CONFIG_ENTRY; use { crate::{ backend::{ConnectorId, DrmDeviceId, InputDeviceId}, - client::{Client, ClientCaps}, config::handler::ConfigProxyHandler, ifs::wl_seat::SeatId, state::State, tree::{TileState, ToplevelData, ToplevelIdentifier}, utils::{ clonecell::CloneCell, - nice::{JAY_NO_REALTIME, dont_allow_config_so}, numcell::NumCell, ptr_ext::PtrExt, - unlink_on_drop::UnlinkOnDrop, - xrd::xrd, }, }, - bincode::Options, jay_config::{ - _private::{ - ConfigEntry, VERSION, bincode_ops, - ipc::{InitMessage, ServerFeature, ServerMessage, V1InitMessage}, + protocol::{ + ClientMessage, ConfigEntry, InitMessage, ServerFeature, ServerHandler, ServerMessage, + Unref, V1InitMessage, VERSION, handle_client_message, init_client, unref_client, }, input::{InputDevice, Seat, SwitchEvent}, keyboard::{mods::Modifiers, syms::KeySym}, video::{Connector, DrmDevice}, window::{self}, }, - libloading::Library, - std::{cell::Cell, io, mem, path::Path, ptr, rc::Rc}, - thiserror::Error, + std::{cell::Cell, mem, ptr, rc::Rc}, }; -#[derive(Debug, Error)] -pub enum ConfigError { - #[error("Could not load the config library")] - CouldNotLoadLibrary(#[source] libloading::Error), - #[error("Config library does not contain the entry symbol")] - LibraryDoesNotContainEntry(#[source] libloading::Error), - #[error("Could not determine the config directory")] - ConfigDirNotSet, - #[error("Could not copy the config file")] - CopyConfigFile(#[source] io::Error), - #[error("XDG_RUNTIME_DIR is not set")] - XrdNotSet, - #[error("Custom config.so is not permitted")] - NotPermitted, -} - pub struct ConfigProxy { handler: CloneCell>>, } @@ -181,16 +158,6 @@ impl ConfigProxy { self.handler.get()?.initial_tile_state(data) } - pub fn update_capabilities( - &self, - data: &Rc, - bounding_caps: ClientCaps, - set_bounding_caps: bool, - ) { - if let Some(handler) = self.handler.get() { - handler.update_capabilities(data, bounding_caps, set_bounding_caps); - } - } } impl Drop for ConfigProxy { @@ -199,41 +166,32 @@ impl Drop for ConfigProxy { } } -unsafe extern "C" fn default_client_init( +unsafe fn default_client_init( srv_data: *const u8, - srv_unref: unsafe extern "C" fn(data: *const u8), - srv_handler: unsafe extern "C" fn(data: *const u8, msg: *const u8, size: usize), - msg: *const u8, - size: usize, + srv_unref: Unref, + srv_handler: ServerHandler, + msg: InitMessage, ) -> *const u8 { - extern "C" fn configure() { + fn configure() { jay_toml_config::configure(); } unsafe { - jay_config::_private::client::init(srv_data, srv_unref, srv_handler, msg, size, configure) + init_client(srv_data, srv_unref, srv_handler, msg, configure) } } impl ConfigProxy { - fn new( - lib: Option, - entry: &ConfigEntry, - state: &Rc, - path: Option, - ) -> Self { + fn new(entry: &ConfigEntry, state: &Rc) -> Self { let version = entry.version.min(VERSION); let data = Rc::new(ConfigProxyHandler { - path, client_data: Cell::new(ptr::null()), dropped: Cell::new(false), - _lib: lib, _version: version, unref: entry.unref, handle_msg: entry.handle_msg, state: state.clone(), next_id: NumCell::new(1), keymaps: Default::default(), - bufs: Default::default(), workspace_ids: NumCell::new(1), workspaces_by_name: Default::default(), workspaces_by_id: Default::default(), @@ -249,8 +207,6 @@ impl ConfigProxy { client_matchers: Default::default(), client_matcher_cache: Default::default(), client_matcher_leafs: Default::default(), - client_matcher_capabilities: Default::default(), - client_matcher_bounding_capabilities: Default::default(), window_matcher_ids: NumCell::new(1), window_matchers: Default::default(), window_matcher_cache: Default::default(), @@ -259,16 +215,13 @@ impl ConfigProxy { window_matcher_no_auto_focus: Default::default(), window_matcher_initial_tile_state: Default::default(), }); - let init_msg = bincode_ops() - .serialize(&InitMessage::V1(V1InitMessage {})) - .unwrap(); + let init_msg = InitMessage::V1(V1InitMessage {}); unsafe { let client_data = (entry.init)( Rc::into_raw(data.clone()) as _, unref, handle_msg, - init_msg.as_ptr(), - init_msg.len(), + init_msg, ); data.client_data.set(client_data); } @@ -288,96 +241,32 @@ impl ConfigProxy { let entry = ConfigEntry { version: VERSION, init: default_client_init, - unref: jay_config::_private::client::unref, - handle_msg: jay_config::_private::client::handle_msg, + unref: unref_client, + handle_msg: handle_client_message, }; - Self::new(None, &entry, state, None) + Self::new(&entry, state) } #[cfg(feature = "it")] pub fn for_test(state: &Rc) -> Self { - Self::new(None, &TEST_CONFIG_ENTRY, state, None) - } - - pub fn from_config_dir(state: &Rc) -> Result { - if dont_allow_config_so() { - if have_config_so(state.config_dir.as_deref()) { - log::warn!("Not loading config.so because"); - log::warn!(" 1. Jay was started with CAP_SYS_NICE"); - log::warn!(" 2. Jay was not started with {}=1", JAY_NO_REALTIME); - log::warn!(" 3. The scheduler was elevated to SCHED_RR"); - log::warn!( - " 4. Jay was not compiled with {}=1", - jay_allow_realtime_config_so!(), - ); - } - return Err(ConfigError::NotPermitted); - } - let dir = match state.config_dir.as_deref() { - Some(d) => d, - _ => return Err(ConfigError::ConfigDirNotSet), - }; - let file = format!("{}/{CONFIG_SO}", dir); - unsafe { Self::from_file(&file, state) } - } - - pub unsafe fn from_file(path: &str, state: &Rc) -> Result { - // Here we have to do a bit of a dance to support reloading. glibc will - // never load a library twice unless it has been unloaded in between. - // glibc identifies libraries by their file path and by their inode - // number. If either of those match, glibc considers the libraries - // identical. If the inode has not changed then this is not a problem - // for us since we don't want glibc to do any unnecessary work. - // However, if the user has created a new config with a new inode, then - // glibc will still not reload the library if we try to load it from - // the canonical location ~/.config/jay/config.so since it already has - // a library with that path loaded. To work around this, create a - // temporary copy with an incrementing number and load the library - // from there. - let xrd = match xrd() { - Some(x) => x, - _ => return Err(ConfigError::XrdNotSet), - }; - let copy = format!( - "{}/.jay_config.so.{}.{}", - xrd, - uapi::getpid(), - state.config_file_id.fetch_add(1) - ); - let _ = uapi::unlink(copy.as_str()); - if let Err(e) = std::fs::copy(path, ©) { - return Err(ConfigError::CopyConfigFile(e)); - } - let unlink = UnlinkOnDrop(©); - let lib = match unsafe { Library::new(©) } { - Ok(l) => l, - Err(e) => return Err(ConfigError::CouldNotLoadLibrary(e)), - }; - let entry = unsafe { lib.get::<&'static ConfigEntry>(b"JAY_CONFIG_ENTRY_V1\0") }; - let entry = match entry { - Ok(e) => *e, - Err(e) => return Err(ConfigError::LibraryDoesNotContainEntry(e)), - }; - mem::forget(unlink); - Ok(Self::new(Some(lib), entry, state, Some(copy))) + Self::new(&TEST_CONFIG_ENTRY, state) } } -unsafe extern "C" fn unref(data: *const u8) { +unsafe fn unref(data: *const u8) { let server = data as *const ConfigProxyHandler; unsafe { drop(Rc::from_raw(server)); } } -unsafe extern "C" fn handle_msg(data: *const u8, msg: *const u8, size: usize) { +unsafe fn handle_msg(data: *const u8, msg: &ClientMessage<'_>) { unsafe { let server = (data as *const ConfigProxyHandler).deref(); if server.dropped.get() { return; } let rc = Rc::from_raw(server); - let msg = std::slice::from_raw_parts(msg, size); rc.handle_request(msg); mem::forget(rc); } @@ -388,15 +277,3 @@ pub struct InvokedShortcut { pub effective_mods: Modifiers, pub sym: KeySym, } - -const CONFIG_SO: &str = "config.so"; - -pub fn have_config_so(config_dir: Option<&str>) -> bool { - let Some(dir) = config_dir else { - return false; - }; - let mut dir = dir.to_owned(); - dir.push_str("/"); - dir.push_str(CONFIG_SO); - Path::new(&dir).exists() -} diff --git a/src/config/handler.rs b/src/config/handler.rs index 526c1cde..3ce8faaf 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -1,16 +1,22 @@ +use std::time::{Duration, SystemTime}; +use jay_theme::{ThemeColor, ThemeSized}; +use jay_config::theme::{BarPosition, colors::Colorable, sized::Resizable}; +use jay_io_uring::TaskResultExt; +use jay_async_engine::SpawnedFuture; +use jay_keyboard::{KbvmError, KbvmMap}; +use jay_units::scale::Scale; use { crate::{ - async_engine::SpawnedFuture, + backend::{ self, BackendColorSpace, BackendEotfs, ConnectorId, DrmDeviceId, InputDeviceAccelProfile, InputDeviceCapability, InputDeviceClickMethod, InputDeviceId, transaction::BackendConnectorTransactionError, }, - client::{CAP_JAY_COMPOSITOR, Client, ClientCaps, ClientId}, + client::{Client, ClientId}, cmm::cmm_eotf::Eotf, compositor::{MAX_EXTENTS, WAYLAND_DISPLAY}, criteria::{ - CritLiteralOrRegex, CritMgrExt, CritTarget, CritUpstreamNode, clm::ClmLeafMatcher, tlm::{TlmLeafMatcher, TlmUpstreamNode}, }, @@ -20,13 +26,11 @@ use { wl_seat::{SeatId, WlSeatGlobal}, wp_content_type_v1::ContentTypeExt, }, - io_uring::TaskResultExt, - kbvm::{KbvmError, KbvmMap}, + output_schedule::map_cursor_hz, - scale::Scale, state::{ConnectorData, DeviceHandlerData, DrmDevData, OutputData, State}, tagged_acceptor::TaggedAcceptorError, - theme::{ThemeColor, ThemeSized}, + tree::{ ContainerSplit, OutputNode, TearingMode, TileState, ToplevelData, ToplevelIdentifier, ToplevelNode, VrrMode, WorkspaceNode, toplevel_create_split, toplevel_parent_container, @@ -37,20 +41,13 @@ use { copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, - oserror::{OsError, OsErrorExt}, - stack::Stack, + oserror::OsErrorExt, timer::{TimerError, TimerFd}, }, }, - bincode::Options, jay_config::{ - _private::{ - ClientCriterionIpc, ClientCriterionStringField, GenericCriterionIpc, PollableId, - WindowCriterionIpc, WindowCriterionStringField, WireMode, bincode_ops, - ipc::{ClientMessage, Response, ServerMessage, WorkspaceSource}, - }, Axis, Direction, Workspace, - client::{Client as ConfigClient, ClientCapabilities, ClientMatcher}, + client::{Client as ConfigClient, ClientMatcher}, input::{ FallbackOutputMode, FocusFollowsMouseMode, InputDevice, LayerDirection, Seat, Timeline, acceleration::{ACCEL_PROFILE_ADAPTIVE, ACCEL_PROFILE_FLAT, AccelProfile}, @@ -64,7 +61,12 @@ use { }, keyboard::{Group, Keymap, mods::Modifiers, syms::KeySym}, logging::LogLevel as ConfigLogLevel, - theme::{BarPosition, colors::Colorable, sized::Resizable}, + protocol::{ + ClientCriterionPayload, ClientCriterionStringField, ClientMessage, ConfigHandler, + GenericCriterionPayload, PollableId, Response, ServerMessage, Unref, + WindowCriterionPayload, WindowCriterionStringField, WireMode, WorkspaceSource, + }, + timer::Timer as JayTimer, video::{ BlendSpace as ConfigBlendSpace, ColorSpace, Connector, DrmDevice, Eotf as ConfigEotf, @@ -75,8 +77,8 @@ use { workspace::WorkspaceDisplayOrder, xwayland::XScalingMode, }, + jay_criteria::{CritLiteralOrRegex, CritMgrExt, CritTarget, CritUpstreamNode}, kbvm::Keycode, - libloading::Library, log::Level, regex::Regex, std::{ @@ -85,24 +87,34 @@ use { hash::Hash, ops::Deref, rc::{Rc, Weak}, - time::{Duration, SystemTime}, + }, thiserror::Error, uapi::{OwnedFd, c, fcntl_dupfd_cloexec}, }; +mod dispatch; +mod input_devices; +mod keymaps; +mod matchers; +mod options; +mod outputs; +mod resources; +mod runtime; +mod seats; +mod theme; +mod windows; +mod workspaces; + pub(super) struct ConfigProxyHandler { - pub path: Option, pub client_data: Cell<*const u8>, pub dropped: Cell, - pub _lib: Option, pub _version: u32, - pub unref: unsafe extern "C" fn(data: *const u8), - pub handle_msg: unsafe extern "C" fn(data: *const u8, msg: *const u8, size: usize), + pub unref: Unref, + pub handle_msg: ConfigHandler, pub state: Rc, pub next_id: NumCell, pub keymaps: CopyHashMap>, - pub bufs: Stack>, pub workspace_ids: NumCell, pub workspaces_by_name: CopyHashMap, u64>, @@ -121,36 +133,21 @@ pub(super) struct ConfigProxyHandler { pub client_matcher_ids: NumCell, pub client_matchers: - CopyHashMap>>>, - pub client_matcher_cache: CriterionCache>, + CopyHashMap>>, + pub client_matcher_cache: CriterionCache, pub client_matcher_leafs: CopyHashMap>, - pub client_matcher_capabilities: CopyHashMap< - ClientMatcher, - ( - Rc>>, - ClientCaps, - ), - >, - pub client_matcher_bounding_capabilities: CopyHashMap< - ClientMatcher, - ( - Rc>>, - ClientCaps, - ), - >, - pub window_matcher_ids: NumCell, pub window_matchers: - CopyHashMap>>, - pub window_matcher_cache: CriterionCache, + CopyHashMap>>, + pub window_matcher_cache: CriterionCache, pub window_matcher_leafs: CopyHashMap>, pub window_matcher_std_kinds: Rc, pub window_matcher_no_auto_focus: - CopyHashMap>>, + CopyHashMap>>, pub window_matcher_initial_tile_state: CopyHashMap< WindowMatcher, ( - Rc>, + Rc>, TileState, ), >, @@ -217,497 +214,18 @@ impl ConfigProxyHandler { self.window_matcher_leafs.clear(); self.window_matchers.clear(); - - if let Some(path) = &self.path - && let Err(e) = uapi::unlink(path.as_str()) - { - log::error!("Could not unlink {}: {}", path, ErrorFmt(OsError(e.0))); - } } pub fn send(&self, msg: &ServerMessage) { - let mut buf = self.bufs.pop().unwrap_or_default(); - buf.clear(); - bincode_ops().serialize_into(&mut buf, msg).unwrap(); unsafe { - (self.handle_msg)(self.client_data.get(), buf.as_ptr(), buf.len()); + (self.handle_msg)(self.client_data.get(), msg); } - self.bufs.push(buf); } pub fn respond(&self, msg: Response) { self.send(&ServerMessage::Response { response: msg }) } - fn id(&self) -> u64 { - self.next_id.fetch_add(1) - } - - fn get_workspace_by_name(&self, name: &String) -> Workspace { - let id = match self.workspaces_by_name.get(name) { - None => { - let id = self.workspace_ids.fetch_add(1); - let name = Rc::new(name.clone()); - self.workspaces_by_name.set(name.clone(), id); - self.workspaces_by_id.set(id, name); - id - } - Some(id) => id, - }; - Workspace(id) - } - - fn handle_log_request( - &self, - level: ConfigLogLevel, - msg: &str, - file: Option<&str>, - line: Option, - ) { - let level = match level { - ConfigLogLevel::Error => Level::Error, - ConfigLogLevel::Warn => Level::Warn, - ConfigLogLevel::Info => Level::Info, - ConfigLogLevel::Debug => Level::Debug, - ConfigLogLevel::Trace => Level::Trace, - }; - let debug = fmt::from_fn(|fmt| { - if let Some(file) = file { - write!(fmt, "{}", file)?; - if let Some(line) = line { - write!(fmt, ":{}", line)?; - } - write!(fmt, ": ")?; - } - write!(fmt, "{}", msg)?; - Ok(()) - }); - log::log!(level, "{:?}", debug); - } - - fn handle_get_seat(&self, name: &str) { - for seat in self.state.globals.seats.lock().values() { - if seat.seat_name() == name { - self.respond(Response::GetSeat { - seat: Seat(seat.id().raw() as _), - }); - return; - } - } - let seat = self.state.create_seat(name); - self.respond(Response::GetSeat { - seat: Seat(seat.id().raw() as _), - }); - } - - fn handle_parse_keymap(&self, keymap: &str) -> Result<(), CphError> { - let (keymap, res) = match self.state.kb_ctx.parse_keymap(keymap.as_bytes()) { - Ok(keymap) => { - let id = Keymap(self.id()); - self.keymaps.set(id, keymap); - (id, Ok(())) - } - Err(e) => (Keymap::INVALID, Err(CphError::ParseKeymapError(e))), - }; - self.respond(Response::ParseKeymap { keymap }); - res - } - - fn handle_keymap_from_names( - &self, - rules: Option<&str>, - model: Option<&str>, - groups: Option>>, - options: Option>, - ) -> Result<(), CphError> { - let kbvm_groups = groups.map(|groups| { - groups - .iter() - .map(|g| kbvm::xkb::rmlvo::Group { - layout: g.layout, - variant: g.variant, - }) - .collect::>() - }); - let (keymap, res) = match self.state.kb_ctx.keymap_from_names( - rules, - model, - kbvm_groups.as_deref(), - options.as_deref(), - ) { - Ok(keymap) => { - let id = Keymap(self.id()); - self.keymaps.set(id, keymap); - (id, Ok(())) - } - Err(e) => (Keymap::INVALID, Err(CphError::ParseKeymapError(e))), - }; - self.respond(Response::KeymapFromNames { keymap }); - res - } - - fn handle_get_connectors( - &self, - dev: Option, - connected_only: bool, - ) -> Result<(), CphError> { - let datas: Vec<_>; - if let Some(dev) = dev { - let dev = self.get_drm_device(dev)?; - datas = dev.connectors.lock().values().cloned().collect(); - } else { - datas = self.state.connectors.lock().values().cloned().collect(); - } - let connectors = datas - .iter() - .flat_map(|d| match (connected_only, d.connected.get()) { - (false, _) | (true, true) => Some(Connector(d.connector.id().raw() as _)), - _ => None, - }) - .collect(); - self.respond(Response::GetConnectors { connectors }); - Ok(()) - } - - fn handle_get_drm_device_syspath(&self, dev: DrmDevice) -> Result<(), CphError> { - let dev = self.get_drm_device(dev)?; - let syspath = dev.syspath.clone().unwrap_or_default(); - self.respond(Response::GetDrmDeviceSyspath { syspath }); - Ok(()) - } - - fn handle_get_drm_device_devnode(&self, dev: DrmDevice) -> Result<(), CphError> { - let dev = self.get_drm_device(dev)?; - let devnode = dev.devnode.clone().unwrap_or_default(); - self.respond(Response::GetDrmDeviceDevnode { devnode }); - Ok(()) - } - - fn handle_get_drm_device_vendor(&self, dev: DrmDevice) -> Result<(), CphError> { - let dev = self.get_drm_device(dev)?; - let vendor = dev.vendor.clone().unwrap_or_default(); - self.respond(Response::GetDrmDeviceVendor { vendor }); - Ok(()) - } - - fn handle_get_drm_devices(&self) { - let devs = self.state.drm_devs.lock(); - let mut res = vec![]; - for dev in devs.values() { - res.push(DrmDevice(dev.dev.id().raw() as _)); - } - self.respond(Response::GetDrmDevices { devices: res }); - } - - fn handle_make_render_device(&self, dev: DrmDevice) -> Result<(), CphError> { - let dev = self.get_drm_device(dev)?; - dev.make_render_device(); - Ok(()) - } - - fn handle_get_drm_device_model(&self, dev: DrmDevice) -> Result<(), CphError> { - let dev = self.get_drm_device(dev)?; - let model = dev.model.clone().unwrap_or_default(); - self.respond(Response::GetDrmDeviceModel { model }); - Ok(()) - } - - fn handle_get_drm_device_pci_id(&self, dev: DrmDevice) -> Result<(), CphError> { - let dev = self.get_drm_device(dev)?; - let pci_id = dev.pci_id.unwrap_or_default(); - self.respond(Response::GetDrmDevicePciId { pci_id }); - Ok(()) - } - - fn handle_reload(&self) { - self.state.reload_config(); - } - - fn handle_get_seat_fullscreen(&self, seat: Seat) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - self.respond(Response::GetFullscreen { - fullscreen: seat.get_fullscreen(), - }); - Ok(()) - } - - fn handle_set_seat_fullscreen(&self, seat: Seat, fullscreen: bool) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.set_fullscreen(fullscreen); - Ok(()) - } - - fn handle_get_window_fullscreen(&self, window: Window) -> Result<(), CphError> { - let tl = self.get_window(window)?; - self.respond(Response::GetWindowFullscreen { - fullscreen: tl.tl_data().is_fullscreen.get(), - }); - Ok(()) - } - - fn handle_set_window_fullscreen( - &self, - window: Window, - fullscreen: bool, - ) -> Result<(), CphError> { - let tl = self.get_window(window)?; - tl.tl_set_fullscreen(fullscreen, None); - Ok(()) - } - - fn handle_set_keymap(&self, seat: Seat, keymap: Keymap) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - let keymap = if keymap.is_invalid() { - self.state.default_keymap.clone() - } else { - self.get_keymap(keymap)? - }; - seat.set_seat_keymap(&keymap); - Ok(()) - } - - fn handle_set_device_keymap( - &self, - device: InputDevice, - keymap: Keymap, - ) -> Result<(), CphError> { - let dev = self.get_device_handler_data(device)?; - let map = if keymap.is_invalid() { - None - } else { - Some(self.get_keymap(keymap)?) - }; - dev.set_keymap(&self.state, map); - Ok(()) - } - - fn handle_set_forward(&self, seat: Seat, forward: bool) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.set_forward(forward); - Ok(()) - } - - fn handle_set_focus_follows_mouse_mode( - &self, - seat: Seat, - mode: FocusFollowsMouseMode, - ) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - let focus_follows_mouse = match mode { - FocusFollowsMouseMode::True => true, - FocusFollowsMouseMode::False => false, - }; - seat.set_focus_follows_mouse(focus_follows_mouse); - Ok(()) - } - - fn handle_set_fallback_output_mode( - &self, - seat: Seat, - mode: FallbackOutputMode, - ) -> Result<(), CphError> { - let Ok(mode) = mode.try_into() else { - return Err(CphError::UnknownFallbackOutputMode(mode)); - }; - let seat = self.get_seat(seat)?; - seat.set_fallback_output_mode(mode); - Ok(()) - } - - fn handle_set_window_management_enabled( - &self, - seat: Seat, - enabled: bool, - ) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.set_window_management_enabled(enabled); - Ok(()) - } - - fn handle_set_input_device_connector( - &self, - input_device: InputDevice, - connector: Connector, - ) -> Result<(), CphError> { - let dev = self.get_device_handler_data(input_device)?; - let output = self.get_output_node(connector)?; - dev.set_output(&self.state, Some(&output.global)); - Ok(()) - } - - fn handle_remove_input_mapping(&self, input_device: InputDevice) -> Result<(), CphError> { - let dev = self.get_device_handler_data(input_device)?; - dev.set_output(&self.state, None); - Ok(()) - } - - fn handle_set_status(&self, status: &str) { - self.state.set_status(status); - } - - fn get_timer(&self, timer: JayTimer) -> Result, CphError> { - match self.timers_by_id.get(&timer.0) { - Some(t) => Ok(t), - _ => Err(CphError::TimerDoesNotExist(timer)), - } - } - - fn handle_remove_timer(&self, timer: JayTimer) -> Result<(), CphError> { - let timer = self.get_timer(timer)?; - self.timers_by_id.remove(&timer.id); - self.timers_by_name.remove(&timer.name); - Ok(()) - } - - fn handle_set_env(&self, key: &str, val: &str) { - if let Some(f) = self.state.forker.get() { - f.setenv(key.as_bytes(), val.as_bytes()); - } - } - - fn handle_unset_env(&self, key: &str) { - if let Some(f) = self.state.forker.get() { - f.unsetenv(key.as_bytes()); - } - } - - fn handle_get_config_dir(&self) { - let dir = self.state.config_dir.clone().unwrap_or_default(); - self.respond(Response::GetConfigDir { dir }); - } - - fn handle_get_workspaces(&self) { - let mut workspaces = vec![]; - for ws in self.state.workspaces.lock().values() { - workspaces.push(self.get_workspace_by_name(&ws.name)); - } - self.respond(Response::GetWorkspaces { workspaces }); - } - - fn handle_program_timer( - &self, - timer: JayTimer, - initial: Option, - periodic: Option, - ) -> Result<(), CphError> { - let timer = self.get_timer(timer)?; - timer.timer.program(initial, periodic)?; - Ok(()) - } - - fn handle_get_timer(self: &Rc, name: &str) -> Result<(), CphError> { - let name = Rc::new(name.to_owned()); - if let Some(t) = self.timers_by_name.get(&name) { - self.respond(Response::GetTimer { - timer: JayTimer(t.id), - }); - return Ok(()); - } - let id = self.timer_ids.fetch_add(1); - let timer = TimerFd::new(c::CLOCK_BOOTTIME)?; - let handler = { - let timer = timer.clone(); - let slf = self.clone(); - self.state.eng.spawn("config timer", async move { - loop { - match timer.expired(&slf.state.ring).await { - Ok(_) => slf.send(&ServerMessage::TimerExpired { - timer: JayTimer(id), - }), - Err(e) => { - log::error!("Could not wait for timer expiration: {}", ErrorFmt(e)); - if let Some(timer) = slf.timers_by_id.remove(&id) { - slf.timers_by_name.remove(&timer.name); - } - return; - } - } - } - }) - }; - let td = Rc::new(TimerData { - timer, - id, - name: name.clone(), - _handler: handler, - }); - self.timers_by_name.set(name.clone(), td.clone()); - self.timers_by_id.set(id, td.clone()); - self.respond(Response::GetTimer { - timer: JayTimer(id), - }); - Ok(()) - } - - fn handle_seat_close(&self, seat: Seat) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.close(); - Ok(()) - } - - fn handle_window_close(&self, window: Window) -> Result<(), CphError> { - let window = self.get_window(window)?; - window.tl_close(); - Ok(()) - } - - fn handle_seat_focus(&self, seat: Seat, direction: Direction) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.move_focus(direction.into()); - Ok(()) - } - - fn handle_seat_move(&self, seat: Seat, direction: Direction) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.move_focused(direction.into()); - Ok(()) - } - - fn handle_window_move(&self, window: Window, direction: Direction) -> Result<(), CphError> { - let window = self.get_window(window)?; - if let Some(c) = toplevel_parent_container(&*window) { - c.move_child(window, direction.into()); - } - Ok(()) - } - - fn handle_get_repeat_rate(&self, seat: Seat) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - let (rate, delay) = seat.get_rate(); - self.respond(Response::GetRepeatRate { rate, delay }); - Ok(()) - } - - fn handle_set_repeat_rate(&self, seat: Seat, rate: i32, delay: i32) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - if rate < 0 { - return Err(CphError::NegativeRepeatRate); - } - if delay < 0 { - return Err(CphError::NegativeRepeatDelay); - } - seat.set_rate(rate, delay); - Ok(()) - } - - fn get_workspace(&self, ws: Workspace) -> Result, CphError> { - match self.workspaces_by_id.get(&ws.0) { - Some(ws) => Ok(ws), - _ => Err(CphError::WorkspaceDoesNotExist(ws)), - } - } - - fn get_existing_workspace(&self, ws: Workspace) -> Result>, CphError> { - self.get_workspace(ws).map(|name| { - self.state - .workspaces - .lock() - .values() - .find(|ws| ws.name.as_str() == name.as_str()) - .cloned() - }) - } - fn get_device_handler_data( &self, device: InputDevice, @@ -779,2767 +297,6 @@ impl ConfigProxyHandler { } } - fn get_keymap(&self, keymap: Keymap) -> Result, CphError> { - match self.keymaps.get(&keymap) { - Some(k) => Ok(k), - None => Err(CphError::KeymapDoesNotExist(keymap)), - } - } - - fn handle_set_seat(&self, device: InputDevice, seat: Seat) -> Result<(), CphError> { - let seat = if seat.is_invalid() { - None - } else { - Some(self.get_seat(seat)?) - }; - let dev = self.get_device_handler_data(device)?; - dev.set_seat(&self.state, seat); - Ok(()) - } - - fn handle_set_left_handed( - &self, - device: InputDevice, - left_handed: bool, - ) -> Result<(), CphError> { - let dev = self.get_device_handler_data(device)?; - dev.set_left_handed(&self.state, left_handed); - Ok(()) - } - - fn handle_set_accel_profile( - &self, - device: InputDevice, - accel_profile: AccelProfile, - ) -> Result<(), CphError> { - let dev = self.get_device_handler_data(device)?; - let profile = match accel_profile { - ACCEL_PROFILE_FLAT => InputDeviceAccelProfile::Flat, - ACCEL_PROFILE_ADAPTIVE => InputDeviceAccelProfile::Adaptive, - _ => return Err(CphError::UnknownAccelProfile(accel_profile)), - }; - dev.set_accel_profile(&self.state, profile); - Ok(()) - } - - fn handle_set_accel_speed(&self, device: InputDevice, speed: f64) -> Result<(), CphError> { - let dev = self.get_device_handler_data(device)?; - dev.set_accel_speed(&self.state, speed); - Ok(()) - } - - fn handle_set_px_per_wheel_scroll(&self, device: InputDevice, px: f64) -> Result<(), CphError> { - let dev = self.get_device_handler_data(device)?; - dev.set_px_per_scroll_wheel(&self.state, px); - Ok(()) - } - - fn handle_set_tap_enabled(&self, device: InputDevice, enabled: bool) -> Result<(), CphError> { - let dev = self.get_device_handler_data(device)?; - dev.set_tap_enabled(&self.state, enabled); - Ok(()) - } - - fn handle_set_drag_enabled(&self, device: InputDevice, enabled: bool) -> Result<(), CphError> { - let dev = self.get_device_handler_data(device)?; - dev.set_drag_enabled(&self.state, enabled); - Ok(()) - } - - fn handle_set_natural_scrolling_enabled( - &self, - device: InputDevice, - enabled: bool, - ) -> Result<(), CphError> { - let dev = self.get_device_handler_data(device)?; - dev.set_natural_scrolling_enabled(&self.state, enabled); - Ok(()) - } - - fn handle_set_drag_lock_enabled( - &self, - device: InputDevice, - enabled: bool, - ) -> Result<(), CphError> { - let dev = self.get_device_handler_data(device)?; - dev.set_drag_lock_enabled(&self.state, enabled); - Ok(()) - } - - fn handle_set_transform_matrix( - &self, - device: InputDevice, - matrix: [[f64; 2]; 2], - ) -> Result<(), CphError> { - let dev = self.get_device_handler_data(device)?; - dev.set_transform_matrix(&self.state, matrix); - Ok(()) - } - - fn handle_set_calibration_matrix( - &self, - device: InputDevice, - matrix: [[f32; 3]; 2], - ) -> Result<(), CphError> { - let dev = self.get_device_handler_data(device)?; - dev.set_calibration_matrix(&self.state, matrix); - Ok(()) - } - - fn handle_set_click_method( - &self, - device: InputDevice, - click_method: ClickMethod, - ) -> Result<(), CphError> { - let dev = self.get_device_handler_data(device)?; - let method = match click_method { - CLICK_METHOD_NONE => InputDeviceClickMethod::None, - CLICK_METHOD_BUTTON_AREAS => InputDeviceClickMethod::ButtonAreas, - CLICK_METHOD_CLICKFINGER => InputDeviceClickMethod::Clickfinger, - _ => return Err(CphError::UnknownClickMethod(click_method)), - }; - dev.set_click_method(&self.state, method); - Ok(()) - } - - fn handle_set_middle_button_emulation_enabled( - &self, - device: InputDevice, - enabled: bool, - ) -> Result<(), CphError> { - let dev = self.get_device_handler_data(device)?; - dev.set_middle_button_emulation_enabled(&self.state, enabled); - Ok(()) - } - - fn handle_set_ei_socket_enabled(&self, enabled: bool) { - self.state.set_ei_socket_enabled(enabled); - } - - fn handle_get_workspace(&self, name: &str) { - self.respond(Response::GetWorkspace { - workspace: self.get_workspace_by_name(&name.to_owned()), - }); - } - - fn handle_get_workspace_capture(&self, workspace: Workspace) -> Result<(), CphError> { - let ws = self.get_existing_workspace(workspace)?; - let capture = match ws { - Some(ws) => ws.may_capture.get(), - None => self.state.default_workspace_capture.get(), - }; - self.respond(Response::GetWorkspaceCapture { capture }); - Ok(()) - } - - fn handle_set_workspace_capture( - &self, - workspace: Workspace, - capture: bool, - ) -> Result<(), CphError> { - if let Some(ws) = self.get_existing_workspace(workspace)? { - ws.may_capture.set(capture); - ws.update_has_captures(); - } - Ok(()) - } - - fn handle_set_gfx_api(&self, device: Option, api: GfxApi) -> Result<(), CphError> { - let Ok(api) = api.try_into() else { - return Err(CphError::UnknownGfxApi(api)); - }; - match device { - Some(dev) => self.get_drm_device(dev)?.dev.set_gfx_api(api), - _ => self.state.default_gfx_api.set(api), - } - Ok(()) - } - - fn handle_set_flip_margin(&self, device: DrmDevice, margin: Duration) -> Result<(), CphError> { - self.get_drm_device(device)?.set_flip_margin( - &self.state, - margin.as_nanos().try_into().unwrap_or(u64::MAX), - ); - Ok(()) - } - - fn handle_set_x_scaling_mode(&self, mode: XScalingMode) -> Result<(), CphError> { - let use_wire_scale = match mode { - XScalingMode::DEFAULT => false, - XScalingMode::DOWNSCALED => true, - _ => return Err(CphError::UnknownXScalingMode(mode)), - }; - self.state.set_xwayland_use_wire_scale(use_wire_scale); - Ok(()) - } - - fn handle_set_x_wayland_enabled(&self, enabled: bool) -> Result<(), CphError> { - self.state.set_xwayland_enabled(enabled); - Ok(()) - } - - fn handle_set_ui_drag_enabled(&self, enabled: bool) { - self.state.set_ui_drag_enabled(enabled); - } - - fn handle_set_ui_drag_threshold(&self, threshold: i32) { - self.state.set_ui_drag_threshold(threshold.max(1)); - } - - fn handle_set_direct_scanout_enabled( - &self, - device: Option, - enabled: bool, - ) -> Result<(), CphError> { - match device { - Some(dev) => self - .get_drm_device(dev)? - .set_direct_scanout_enabled(&self.state, enabled), - _ => self.state.direct_scanout_enabled.set(enabled), - } - Ok(()) - } - - fn handle_get_default_workspace_capture(&self) { - self.respond(Response::GetDefaultWorkspaceCapture { - capture: self.state.default_workspace_capture.get(), - }); - } - - fn handle_set_default_workspace_capture(&self, capture: bool) { - self.state.default_workspace_capture.set(capture); - } - - fn handle_set_double_click_interval_usec(&self, usec: u64) { - self.state.double_click_interval_usec.set(usec); - } - - fn handle_set_double_click_distance(&self, dist: i32) { - self.state.double_click_distance.set(dist); - } - - fn handle_get_seat_cursor_workspace(&self, seat: Seat) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - let output = seat.get_cursor_output(); - let mut workspace = Workspace(0); - if !output.is_dummy - && let Some(ws) = output.workspace.get() - { - workspace = self.get_workspace_by_name(&ws.name); - } - self.respond(Response::GetSeatCursorWorkspace { workspace }); - Ok(()) - } - - fn handle_get_seat_keyboard_workspace(&self, seat: Seat) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - let mut workspace = Workspace(0); - if let Some(output) = seat.get_keyboard_output() - && !output.is_dummy - && let Some(ws) = output.workspace.get() - { - workspace = self.get_workspace_by_name(&ws.name); - } - self.respond(Response::GetSeatKeyboardWorkspace { workspace }); - Ok(()) - } - - fn handle_show_workspace( - &self, - seat: Seat, - ws: Workspace, - output: Option, - ) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - let name = self.get_workspace(ws)?; - let output = output.map(|o| self.get_output_node(o)).transpose()?; - self.state.show_workspace(&seat, &name, output); - Ok(()) - } - - fn handle_set_seat_workspace(&self, seat: Seat, ws: Workspace) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - let name = self.get_workspace(ws)?; - let output = seat.get_fallback_output(); - let workspace = match output.find_workspace(name.deref()) { - Some(ws) => ws, - _ => output.create_workspace(name.deref()), - }; - seat.set_workspace(&workspace); - Ok(()) - } - - fn handle_set_window_workspace(&self, window: Window, ws: Workspace) -> Result<(), CphError> { - let window = self.get_window(window)?; - let name = self.get_workspace(ws)?; - let Some(output) = window.node_output() else { - return Ok(()); - }; - let workspace = match output.find_workspace(name.deref()) { - Some(ws) => ws, - _ => output.create_workspace(name.deref()), - }; - toplevel_set_workspace(&self.state, window, &workspace); - Ok(()) - } - - fn handle_get_device_name(&self, device: InputDevice) -> Result<(), CphError> { - let dev = self.get_device_handler_data(device)?; - let name = dev.device.name(); - self.respond(Response::GetDeviceName { - name: name.to_string(), - }); - Ok(()) - } - - fn handle_get_input_device_syspath(&self, device: InputDevice) -> Result<(), CphError> { - let dev = self.get_device_handler_data(device)?; - self.respond(Response::GetInputDeviceSyspath { - syspath: dev.syspath.clone().unwrap_or_default(), - }); - Ok(()) - } - - fn handle_get_input_device_devnode(&self, device: InputDevice) -> Result<(), CphError> { - let dev = self.get_device_handler_data(device)?; - self.respond(Response::GetInputDeviceDevnode { - devnode: dev.devnode.clone().unwrap_or_default(), - }); - Ok(()) - } - - fn handle_move_to_output( - &self, - workspace: WorkspaceSource, - connector: Connector, - ) -> Result<(), CphError> { - let output = self.get_output_node(connector)?; - let ws = match workspace { - WorkspaceSource::Explicit(ws) => match self.get_existing_workspace(ws)? { - Some(ws) => ws, - _ => return Ok(()), - }, - WorkspaceSource::Seat(s) => { - match self.get_seat(s)?.get_fallback_output().workspace.get() { - Some(ws) => ws, - _ => return Ok(()), - } - } - }; - self.state.move_ws_to_output(&ws, &output); - Ok(()) - } - - fn handle_set_idle(&self, timeout: Duration) { - self.state.idle.set_timeout(&self.state, timeout); - } - - fn handle_set_idle_grace_period(&self, period: Duration) { - self.state.idle.set_grace_period(&self.state, period); - } - - fn handle_set_explicit_sync_enabled(&self, enabled: bool) { - self.state.set_explicit_sync_enabled(enabled); - } - - fn handle_set_color_management_enabled(&self, enabled: bool) { - self.state.set_color_management_enabled(enabled); - } - - fn handle_get_socket_path(&self) { - match self.state.acceptor.get() { - Some(a) => { - self.respond(Response::GetSocketPath { - path: a.socket_name().to_string(), - }); - } - _ => { - log::warn!("There is no acceptor"); - } - } - } - - fn handle_connector_connected(&self, connector: Connector) -> Result<(), CphError> { - let connector = self.get_connector(connector)?; - self.respond(Response::ConnectorConnected { - connected: connector.connected.get(), - }); - Ok(()) - } - - fn handle_connector_type(&self, connector: Connector) -> Result<(), CphError> { - let connector = self.get_connector(connector)?; - self.respond(Response::ConnectorType { - ty: connector.connector.kernel_id().ty.to_config(), - }); - Ok(()) - } - - fn handle_connector_mode(&self, connector: Connector) -> Result<(), CphError> { - let connector = self.get_output_node(connector)?; - let mode = connector.global.mode.get(); - self.respond(Response::ConnectorMode { - width: mode.width, - height: mode.height, - refresh_millihz: mode.refresh_rate_millihz, - }); - Ok(()) - } - - fn handle_connector_set_mode( - &self, - connector: Connector, - mode: WireMode, - ) -> Result<(), CphError> { - let connector = self.get_connector(connector)?; - connector - .modify_state(&self.state, |s| { - s.mode = backend::Mode { - width: mode.width, - height: mode.height, - refresh_rate_millihz: mode.refresh_millihz, - }; - }) - .map_err(CphError::ModifyConnectorState)?; - Ok(()) - } - - fn handle_connector_modes(&self, connector: Connector) -> Result<(), CphError> { - let connector = self.get_output_node(connector)?; - self.respond(Response::ConnectorModes { - modes: connector - .global - .modes - .iter() - .flatten() - .map(|m| WireMode { - width: m.width, - height: m.height, - refresh_millihz: m.refresh_rate_millihz, - }) - .collect(), - }); - Ok(()) - } - - fn handle_connector_supports_arbitrary_modes( - &self, - connector: Connector, - ) -> Result<(), CphError> { - let connector = self.get_output_node(connector)?; - self.respond(Response::ConnectorSupportsArbitraryModes { - supports_arbitrary_modes: connector.global.modes.is_none(), - }); - Ok(()) - } - - fn handle_connector_name(&self, connector: Connector) -> Result<(), CphError> { - let connector = self.get_connector(connector)?; - self.respond(Response::GetConnectorName { - name: connector.name.deref().clone(), - }); - Ok(()) - } - - fn handle_connector_model(&self, connector: Connector) -> Result<(), CphError> { - let connector = self.get_output(connector)?; - self.respond(Response::GetConnectorModel { - model: connector.monitor_info.output_id.model.clone(), - }); - Ok(()) - } - - fn handle_connector_manufacturer(&self, connector: Connector) -> Result<(), CphError> { - let connector = self.get_output(connector)?; - self.respond(Response::GetConnectorManufacturer { - manufacturer: connector.monitor_info.output_id.manufacturer.clone(), - }); - Ok(()) - } - - fn handle_connector_serial_number(&self, connector: Connector) -> Result<(), CphError> { - let connector = self.get_output(connector)?; - self.respond(Response::GetConnectorSerialNumber { - serial_number: connector.monitor_info.output_id.serial_number.clone(), - }); - Ok(()) - } - - fn handle_set_cursor_size(&self, seat: Seat, size: i32) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - if size < 0 { - return Err(CphError::NegativeCursorSize); - } - seat.cursor_group().set_cursor_size(size as _); - Ok(()) - } - - fn handle_disable_pointer_constraint(&self, seat: Seat) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.disable_pointer_constraint(); - Ok(()) - } - - fn handle_set_use_hardware_cursor( - &self, - seat: Seat, - use_hardware_cursor: bool, - ) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.cursor_group().set_hardware_cursor(use_hardware_cursor); - self.state.refresh_hardware_cursors(); - Ok(()) - } - - fn handle_connector_size(&self, connector: Connector) -> Result<(), CphError> { - let connector = self.get_output_node(connector)?; - let pos = connector.global.pos.get(); - self.respond(Response::ConnectorSize { - width: pos.width(), - height: pos.height(), - }); - Ok(()) - } - - fn handle_connector_get_scale(&self, connector: Connector) -> Result<(), CphError> { - let connector = self.get_output_node(connector)?; - self.respond(Response::ConnectorGetScale { - scale: connector.global.persistent.scale.get().to_f64(), - }); - Ok(()) - } - - fn handle_connector_set_scale(&self, connector: Connector, scale: f64) -> Result<(), CphError> { - if scale < 0.1 { - return Err(CphError::ScaleTooSmall(scale)); - } - if scale > 1000.0 { - return Err(CphError::ScaleTooLarge(scale)); - } - let scale = Scale::from_f64(scale); - let connector = self.get_output_node(connector)?; - connector.set_preferred_scale(scale); - Ok(()) - } - - fn handle_connector_set_format( - &self, - connector: Connector, - format: ConfigFormat, - ) -> Result<(), CphError> { - let Some(&format) = config_formats().get(&format) else { - return Err(CphError::UnknownFormat(format)); - }; - let connector = self.get_connector(connector)?; - connector - .modify_state(&self.state, |s| s.format = format) - .map_err(CphError::ModifyConnectorState)?; - Ok(()) - } - - fn handle_connector_set_colors( - &self, - connector: Connector, - color_space: ColorSpace, - eotf: ConfigEotf, - ) -> Result<(), CphError> { - let bcs = match color_space { - ColorSpace::DEFAULT => BackendColorSpace::Default, - ColorSpace::BT2020 => BackendColorSpace::Bt2020, - _ => return Err(CphError::UnknownColorSpace(color_space)), - }; - let btf = match eotf { - ConfigEotf::DEFAULT => BackendEotfs::Default, - ConfigEotf::PQ => BackendEotfs::Pq, - _ => return Err(CphError::UnknownEotf(eotf)), - }; - let connector = self.get_connector(connector)?; - connector - .modify_state(&self.state, |s| { - s.color_space = bcs; - s.eotf = btf; - }) - .map_err(CphError::ModifyConnectorState)?; - Ok(()) - } - - fn handle_connector_set_blend_space( - &self, - connector: Connector, - blend_space: ConfigBlendSpace, - ) -> Result<(), CphError> { - let blend_space = match blend_space { - ConfigBlendSpace::SRGB => BlendSpace::Srgb, - ConfigBlendSpace::LINEAR => BlendSpace::Linear, - _ => return Err(CphError::UnknownBlendSpace(blend_space)), - }; - let connector = self.get_output_node(connector)?; - connector.set_blend_space(blend_space); - Ok(()) - } - - fn handle_connector_set_brightness( - &self, - connector: Connector, - brightness: Option, - ) -> Result<(), CphError> { - let connector = self.get_output_node(connector)?; - connector.set_brightness(brightness); - Ok(()) - } - - fn handle_connector_set_use_native_gamut( - &self, - connector: Connector, - use_native_gamut: bool, - ) -> Result<(), CphError> { - let connector = self.get_output_node(connector)?; - connector.set_use_native_gamut(use_native_gamut); - Ok(()) - } - - fn handle_set_float_above_fullscreen(&self, above: bool) { - self.state.set_float_above_fullscreen(above); - } - - fn handle_get_float_above_fullscreen(&self) { - self.respond(Response::GetFloatAboveFullscreen { - above: self.state.float_above_fullscreen.get(), - }); - } - - fn handle_set_show_bar(&self, show: bool) { - self.state.set_show_bar(show); - } - - fn handle_get_show_bar(&self) { - self.respond(Response::GetShowBar { - show: self.state.show_bar.get(), - }); - } - - fn handle_set_show_titles(&self, _show: bool) { - // no-op: titles have been removed - } - - fn handle_get_show_titles(&self) { - self.respond(Response::GetShowTitles { show: false }); - } - - fn handle_set_floating_titles(&self, _floating: bool) { - // no-op: titles have been removed - } - - fn handle_get_floating_titles(&self) { - self.respond(Response::GetFloatingTitles { floating: false }); - } - - fn handle_set_bar_position(&self, position: BarPosition) -> Result<(), CphError> { - let Ok(position) = position.try_into() else { - return Err(CphError::UnknownBarPosition(position)); - }; - self.state.set_bar_position(position); - Ok(()) - } - - fn handle_get_bar_position(&self) { - self.respond(Response::GetBarPosition { - position: self.state.theme.bar_position.get().into(), - }); - } - - fn handle_set_corner_radius(&self, radius: f32) { - use crate::theme::CornerRadius; - let radius = radius.max(0.0).min(1000.0); - self.state - .theme - .corner_radius - .set(CornerRadius::from(radius)); - self.state.damage(self.state.root.extents.get()); - } - - fn handle_get_corner_radius(&self) { - self.respond(Response::GetCornerRadius { - radius: self.state.theme.corner_radius.get().top_left, - }); - } - - fn handle_set_show_float_pin_icon(&self, _show: bool) { - // no-op: titles have been removed, pin icon was in title bar - } - - fn handle_set_workspace_display_order(&self, order: WorkspaceDisplayOrder) { - self.state.set_workspace_display_order(order.into()); - } - - fn handle_get_seat_float_pinned(&self, seat: Seat) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - self.respond(Response::GetFloatPinned { - pinned: seat.pinned(), - }); - Ok(()) - } - - fn handle_set_seat_float_pinned(&self, seat: Seat, pinned: bool) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.set_pinned(pinned); - Ok(()) - } - - fn handle_get_window_float_pinned(&self, window: Window) -> Result<(), CphError> { - let window = self.get_window(window)?; - self.respond(Response::GetWindowFloatPinned { - pinned: window.tl_pinned(), - }); - Ok(()) - } - - fn handle_set_window_float_pinned(&self, window: Window, pinned: bool) -> Result<(), CphError> { - let window = self.get_window(window)?; - window.tl_set_pinned(true, pinned); - Ok(()) - } - - fn handle_set_vrr_mode( - &self, - connector: Option, - mode: ConfigVrrMode, - ) -> Result<(), CphError> { - let Some(mode) = VrrMode::from_config(mode) else { - return Err(CphError::UnknownVrrMode(mode)); - }; - match connector { - Some(c) => { - let connector = self.get_output_node(c)?; - connector.set_vrr_mode(mode); - } - _ => self.state.default_vrr_mode.set(*mode), - } - Ok(()) - } - - fn handle_set_vrr_cursor_hz( - &self, - connector: Option, - hz: f64, - ) -> Result<(), CphError> { - match connector { - Some(c) => { - let connector = self.get_output_node(c)?; - connector.schedule.set_cursor_hz(&self.state, hz); - } - _ => { - let Some((hz, _)) = map_cursor_hz(hz) else { - return Err(CphError::InvalidCursorHz(hz)); - }; - self.state.default_vrr_cursor_hz.set(hz) - } - } - Ok(()) - } - - fn handle_set_tearing_mode( - &self, - connector: Option, - mode: ConfigTearingMode, - ) -> Result<(), CphError> { - let Some(mode) = TearingMode::from_config(mode) else { - return Err(CphError::UnknownTearingMode(mode)); - }; - match connector { - Some(c) => { - let connector = self.get_output_node(c)?; - connector.set_tearing_mode(mode); - } - _ => self.state.default_tearing_mode.set(*mode), - } - Ok(()) - } - - fn handle_connector_set_transform( - &self, - connector: Connector, - transform: Transform, - ) -> Result<(), CphError> { - let connector = self.get_output_node(connector)?; - connector.update_transform(transform.into()); - Ok(()) - } - - fn handle_connector_set_position( - &self, - connector: Connector, - x: i32, - y: i32, - ) -> Result<(), CphError> { - let connector = self.get_output_node(connector)?; - if x < 0 || y < 0 || x > MAX_EXTENTS || y > MAX_EXTENTS { - return Err(CphError::InvalidConnectorPosition(x, y)); - } - connector.set_position(x, y); - Ok(()) - } - - fn handle_connector_get_position(&self, connector: Connector) -> Result<(), CphError> { - let connector = self.get_output_node(connector)?; - let (x, y) = connector.global.pos.get().position(); - self.respond(Response::ConnectorGetPosition { x, y }); - Ok(()) - } - - fn handle_connector_set_enabled( - &self, - connector: Connector, - enabled: bool, - ) -> Result<(), CphError> { - let connector = self.get_connector(connector)?; - connector - .modify_state(&self.state, |s| { - s.enabled = enabled; - }) - .map_err(CphError::ModifyConnectorState)?; - Ok(()) - } - - fn handle_get_connector( - &self, - ty: jay_config::video::connector_type::ConnectorType, - idx: u32, - ) -> Result<(), CphError> { - let connectors = self.state.connectors.lock(); - let connector = 'get_connector: { - for connector in connectors.values() { - let kid = connector.connector.kernel_id(); - if ty == kid.ty.to_config() && idx == kid.idx { - break 'get_connector Connector(connector.connector.id().raw() as _); - } - } - Connector(0) - }; - self.respond(Response::GetConnector { connector }); - Ok(()) - } - - fn handle_get_connector_by_name(&self, name: &str) { - let connector = self - .state - .connectors - .lock() - .values() - .find(|c| *c.name == name) - .map(|c| c.connector.id().raw() as _) - .map(Connector) - .unwrap_or(Connector(0)); - self.respond(Response::GetConnector { connector }); - } - - fn handle_create_virtual_output(&self, name: &str) { - self.state.virtual_outputs.get_or_create(&self.state, name); - } - - fn handle_remove_virtual_output(&self, name: &str) { - self.state.virtual_outputs.remove_output(&self.state, name); - } - - fn handle_get_connector_active_workspace(&self, connector: Connector) -> Result<(), CphError> { - let output = self.get_output_node(connector)?; - let workspace = output - .workspace - .get() - .map_or(Workspace(0), |ws| self.get_workspace_by_name(&ws.name)); - self.respond(Response::GetConnectorActiveWorkspace { workspace }); - Ok(()) - } - - fn handle_get_connector_workspaces(&self, connector: Connector) -> Result<(), CphError> { - let output = self.get_output_node(connector)?; - let workspaces = output - .workspaces - .iter() - .map(|ws| self.get_workspace_by_name(&ws.name)) - .collect::>(); - self.respond(Response::GetConnectorWorkspaces { workspaces }); - Ok(()) - } - - fn handle_get_workspace_connector(&self, workspace: Workspace) -> Result<(), CphError> { - let connector = self - .get_existing_workspace(workspace)? - .map(|ws| ws.output.get()) - .filter(|o| !o.is_dummy) - .map(|o| Connector(o.global.connector.id.raw() as _)) - .unwrap_or(Connector(0)); - self.respond(Response::GetWorkspaceConnector { connector }); - Ok(()) - } - - fn handle_get_connector_in_direction( - &self, - connector: Connector, - direction: Direction, - ) -> Result<(), CphError> { - let source_output = self.get_output_node(connector)?; - let connector = self - .state - .find_output_in_direction(&source_output, direction.into()) - .map(|o| Connector(o.global.connector.id.raw() as u64)) - .unwrap_or(Connector(0)); - self.respond(Response::GetConnectorInDirection { connector }); - Ok(()) - } - - fn handle_has_capability(&self, device: InputDevice, cap: Capability) -> Result<(), CphError> { - let dev = self.get_device_handler_data(device)?; - let mut is_unknown = false; - let has_cap = 'has_cap: { - let cap = match cap { - CAP_KEYBOARD => InputDeviceCapability::Keyboard, - CAP_POINTER => InputDeviceCapability::Pointer, - CAP_TOUCH => InputDeviceCapability::Touch, - CAP_TABLET_TOOL => InputDeviceCapability::TabletTool, - CAP_TABLET_PAD => InputDeviceCapability::TabletPad, - CAP_GESTURE => InputDeviceCapability::Gesture, - CAP_SWITCH => InputDeviceCapability::Switch, - _ => { - is_unknown = true; - break 'has_cap false; - } - }; - dev.device.has_capability(cap) - }; - self.respond(Response::HasCapability { has: has_cap }); - if is_unknown { - Err(CphError::UnknownCapability(cap)) - } else { - Ok(()) - } - } - - fn handle_get_seat_mono(&self, seat: Seat) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - self.respond(Response::GetMono { - mono: seat.get_mono().unwrap_or(false), - }); - Ok(()) - } - - fn handle_set_seat_mono(&self, seat: Seat, mono: bool) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.set_mono(mono); - Ok(()) - } - - fn handle_get_window_mono(&self, window: Window) -> Result<(), CphError> { - let window = self.get_window(window)?; - self.respond(Response::GetWindowMono { - mono: toplevel_parent_container(&*window) - .map(|c| c.mono_child.is_some()) - .unwrap_or(false), - }); - Ok(()) - } - - fn handle_set_window_mono(&self, window: Window, mono: bool) -> Result<(), CphError> { - let window = self.get_window(window)?; - if let Some(c) = toplevel_parent_container(&*window) { - c.set_mono(mono.then_some(window.as_ref())); - } - Ok(()) - } - - fn handle_get_seat_split(&self, seat: Seat) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - self.respond(Response::GetSplit { - axis: seat - .get_split() - .unwrap_or(ContainerSplit::Horizontal) - .into(), - }); - Ok(()) - } - - fn handle_set_seat_split(&self, seat: Seat, axis: Axis) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.set_split(axis.into()); - Ok(()) - } - - fn handle_seat_toggle_tab(&self, seat: Seat) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.toggle_tab(); - Ok(()) - } - - fn handle_seat_make_group( - &self, - seat: Seat, - axis: Axis, - ephemeral: bool, - ) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.make_group(axis.into(), ephemeral); - Ok(()) - } - - fn handle_seat_change_group_opposite(&self, seat: Seat) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.change_group_opposite(); - Ok(()) - } - - fn handle_seat_equalize(&self, seat: Seat, recursive: bool) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.equalize(recursive); - Ok(()) - } - - fn handle_seat_move_tab(&self, seat: Seat, right: bool) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.move_tab(right); - Ok(()) - } - - fn handle_get_window_split(&self, window: Window) -> Result<(), CphError> { - let window = self.get_window(window)?; - self.respond(Response::GetWindowSplit { - axis: toplevel_parent_container(&*window) - .map(|c| c.split.get()) - .unwrap_or(ContainerSplit::Horizontal) - .into(), - }); - Ok(()) - } - - fn handle_set_window_split(&self, window: Window, axis: Axis) -> Result<(), CphError> { - let window = self.get_window(window)?; - if let Some(c) = toplevel_parent_container(&*window) { - c.set_split(axis.into()); - } - Ok(()) - } - - fn handle_add_shortcut( - &self, - seat: Seat, - mod_mask: Modifiers, - mods: Modifiers, - sym: KeySym, - ) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.add_shortcut(mod_mask, mods, sym); - Ok(()) - } - - fn handle_remove_shortcut( - &self, - seat: Seat, - mods: Modifiers, - sym: KeySym, - ) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.remove_shortcut(mods, sym); - Ok(()) - } - - fn handle_get_input_devices(&self, seat: Option) { - let id = seat.map(|s| SeatId::from_raw(s.0 as _)); - let matches = |dhd: &DeviceHandlerData| { - let id = match id { - Some(id) => id, - _ => return true, - }; - if let Some(seat) = dhd.seat.get() { - return seat.id() == id; - } - false - }; - let mut res = vec![]; - { - let devs = self.state.input_device_handlers.borrow_mut(); - for dev in devs.values() { - if matches(&dev.data) { - res.push(InputDevice(dev.id.raw() as _)); - } - } - } - self.respond(Response::GetInputDevices { devices: res }); - } - - fn handle_get_seats(&self) { - let seats = { - let seats = self.state.globals.seats.lock(); - seats - .values() - .map(|seat| Seat::from_raw(seat.id().raw() as _)) - .collect() - }; - self.respond(Response::GetSeats { seats }); - } - - fn handle_run( - &self, - prog: &str, - args: Vec, - mut env: Vec<(String, String)>, - fds: Vec<(i32, i32)>, - tag: Option<&str>, - ) -> Result<(), CphError> { - if let Some(tag) = tag { - let display = self - .state - .tagged_acceptors - .get(&self.state, tag) - .map_err(CphError::CreateTaggedAcceptor)?; - env.push((WAYLAND_DISPLAY.to_string(), display.to_string())); - } - let fds: Vec<_> = fds - .into_iter() - .map(|(a, b)| (a, Rc::new(OwnedFd::new(b)))) - .collect(); - let forker = match self.state.forker.get() { - Some(f) => f, - _ => return Err(CphError::NoForker), - }; - let env = env.into_iter().map(|(k, v)| (k, Some(v))).collect(); - forker.spawn(prog.to_string(), args, env, fds); - Ok(()) - } - - fn handle_set_log_level(&self, level: ConfigLogLevel) { - self.state.set_log_level(level.into()); - } - - fn handle_clean_logs_older_than(&self, time: SystemTime) { - self.state.clean_logs_older_than.set(Some(time)); - } - - fn handle_grab(&self, kb: InputDevice, grab: bool) -> Result<(), CphError> { - let kb = self.get_kb(kb)?; - kb.grab(grab); - Ok(()) - } - - fn handle_create_seat_split(&self, seat: Seat, axis: Axis) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.create_split(axis.into()); - Ok(()) - } - - fn handle_create_window_split(&self, window: Window, axis: Axis) -> Result<(), CphError> { - let window = self.get_window(window)?; - toplevel_create_split(&self.state, window, axis.into()); - Ok(()) - } - - fn handle_focus_seat_parent(&self, seat: Seat) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.focus_parent(); - Ok(()) - } - - fn handle_quit(&self) { - log::info!("Quitting"); - self.state.ring.stop(); - } - - fn handle_switch_to(&self, vtnr: u32) { - self.state.backend.get().switch_to(vtnr); - } - - fn handle_get_seat_floating(&self, seat: Seat) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - self.respond(Response::GetFloating { - floating: seat.get_floating().unwrap_or(false), - }); - Ok(()) - } - - fn handle_set_seat_floating(&self, seat: Seat, floating: bool) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.set_floating(floating); - Ok(()) - } - - fn handle_get_window_floating(&self, window: Window) -> Result<(), CphError> { - let window = self.get_window(window)?; - self.respond(Response::GetWindowFloating { - floating: window.tl_data().parent_is_float.get(), - }); - Ok(()) - } - - fn handle_set_window_floating(&self, window: Window, floating: bool) -> Result<(), CphError> { - let window = self.get_window(window)?; - toplevel_set_floating(&self.state, window, floating); - Ok(()) - } - - fn handle_add_pollable(self: &Rc, fd: i32) -> Result<(), CphError> { - let fd = match fcntl_dupfd_cloexec(fd, 0).to_os_error() { - Ok(fd) => Rc::new(fd), - Err(e) => { - let err = format!("Could not invoke F_DUPFD_CLOEXEC: {}", ErrorFmt(e)); - log::error!("{}", err); - self.respond(Response::AddPollable { id: Err(err) }); - return Ok(()); - } - }; - let id = self.pollable_id.fetch_add(1); - let id = PollableId(id); - let create = |writable: bool, events: c::c_short| { - let event = Rc::new(AsyncEvent::default()); - let slf = self.clone(); - let trigger = event.clone(); - let fd = fd.clone(); - let future = self.state.eng.spawn("config fd poller", async move { - loop { - trigger.triggered().await; - let res = slf.state.ring.poll(&fd, events).await.merge(); - if let Err(e) = &res { - log::warn!("Could not poll fd: {}", ErrorFmt(e)); - } - let res = res.map_err(|e| ErrorFmt(e).to_string()).map(drop); - slf.send(&ServerMessage::InterestReady { id, writable, res }); - } - }); - (event, future) - }; - let (read_trigger, _read_future) = create(false, c::POLLIN); - let (write_trigger, _write_future) = create(true, c::POLLOUT); - self.pollables.set( - id, - Rc::new(Pollable { - write_trigger, - _write_future, - read_trigger, - _read_future, - }), - ); - self.respond(Response::AddPollable { id: Ok(id) }); - Ok(()) - } - - fn handle_remove_pollable(self: &Rc, id: PollableId) { - self.pollables.remove(&id); - } - - fn handle_add_interest( - self: &Rc, - id: PollableId, - writable: bool, - ) -> Result<(), CphError> { - let Some(pollable) = self.pollables.get(&id) else { - return Err(CphError::PollableDoesNotExist); - }; - let trigger = match writable { - true => &pollable.write_trigger, - false => &pollable.read_trigger, - }; - trigger.trigger(); - Ok(()) - } - - fn tl_to_window(&self, tl: &dyn ToplevelNode) -> Window { - self.tl_id_to_window(tl.tl_data().identifier.get()) - } - - fn tl_id_to_window(&self, tl: ToplevelIdentifier) -> Window { - if let Some(win) = self.windows_from_tl_id.get(&tl) { - return win; - } - let id = Window(self.window_ids.fetch_add(1)); - self.windows_from_tl_id.set(tl, id); - self.windows_to_tl_id.set(id, tl); - id - } - - fn get_window(&self, window: Window) -> Result, CphError> { - self.windows_to_tl_id - .get(&window) - .and_then(|id| self.state.toplevels.get(&id)) - .and_then(|tl| tl.upgrade()) - .ok_or(CphError::WindowDoesNotExist(window)) - } - - fn get_client_matcher( - &self, - matcher: ClientMatcher, - ) -> Result>>, CphError> { - self.client_matchers - .get(&matcher) - .ok_or(CphError::ClientMatcherDoesNotExist(matcher)) - } - - fn sort_generic_matcher( - &self, - generic: &mut GenericCriterionIpc, - key: impl FnMut(&T) -> K, - ) where - K: Ord, - { - match generic { - GenericCriterionIpc::List { list, .. } | GenericCriterionIpc::Exactly { list, .. } => { - list.sort_by_key(key) - } - GenericCriterionIpc::Matcher(_) | GenericCriterionIpc::Not(_) => {} - } - } - - fn create_generic_matcher( - &self, - mgr: &Mgr, - generic: &GenericCriterionIpc, - upstream: &mut Vec>>, - get_matcher: impl Fn(&Matcher) -> Result>, CphError>, - ) -> Result>, CphError> - where - Crit: Clone + Hash + Eq, - Mgr: CritMgrExt, - { - let mut get_upstream = |m: &Matcher| -> Result<_, CphError> { - let m = get_matcher(m)?; - let node = m.node.clone(); - upstream.push(m); - Ok(node) - }; - let node = match generic { - GenericCriterionIpc::Matcher(m) => get_matcher(m)?.node.clone(), - GenericCriterionIpc::Not(m) => mgr.not(&get_upstream(m)?), - GenericCriterionIpc::List { list, all } => { - let mut m = Vec::with_capacity(list.len()); - for c in list { - m.push(get_upstream(c)?); - } - mgr.list(&m, *all) - } - GenericCriterionIpc::Exactly { list, num } => { - let mut m = Vec::with_capacity(list.len()); - for c in list { - m.push(get_upstream(c)?); - } - mgr.exactly(&m, *num) - } - }; - Ok(node) - } - - fn handle_create_client_matcher( - &self, - mut criterion: ClientCriterionIpc, - ) -> Result<(), CphError> { - if let ClientCriterionIpc::Generic(generic) = &mut criterion { - self.sort_generic_matcher(generic, |m| m.0); - } - let id = ClientMatcher(self.client_matcher_ids.fetch_add(1)); - let cache = &self.client_matcher_cache; - if let Some(matcher) = cache.get(&criterion) - && let Some(matcher) = matcher.upgrade() - { - self.client_matchers.set(id, matcher); - self.respond(Response::CreateClientMatcher { matcher: id }); - return Ok(()); - } - let mgr = &self.state.cl_matcher_manager; - let mut upstream = vec![]; - let matcher = match &criterion { - ClientCriterionIpc::Generic(m) => { - self.create_generic_matcher(mgr, m, &mut upstream, |m| self.get_client_matcher(*m))? - } - ClientCriterionIpc::String { - string, - field, - regex, - } => { - let needle = match *regex { - true => { - let regex = Regex::new(string).map_err(CphError::InvalidRegex)?; - CritLiteralOrRegex::Regex(regex) - } - false => CritLiteralOrRegex::Literal(string.to_string()), - }; - match *field { - ClientCriterionStringField::SandboxEngine => mgr.sandbox_engine(needle), - ClientCriterionStringField::SandboxAppId => mgr.sandbox_app_id(needle), - ClientCriterionStringField::SandboxInstanceId => { - mgr.sandbox_instance_id(needle) - } - ClientCriterionStringField::Comm => mgr.comm(needle), - ClientCriterionStringField::Exe => mgr.exe(needle), - ClientCriterionStringField::Tag => mgr.tag(needle), - } - } - ClientCriterionIpc::Sandboxed => mgr.sandboxed(), - ClientCriterionIpc::Uid(p) => mgr.uid(*p), - ClientCriterionIpc::Pid(p) => mgr.pid(*p), - ClientCriterionIpc::IsXwayland => mgr.is_xwayland(), - }; - let cached = Rc::new(CachedCriterion { - crit: criterion.clone(), - cache: cache.clone(), - upstream, - node: matcher.clone(), - }); - cache.set(criterion, Rc::downgrade(&cached)); - self.client_matchers.set(id, cached); - self.respond(Response::CreateClientMatcher { matcher: id }); - Ok(()) - } - - fn handle_destroy_client_matcher(&self, matcher: ClientMatcher) { - self.client_matchers.remove(&matcher); - self.client_matcher_leafs.remove(&matcher); - self.client_matcher_capabilities.remove(&matcher); - self.client_matcher_bounding_capabilities.remove(&matcher); - } - - fn handle_enable_client_matcher_events( - self: &Rc, - matcher: ClientMatcher, - ) -> Result<(), CphError> { - if self.client_matcher_leafs.contains(&matcher) { - return Ok(()); - } - let upstream = self.get_client_matcher(matcher)?; - let slf = self.clone(); - let leaf = self - .state - .cl_matcher_manager - .leaf(&upstream.node, move |id| { - let client = ConfigClient(id.raw()); - slf.send(&ServerMessage::ClientMatcherMatched { matcher, client }); - let slf = slf.clone(); - Box::new(move || { - slf.send(&ServerMessage::ClientMatcherUnmatched { matcher, client }); - }) - }); - self.client_matcher_leafs.set(matcher, leaf); - self.state.cl_matcher_manager.rematch_all(&self.state); - Ok(()) - } - - fn get_window_matcher( - &self, - matcher: WindowMatcher, - ) -> Result>, CphError> { - self.window_matchers - .get(&matcher) - .ok_or(CphError::WindowMatcherDoesNotExist(matcher)) - } - - fn handle_create_window_matcher( - &self, - mut criterion: WindowCriterionIpc, - ) -> Result<(), CphError> { - if let WindowCriterionIpc::Generic(generic) = &mut criterion { - self.sort_generic_matcher(generic, |m| m.0); - } - let id = WindowMatcher(self.window_matcher_ids.fetch_add(1)); - let cache = &self.window_matcher_cache; - if let Some(matcher) = cache.get(&criterion) - && let Some(matcher) = matcher.upgrade() - { - self.window_matchers.set(id, matcher); - self.respond(Response::CreateWindowMatcher { matcher: id }); - return Ok(()); - } - let mgr = &self.state.tl_matcher_manager; - let mut upstream = vec![]; - let matcher = match &criterion { - WindowCriterionIpc::Generic(m) => { - self.create_generic_matcher(mgr, m, &mut upstream, |m| self.get_window_matcher(*m))? - } - WindowCriterionIpc::String { - string, - field, - regex, - } => { - let needle = match *regex { - true => { - let regex = Regex::new(string).map_err(CphError::InvalidRegex)?; - CritLiteralOrRegex::Regex(regex) - } - false => CritLiteralOrRegex::Literal(string.to_string()), - }; - match *field { - WindowCriterionStringField::Title => mgr.title(needle), - WindowCriterionStringField::AppId => mgr.app_id(needle), - WindowCriterionStringField::Tag => mgr.tag(needle), - WindowCriterionStringField::XClass => mgr.class(needle), - WindowCriterionStringField::XInstance => mgr.instance(needle), - WindowCriterionStringField::XRole => mgr.role(needle), - WindowCriterionStringField::Workspace => mgr.workspace(needle), - } - } - WindowCriterionIpc::Types(t) => mgr.kind(*t), - WindowCriterionIpc::Client(c) => { - self.state.cl_matcher_manager.rematch_all(&self.state); - mgr.client(&self.state, &self.get_client_matcher(*c)?.node) - } - WindowCriterionIpc::Floating => mgr.floating(), - WindowCriterionIpc::Visible => mgr.visible(), - WindowCriterionIpc::Urgent => mgr.urgent(), - WindowCriterionIpc::SeatFocus(seat) => mgr.seat_focus(&*self.get_seat(*seat)?), - WindowCriterionIpc::Fullscreen => mgr.fullscreen(), - WindowCriterionIpc::JustMapped => mgr.just_mapped(), - WindowCriterionIpc::Workspace(w) => mgr.workspace(CritLiteralOrRegex::Literal( - self.get_workspace(*w)?.to_string(), - )), - WindowCriterionIpc::ContentTypes(t) => mgr.content_type(*t), - }; - let cached = Rc::new(CachedCriterion { - crit: criterion.clone(), - cache: cache.clone(), - upstream, - node: matcher.clone(), - }); - cache.set(criterion, Rc::downgrade(&cached)); - self.window_matchers.set(id, cached); - self.respond(Response::CreateWindowMatcher { matcher: id }); - Ok(()) - } - - fn handle_destroy_window_matcher(&self, matcher: WindowMatcher) { - self.window_matchers.remove(&matcher); - self.window_matcher_leafs.remove(&matcher); - self.window_matcher_no_auto_focus.remove(&matcher); - self.window_matcher_initial_tile_state.remove(&matcher); - } - - fn handle_enable_window_matcher_events( - self: &Rc, - matcher: WindowMatcher, - ) -> Result<(), CphError> { - if self.window_matcher_leafs.contains(&matcher) { - return Ok(()); - } - let upstream = self.get_window_matcher(matcher)?; - let mut node = upstream.node.clone(); - if !upstream.any(&|crit| matches!(crit, WindowCriterionIpc::Types(_))) { - let list = [self.window_matcher_std_kinds.clone(), node]; - node = self.state.tl_matcher_manager.list(&list, true); - } - let slf = self.clone(); - let leaf = self.state.tl_matcher_manager.leaf(&node, move |tl| { - let window = slf.tl_id_to_window(tl); - slf.send(&ServerMessage::WindowMatcherMatched { matcher, window }); - let slf = slf.clone(); - Box::new(move || { - slf.send(&ServerMessage::WindowMatcherUnmatched { matcher, window }); - }) - }); - self.window_matcher_leafs.set(matcher, leaf); - self.state.tl_matcher_manager.rematch_all(&self.state); - Ok(()) - } - - fn handle_set_window_matcher_auto_focus( - &self, - matcher: WindowMatcher, - auto_focus: bool, - ) -> Result<(), CphError> { - if auto_focus { - self.window_matcher_no_auto_focus.remove(&matcher); - } else { - let m = self.get_window_matcher(matcher)?; - self.window_matcher_no_auto_focus.set(matcher, m); - } - Ok(()) - } - - fn handle_set_window_matcher_initial_tile_state( - &self, - matcher: WindowMatcher, - tile_state: ConfigTileState, - ) -> Result<(), CphError> { - let Ok(tile_state) = tile_state.try_into() else { - return Err(CphError::UnknownTileState(tile_state)); - }; - let m = self.get_window_matcher(matcher)?; - self.window_matcher_initial_tile_state - .set(matcher, (m, tile_state)); - Ok(()) - } - - fn handle_set_pointer_revert_key(&self, seat: Seat, key: KeySym) -> Result<(), CphError> { - self.get_seat(seat)?.set_pointer_revert_key(key); - Ok(()) - } - - fn handle_seat_focus_history(&self, seat: Seat, timeline: Timeline) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - match timeline { - Timeline::Older => seat.focus_prev(), - Timeline::Newer => seat.focus_next(), - } - Ok(()) - } - - fn handle_seat_focus_history_set_only_visible( - &self, - seat: Seat, - visible: bool, - ) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.focus_history_set_visible(visible); - Ok(()) - } - - fn handle_seat_focus_history_set_same_workspace( - &self, - seat: Seat, - same_workspace: bool, - ) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.focus_history_set_same_workspace(same_workspace); - Ok(()) - } - - fn handle_seat_focus_layer_rel( - &self, - seat: Seat, - direction: LayerDirection, - ) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - match direction { - LayerDirection::Below => seat.focus_layer_below(), - LayerDirection::Above => seat.focus_layer_above(), - } - Ok(()) - } - - fn handle_seat_focus_tiles(&self, seat: Seat) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.focus_tiles(); - Ok(()) - } - - fn handle_seat_focus_floats(&self, seat: Seat) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.focus_floats(); - Ok(()) - } - - fn handle_seat_toggle_focus_float_tiled(&self, seat: Seat) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.toggle_focus_float_tiled(); - Ok(()) - } - - fn handle_set_middle_click_paste_enabled(&self, enabled: bool) { - self.state.set_primary_selection_enabled(enabled); - } - - fn handle_seat_create_mark(&self, seat: Seat, kc: Option) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - if let Some(kc) = kc { - seat.create_mark(Keycode::from_evdev(kc)); - } else { - seat.create_mark_interactive(); - } - Ok(()) - } - - fn handle_seat_jump_to_mark(&self, seat: Seat, kc: Option) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - if let Some(kc) = kc { - seat.jump_to_mark(Keycode::from_evdev(kc)); - } else { - seat.jump_to_mark_interactive(); - } - Ok(()) - } - - fn handle_seat_copy_mark(&self, seat: Seat, src: u32, dst: u32) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.copy_mark(Keycode::from_evdev(src), Keycode::from_evdev(dst)); - Ok(()) - } - - fn handle_seat_set_simple_im_enabled(&self, seat: Seat, enabled: bool) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.set_simple_im_enabled(enabled); - Ok(()) - } - - fn handle_seat_get_simple_im_enabled(&self, seat: Seat) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - self.respond(Response::SeatGetSimpleImEnabled { - enabled: seat.simple_im_enabled(), - }); - Ok(()) - } - - fn handle_seat_reload_simple_im(&self, seat: Seat) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.reload_simple_im(); - Ok(()) - } - - fn handle_seat_enable_unicode_input(&self, seat: Seat) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.enable_unicode_input(); - Ok(()) - } - - fn handle_seat_warp_mouse_to_focus(&self, seat: Seat) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.schedule_warp_mouse_to_focus(); - Ok(()) - } - - fn handle_seat_set_mouse_follows_focus( - &self, - seat: Seat, - enabled: bool, - ) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - seat.set_mouse_follows_focus(enabled); - Ok(()) - } - - fn get_sized(&self, sized: Resizable) -> Result { - use jay_config::theme::sized::*; - let sized = match sized { - TITLE_HEIGHT => ThemeSized::title_height, - BORDER_WIDTH => ThemeSized::border_width, - BAR_HEIGHT => ThemeSized::bar_height, - BAR_SEPARATOR_WIDTH => ThemeSized::bar_separator_width, - GAP => ThemeSized::gap, - TITLE_GAP => ThemeSized::title_gap, - TAB_BAR_HEIGHT => ThemeSized::tab_bar_height, - TAB_BAR_PADDING => ThemeSized::tab_bar_padding, - TAB_BAR_RADIUS => ThemeSized::tab_bar_radius, - TAB_BAR_BORDER_WIDTH => ThemeSized::tab_bar_border_width, - TAB_BAR_TEXT_PADDING => ThemeSized::tab_bar_text_padding, - TAB_BAR_GAP => ThemeSized::tab_bar_gap, - _ => return Err(CphError::UnknownSized(sized.0)), - }; - Ok(sized) - } - - fn handle_get_size(&self, sized: Resizable) -> Result<(), CphError> { - let sized = self.get_sized(sized)?; - let size = sized.field(&self.state.theme).val.get(); - self.respond(Response::GetSize { size }); - Ok(()) - } - - fn handle_set_size(&self, sized: Resizable, size: i32) -> Result<(), CphError> { - let sized = self.get_sized(sized)?; - if size < sized.min() { - return Err(CphError::InvalidSize(size, sized)); - } - if size > sized.max() { - return Err(CphError::InvalidSize(size, sized)); - } - self.state.set_size(sized, size); - Ok(()) - } - - fn handle_reset_colors(&self) { - self.state.reset_colors(); - } - - fn handle_reset_sizes(&self) { - self.state.reset_sizes(); - } - - fn handle_reset_font(&self) { - self.state.reset_fonts(); - } - - fn handle_set_font(&self, font: &str) { - self.state.set_font(font); - } - - fn handle_set_bar_font(&self, font: &str) { - self.state.set_bar_font(Some(font)); - } - - fn handle_set_title_font(&self, _font: &str) { - // no-op: titles have been removed - } - - fn handle_get_font(&self) { - let font = self.state.theme.font.get().to_string(); - self.respond(Response::GetFont { font }); - } - - fn get_color(&self, colorable: Colorable) -> Result { - use jay_config::theme::colors::*; - let colorable = match colorable { - UNFOCUSED_TITLE_BACKGROUND_COLOR => ThemeColor::unfocused_title_background, - FOCUSED_TITLE_BACKGROUND_COLOR => ThemeColor::focused_title_background, - CAPTURED_UNFOCUSED_TITLE_BACKGROUND_COLOR => { - ThemeColor::captured_unfocused_title_background - } - CAPTURED_FOCUSED_TITLE_BACKGROUND_COLOR => { - ThemeColor::captured_focused_title_background - } - FOCUSED_INACTIVE_TITLE_BACKGROUND_COLOR => { - ThemeColor::focused_inactive_title_background - } - BACKGROUND_COLOR => ThemeColor::background, - BAR_BACKGROUND_COLOR => ThemeColor::bar_background, - SEPARATOR_COLOR => ThemeColor::separator, - BORDER_COLOR => ThemeColor::border, - ACTIVE_BORDER_COLOR => ThemeColor::active_border, - UNFOCUSED_TITLE_TEXT_COLOR => ThemeColor::unfocused_title_text, - FOCUSED_TITLE_TEXT_COLOR => ThemeColor::focused_title_text, - FOCUSED_INACTIVE_TITLE_TEXT_COLOR => ThemeColor::focused_inactive_title_text, - BAR_STATUS_TEXT_COLOR => ThemeColor::bar_text, - ATTENTION_REQUESTED_BACKGROUND_COLOR => ThemeColor::attention_requested_background, - HIGHLIGHT_COLOR => ThemeColor::highlight, - TAB_ACTIVE_BACKGROUND_COLOR => ThemeColor::tab_active_background, - TAB_ACTIVE_BORDER_COLOR => ThemeColor::tab_active_border, - TAB_INACTIVE_BACKGROUND_COLOR => ThemeColor::tab_inactive_background, - TAB_INACTIVE_BORDER_COLOR => ThemeColor::tab_inactive_border, - TAB_ACTIVE_TEXT_COLOR => ThemeColor::tab_active_text, - TAB_INACTIVE_TEXT_COLOR => ThemeColor::tab_inactive_text, - TAB_BAR_BACKGROUND_COLOR => ThemeColor::tab_bar_background, - TAB_ATTENTION_BACKGROUND_COLOR => ThemeColor::tab_attention_background, - _ => return Err(CphError::UnknownColor(colorable.0)), - }; - Ok(colorable) - } - - fn handle_get_color(&self, colorable: Colorable) -> Result<(), CphError> { - let color = self.get_color(colorable)?.field(&self.state.theme).get(); - let [r, g, b, a] = color.to_array(Eotf::Gamma22); - let color = jay_config::theme::Color::new_f32_premultiplied(r, g, b, a); - self.respond(Response::GetColor { color }); - Ok(()) - } - - fn handle_set_color( - &self, - colorable: Colorable, - color: jay_config::theme::Color, - ) -> Result<(), CphError> { - self.state - .set_color(self.get_color(colorable)?, color.into()); - Ok(()) - } - - fn handle_destroy_keymap(&self, keymap: Keymap) { - self.keymaps.remove(&keymap); - } - - fn get_client(&self, client: ConfigClient) -> Result, CphError> { - self.state - .clients - .get(ClientId::from_raw(client.0)) - .ok() - .ok_or(CphError::ClientDoesNotExist(client)) - } - - fn handle_get_clients(&self) { - let mut clients = vec![]; - for client in self.state.clients.clients.borrow().values() { - clients.push(ConfigClient(client.data.id.raw())); - } - self.respond(Response::GetClients { clients }); - } - - fn handle_client_exists(&self, client: ConfigClient) { - self.respond(Response::ClientExists { - exists: self.get_client(client).is_ok(), - }); - } - - fn handle_client_is_xwayland(&self, client: ConfigClient) -> Result<(), CphError> { - self.respond(Response::ClientIsXwayland { - is_xwayland: self.get_client(client)?.is_xwayland, - }); - Ok(()) - } - - fn handle_client_kill(&self, client: ConfigClient) { - self.state.clients.kill(ClientId::from_raw(client.0)); - } - - fn handle_get_workspace_window(&self, ws: Workspace) -> Result<(), CphError> { - let window = self - .get_existing_workspace(ws)? - .and_then(|ws| ws.container.get()) - .map(|c| self.tl_to_window(&*c)) - .unwrap_or(Window(0)); - self.respond(Response::GetWorkspaceWindow { window }); - Ok(()) - } - - fn handle_get_seat_keyboard_window(&self, seat: Seat) -> Result<(), CphError> { - let window = self - .get_seat(seat)? - .get_keyboard_node() - .node_toplevel() - .map(|tl| self.tl_to_window(&*tl)) - .unwrap_or(Window(0)); - self.respond(Response::GetSeatKeyboardWindow { window }); - Ok(()) - } - - fn handle_seat_focus_window(&self, seat: Seat, window_id: Window) -> Result<(), CphError> { - let seat = self.get_seat(seat)?; - let window = self.get_window(window_id)?; - if !window.node_visible() { - return Err(CphError::WindowNotVisible(window_id)); - } - seat.focus_toplevel(window); - seat.maybe_schedule_warp_mouse_to_focus(); - Ok(()) - } - - fn handle_get_window_title(&self, window: Window) -> Result<(), CphError> { - let title = self.get_window(window)?.tl_data().title.borrow().clone(); - self.respond(Response::GetWindowTitle { title }); - Ok(()) - } - - fn handle_get_window_type(&self, window: Window) -> Result<(), CphError> { - let kind = self.get_window(window)?.tl_data().kind.to_window_type(); - self.respond(Response::GetWindowType { kind }); - Ok(()) - } - - fn handle_get_content_type(&self, window: Window) -> Result<(), CphError> { - let kind = self - .get_window(window)? - .tl_data() - .content_type - .get() - .to_config(); - self.respond(Response::GetContentType { kind }); - 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(), - }); - } - - fn handle_get_window_id(&self, window: Window) -> Result<(), CphError> { - let id = self - .get_window(window)? - .tl_data() - .identifier - .get() - .to_string(); - self.respond(Response::GetWindowId { id: id.to_string() }); - Ok(()) - } - - fn handle_get_window_is_visible(&self, window: Window) -> Result<(), CphError> { - let window = self.get_window(window)?; - self.respond(Response::GetWindowIsVisible { - visible: window.node_visible(), - }); - Ok(()) - } - - fn handle_get_window_client(&self, window: Window) -> Result<(), CphError> { - let window = self.get_window(window)?; - self.respond(Response::GetWindowClient { - client: window - .tl_data() - .client - .as_ref() - .map(|c| ConfigClient(c.id.raw())) - .unwrap_or(ConfigClient(0)), - }); - Ok(()) - } - - fn handle_get_window_parent(&self, window: Window) -> Result<(), CphError> { - let window = self - .get_window(window)? - .tl_data() - .parent - .get() - .and_then(|tl| tl.node_into_toplevel()) - .map(|tl| self.tl_to_window(&*tl)) - .unwrap_or(Window(0)); - self.respond(Response::GetWindowParent { window }); - Ok(()) - } - - fn handle_get_window_workspace(&self, window: Window) -> Result<(), CphError> { - let workspace = self - .get_window(window)? - .tl_data() - .workspace - .get() - .map(|ws| self.get_workspace_by_name(&ws.name)) - .unwrap_or(Workspace(0)); - self.respond(Response::GetWindowWorkspace { workspace }); - Ok(()) - } - - fn handle_get_window_children(&self, window: Window) -> Result<(), CphError> { - let mut windows = vec![]; - if let Some(c) = self.get_window(window)?.node_into_container() { - for c in c.children.iter() { - windows.push(self.tl_to_window(&*c.node)); - } - } - self.respond(Response::GetWindowChildren { windows }); - Ok(()) - } - - fn handle_set_client_matcher_capabilities( - &self, - matcher: ClientMatcher, - caps: ClientCapabilities, - ) -> Result<(), CphError> { - let m = self.get_client_matcher(matcher)?; - self.client_matcher_capabilities - .set(matcher, (m, caps.to_client_caps())); - Ok(()) - } - - fn handle_set_client_matcher_bounding_capabilities( - &self, - matcher: ClientMatcher, - caps: ClientCapabilities, - ) -> Result<(), CphError> { - let m = self.get_client_matcher(matcher)?; - self.client_matcher_bounding_capabilities - .set(matcher, (m, caps.to_client_caps())); - Ok(()) - } - - pub fn handle_request(self: &Rc, msg: &[u8]) { - if let Err(e) = self.handle_request_(msg) { - log::error!("Could not handle client request: {}", ErrorFmt(e)); - } - } - - fn handle_request_(self: &Rc, msg: &[u8]) -> Result<(), CphError> { - let request = match bincode_ops().deserialize::(msg) { - Ok(msg) => msg, - Err(e) => return Err(CphError::ParsingFailed(e)), - }; - match request { - ClientMessage::Log { - level, - msg, - file, - line, - } => self.handle_log_request(level, msg, file, line), - ClientMessage::GetSeat { name } => self.handle_get_seat(name), - ClientMessage::ParseKeymap { keymap } => { - self.handle_parse_keymap(keymap).wrn("parse_keymap")? - } - ClientMessage::SeatSetKeymap { seat, keymap } => { - self.handle_set_keymap(seat, keymap).wrn("set_keymap")? - } - ClientMessage::SeatGetRepeatRate { seat } => { - self.handle_get_repeat_rate(seat).wrn("get_repeat_rate")? - } - ClientMessage::SeatSetRepeatRate { seat, rate, delay } => self - .handle_set_repeat_rate(seat, rate, delay) - .wrn("set_repeat_rate")?, - ClientMessage::SetSeat { device, seat } => { - self.handle_set_seat(device, seat).wrn("set_seat")? - } - ClientMessage::GetSeatMono { seat } => { - self.handle_get_seat_mono(seat).wrn("get_seat_mono")? - } - ClientMessage::SetSeatMono { seat, mono } => { - self.handle_set_seat_mono(seat, mono).wrn("set_seat_mono")? - } - ClientMessage::GetSeatSplit { seat } => { - self.handle_get_seat_split(seat).wrn("get_seat_split")? - } - ClientMessage::SetSeatSplit { seat, axis } => self - .handle_set_seat_split(seat, axis) - .wrn("set_seat_split")?, - ClientMessage::AddShortcut { seat, mods, sym } => self - .handle_add_shortcut(seat, Modifiers(!0), mods, sym) - .wrn("add_shortcut")?, - ClientMessage::RemoveShortcut { seat, mods, sym } => self - .handle_remove_shortcut(seat, mods, sym) - .wrn("remove_shortcut")?, - ClientMessage::SeatFocus { seat, direction } => { - self.handle_seat_focus(seat, direction).wrn("seat_focus")? - } - ClientMessage::SeatMove { seat, direction } => { - self.handle_seat_move(seat, direction).wrn("seat_move")? - } - ClientMessage::GetInputDevices { seat } => self.handle_get_input_devices(seat), - ClientMessage::GetSeats => self.handle_get_seats(), - ClientMessage::RemoveSeat { .. } => {} - ClientMessage::Run { prog, args, env } => { - self.handle_run(prog, args, env, vec![], None).wrn("run")? - } - ClientMessage::GrabKb { kb, grab } => self.handle_grab(kb, grab).wrn("grab")?, - ClientMessage::SetColor { colorable, color } => { - self.handle_set_color(colorable, color).wrn("set_color")? - } - ClientMessage::GetColor { colorable } => { - self.handle_get_color(colorable).wrn("get_color")? - } - ClientMessage::CreateSeatSplit { seat, axis } => self - .handle_create_seat_split(seat, axis) - .wrn("create_seat_split")?, - ClientMessage::FocusSeatParent { seat } => self - .handle_focus_seat_parent(seat) - .wrn("focus_seat_parent")?, - ClientMessage::GetSeatFloating { seat } => self - .handle_get_seat_floating(seat) - .wrn("get_seat_floating")?, - ClientMessage::SetSeatFloating { seat, floating } => self - .handle_set_seat_floating(seat, floating) - .wrn("set_seat_floating")?, - ClientMessage::Quit => self.handle_quit(), - ClientMessage::SwitchTo { vtnr } => self.handle_switch_to(vtnr), - ClientMessage::HasCapability { device, cap } => self - .handle_has_capability(device, cap) - .wrn("has_capability")?, - ClientMessage::SetLeftHanded { - device, - left_handed, - } => self - .handle_set_left_handed(device, left_handed) - .wrn("set_left_handed")?, - ClientMessage::SetAccelProfile { device, profile } => self - .handle_set_accel_profile(device, profile) - .wrn("set_accel_profile")?, - ClientMessage::SetAccelSpeed { device, speed } => self - .handle_set_accel_speed(device, speed) - .wrn("set_accel_speed")?, - ClientMessage::SetTransformMatrix { device, matrix } => self - .handle_set_transform_matrix(device, matrix) - .wrn("set_transform_matrix")?, - ClientMessage::GetDeviceName { device } => { - self.handle_get_device_name(device).wrn("get_device_name")? - } - ClientMessage::GetWorkspace { name } => self.handle_get_workspace(name), - ClientMessage::ShowWorkspace { seat, workspace } => self - .handle_show_workspace(seat, workspace, None) - .wrn("show_workspace")?, - ClientMessage::SetSeatWorkspace { seat, workspace } => self - .handle_set_seat_workspace(seat, workspace) - .wrn("set_seat_workspace")?, - ClientMessage::GetConnector { ty, idx } => { - self.handle_get_connector(ty, idx).wrn("get_connector")? - } - ClientMessage::ConnectorConnected { connector } => self - .handle_connector_connected(connector) - .wrn("connector_connected")?, - ClientMessage::ConnectorType { connector } => self - .handle_connector_type(connector) - .wrn("connector_type")?, - ClientMessage::ConnectorMode { connector } => self - .handle_connector_mode(connector) - .wrn("connector_mode")?, - ClientMessage::ConnectorSetPosition { connector, x, y } => self - .handle_connector_set_position(connector, x, y) - .wrn("connector_set_position")?, - ClientMessage::ConnectorSetEnabled { connector, enabled } => self - .handle_connector_set_enabled(connector, enabled) - .wrn("connector_set_enabled")?, - ClientMessage::SeatClose { seat } => self.handle_seat_close(seat).wrn("seat_close")?, - ClientMessage::SetStatus { status } => self.handle_set_status(status), - ClientMessage::GetTimer { name } => self.handle_get_timer(name).wrn("get_timer")?, - ClientMessage::RemoveTimer { timer } => { - self.handle_remove_timer(timer).wrn("remove_timer")? - } - ClientMessage::ProgramTimer { - timer, - initial, - periodic, - } => self - .handle_program_timer(timer, initial, periodic) - .wrn("program_timer")?, - ClientMessage::SetEnv { key, val } => self.handle_set_env(key, val), - ClientMessage::SetSeatFullscreen { seat, fullscreen } => self - .handle_set_seat_fullscreen(seat, fullscreen) - .wrn("set_seat_fullscreen")?, - ClientMessage::GetSeatFullscreen { seat } => self - .handle_get_seat_fullscreen(seat) - .wrn("get_seat_fullscreen")?, - ClientMessage::Reload => self.handle_reload(), - ClientMessage::GetDeviceConnectors { device } => self - .handle_get_connectors(Some(device), false) - .wrn("get_device_connectors")?, - ClientMessage::GetDrmDeviceSyspath { device } => self - .handle_get_drm_device_syspath(device) - .wrn("get_drm_device_syspath")?, - ClientMessage::GetDrmDeviceVendor { device } => self - .handle_get_drm_device_vendor(device) - .wrn("get_drm_device_vendor")?, - ClientMessage::GetDrmDeviceModel { device } => self - .handle_get_drm_device_model(device) - .wrn("get_drm_device_model")?, - ClientMessage::GetDrmDevices => self.handle_get_drm_devices(), - ClientMessage::GetDrmDevicePciId { device } => self - .handle_get_drm_device_pci_id(device) - .wrn("get_drm_device_pci_id")?, - ClientMessage::ResetColors => self.handle_reset_colors(), - ClientMessage::ResetSizes => self.handle_reset_sizes(), - ClientMessage::GetSize { sized } => self.handle_get_size(sized).wrn("get_size")?, - ClientMessage::SetSize { sized, size } => { - self.handle_set_size(sized, size).wrn("set_size")? - } - ClientMessage::ResetFont => self.handle_reset_font(), - ClientMessage::GetFont => self.handle_get_font(), - ClientMessage::SetFont { font } => self.handle_set_font(font), - ClientMessage::SetPxPerWheelScroll { device, px } => self - .handle_set_px_per_wheel_scroll(device, px) - .wrn("set_px_per_wheel_scroll")?, - ClientMessage::ConnectorSetScale { connector, scale } => self - .handle_connector_set_scale(connector, scale) - .wrn("connector_set_scale")?, - ClientMessage::ConnectorGetScale { connector } => self - .handle_connector_get_scale(connector) - .wrn("connector_get_scale")?, - ClientMessage::ConnectorSize { connector } => self - .handle_connector_size(connector) - .wrn("connector_size")?, - ClientMessage::SetCursorSize { seat, size } => self - .handle_set_cursor_size(seat, size) - .wrn("set_cursor_size")?, - ClientMessage::SetTapEnabled { device, enabled } => self - .handle_set_tap_enabled(device, enabled) - .wrn("set_tap_enabled")?, - ClientMessage::SetDragEnabled { device, enabled } => self - .handle_set_drag_enabled(device, enabled) - .wrn("set_drag_enabled")?, - ClientMessage::SetDragLockEnabled { device, enabled } => self - .handle_set_drag_lock_enabled(device, enabled) - .wrn("set_drag_lock_enabled")?, - ClientMessage::SetUseHardwareCursor { - seat, - use_hardware_cursor, - } => self - .handle_set_use_hardware_cursor(seat, use_hardware_cursor) - .wrn("set_use_hardware_cursor")?, - ClientMessage::DisablePointerConstraint { seat } => self - .handle_disable_pointer_constraint(seat) - .wrn("disable_pointer_constraint")?, - ClientMessage::MakeRenderDevice { device } => self - .handle_make_render_device(device) - .wrn("make_render_device")?, - ClientMessage::GetSeatCursorWorkspace { seat } => self - .handle_get_seat_cursor_workspace(seat) - .wrn("get_seat_cursor_workspace")?, - ClientMessage::GetSeatKeyboardWorkspace { seat } => self - .handle_get_seat_keyboard_workspace(seat) - .wrn("get_seat_keyboard_workspace")?, - ClientMessage::SetDefaultWorkspaceCapture { capture } => { - self.handle_set_default_workspace_capture(capture) - } - ClientMessage::GetDefaultWorkspaceCapture => { - self.handle_get_default_workspace_capture() - } - ClientMessage::SetWorkspaceCapture { workspace, capture } => self - .handle_set_workspace_capture(workspace, capture) - .wrn("set_workspace_capture")?, - ClientMessage::GetWorkspaceCapture { workspace } => self - .handle_get_workspace_capture(workspace) - .wrn("get_workspace_capture")?, - ClientMessage::SetNaturalScrollingEnabled { device, enabled } => self - .handle_set_natural_scrolling_enabled(device, enabled) - .wrn("set_natural_scrolling_enabled")?, - ClientMessage::SetGfxApi { device, api } => { - self.handle_set_gfx_api(device, api).wrn("set_gfx_api")? - } - ClientMessage::SetDirectScanoutEnabled { device, enabled } => self - .handle_set_direct_scanout_enabled(device, enabled) - .wrn("set_direct_scanout_enabled")?, - ClientMessage::ConnectorSetTransform { - connector, - transform, - } => self - .handle_connector_set_transform(connector, transform) - .wrn("connector_set_transform")?, - ClientMessage::SetDoubleClickIntervalUsec { usec } => { - self.handle_set_double_click_interval_usec(usec) - } - ClientMessage::SetDoubleClickDistance { dist } => { - self.handle_set_double_click_distance(dist) - } - ClientMessage::ConnectorModes { connector } => self - .handle_connector_modes(connector) - .wrn("connector_modes")?, - ClientMessage::ConnectorSetMode { connector, mode } => self - .handle_connector_set_mode(connector, mode) - .wrn("connector_set_mode")?, - ClientMessage::AddPollable { fd } => { - self.handle_add_pollable(fd).wrn("add_pollable")? - } - ClientMessage::RemovePollable { id } => self.handle_remove_pollable(id), - ClientMessage::AddInterest { pollable, writable } => self - .handle_add_interest(pollable, writable) - .wrn("add_interest")?, - ClientMessage::Run2 { - prog, - args, - env, - fds, - } => self.handle_run(prog, args, env, fds, None).wrn("run")?, - ClientMessage::DisableDefaultSeat => self.state.create_default_seat.set(false), - ClientMessage::DestroyKeymap { keymap } => self.handle_destroy_keymap(keymap), - ClientMessage::GetConnectorName { connector } => self - .handle_connector_name(connector) - .wrn("connector_name")?, - ClientMessage::GetConnectorModel { connector } => self - .handle_connector_model(connector) - .wrn("connector_model")?, - ClientMessage::GetConnectorManufacturer { connector } => self - .handle_connector_manufacturer(connector) - .wrn("connector_manufacturer")?, - ClientMessage::GetConnectorSerialNumber { connector } => self - .handle_connector_serial_number(connector) - .wrn("connector_serial_number")?, - ClientMessage::GetConnectors { - device, - connected_only, - } => self - .handle_get_connectors(device, connected_only) - .wrn("get_connectors")?, - ClientMessage::ConnectorGetPosition { connector } => self - .handle_connector_get_position(connector) - .wrn("connector_get_position")?, - ClientMessage::GetConfigDir => self.handle_get_config_dir(), - ClientMessage::GetWorkspaces => self.handle_get_workspaces(), - ClientMessage::UnsetEnv { key } => self.handle_unset_env(key), - ClientMessage::SetLogLevel { level } => self.handle_set_log_level(level), - ClientMessage::GetDrmDeviceDevnode { device } => self - .handle_get_drm_device_devnode(device) - .wrn("get_drm_device_devnode")?, - ClientMessage::GetInputDeviceSyspath { device } => self - .handle_get_input_device_syspath(device) - .wrn("get_input_device_syspath")?, - ClientMessage::GetInputDeviceDevnode { device } => self - .handle_get_input_device_devnode(device) - .wrn("get_input_device_devnode")?, - ClientMessage::SetIdle { timeout } => self.handle_set_idle(timeout), - ClientMessage::MoveToOutput { - workspace, - connector, - } => self - .handle_move_to_output(workspace, connector) - .wrn("move_to_output")?, - ClientMessage::SetExplicitSyncEnabled { enabled } => { - self.handle_set_explicit_sync_enabled(enabled) - } - ClientMessage::GetSocketPath => self.handle_get_socket_path(), - ClientMessage::DeviceSetKeymap { device, keymap } => self - .handle_set_device_keymap(device, keymap) - .wrn("set_device_keymap")?, - ClientMessage::SetForward { seat, forward } => { - self.handle_set_forward(seat, forward).wrn("set_forward")? - } - ClientMessage::AddShortcut2 { - seat, - mod_mask, - mods, - sym, - } => self - .handle_add_shortcut(seat, mod_mask, mods, sym) - .wrn("add_shortcut")?, - ClientMessage::SetFocusFollowsMouseMode { seat, mode } => self - .handle_set_focus_follows_mouse_mode(seat, mode) - .wrn("set_focus_follows_mouse_mode")?, - ClientMessage::SetInputDeviceConnector { - input_device, - connector, - } => self - .handle_set_input_device_connector(input_device, connector) - .wrn("set_input_device_connector")?, - ClientMessage::RemoveInputMapping { input_device } => self - .handle_remove_input_mapping(input_device) - .wrn("remove_input_mapping")?, - ClientMessage::SetWindowManagementEnabled { seat, enabled } => self - .handle_set_window_management_enabled(seat, enabled) - .wrn("set_window_management_enabled")?, - ClientMessage::SetVrrMode { connector, mode } => self - .handle_set_vrr_mode(connector, mode) - .wrn("set_vrr_mode")?, - ClientMessage::SetVrrCursorHz { connector, hz } => self - .handle_set_vrr_cursor_hz(connector, hz) - .wrn("set_vrr_cursor_hz")?, - ClientMessage::SetTearingMode { connector, mode } => self - .handle_set_tearing_mode(connector, mode) - .wrn("set_tearing_mode")?, - ClientMessage::SetCalibrationMatrix { device, matrix } => self - .handle_set_calibration_matrix(device, matrix) - .wrn("set_calibration_matrix")?, - ClientMessage::SetEiSocketEnabled { enabled } => { - self.handle_set_ei_socket_enabled(enabled) - } - ClientMessage::ConnectorSetFormat { connector, format } => self - .handle_connector_set_format(connector, format) - .wrn("connector_set_format")?, - ClientMessage::SetFlipMargin { device, margin } => self - .handle_set_flip_margin(device, margin) - .wrn("set_flip_margin")?, - ClientMessage::SetUiDragEnabled { enabled } => self.handle_set_ui_drag_enabled(enabled), - ClientMessage::SetUiDragThreshold { threshold } => { - self.handle_set_ui_drag_threshold(threshold) - } - ClientMessage::SetXScalingMode { mode } => self - .handle_set_x_scaling_mode(mode) - .wrn("set_x_scaling_mode")?, - ClientMessage::SetIdleGracePeriod { period } => { - self.handle_set_idle_grace_period(period) - } - ClientMessage::SetColorManagementEnabled { enabled } => { - self.handle_set_color_management_enabled(enabled) - } - ClientMessage::ConnectorSetColors { - connector, - color_space, - eotf, - } => self - .handle_connector_set_colors(connector, color_space, eotf) - .wrn("connector_set_colors")?, - ClientMessage::ConnectorSetBrightness { - connector, - brightness, - } => self - .handle_connector_set_brightness(connector, brightness) - .wrn("connector_set_brightness")?, - ClientMessage::SetFloatAboveFullscreen { above } => { - self.handle_set_float_above_fullscreen(above) - } - ClientMessage::GetFloatAboveFullscreen => self.handle_get_float_above_fullscreen(), - ClientMessage::GetSeatFloatPinned { seat } => self - .handle_get_seat_float_pinned(seat) - .wrn("get_seat_float_pinned")?, - ClientMessage::SetSeatFloatPinned { seat, pinned } => self - .handle_set_seat_float_pinned(seat, pinned) - .wrn("set_seat_float_pinned")?, - ClientMessage::SetShowFloatPinIcon { show } => { - self.handle_set_show_float_pin_icon(show) - } - ClientMessage::GetConnectorActiveWorkspace { connector } => self - .handle_get_connector_active_workspace(connector) - .wrn("get_connector_active_workspace")?, - ClientMessage::GetConnectorWorkspaces { connector } => self - .handle_get_connector_workspaces(connector) - .wrn("get_connector_workspaces")?, - ClientMessage::GetWorkspaceConnector { workspace } => self - .handle_get_workspace_connector(workspace) - .wrn("get_workspace_connector")?, - ClientMessage::GetConnectorInDirection { - connector, - direction, - } => self - .handle_get_connector_in_direction(connector, direction) - .wrn("get_connector_in_direction")?, - ClientMessage::GetClients => self.handle_get_clients(), - ClientMessage::ClientExists { client } => self.handle_client_exists(client), - ClientMessage::ClientIsXwayland { client } => self - .handle_client_is_xwayland(client) - .wrn("client_is_xwayland")?, - ClientMessage::ClientKill { client } => self.handle_client_kill(client), - ClientMessage::WindowExists { window } => self.handle_window_exists(window), - ClientMessage::GetWorkspaceWindow { workspace } => self - .handle_get_workspace_window(workspace) - .wrn("get_workspace_window")?, - ClientMessage::GetSeatKeyboardWindow { seat } => self - .handle_get_seat_keyboard_window(seat) - .wrn("get_seat_keyboard_window")?, - ClientMessage::SeatFocusWindow { seat, window } => self - .handle_seat_focus_window(seat, window) - .wrn("seat_focus_window")?, - ClientMessage::GetWindowTitle { window } => self - .handle_get_window_title(window) - .wrn("get_window_title")?, - ClientMessage::GetWindowType { window } => { - self.handle_get_window_type(window).wrn("get_window_type")? - } - ClientMessage::GetWindowId { window } => { - self.handle_get_window_id(window).wrn("get_window_id")? - } - ClientMessage::GetWindowParent { window } => self - .handle_get_window_parent(window) - .wrn("get_window_parent")?, - ClientMessage::GetWindowWorkspace { window } => self - .handle_get_window_workspace(window) - .wrn("get_window_workspace")?, - ClientMessage::GetWindowChildren { window } => self - .handle_get_window_children(window) - .wrn("get_window_children")?, - ClientMessage::GetWindowSplit { window } => self - .handle_get_window_split(window) - .wrn("get_window_split")?, - ClientMessage::SetWindowSplit { window, axis } => self - .handle_set_window_split(window, axis) - .wrn("set_window_split")?, - ClientMessage::GetWindowMono { window } => { - self.handle_get_window_mono(window).wrn("get_window_mono")? - } - ClientMessage::SetWindowMono { window, mono } => self - .handle_set_window_mono(window, mono) - .wrn("set_window_mono")?, - ClientMessage::WindowMove { window, direction } => self - .handle_window_move(window, direction) - .wrn("window_move")?, - ClientMessage::CreateWindowSplit { window, axis } => self - .handle_create_window_split(window, axis) - .wrn("create_window_split")?, - ClientMessage::WindowClose { window } => { - self.handle_window_close(window).wrn("close_window")? - } - ClientMessage::GetWindowFloating { window } => self - .handle_get_window_floating(window) - .wrn("get_window_floating")?, - ClientMessage::SetWindowFloating { window, floating } => self - .handle_set_window_floating(window, floating) - .wrn("set_window_floating")?, - ClientMessage::SetWindowWorkspace { window, workspace } => self - .handle_set_window_workspace(window, workspace) - .wrn("set_window_workspace")?, - ClientMessage::SetWindowFullscreen { window, fullscreen } => self - .handle_set_window_fullscreen(window, fullscreen) - .wrn("set_window_fullscreen")?, - ClientMessage::GetWindowFullscreen { window } => self - .handle_get_window_fullscreen(window) - .wrn("get_window_fullscreen")?, - ClientMessage::GetWindowFloatPinned { window } => self - .handle_get_window_float_pinned(window) - .wrn("get_window_float_pinned")?, - ClientMessage::SetWindowFloatPinned { window, pinned } => self - .handle_set_window_float_pinned(window, pinned) - .wrn("set_window_float_pinned")?, - ClientMessage::GetWindowIsVisible { window } => self - .handle_get_window_is_visible(window) - .wrn("get_window_is_visible")?, - ClientMessage::GetWindowClient { window } => self - .handle_get_window_client(window) - .wrn("get_window_client")?, - ClientMessage::CreateClientMatcher { criterion } => self - .handle_create_client_matcher(criterion) - .wrn("create_window_matcher")?, - ClientMessage::DestroyClientMatcher { matcher } => { - self.handle_destroy_client_matcher(matcher) - } - ClientMessage::EnableClientMatcherEvents { matcher } => self - .handle_enable_client_matcher_events(matcher) - .wrn("enable_window_matcher_events")?, - ClientMessage::CreateWindowMatcher { criterion } => self - .handle_create_window_matcher(criterion) - .wrn("create_window_matcher")?, - ClientMessage::DestroyWindowMatcher { matcher } => { - self.handle_destroy_window_matcher(matcher) - } - ClientMessage::EnableWindowMatcherEvents { matcher } => self - .handle_enable_window_matcher_events(matcher) - .wrn("enable_window_matcher_events")?, - ClientMessage::SetWindowMatcherAutoFocus { - matcher, - auto_focus, - } => self - .handle_set_window_matcher_auto_focus(matcher, auto_focus) - .wrn("set_window_matcher_auto_focus")?, - ClientMessage::SetWindowMatcherInitialTileState { - matcher, - tile_state, - } => self - .handle_set_window_matcher_initial_tile_state(matcher, tile_state) - .wrn("set_window_matcher_initial_tile_state")?, - ClientMessage::SetPointerRevertKey { seat, key } => self - .handle_set_pointer_revert_key(seat, key) - .wrn("set_pointer_revert_key")?, - ClientMessage::SetClickMethod { device, method } => self - .handle_set_click_method(device, method) - .wrn("set_click_method")?, - ClientMessage::SetMiddleButtonEmulationEnabled { device, enabled } => self - .handle_set_middle_button_emulation_enabled(device, enabled) - .wrn("set_middle_button_emulation_enabled")?, - ClientMessage::GetContentType { window } => self - .handle_get_content_type(window) - .wrn("get_content_type")?, - ClientMessage::SetShowBar { show } => self.handle_set_show_bar(show), - ClientMessage::GetShowBar => self.handle_get_show_bar(), - ClientMessage::SetShowTitles { show } => self.handle_set_show_titles(show), - ClientMessage::GetShowTitles => self.handle_get_show_titles(), - ClientMessage::SetFloatingTitles { floating } => { - self.handle_set_floating_titles(floating) - } - ClientMessage::GetFloatingTitles => self.handle_get_floating_titles(), - ClientMessage::SetBarPosition { position } => self - .handle_set_bar_position(position) - .wrn("set_bar_position")?, - ClientMessage::GetBarPosition => self.handle_get_bar_position(), - ClientMessage::SetCornerRadius { radius } => self.handle_set_corner_radius(radius), - ClientMessage::GetCornerRadius => self.handle_get_corner_radius(), - ClientMessage::SeatFocusHistory { seat, timeline } => self - .handle_seat_focus_history(seat, timeline) - .wrn("seat_focus_history")?, - ClientMessage::SeatFocusHistorySetOnlyVisible { seat, only_visible } => self - .handle_seat_focus_history_set_only_visible(seat, only_visible) - .wrn("seat_focus_history_set_only_visible")?, - ClientMessage::SeatFocusHistorySetSameWorkspace { - seat, - same_workspace, - } => self - .handle_seat_focus_history_set_same_workspace(seat, same_workspace) - .wrn("seat_focus_history_set_same_workspace")?, - ClientMessage::SeatFocusLayerRel { seat, direction } => self - .handle_seat_focus_layer_rel(seat, direction) - .wrn("seat_focus_layer_rel")?, - ClientMessage::SeatFocusTiles { seat } => { - self.handle_seat_focus_tiles(seat).wrn("seat_focus_tiles")? - } - ClientMessage::SeatFocusFloats { seat } => self - .handle_seat_focus_floats(seat) - .wrn("seat_focus_floats")?, - ClientMessage::SeatToggleFocusFloatTiled { seat } => self - .handle_seat_toggle_focus_float_tiled(seat) - .wrn("seat_toggle_focus_float_tiled")?, - ClientMessage::SetMiddleClickPasteEnabled { enabled } => { - self.handle_set_middle_click_paste_enabled(enabled) - } - ClientMessage::SetWorkspaceDisplayOrder { order } => { - self.handle_set_workspace_display_order(order) - } - ClientMessage::SeatCreateMark { seat, kc } => self - .handle_seat_create_mark(seat, kc) - .wrn("seat_create_mark")?, - ClientMessage::SeatJumpToMark { seat, kc } => self - .handle_seat_jump_to_mark(seat, kc) - .wrn("seat_jump_to_mark")?, - ClientMessage::SeatCopyMark { seat, src, dst } => self - .handle_seat_copy_mark(seat, src, dst) - .wrn("seat_copy_mark")?, - ClientMessage::ConnectorSetBlendSpace { - connector, - blend_space, - } => self - .handle_connector_set_blend_space(connector, blend_space) - .wrn("connector_set_blend_space")?, - ClientMessage::SetBarFont { font } => self.handle_set_bar_font(font), - ClientMessage::SetTitleFont { font } => self.handle_set_title_font(font), - ClientMessage::SetClientMatcherCapabilities { matcher, caps } => self - .handle_set_client_matcher_capabilities(matcher, caps) - .wrn("set_client_matcher_capabilities")?, - ClientMessage::SetClientMatcherBoundingCapabilities { matcher, caps } => self - .handle_set_client_matcher_bounding_capabilities(matcher, caps) - .wrn("set_client_matcher_bounding_capabilities")?, - ClientMessage::ShowWorkspaceOn { - seat, - workspace, - connector, - } => self - .handle_show_workspace(seat, workspace, Some(connector)) - .wrn("show_workspace_on")?, - ClientMessage::SeatSetSimpleImEnabled { seat, enabled } => self - .handle_seat_set_simple_im_enabled(seat, enabled) - .wrn("seat_set_simple_im_enabled")?, - ClientMessage::SeatGetSimpleImEnabled { seat } => self - .handle_seat_get_simple_im_enabled(seat) - .wrn("seat_get_simple_im_enabled")?, - ClientMessage::SeatReloadSimpleIm { seat } => self - .handle_seat_reload_simple_im(seat) - .wrn("seat_reload_simple_im")?, - ClientMessage::SeatEnableUnicodeInput { seat } => self - .handle_seat_enable_unicode_input(seat) - .wrn("seat_enable_unicode_input")?, - ClientMessage::SeatWarpMouseToFocus { seat } => self - .handle_seat_warp_mouse_to_focus(seat) - .wrn("seat_warp_mouse_to_focus")?, - ClientMessage::SeatSetMouseFollowsFocus { seat, enabled } => self - .handle_seat_set_mouse_follows_focus(seat, enabled) - .wrn("seat_set_mouse_follows_focus")?, - ClientMessage::ConnectorSetUseNativeGamut { - connector, - use_native_gamut, - } => self - .handle_connector_set_use_native_gamut(connector, use_native_gamut) - .wrn("connector_set_use_native_gamut")?, - ClientMessage::KeymapFromNames { - rules, - model, - groups, - options, - } => self - .handle_keymap_from_names(rules, model, groups, options) - .wrn("keymap_from_names")?, - ClientMessage::SetFallbackOutputMode { seat, mode } => self - .handle_set_fallback_output_mode(seat, mode) - .wrn("set_fallback_output_mode")?, - ClientMessage::SetXWaylandEnabled { enabled } => self - .handle_set_x_wayland_enabled(enabled) - .wrn("set_x_wayland_enabled")?, - ClientMessage::Run3 { - prog, - args, - env, - fds, - tag, - } => self.handle_run(prog, args, env, fds, tag).wrn("run")?, - ClientMessage::ConnectorSupportsArbitraryModes { connector } => self - .handle_connector_supports_arbitrary_modes(connector) - .wrn("connector_supports_arbitrary_modes")?, - ClientMessage::GetConnectorByName { name } => self.handle_get_connector_by_name(name), - 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")?, - ClientMessage::SeatToggleTab { seat } => { - self.handle_seat_toggle_tab(seat).wrn("seat_toggle_tab")? - } - ClientMessage::SeatMakeGroup { - seat, - axis, - ephemeral, - } => self - .handle_seat_make_group(seat, axis, ephemeral) - .wrn("seat_make_group")?, - ClientMessage::SeatChangeGroupOpposite { seat } => self - .handle_seat_change_group_opposite(seat) - .wrn("seat_change_group_opposite")?, - ClientMessage::SeatEqualize { seat, recursive } => self - .handle_seat_equalize(seat, recursive) - .wrn("seat_equalize")?, - ClientMessage::SetAutotile { enabled } => { - self.state.theme.autotile_enabled.set(enabled); - } - ClientMessage::SeatToggleExpand { .. } => { - // Removed feature; kept for binary protocol compatibility. - } - ClientMessage::SetTabTitleAlign { align } => { - use crate::theme::TabTitleAlign; - let val = match align { - 1 => TabTitleAlign::Center, - 2 => TabTitleAlign::End, - _ => TabTitleAlign::Start, - }; - self.state.theme.tab_title_align.set(val); - } - ClientMessage::SeatMoveTab { seat, right } => self - .handle_seat_move_tab(seat, right) - .wrn("seat_move_tab")?, - } - Ok(()) - } - pub fn auto_focus(&self, data: &ToplevelData) -> bool { for matcher in self.window_matcher_no_auto_focus.lock().values() { if matcher.node.pull(data) { @@ -3557,42 +314,6 @@ impl ConfigProxyHandler { } None } - - pub fn update_capabilities( - &self, - data: &Rc, - bounding_caps: ClientCaps, - set_bounding_caps: bool, - ) { - let mut have_caps = false; - let mut have_bounding_caps = false; - let mut caps = ClientCaps::none(); - let mut new_bounding_caps = ClientCaps::none(); - for (matcher, state) in self.client_matcher_capabilities.lock().values() { - if matcher.node.pull(data) { - have_caps = true; - caps |= *state; - } - } - for (matcher, state) in self.client_matcher_bounding_capabilities.lock().values() { - if matcher.node.pull(data) { - have_bounding_caps = true; - new_bounding_caps |= *state; - } - } - if have_caps { - caps &= bounding_caps; - data.effective_caps.set(caps); - } - if !have_bounding_caps && set_bounding_caps { - have_bounding_caps = true; - new_bounding_caps = data.effective_caps.get(); - } - if have_bounding_caps { - new_bounding_caps &= bounding_caps; - data.bounding_caps_for_children.set(new_bounding_caps); - } - } } #[derive(Debug, Error)] @@ -3639,8 +360,6 @@ enum CphError { UnknownColor(u32), #[error("Sized element {0} is not known")] UnknownSized(u32), - #[error("Could not parse the message")] - ParsingFailed(#[source] bincode::Error), #[error("Could not process a `{0}` request")] FailedRequest(&'static str, #[source] Box), #[error(transparent)] @@ -3691,7 +410,7 @@ enum CphError { UnknownFallbackOutputMode(FallbackOutputMode), #[error("Unknown tile state {0:?}")] UnknownTileState(ConfigTileState), - #[error("Could not create a tagged acceptor")] + #[error("Could not create tagged acceptor")] CreateTaggedAcceptor(#[source] TaggedAcceptorError), } @@ -3704,13 +423,3 @@ impl WithRequestName for Result<(), CphError> { self.map_err(move |e| CphError::FailedRequest(request, Box::new(e))) } } - -trait ClientCapabilitiesExt { - fn to_client_caps(self) -> ClientCaps; -} - -impl ClientCapabilitiesExt for ClientCapabilities { - fn to_client_caps(self) -> ClientCaps { - ClientCaps(self.0 as u32) & !CAP_JAY_COMPOSITOR & ClientCaps::all() - } -} diff --git a/src/config/handler/dispatch.rs b/src/config/handler/dispatch.rs new file mode 100644 index 00000000..20f46b33 --- /dev/null +++ b/src/config/handler/dispatch.rs @@ -0,0 +1,747 @@ +use super::*; + +impl ConfigProxyHandler { + pub fn handle_request(self: &Rc, msg: &ClientMessage<'_>) { + if let Err(e) = self.handle_request_(msg) { + log::error!("Could not handle client request: {}", ErrorFmt(e)); + } + } + + fn handle_request_(self: &Rc, request: &ClientMessage<'_>) -> Result<(), CphError> { + let request = request.clone(); + match request { + ClientMessage::Log { + level, + msg, + file, + line, + } => self.handle_log_request(level, msg, file, line), + ClientMessage::GetSeat { name } => self.handle_get_seat(name), + ClientMessage::ParseKeymap { keymap } => { + self.handle_parse_keymap(keymap).wrn("parse_keymap")? + } + ClientMessage::SeatSetKeymap { seat, keymap } => { + self.handle_set_keymap(seat, keymap).wrn("set_keymap")? + } + ClientMessage::SeatGetRepeatRate { seat } => { + self.handle_get_repeat_rate(seat).wrn("get_repeat_rate")? + } + ClientMessage::SeatSetRepeatRate { seat, rate, delay } => self + .handle_set_repeat_rate(seat, rate, delay) + .wrn("set_repeat_rate")?, + ClientMessage::SetSeat { device, seat } => { + self.handle_set_seat(device, seat).wrn("set_seat")? + } + ClientMessage::GetSeatMono { seat } => { + self.handle_get_seat_mono(seat).wrn("get_seat_mono")? + } + ClientMessage::SetSeatMono { seat, mono } => { + self.handle_set_seat_mono(seat, mono).wrn("set_seat_mono")? + } + ClientMessage::GetSeatSplit { seat } => { + self.handle_get_seat_split(seat).wrn("get_seat_split")? + } + ClientMessage::SetSeatSplit { seat, axis } => self + .handle_set_seat_split(seat, axis) + .wrn("set_seat_split")?, + ClientMessage::AddShortcut { seat, mods, sym } => self + .handle_add_shortcut(seat, Modifiers(!0), mods, sym) + .wrn("add_shortcut")?, + ClientMessage::RemoveShortcut { seat, mods, sym } => self + .handle_remove_shortcut(seat, mods, sym) + .wrn("remove_shortcut")?, + ClientMessage::SeatFocus { seat, direction } => { + self.handle_seat_focus(seat, direction).wrn("seat_focus")? + } + ClientMessage::SeatMove { seat, direction } => { + self.handle_seat_move(seat, direction).wrn("seat_move")? + } + ClientMessage::GetInputDevices { seat } => self.handle_get_input_devices(seat), + ClientMessage::GetSeats => self.handle_get_seats(), + ClientMessage::RemoveSeat { .. } => {} + ClientMessage::Run { prog, args, env } => { + self.handle_run(prog, args, env, vec![], None).wrn("run")? + } + ClientMessage::GrabKb { kb, grab } => self.handle_grab(kb, grab).wrn("grab")?, + ClientMessage::SetColor { colorable, color } => { + self.handle_set_color(colorable, color).wrn("set_color")? + } + ClientMessage::GetColor { colorable } => { + self.handle_get_color(colorable).wrn("get_color")? + } + ClientMessage::CreateSeatSplit { seat, axis } => self + .handle_create_seat_split(seat, axis) + .wrn("create_seat_split")?, + ClientMessage::FocusSeatParent { seat } => self + .handle_focus_seat_parent(seat) + .wrn("focus_seat_parent")?, + ClientMessage::GetSeatFloating { seat } => self + .handle_get_seat_floating(seat) + .wrn("get_seat_floating")?, + ClientMessage::SetSeatFloating { seat, floating } => self + .handle_set_seat_floating(seat, floating) + .wrn("set_seat_floating")?, + ClientMessage::Quit => self.handle_quit(), + ClientMessage::SwitchTo { vtnr } => self.handle_switch_to(vtnr), + ClientMessage::HasCapability { device, cap } => self + .handle_has_capability(device, cap) + .wrn("has_capability")?, + ClientMessage::SetLeftHanded { + device, + left_handed, + } => self + .handle_set_left_handed(device, left_handed) + .wrn("set_left_handed")?, + ClientMessage::SetAccelProfile { device, profile } => self + .handle_set_accel_profile(device, profile) + .wrn("set_accel_profile")?, + ClientMessage::SetAccelSpeed { device, speed } => self + .handle_set_accel_speed(device, speed) + .wrn("set_accel_speed")?, + ClientMessage::SetTransformMatrix { device, matrix } => self + .handle_set_transform_matrix(device, matrix) + .wrn("set_transform_matrix")?, + ClientMessage::GetDeviceName { device } => { + self.handle_get_device_name(device).wrn("get_device_name")? + } + ClientMessage::GetWorkspace { name } => self.handle_get_workspace(name), + ClientMessage::ShowWorkspace { seat, workspace } => self + .handle_show_workspace(seat, workspace, None) + .wrn("show_workspace")?, + ClientMessage::SetSeatWorkspace { seat, workspace } => self + .handle_set_seat_workspace(seat, workspace) + .wrn("set_seat_workspace")?, + ClientMessage::SeatSendToScratchpad { seat, name } => self + .handle_seat_send_to_scratchpad(seat, name) + .wrn("seat_send_to_scratchpad")?, + ClientMessage::SeatToggleScratchpad { seat, name } => self + .handle_seat_toggle_scratchpad(seat, name) + .wrn("seat_toggle_scratchpad")?, + ClientMessage::SeatCycleScratchpad { seat, name } => self + .handle_seat_cycle_scratchpad(seat, name) + .wrn("seat_cycle_scratchpad")?, + ClientMessage::GetConnector { ty, idx } => { + self.handle_get_connector(ty, idx).wrn("get_connector")? + } + ClientMessage::ConnectorConnected { connector } => self + .handle_connector_connected(connector) + .wrn("connector_connected")?, + ClientMessage::ConnectorType { connector } => self + .handle_connector_type(connector) + .wrn("connector_type")?, + ClientMessage::ConnectorMode { connector } => self + .handle_connector_mode(connector) + .wrn("connector_mode")?, + ClientMessage::ConnectorSetPosition { connector, x, y } => self + .handle_connector_set_position(connector, x, y) + .wrn("connector_set_position")?, + ClientMessage::ConnectorSetEnabled { connector, enabled } => self + .handle_connector_set_enabled(connector, enabled) + .wrn("connector_set_enabled")?, + ClientMessage::SeatClose { seat } => self.handle_seat_close(seat).wrn("seat_close")?, + ClientMessage::SetStatus { status } => self.handle_set_status(status), + ClientMessage::GetTimer { name } => self.handle_get_timer(name).wrn("get_timer")?, + ClientMessage::RemoveTimer { timer } => { + self.handle_remove_timer(timer).wrn("remove_timer")? + } + ClientMessage::ProgramTimer { + timer, + initial, + periodic, + } => self + .handle_program_timer(timer, initial, periodic) + .wrn("program_timer")?, + ClientMessage::SetEnv { key, val } => self.handle_set_env(key, val), + ClientMessage::SetSeatFullscreen { seat, fullscreen } => self + .handle_set_seat_fullscreen(seat, fullscreen) + .wrn("set_seat_fullscreen")?, + ClientMessage::GetSeatFullscreen { seat } => self + .handle_get_seat_fullscreen(seat) + .wrn("get_seat_fullscreen")?, + ClientMessage::Reload => self.handle_reload(), + ClientMessage::GetDeviceConnectors { device } => self + .handle_get_connectors(Some(device), false) + .wrn("get_device_connectors")?, + ClientMessage::GetDrmDeviceSyspath { device } => self + .handle_get_drm_device_syspath(device) + .wrn("get_drm_device_syspath")?, + ClientMessage::GetDrmDeviceVendor { device } => self + .handle_get_drm_device_vendor(device) + .wrn("get_drm_device_vendor")?, + ClientMessage::GetDrmDeviceModel { device } => self + .handle_get_drm_device_model(device) + .wrn("get_drm_device_model")?, + ClientMessage::GetDrmDevices => self.handle_get_drm_devices(), + ClientMessage::GetDrmDevicePciId { device } => self + .handle_get_drm_device_pci_id(device) + .wrn("get_drm_device_pci_id")?, + ClientMessage::ResetColors => self.handle_reset_colors(), + ClientMessage::ResetSizes => self.handle_reset_sizes(), + ClientMessage::GetSize { sized } => self.handle_get_size(sized).wrn("get_size")?, + ClientMessage::SetSize { sized, size } => { + self.handle_set_size(sized, size).wrn("set_size")? + } + ClientMessage::ResetFont => self.handle_reset_font(), + ClientMessage::GetFont => self.handle_get_font(), + ClientMessage::SetFont { font } => self.handle_set_font(font), + ClientMessage::SetPxPerWheelScroll { device, px } => self + .handle_set_px_per_wheel_scroll(device, px) + .wrn("set_px_per_wheel_scroll")?, + ClientMessage::ConnectorSetScale { connector, scale } => self + .handle_connector_set_scale(connector, scale) + .wrn("connector_set_scale")?, + ClientMessage::ConnectorGetScale { connector } => self + .handle_connector_get_scale(connector) + .wrn("connector_get_scale")?, + ClientMessage::ConnectorSize { connector } => self + .handle_connector_size(connector) + .wrn("connector_size")?, + ClientMessage::SetCursorSize { seat, size } => self + .handle_set_cursor_size(seat, size) + .wrn("set_cursor_size")?, + ClientMessage::SetTapEnabled { device, enabled } => self + .handle_set_tap_enabled(device, enabled) + .wrn("set_tap_enabled")?, + ClientMessage::SetDragEnabled { device, enabled } => self + .handle_set_drag_enabled(device, enabled) + .wrn("set_drag_enabled")?, + ClientMessage::SetDragLockEnabled { device, enabled } => self + .handle_set_drag_lock_enabled(device, enabled) + .wrn("set_drag_lock_enabled")?, + ClientMessage::SetUseHardwareCursor { + seat, + use_hardware_cursor, + } => self + .handle_set_use_hardware_cursor(seat, use_hardware_cursor) + .wrn("set_use_hardware_cursor")?, + ClientMessage::DisablePointerConstraint { seat } => self + .handle_disable_pointer_constraint(seat) + .wrn("disable_pointer_constraint")?, + ClientMessage::MakeRenderDevice { device } => self + .handle_make_render_device(device) + .wrn("make_render_device")?, + ClientMessage::GetSeatCursorWorkspace { seat } => self + .handle_get_seat_cursor_workspace(seat) + .wrn("get_seat_cursor_workspace")?, + ClientMessage::GetSeatKeyboardWorkspace { seat } => self + .handle_get_seat_keyboard_workspace(seat) + .wrn("get_seat_keyboard_workspace")?, + ClientMessage::SetDefaultWorkspaceCapture { capture } => { + self.handle_set_default_workspace_capture(capture) + } + ClientMessage::GetDefaultWorkspaceCapture => { + self.handle_get_default_workspace_capture() + } + ClientMessage::SetWorkspaceCapture { workspace, capture } => self + .handle_set_workspace_capture(workspace, capture) + .wrn("set_workspace_capture")?, + ClientMessage::GetWorkspaceCapture { workspace } => self + .handle_get_workspace_capture(workspace) + .wrn("get_workspace_capture")?, + ClientMessage::SetNaturalScrollingEnabled { device, enabled } => self + .handle_set_natural_scrolling_enabled(device, enabled) + .wrn("set_natural_scrolling_enabled")?, + ClientMessage::SetGfxApi { device, api } => { + self.handle_set_gfx_api(device, api).wrn("set_gfx_api")? + } + ClientMessage::SetDirectScanoutEnabled { device, enabled } => self + .handle_set_direct_scanout_enabled(device, enabled) + .wrn("set_direct_scanout_enabled")?, + ClientMessage::ConnectorSetTransform { + connector, + transform, + } => self + .handle_connector_set_transform(connector, transform) + .wrn("connector_set_transform")?, + ClientMessage::SetDoubleClickIntervalUsec { usec } => { + self.handle_set_double_click_interval_usec(usec) + } + ClientMessage::SetDoubleClickDistance { dist } => { + self.handle_set_double_click_distance(dist) + } + ClientMessage::ConnectorModes { connector } => self + .handle_connector_modes(connector) + .wrn("connector_modes")?, + ClientMessage::ConnectorSetMode { connector, mode } => self + .handle_connector_set_mode(connector, mode) + .wrn("connector_set_mode")?, + ClientMessage::AddPollable { fd } => { + self.handle_add_pollable(fd).wrn("add_pollable")? + } + ClientMessage::RemovePollable { id } => self.handle_remove_pollable(id), + ClientMessage::AddInterest { pollable, writable } => self + .handle_add_interest(pollable, writable) + .wrn("add_interest")?, + ClientMessage::Run2 { + prog, + args, + env, + fds, + } => self.handle_run(prog, args, env, fds, None).wrn("run")?, + ClientMessage::Run3 { + prog, + args, + env, + fds, + tag, + } => self.handle_run(prog, args, env, fds, tag).wrn("run")?, + ClientMessage::DisableDefaultSeat => self.state.create_default_seat.set(false), + ClientMessage::DestroyKeymap { keymap } => self.handle_destroy_keymap(keymap), + ClientMessage::GetConnectorName { connector } => self + .handle_connector_name(connector) + .wrn("connector_name")?, + ClientMessage::GetConnectorModel { connector } => self + .handle_connector_model(connector) + .wrn("connector_model")?, + ClientMessage::GetConnectorManufacturer { connector } => self + .handle_connector_manufacturer(connector) + .wrn("connector_manufacturer")?, + ClientMessage::GetConnectorSerialNumber { connector } => self + .handle_connector_serial_number(connector) + .wrn("connector_serial_number")?, + ClientMessage::GetConnectors { + device, + connected_only, + } => self + .handle_get_connectors(device, connected_only) + .wrn("get_connectors")?, + ClientMessage::ConnectorGetPosition { connector } => self + .handle_connector_get_position(connector) + .wrn("connector_get_position")?, + ClientMessage::GetConfigDir => self.handle_get_config_dir(), + ClientMessage::GetWorkspaces => self.handle_get_workspaces(), + ClientMessage::UnsetEnv { key } => self.handle_unset_env(key), + ClientMessage::SetLogLevel { level } => self.handle_set_log_level(level), + ClientMessage::GetDrmDeviceDevnode { device } => self + .handle_get_drm_device_devnode(device) + .wrn("get_drm_device_devnode")?, + ClientMessage::GetInputDeviceSyspath { device } => self + .handle_get_input_device_syspath(device) + .wrn("get_input_device_syspath")?, + ClientMessage::GetInputDeviceDevnode { device } => self + .handle_get_input_device_devnode(device) + .wrn("get_input_device_devnode")?, + ClientMessage::SetIdle { timeout } => self.handle_set_idle(timeout), + ClientMessage::SetKeyPressEnablesDpms { enabled } => { + self.handle_set_key_press_enables_dpms(enabled) + } + ClientMessage::SetMouseMoveEnablesDpms { enabled } => { + self.handle_set_mouse_move_enables_dpms(enabled) + } + ClientMessage::MoveToOutput { + workspace, + connector, + } => self + .handle_move_to_output(workspace, connector) + .wrn("move_to_output")?, + ClientMessage::SetExplicitSyncEnabled { enabled } => { + self.handle_set_explicit_sync_enabled(enabled) + } + ClientMessage::DeviceSetKeymap { device, keymap } => self + .handle_set_device_keymap(device, keymap) + .wrn("set_device_keymap")?, + ClientMessage::SetForward { seat, forward } => { + self.handle_set_forward(seat, forward).wrn("set_forward")? + } + ClientMessage::AddShortcut2 { + seat, + mod_mask, + mods, + sym, + } => self + .handle_add_shortcut(seat, mod_mask, mods, sym) + .wrn("add_shortcut")?, + ClientMessage::SetFocusFollowsMouseMode { seat, mode } => self + .handle_set_focus_follows_mouse_mode(seat, mode) + .wrn("set_focus_follows_mouse_mode")?, + ClientMessage::SetInputDeviceConnector { + input_device, + connector, + } => self + .handle_set_input_device_connector(input_device, connector) + .wrn("set_input_device_connector")?, + ClientMessage::RemoveInputMapping { input_device } => self + .handle_remove_input_mapping(input_device) + .wrn("remove_input_mapping")?, + ClientMessage::SetWindowManagementEnabled { seat, enabled } => self + .handle_set_window_management_enabled(seat, enabled) + .wrn("set_window_management_enabled")?, + ClientMessage::SetVrrMode { connector, mode } => self + .handle_set_vrr_mode(connector, mode) + .wrn("set_vrr_mode")?, + ClientMessage::SetVrrCursorHz { connector, hz } => self + .handle_set_vrr_cursor_hz(connector, hz) + .wrn("set_vrr_cursor_hz")?, + ClientMessage::SetTearingMode { connector, mode } => self + .handle_set_tearing_mode(connector, mode) + .wrn("set_tearing_mode")?, + ClientMessage::SetCalibrationMatrix { device, matrix } => self + .handle_set_calibration_matrix(device, matrix) + .wrn("set_calibration_matrix")?, + ClientMessage::SetEiSocketEnabled { enabled } => { + self.handle_set_ei_socket_enabled(enabled) + } + ClientMessage::ConnectorSetFormat { connector, format } => self + .handle_connector_set_format(connector, format) + .wrn("connector_set_format")?, + ClientMessage::SetFlipMargin { device, margin } => self + .handle_set_flip_margin(device, margin) + .wrn("set_flip_margin")?, + ClientMessage::SetUiDragEnabled { enabled } => self.handle_set_ui_drag_enabled(enabled), + ClientMessage::SetUiDragThreshold { threshold } => { + self.handle_set_ui_drag_threshold(threshold) + } + ClientMessage::SetAnimationsEnabled { enabled } => { + self.handle_set_animations_enabled(enabled) + } + ClientMessage::SetAnimationDurationMs { duration_ms } => { + self.handle_set_animation_duration_ms(duration_ms) + } + ClientMessage::SetAnimationCurve { curve } => self.handle_set_animation_curve(curve), + ClientMessage::SetAnimationStyle { style } => self.handle_set_animation_style(style), + ClientMessage::SetAnimationCubicBezier { x1, y1, x2, y2 } => { + self.handle_set_animation_cubic_bezier(x1, y1, x2, y2) + } + ClientMessage::SetXScalingMode { mode } => self + .handle_set_x_scaling_mode(mode) + .wrn("set_x_scaling_mode")?, + ClientMessage::SetIdleGracePeriod { period } => { + self.handle_set_idle_grace_period(period) + } + ClientMessage::SetColorManagementEnabled { enabled } => { + self.handle_set_color_management_enabled(enabled) + } + ClientMessage::ConnectorSetColors { + connector, + color_space, + eotf, + } => self + .handle_connector_set_colors(connector, color_space, eotf) + .wrn("connector_set_colors")?, + ClientMessage::ConnectorSetBrightness { + connector, + brightness, + } => self + .handle_connector_set_brightness(connector, brightness) + .wrn("connector_set_brightness")?, + ClientMessage::SetFloatAboveFullscreen { above } => { + self.handle_set_float_above_fullscreen(above) + } + ClientMessage::GetFloatAboveFullscreen => self.handle_get_float_above_fullscreen(), + ClientMessage::GetSeatFloatPinned { seat } => self + .handle_get_seat_float_pinned(seat) + .wrn("get_seat_float_pinned")?, + ClientMessage::SetSeatFloatPinned { seat, pinned } => self + .handle_set_seat_float_pinned(seat, pinned) + .wrn("set_seat_float_pinned")?, + ClientMessage::SetShowFloatPinIcon { show } => { + self.handle_set_show_float_pin_icon(show) + } + ClientMessage::GetConnectorActiveWorkspace { connector } => self + .handle_get_connector_active_workspace(connector) + .wrn("get_connector_active_workspace")?, + ClientMessage::GetConnectorWorkspaces { connector } => self + .handle_get_connector_workspaces(connector) + .wrn("get_connector_workspaces")?, + ClientMessage::GetWorkspaceConnector { workspace } => self + .handle_get_workspace_connector(workspace) + .wrn("get_workspace_connector")?, + ClientMessage::GetConnectorInDirection { + connector, + direction, + } => self + .handle_get_connector_in_direction(connector, direction) + .wrn("get_connector_in_direction")?, + ClientMessage::GetClients => self.handle_get_clients(), + ClientMessage::ClientExists { client } => self.handle_client_exists(client), + ClientMessage::ClientIsXwayland { client } => self + .handle_client_is_xwayland(client) + .wrn("client_is_xwayland")?, + ClientMessage::ClientKill { client } => self.handle_client_kill(client), + ClientMessage::WindowExists { window } => self.handle_window_exists(window), + ClientMessage::GetWorkspaceWindow { workspace } => self + .handle_get_workspace_window(workspace) + .wrn("get_workspace_window")?, + ClientMessage::GetSeatKeyboardWindow { seat } => self + .handle_get_seat_keyboard_window(seat) + .wrn("get_seat_keyboard_window")?, + ClientMessage::SeatFocusWindow { seat, window } => self + .handle_seat_focus_window(seat, window) + .wrn("seat_focus_window")?, + ClientMessage::GetWindowTitle { window } => self + .handle_get_window_title(window) + .wrn("get_window_title")?, + ClientMessage::GetWindowType { window } => { + self.handle_get_window_type(window).wrn("get_window_type")? + } + ClientMessage::GetWindowId { window } => { + self.handle_get_window_id(window).wrn("get_window_id")? + } + ClientMessage::GetWindowParent { window } => self + .handle_get_window_parent(window) + .wrn("get_window_parent")?, + ClientMessage::GetWindowWorkspace { window } => self + .handle_get_window_workspace(window) + .wrn("get_window_workspace")?, + ClientMessage::GetWindowChildren { window } => self + .handle_get_window_children(window) + .wrn("get_window_children")?, + ClientMessage::GetWindowSplit { window } => self + .handle_get_window_split(window) + .wrn("get_window_split")?, + ClientMessage::SetWindowSplit { window, axis } => self + .handle_set_window_split(window, axis) + .wrn("set_window_split")?, + ClientMessage::GetWindowMono { window } => { + self.handle_get_window_mono(window).wrn("get_window_mono")? + } + ClientMessage::SetWindowMono { window, mono } => self + .handle_set_window_mono(window, mono) + .wrn("set_window_mono")?, + ClientMessage::WindowMove { window, direction } => self + .handle_window_move(window, direction) + .wrn("window_move")?, + ClientMessage::CreateWindowSplit { window, axis } => self + .handle_create_window_split(window, axis) + .wrn("create_window_split")?, + ClientMessage::WindowClose { window } => { + self.handle_window_close(window).wrn("close_window")? + } + ClientMessage::GetWindowFloating { window } => self + .handle_get_window_floating(window) + .wrn("get_window_floating")?, + ClientMessage::SetWindowFloating { window, floating } => self + .handle_set_window_floating(window, floating) + .wrn("set_window_floating")?, + ClientMessage::SetWindowWorkspace { window, workspace } => self + .handle_set_window_workspace(window, workspace) + .wrn("set_window_workspace")?, + ClientMessage::WindowSendToScratchpad { window, name } => self + .handle_window_send_to_scratchpad(window, name) + .wrn("window_send_to_scratchpad")?, + ClientMessage::SetWindowFullscreen { window, fullscreen } => self + .handle_set_window_fullscreen(window, fullscreen) + .wrn("set_window_fullscreen")?, + ClientMessage::GetWindowFullscreen { window } => self + .handle_get_window_fullscreen(window) + .wrn("get_window_fullscreen")?, + ClientMessage::GetWindowFloatPinned { window } => self + .handle_get_window_float_pinned(window) + .wrn("get_window_float_pinned")?, + ClientMessage::SetWindowFloatPinned { window, pinned } => self + .handle_set_window_float_pinned(window, pinned) + .wrn("set_window_float_pinned")?, + ClientMessage::GetWindowIsVisible { window } => self + .handle_get_window_is_visible(window) + .wrn("get_window_is_visible")?, + ClientMessage::GetWindowClient { window } => self + .handle_get_window_client(window) + .wrn("get_window_client")?, + ClientMessage::CreateClientMatcher { criterion } => self + .handle_create_client_matcher(criterion) + .wrn("create_window_matcher")?, + ClientMessage::DestroyClientMatcher { matcher } => { + self.handle_destroy_client_matcher(matcher) + } + ClientMessage::EnableClientMatcherEvents { matcher } => self + .handle_enable_client_matcher_events(matcher) + .wrn("enable_window_matcher_events")?, + ClientMessage::CreateWindowMatcher { criterion } => self + .handle_create_window_matcher(criterion) + .wrn("create_window_matcher")?, + ClientMessage::DestroyWindowMatcher { matcher } => { + self.handle_destroy_window_matcher(matcher) + } + ClientMessage::EnableWindowMatcherEvents { matcher } => self + .handle_enable_window_matcher_events(matcher) + .wrn("enable_window_matcher_events")?, + ClientMessage::SetWindowMatcherAutoFocus { + matcher, + auto_focus, + } => self + .handle_set_window_matcher_auto_focus(matcher, auto_focus) + .wrn("set_window_matcher_auto_focus")?, + ClientMessage::SetWindowMatcherInitialTileState { + matcher, + tile_state, + } => self + .handle_set_window_matcher_initial_tile_state(matcher, tile_state) + .wrn("set_window_matcher_initial_tile_state")?, + ClientMessage::SetPointerRevertKey { seat, key } => self + .handle_set_pointer_revert_key(seat, key) + .wrn("set_pointer_revert_key")?, + ClientMessage::SetClickMethod { device, method } => self + .handle_set_click_method(device, method) + .wrn("set_click_method")?, + ClientMessage::SetMiddleButtonEmulationEnabled { device, enabled } => self + .handle_set_middle_button_emulation_enabled(device, enabled) + .wrn("set_middle_button_emulation_enabled")?, + ClientMessage::GetContentType { window } => self + .handle_get_content_type(window) + .wrn("get_content_type")?, + ClientMessage::SetShowBar { show } => self.handle_set_show_bar(show), + ClientMessage::GetShowBar => self.handle_get_show_bar(), + ClientMessage::SetShowTitles { show } => self.handle_set_show_titles(show), + ClientMessage::GetShowTitles => self.handle_get_show_titles(), + ClientMessage::SetFloatingTitles { floating } => { + self.handle_set_floating_titles(floating) + } + ClientMessage::GetFloatingTitles => self.handle_get_floating_titles(), + ClientMessage::SetBarPosition { position } => self + .handle_set_bar_position(position) + .wrn("set_bar_position")?, + ClientMessage::GetBarPosition => self.handle_get_bar_position(), + ClientMessage::SetCornerRadius { radius } => self.handle_set_corner_radius(radius), + ClientMessage::GetCornerRadius => self.handle_get_corner_radius(), + ClientMessage::SeatFocusHistory { seat, timeline } => self + .handle_seat_focus_history(seat, timeline) + .wrn("seat_focus_history")?, + ClientMessage::SeatFocusHistorySetOnlyVisible { seat, only_visible } => self + .handle_seat_focus_history_set_only_visible(seat, only_visible) + .wrn("seat_focus_history_set_only_visible")?, + ClientMessage::SeatFocusHistorySetSameWorkspace { + seat, + same_workspace, + } => self + .handle_seat_focus_history_set_same_workspace(seat, same_workspace) + .wrn("seat_focus_history_set_same_workspace")?, + ClientMessage::SeatFocusLayerRel { seat, direction } => self + .handle_seat_focus_layer_rel(seat, direction) + .wrn("seat_focus_layer_rel")?, + ClientMessage::SeatFocusTiles { seat } => { + self.handle_seat_focus_tiles(seat).wrn("seat_focus_tiles")? + } + ClientMessage::SeatFocusFloats { seat } => self + .handle_seat_focus_floats(seat) + .wrn("seat_focus_floats")?, + ClientMessage::SeatToggleFocusFloatTiled { seat } => self + .handle_seat_toggle_focus_float_tiled(seat) + .wrn("seat_toggle_focus_float_tiled")?, + ClientMessage::SetMiddleClickPasteEnabled { enabled } => { + self.handle_set_middle_click_paste_enabled(enabled) + } + ClientMessage::SetWorkspaceDisplayOrder { order } => { + self.handle_set_workspace_display_order(order) + } + ClientMessage::SeatCreateMark { seat, kc } => self + .handle_seat_create_mark(seat, kc) + .wrn("seat_create_mark")?, + ClientMessage::SeatJumpToMark { seat, kc } => self + .handle_seat_jump_to_mark(seat, kc) + .wrn("seat_jump_to_mark")?, + ClientMessage::SeatCopyMark { seat, src, dst } => self + .handle_seat_copy_mark(seat, src, dst) + .wrn("seat_copy_mark")?, + ClientMessage::ConnectorSetBlendSpace { + connector, + blend_space, + } => self + .handle_connector_set_blend_space(connector, blend_space) + .wrn("connector_set_blend_space")?, + ClientMessage::SetBarFont { font } => self.handle_set_bar_font(font), + ClientMessage::SetTitleFont { font } => self.handle_set_title_font(font), + ClientMessage::ShowWorkspaceOn { + seat, + workspace, + connector, + } => self + .handle_show_workspace(seat, workspace, Some(connector)) + .wrn("show_workspace_on")?, + ClientMessage::SeatSetSimpleImEnabled { seat, enabled } => self + .handle_seat_set_simple_im_enabled(seat, enabled) + .wrn("seat_set_simple_im_enabled")?, + ClientMessage::SeatGetSimpleImEnabled { seat } => self + .handle_seat_get_simple_im_enabled(seat) + .wrn("seat_get_simple_im_enabled")?, + ClientMessage::SeatReloadSimpleIm { seat } => self + .handle_seat_reload_simple_im(seat) + .wrn("seat_reload_simple_im")?, + ClientMessage::SeatEnableUnicodeInput { seat } => self + .handle_seat_enable_unicode_input(seat) + .wrn("seat_enable_unicode_input")?, + ClientMessage::SeatWarpMouseToFocus { seat } => self + .handle_seat_warp_mouse_to_focus(seat) + .wrn("seat_warp_mouse_to_focus")?, + ClientMessage::SeatSetMouseFollowsFocus { seat, enabled } => self + .handle_seat_set_mouse_follows_focus(seat, enabled) + .wrn("seat_set_mouse_follows_focus")?, + ClientMessage::ConnectorSetUseNativeGamut { + connector, + use_native_gamut, + } => self + .handle_connector_set_use_native_gamut(connector, use_native_gamut) + .wrn("connector_set_use_native_gamut")?, + ClientMessage::KeymapFromNames { + rules, + model, + groups, + options, + } => self + .handle_keymap_from_names(rules, model, groups, options) + .wrn("keymap_from_names")?, + ClientMessage::SetFallbackOutputMode { seat, mode } => self + .handle_set_fallback_output_mode(seat, mode) + .wrn("set_fallback_output_mode")?, + ClientMessage::SetXWaylandEnabled { enabled } => self + .handle_set_x_wayland_enabled(enabled) + .wrn("set_x_wayland_enabled")?, + ClientMessage::ConnectorSupportsArbitraryModes { connector } => self + .handle_connector_supports_arbitrary_modes(connector) + .wrn("connector_supports_arbitrary_modes")?, + ClientMessage::GetConnectorByName { name } => self.handle_get_connector_by_name(name), + 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")?, + ClientMessage::SeatToggleTab { seat } => { + self.handle_seat_toggle_tab(seat).wrn("seat_toggle_tab")? + } + ClientMessage::SeatMakeGroup { + seat, + axis, + ephemeral, + } => self + .handle_seat_make_group(seat, axis, ephemeral) + .wrn("seat_make_group")?, + ClientMessage::SeatChangeGroupOpposite { seat } => self + .handle_seat_change_group_opposite(seat) + .wrn("seat_change_group_opposite")?, + ClientMessage::SeatEqualize { seat, recursive } => self + .handle_seat_equalize(seat, recursive) + .wrn("seat_equalize")?, + ClientMessage::SetAutotile { enabled } => { + self.state.theme.autotile_enabled.set(enabled); + } + ClientMessage::GetAutotile => { + self.respond(Response::GetAutotile { + enabled: self.state.theme.autotile_enabled.get(), + }); + } + ClientMessage::SeatToggleExpand { .. } => { + // Removed feature; kept for binary protocol compatibility. + } + ClientMessage::SetTabTitleAlign { align } => { + use jay_theme::TabTitleAlign; + let val = match align { + 1 => TabTitleAlign::Center, + 2 => TabTitleAlign::End, + _ => TabTitleAlign::Start, + }; + self.state.theme.tab_title_align.set(val); + } + ClientMessage::SeatMoveTab { seat, right } => self + .handle_seat_move_tab(seat, right) + .wrn("seat_move_tab")?, + } + Ok(()) + } + +} diff --git a/src/config/handler/input_devices.rs b/src/config/handler/input_devices.rs new file mode 100644 index 00000000..23dd126c --- /dev/null +++ b/src/config/handler/input_devices.rs @@ -0,0 +1,276 @@ +use super::*; + +impl ConfigProxyHandler { + pub(super) fn handle_set_device_keymap( + &self, + device: InputDevice, + keymap: Keymap, + ) -> Result<(), CphError> { + let dev = self.get_device_handler_data(device)?; + let map = if keymap.is_invalid() { + None + } else { + Some(self.get_keymap(keymap)?) + }; + dev.set_keymap(&self.state, map); + Ok(()) + } + + pub(super) fn handle_set_input_device_connector( + &self, + input_device: InputDevice, + connector: Connector, + ) -> Result<(), CphError> { + let dev = self.get_device_handler_data(input_device)?; + let output = self.get_output_node(connector)?; + dev.set_output(&self.state, Some(&output.global)); + Ok(()) + } + + pub(super) fn handle_remove_input_mapping( + &self, + input_device: InputDevice, + ) -> Result<(), CphError> { + let dev = self.get_device_handler_data(input_device)?; + dev.set_output(&self.state, None); + Ok(()) + } + + pub(super) fn handle_set_seat( + &self, + device: InputDevice, + seat: Seat, + ) -> Result<(), CphError> { + let seat = if seat.is_invalid() { + None + } else { + Some(self.get_seat(seat)?) + }; + let dev = self.get_device_handler_data(device)?; + dev.set_seat(&self.state, seat); + Ok(()) + } + + pub(super) fn handle_set_left_handed( + &self, + device: InputDevice, + left_handed: bool, + ) -> Result<(), CphError> { + let dev = self.get_device_handler_data(device)?; + dev.set_left_handed(&self.state, left_handed); + Ok(()) + } + + pub(super) fn handle_set_accel_profile( + &self, + device: InputDevice, + accel_profile: AccelProfile, + ) -> Result<(), CphError> { + let dev = self.get_device_handler_data(device)?; + let profile = match accel_profile { + ACCEL_PROFILE_FLAT => InputDeviceAccelProfile::Flat, + ACCEL_PROFILE_ADAPTIVE => InputDeviceAccelProfile::Adaptive, + _ => return Err(CphError::UnknownAccelProfile(accel_profile)), + }; + dev.set_accel_profile(&self.state, profile); + Ok(()) + } + + pub(super) fn handle_set_accel_speed( + &self, + device: InputDevice, + speed: f64, + ) -> Result<(), CphError> { + let dev = self.get_device_handler_data(device)?; + dev.set_accel_speed(&self.state, speed); + Ok(()) + } + + pub(super) fn handle_set_px_per_wheel_scroll( + &self, + device: InputDevice, + px: f64, + ) -> Result<(), CphError> { + let dev = self.get_device_handler_data(device)?; + dev.set_px_per_scroll_wheel(&self.state, px); + Ok(()) + } + + pub(super) fn handle_set_tap_enabled( + &self, + device: InputDevice, + enabled: bool, + ) -> Result<(), CphError> { + let dev = self.get_device_handler_data(device)?; + dev.set_tap_enabled(&self.state, enabled); + Ok(()) + } + + pub(super) fn handle_set_drag_enabled( + &self, + device: InputDevice, + enabled: bool, + ) -> Result<(), CphError> { + let dev = self.get_device_handler_data(device)?; + dev.set_drag_enabled(&self.state, enabled); + Ok(()) + } + + pub(super) fn handle_set_natural_scrolling_enabled( + &self, + device: InputDevice, + enabled: bool, + ) -> Result<(), CphError> { + let dev = self.get_device_handler_data(device)?; + dev.set_natural_scrolling_enabled(&self.state, enabled); + Ok(()) + } + + pub(super) fn handle_set_drag_lock_enabled( + &self, + device: InputDevice, + enabled: bool, + ) -> Result<(), CphError> { + let dev = self.get_device_handler_data(device)?; + dev.set_drag_lock_enabled(&self.state, enabled); + Ok(()) + } + + pub(super) fn handle_set_transform_matrix( + &self, + device: InputDevice, + matrix: [[f64; 2]; 2], + ) -> Result<(), CphError> { + let dev = self.get_device_handler_data(device)?; + dev.set_transform_matrix(&self.state, matrix); + Ok(()) + } + + pub(super) fn handle_set_calibration_matrix( + &self, + device: InputDevice, + matrix: [[f32; 3]; 2], + ) -> Result<(), CphError> { + let dev = self.get_device_handler_data(device)?; + dev.set_calibration_matrix(&self.state, matrix); + Ok(()) + } + + pub(super) fn handle_set_click_method( + &self, + device: InputDevice, + click_method: ClickMethod, + ) -> Result<(), CphError> { + let dev = self.get_device_handler_data(device)?; + let method = match click_method { + CLICK_METHOD_NONE => InputDeviceClickMethod::None, + CLICK_METHOD_BUTTON_AREAS => InputDeviceClickMethod::ButtonAreas, + CLICK_METHOD_CLICKFINGER => InputDeviceClickMethod::Clickfinger, + _ => return Err(CphError::UnknownClickMethod(click_method)), + }; + dev.set_click_method(&self.state, method); + Ok(()) + } + + pub(super) fn handle_set_middle_button_emulation_enabled( + &self, + device: InputDevice, + enabled: bool, + ) -> Result<(), CphError> { + let dev = self.get_device_handler_data(device)?; + dev.set_middle_button_emulation_enabled(&self.state, enabled); + Ok(()) + } + + pub(super) fn handle_get_device_name(&self, device: InputDevice) -> Result<(), CphError> { + let dev = self.get_device_handler_data(device)?; + let name = dev.device.name(); + self.respond(Response::GetDeviceName { + name: name.to_string(), + }); + Ok(()) + } + + pub(super) fn handle_get_input_device_syspath( + &self, + device: InputDevice, + ) -> Result<(), CphError> { + let dev = self.get_device_handler_data(device)?; + self.respond(Response::GetInputDeviceSyspath { + syspath: dev.syspath.clone().unwrap_or_default(), + }); + Ok(()) + } + + pub(super) fn handle_get_input_device_devnode( + &self, + device: InputDevice, + ) -> Result<(), CphError> { + let dev = self.get_device_handler_data(device)?; + self.respond(Response::GetInputDeviceDevnode { + devnode: dev.devnode.clone().unwrap_or_default(), + }); + Ok(()) + } + + pub(super) fn handle_has_capability( + &self, + device: InputDevice, + cap: Capability, + ) -> Result<(), CphError> { + let dev = self.get_device_handler_data(device)?; + let mut is_unknown = false; + let has_cap = 'has_cap: { + let cap = match cap { + CAP_KEYBOARD => InputDeviceCapability::Keyboard, + CAP_POINTER => InputDeviceCapability::Pointer, + CAP_TOUCH => InputDeviceCapability::Touch, + CAP_TABLET_TOOL => InputDeviceCapability::TabletTool, + CAP_TABLET_PAD => InputDeviceCapability::TabletPad, + CAP_GESTURE => InputDeviceCapability::Gesture, + CAP_SWITCH => InputDeviceCapability::Switch, + _ => { + is_unknown = true; + break 'has_cap false; + } + }; + dev.device.has_capability(cap) + }; + self.respond(Response::HasCapability { has: has_cap }); + if is_unknown { + Err(CphError::UnknownCapability(cap)) + } else { + Ok(()) + } + } + + pub(super) fn handle_get_input_devices(&self, seat: Option) { + let id = seat.map(|s| SeatId::from_raw(s.0 as _)); + let matches = |dhd: &DeviceHandlerData| { + let id = match id { + Some(id) => id, + _ => return true, + }; + if let Some(seat) = dhd.seat.get() { + return seat.id() == id; + } + false + }; + let mut res = vec![]; + { + let devs = self.state.input_device_handlers.borrow_mut(); + for dev in devs.values() { + if matches(&dev.data) { + res.push(InputDevice(dev.id.raw() as _)); + } + } + } + self.respond(Response::GetInputDevices { devices: res }); + } + + pub(super) fn handle_grab(&self, kb: InputDevice, grab: bool) -> Result<(), CphError> { + let kb = self.get_kb(kb)?; + kb.grab(grab); + Ok(()) + } +} diff --git a/src/config/handler/keymaps.rs b/src/config/handler/keymaps.rs new file mode 100644 index 00000000..2b493466 --- /dev/null +++ b/src/config/handler/keymaps.rs @@ -0,0 +1,64 @@ +use super::*; + +impl ConfigProxyHandler { + fn id(&self) -> u64 { + self.next_id.fetch_add(1) + } + + pub(super) fn handle_parse_keymap(&self, keymap: &str) -> Result<(), CphError> { + let (keymap, res) = match self.state.kb_ctx.parse_keymap(keymap.as_bytes()) { + Ok(keymap) => { + let id = Keymap(self.id()); + self.keymaps.set(id, keymap); + (id, Ok(())) + } + Err(e) => (Keymap::INVALID, Err(CphError::ParseKeymapError(e))), + }; + self.respond(Response::ParseKeymap { keymap }); + res + } + + pub(super) fn handle_keymap_from_names( + &self, + rules: Option<&str>, + model: Option<&str>, + groups: Option>>, + options: Option>, + ) -> Result<(), CphError> { + let kbvm_groups = groups.map(|groups| { + groups + .iter() + .map(|g| kbvm::xkb::rmlvo::Group { + layout: g.layout, + variant: g.variant, + }) + .collect::>() + }); + let (keymap, res) = match self.state.kb_ctx.keymap_from_names( + rules, + model, + kbvm_groups.as_deref(), + options.as_deref(), + ) { + Ok(keymap) => { + let id = Keymap(self.id()); + self.keymaps.set(id, keymap); + (id, Ok(())) + } + Err(e) => (Keymap::INVALID, Err(CphError::ParseKeymapError(e))), + }; + self.respond(Response::KeymapFromNames { keymap }); + res + } + + pub(super) fn get_keymap(&self, keymap: Keymap) -> Result, CphError> { + match self.keymaps.get(&keymap) { + Some(k) => Ok(k), + None => Err(CphError::KeymapDoesNotExist(keymap)), + } + } + + pub(super) fn handle_destroy_keymap(&self, keymap: Keymap) { + self.keymaps.remove(&keymap); + } +} diff --git a/src/config/handler/matchers.rs b/src/config/handler/matchers.rs new file mode 100644 index 00000000..ed9577d1 --- /dev/null +++ b/src/config/handler/matchers.rs @@ -0,0 +1,300 @@ +use super::*; + +impl ConfigProxyHandler { + fn get_client_matcher( + &self, + matcher: ClientMatcher, + ) -> Result>, CphError> { + self.client_matchers + .get(&matcher) + .ok_or(CphError::ClientMatcherDoesNotExist(matcher)) + } + + fn sort_generic_matcher( + &self, + generic: &mut GenericCriterionPayload, + key: impl FnMut(&T) -> K, + ) where + K: Ord, + { + match generic { + GenericCriterionPayload::List { list, .. } | GenericCriterionPayload::Exactly { list, .. } => { + list.sort_by_key(key) + } + GenericCriterionPayload::Matcher(_) | GenericCriterionPayload::Not(_) => {} + } + } + + fn create_generic_matcher( + &self, + mgr: &Mgr, + generic: &GenericCriterionPayload, + upstream: &mut Vec>>, + get_matcher: impl Fn(&Matcher) -> Result>, CphError>, + ) -> Result>, CphError> + where + Crit: Clone + Hash + Eq, + Mgr: CritMgrExt, + { + let mut get_upstream = |m: &Matcher| -> Result<_, CphError> { + let m = get_matcher(m)?; + let node = m.node.clone(); + upstream.push(m); + Ok(node) + }; + let node = match generic { + GenericCriterionPayload::Matcher(m) => get_matcher(m)?.node.clone(), + GenericCriterionPayload::Not(m) => mgr.not(&get_upstream(m)?), + GenericCriterionPayload::List { list, all } => { + let mut m = Vec::with_capacity(list.len()); + for c in list { + m.push(get_upstream(c)?); + } + mgr.list(&m, *all) + } + GenericCriterionPayload::Exactly { list, num } => { + let mut m = Vec::with_capacity(list.len()); + for c in list { + m.push(get_upstream(c)?); + } + mgr.exactly(&m, *num) + } + }; + Ok(node) + } + + pub(super) fn handle_create_client_matcher( + &self, + mut criterion: ClientCriterionPayload, + ) -> Result<(), CphError> { + if let ClientCriterionPayload::Generic(generic) = &mut criterion { + self.sort_generic_matcher(generic, |m| m.0); + } + let id = ClientMatcher(self.client_matcher_ids.fetch_add(1)); + let cache = &self.client_matcher_cache; + if let Some(matcher) = cache.get(&criterion) + && let Some(matcher) = matcher.upgrade() + { + self.client_matchers.set(id, matcher); + self.respond(Response::CreateClientMatcher { matcher: id }); + return Ok(()); + } + let mgr = &self.state.cl_matcher_manager; + let mut upstream = vec![]; + let matcher = match &criterion { + ClientCriterionPayload::Generic(m) => { + self.create_generic_matcher(mgr, m, &mut upstream, |m| self.get_client_matcher(*m))? + } + ClientCriterionPayload::String { + string, + field, + regex, + } => { + let needle = match *regex { + true => { + let regex = Regex::new(string).map_err(CphError::InvalidRegex)?; + CritLiteralOrRegex::Regex(regex) + } + false => CritLiteralOrRegex::Literal(string.to_string()), + }; + match *field { + ClientCriterionStringField::SandboxEngine => mgr.sandbox_engine(needle), + ClientCriterionStringField::SandboxAppId => mgr.sandbox_app_id(needle), + ClientCriterionStringField::SandboxInstanceId => { + mgr.sandbox_instance_id(needle) + } + ClientCriterionStringField::Comm => mgr.comm(needle), + ClientCriterionStringField::Exe => mgr.exe(needle), + ClientCriterionStringField::Tag => mgr.tag(needle), + } + } + ClientCriterionPayload::Sandboxed => mgr.sandboxed(), + ClientCriterionPayload::Uid(p) => mgr.uid(*p), + ClientCriterionPayload::Pid(p) => mgr.pid(*p), + ClientCriterionPayload::IsXwayland => mgr.is_xwayland(), + }; + let cached = Rc::new(CachedCriterion { + crit: criterion.clone(), + cache: cache.clone(), + upstream, + node: matcher.clone(), + }); + cache.set(criterion, Rc::downgrade(&cached)); + self.client_matchers.set(id, cached); + self.respond(Response::CreateClientMatcher { matcher: id }); + Ok(()) + } + + pub(super) fn handle_destroy_client_matcher(&self, matcher: ClientMatcher) { + self.client_matchers.remove(&matcher); + self.client_matcher_leafs.remove(&matcher); + } + + pub(super) fn handle_enable_client_matcher_events( + self: &Rc, + matcher: ClientMatcher, + ) -> Result<(), CphError> { + if self.client_matcher_leafs.contains(&matcher) { + return Ok(()); + } + let upstream = self.get_client_matcher(matcher)?; + let slf = self.clone(); + let leaf = self + .state + .cl_matcher_manager + .leaf(&upstream.node, move |id| { + let client = ConfigClient(id.raw()); + slf.send(&ServerMessage::ClientMatcherMatched { matcher, client }); + let slf = slf.clone(); + Box::new(move || { + slf.send(&ServerMessage::ClientMatcherUnmatched { matcher, client }); + }) + }); + self.client_matcher_leafs.set(matcher, leaf); + self.state.cl_matcher_manager.rematch_all(&self.state); + Ok(()) + } + + fn get_window_matcher( + &self, + matcher: WindowMatcher, + ) -> Result>, CphError> { + self.window_matchers + .get(&matcher) + .ok_or(CphError::WindowMatcherDoesNotExist(matcher)) + } + + pub(super) fn handle_create_window_matcher( + &self, + mut criterion: WindowCriterionPayload, + ) -> Result<(), CphError> { + if let WindowCriterionPayload::Generic(generic) = &mut criterion { + self.sort_generic_matcher(generic, |m| m.0); + } + let id = WindowMatcher(self.window_matcher_ids.fetch_add(1)); + let cache = &self.window_matcher_cache; + if let Some(matcher) = cache.get(&criterion) + && let Some(matcher) = matcher.upgrade() + { + self.window_matchers.set(id, matcher); + self.respond(Response::CreateWindowMatcher { matcher: id }); + return Ok(()); + } + let mgr = &self.state.tl_matcher_manager; + let mut upstream = vec![]; + let matcher = match &criterion { + WindowCriterionPayload::Generic(m) => { + self.create_generic_matcher(mgr, m, &mut upstream, |m| self.get_window_matcher(*m))? + } + WindowCriterionPayload::String { + string, + field, + regex, + } => { + let needle = match *regex { + true => { + let regex = Regex::new(string).map_err(CphError::InvalidRegex)?; + CritLiteralOrRegex::Regex(regex) + } + false => CritLiteralOrRegex::Literal(string.to_string()), + }; + match *field { + WindowCriterionStringField::Title => mgr.title(needle), + WindowCriterionStringField::AppId => mgr.app_id(needle), + WindowCriterionStringField::Tag => mgr.tag(needle), + WindowCriterionStringField::XClass => mgr.class(needle), + WindowCriterionStringField::XInstance => mgr.instance(needle), + WindowCriterionStringField::XRole => mgr.role(needle), + WindowCriterionStringField::Workspace => mgr.workspace(needle), + } + } + WindowCriterionPayload::Types(t) => mgr.kind(*t), + WindowCriterionPayload::Client(c) => { + self.state.cl_matcher_manager.rematch_all(&self.state); + mgr.client(&self.state, &self.get_client_matcher(*c)?.node) + } + WindowCriterionPayload::Floating => mgr.floating(), + WindowCriterionPayload::Visible => mgr.visible(), + WindowCriterionPayload::Urgent => mgr.urgent(), + WindowCriterionPayload::SeatFocus(seat) => mgr.seat_focus(&*self.get_seat(*seat)?), + WindowCriterionPayload::Fullscreen => mgr.fullscreen(), + WindowCriterionPayload::JustMapped => mgr.just_mapped(), + WindowCriterionPayload::Workspace(w) => mgr.workspace(CritLiteralOrRegex::Literal( + self.get_workspace(*w)?.to_string(), + )), + WindowCriterionPayload::ContentTypes(t) => mgr.content_type(*t), + }; + let cached = Rc::new(CachedCriterion { + crit: criterion.clone(), + cache: cache.clone(), + upstream, + node: matcher.clone(), + }); + cache.set(criterion, Rc::downgrade(&cached)); + self.window_matchers.set(id, cached); + self.respond(Response::CreateWindowMatcher { matcher: id }); + Ok(()) + } + + pub(super) fn handle_destroy_window_matcher(&self, matcher: WindowMatcher) { + self.window_matchers.remove(&matcher); + self.window_matcher_leafs.remove(&matcher); + self.window_matcher_no_auto_focus.remove(&matcher); + self.window_matcher_initial_tile_state.remove(&matcher); + } + + pub(super) fn handle_enable_window_matcher_events( + self: &Rc, + matcher: WindowMatcher, + ) -> Result<(), CphError> { + if self.window_matcher_leafs.contains(&matcher) { + return Ok(()); + } + let upstream = self.get_window_matcher(matcher)?; + let mut node = upstream.node.clone(); + if !upstream.any(&|crit| matches!(crit, WindowCriterionPayload::Types(_))) { + let list = [self.window_matcher_std_kinds.clone(), node]; + node = self.state.tl_matcher_manager.list(&list, true); + } + let slf = self.clone(); + let leaf = self.state.tl_matcher_manager.leaf(&node, move |tl| { + let window = slf.tl_id_to_window(tl); + slf.send(&ServerMessage::WindowMatcherMatched { matcher, window }); + let slf = slf.clone(); + Box::new(move || { + slf.send(&ServerMessage::WindowMatcherUnmatched { matcher, window }); + }) + }); + self.window_matcher_leafs.set(matcher, leaf); + self.state.tl_matcher_manager.rematch_all(&self.state); + Ok(()) + } + + pub(super) fn handle_set_window_matcher_auto_focus( + &self, + matcher: WindowMatcher, + auto_focus: bool, + ) -> Result<(), CphError> { + if auto_focus { + self.window_matcher_no_auto_focus.remove(&matcher); + } else { + let m = self.get_window_matcher(matcher)?; + self.window_matcher_no_auto_focus.set(matcher, m); + } + Ok(()) + } + + pub(super) fn handle_set_window_matcher_initial_tile_state( + &self, + matcher: WindowMatcher, + tile_state: ConfigTileState, + ) -> Result<(), CphError> { + let Ok(tile_state) = tile_state.try_into() else { + return Err(CphError::UnknownTileState(tile_state)); + }; + let m = self.get_window_matcher(matcher)?; + self.window_matcher_initial_tile_state + .set(matcher, (m, tile_state)); + Ok(()) + } +} diff --git a/src/config/handler/options.rs b/src/config/handler/options.rs new file mode 100644 index 00000000..065c8571 --- /dev/null +++ b/src/config/handler/options.rs @@ -0,0 +1,131 @@ +use super::*; + +impl ConfigProxyHandler { + pub(super) fn handle_set_ei_socket_enabled(&self, enabled: bool) { + self.state.set_ei_socket_enabled(enabled); + } + + pub(super) fn handle_set_gfx_api( + &self, + device: Option, + api: GfxApi, + ) -> Result<(), CphError> { + let Ok(api) = api.try_into() else { + return Err(CphError::UnknownGfxApi(api)); + }; + match device { + Some(dev) => self.get_drm_device(dev)?.dev.set_gfx_api(api), + _ => self.state.default_gfx_api.set(api), + } + Ok(()) + } + + pub(super) fn handle_set_flip_margin( + &self, + device: DrmDevice, + margin: Duration, + ) -> Result<(), CphError> { + self.get_drm_device(device)?.set_flip_margin( + &self.state, + margin.as_nanos().try_into().unwrap_or(u64::MAX), + ); + Ok(()) + } + + pub(super) fn handle_set_x_scaling_mode( + &self, + mode: XScalingMode, + ) -> Result<(), CphError> { + let use_wire_scale = match mode { + XScalingMode::DEFAULT => false, + XScalingMode::DOWNSCALED => true, + _ => return Err(CphError::UnknownXScalingMode(mode)), + }; + self.state.set_xwayland_use_wire_scale(use_wire_scale); + Ok(()) + } + + pub(super) fn handle_set_x_wayland_enabled(&self, enabled: bool) -> Result<(), CphError> { + self.state.set_xwayland_enabled(enabled); + Ok(()) + } + + pub(super) fn handle_set_ui_drag_enabled(&self, enabled: bool) { + self.state.set_ui_drag_enabled(enabled); + } + + pub(super) fn handle_set_ui_drag_threshold(&self, threshold: i32) { + self.state.set_ui_drag_threshold(threshold.max(1)); + } + + pub(super) fn handle_set_animations_enabled(&self, enabled: bool) { + self.state.set_animations_enabled(enabled); + } + + pub(super) fn handle_set_animation_duration_ms(&self, duration_ms: u32) { + self.state + .set_animation_duration_ms(duration_ms.min(10_000)); + } + + pub(super) fn handle_set_animation_curve(&self, curve: u32) { + self.state.set_animation_curve(curve); + } + + pub(super) fn handle_set_animation_style(&self, style: u32) { + if !self.state.set_animation_style(style) { + log::warn!("Ignoring invalid animation style"); + } + } + + pub(super) fn handle_set_animation_cubic_bezier(&self, x1: f32, y1: f32, x2: f32, y2: f32) { + if !self.state.set_animation_cubic_bezier(x1, y1, x2, y2) { + log::warn!("Ignoring invalid animation cubic-bezier curve"); + } + } + + pub(super) fn handle_set_direct_scanout_enabled( + &self, + device: Option, + enabled: bool, + ) -> Result<(), CphError> { + match device { + Some(dev) => self + .get_drm_device(dev)? + .set_direct_scanout_enabled(&self.state, enabled), + _ => self.state.direct_scanout_enabled.set(enabled), + } + Ok(()) + } + + pub(super) fn handle_set_double_click_interval_usec(&self, usec: u64) { + self.state.double_click_interval_usec.set(usec); + } + + pub(super) fn handle_set_double_click_distance(&self, dist: i32) { + self.state.double_click_distance.set(dist); + } + + pub(super) fn handle_set_idle(&self, timeout: Duration) { + self.state.idle.set_timeout(&self.state, timeout); + } + + pub(super) fn handle_set_key_press_enables_dpms(&self, enabled: bool) { + self.state.idle.key_press_enables_dpms.set(enabled); + } + + pub(super) fn handle_set_mouse_move_enables_dpms(&self, enabled: bool) { + self.state.idle.mouse_move_enables_dpms.set(enabled); + } + + pub(super) fn handle_set_idle_grace_period(&self, period: Duration) { + self.state.idle.set_grace_period(&self.state, period); + } + + pub(super) fn handle_set_explicit_sync_enabled(&self, enabled: bool) { + self.state.set_explicit_sync_enabled(enabled); + } + + pub(super) fn handle_set_color_management_enabled(&self, enabled: bool) { + self.state.set_color_management_enabled(enabled); + } +} diff --git a/src/config/handler/outputs.rs b/src/config/handler/outputs.rs new file mode 100644 index 00000000..85cfb27c --- /dev/null +++ b/src/config/handler/outputs.rs @@ -0,0 +1,499 @@ +use super::*; + +impl ConfigProxyHandler { + pub(super) fn handle_get_connectors( + &self, + dev: Option, + connected_only: bool, + ) -> Result<(), CphError> { + let datas: Vec<_>; + if let Some(dev) = dev { + let dev = self.get_drm_device(dev)?; + datas = dev.connectors.lock().values().cloned().collect(); + } else { + datas = self.state.connectors.lock().values().cloned().collect(); + } + let connectors = datas + .iter() + .flat_map(|d| match (connected_only, d.connected.get()) { + (false, _) | (true, true) => Some(Connector(d.connector.id().raw() as _)), + _ => None, + }) + .collect(); + self.respond(Response::GetConnectors { connectors }); + Ok(()) + } + + pub(super) fn handle_get_drm_device_syspath(&self, dev: DrmDevice) -> Result<(), CphError> { + let dev = self.get_drm_device(dev)?; + let syspath = dev.syspath.clone().unwrap_or_default(); + self.respond(Response::GetDrmDeviceSyspath { syspath }); + Ok(()) + } + + pub(super) fn handle_get_drm_device_devnode(&self, dev: DrmDevice) -> Result<(), CphError> { + let dev = self.get_drm_device(dev)?; + let devnode = dev.devnode.clone().unwrap_or_default(); + self.respond(Response::GetDrmDeviceDevnode { devnode }); + Ok(()) + } + + pub(super) fn handle_get_drm_device_vendor(&self, dev: DrmDevice) -> Result<(), CphError> { + let dev = self.get_drm_device(dev)?; + let vendor = dev.vendor.clone().unwrap_or_default(); + self.respond(Response::GetDrmDeviceVendor { vendor }); + Ok(()) + } + + pub(super) fn handle_get_drm_devices(&self) { + let devs = self.state.drm_devs.lock(); + let mut res = vec![]; + for dev in devs.values() { + res.push(DrmDevice(dev.dev.id().raw() as _)); + } + self.respond(Response::GetDrmDevices { devices: res }); + } + + pub(super) fn handle_make_render_device(&self, dev: DrmDevice) -> Result<(), CphError> { + let dev = self.get_drm_device(dev)?; + dev.make_render_device(); + Ok(()) + } + + pub(super) fn handle_get_drm_device_model(&self, dev: DrmDevice) -> Result<(), CphError> { + let dev = self.get_drm_device(dev)?; + let model = dev.model.clone().unwrap_or_default(); + self.respond(Response::GetDrmDeviceModel { model }); + Ok(()) + } + + pub(super) fn handle_get_drm_device_pci_id(&self, dev: DrmDevice) -> Result<(), CphError> { + let dev = self.get_drm_device(dev)?; + let pci_id = dev.pci_id.unwrap_or_default(); + self.respond(Response::GetDrmDevicePciId { pci_id }); + Ok(()) + } + + pub(super) fn handle_connector_connected(&self, connector: Connector) -> Result<(), CphError> { + let connector = self.get_connector(connector)?; + self.respond(Response::ConnectorConnected { + connected: connector.connected.get(), + }); + Ok(()) + } + + pub(super) fn handle_connector_type(&self, connector: Connector) -> Result<(), CphError> { + let connector = self.get_connector(connector)?; + self.respond(Response::ConnectorType { + ty: connector.connector.kernel_id().ty.to_config(), + }); + Ok(()) + } + + pub(super) fn handle_connector_mode(&self, connector: Connector) -> Result<(), CphError> { + let connector = self.get_output_node(connector)?; + let mode = connector.global.mode.get(); + self.respond(Response::ConnectorMode { + width: mode.width, + height: mode.height, + refresh_millihz: mode.refresh_rate_millihz, + }); + Ok(()) + } + + pub(super) fn handle_connector_set_mode( + &self, + connector: Connector, + mode: WireMode, + ) -> Result<(), CphError> { + let connector = self.get_connector(connector)?; + connector + .modify_state(&self.state, |s| { + s.mode = backend::Mode { + width: mode.width, + height: mode.height, + refresh_rate_millihz: mode.refresh_millihz, + }; + }) + .map_err(CphError::ModifyConnectorState)?; + Ok(()) + } + + pub(super) fn handle_connector_modes(&self, connector: Connector) -> Result<(), CphError> { + let connector = self.get_output_node(connector)?; + self.respond(Response::ConnectorModes { + modes: connector + .global + .modes + .iter() + .flatten() + .map(|m| WireMode { + width: m.width, + height: m.height, + refresh_millihz: m.refresh_rate_millihz, + }) + .collect(), + }); + Ok(()) + } + + pub(super) fn handle_connector_supports_arbitrary_modes( + &self, + connector: Connector, + ) -> Result<(), CphError> { + let connector = self.get_output_node(connector)?; + self.respond(Response::ConnectorSupportsArbitraryModes { + supports_arbitrary_modes: connector.global.modes.is_none(), + }); + Ok(()) + } + + pub(super) fn handle_connector_name(&self, connector: Connector) -> Result<(), CphError> { + let connector = self.get_connector(connector)?; + self.respond(Response::GetConnectorName { + name: connector.name.deref().clone(), + }); + Ok(()) + } + + pub(super) fn handle_connector_model(&self, connector: Connector) -> Result<(), CphError> { + let connector = self.get_output(connector)?; + self.respond(Response::GetConnectorModel { + model: connector.monitor_info.output_id.model.clone(), + }); + Ok(()) + } + + pub(super) fn handle_connector_manufacturer( + &self, + connector: Connector, + ) -> Result<(), CphError> { + let connector = self.get_output(connector)?; + self.respond(Response::GetConnectorManufacturer { + manufacturer: connector.monitor_info.output_id.manufacturer.clone(), + }); + Ok(()) + } + + pub(super) fn handle_connector_serial_number( + &self, + connector: Connector, + ) -> Result<(), CphError> { + let connector = self.get_output(connector)?; + self.respond(Response::GetConnectorSerialNumber { + serial_number: connector.monitor_info.output_id.serial_number.clone(), + }); + Ok(()) + } + + pub(super) fn handle_connector_size(&self, connector: Connector) -> Result<(), CphError> { + let connector = self.get_output_node(connector)?; + let pos = connector.global.pos.get(); + self.respond(Response::ConnectorSize { + width: pos.width(), + height: pos.height(), + }); + Ok(()) + } + + pub(super) fn handle_connector_get_scale(&self, connector: Connector) -> Result<(), CphError> { + let connector = self.get_output_node(connector)?; + self.respond(Response::ConnectorGetScale { + scale: connector.global.persistent.scale.get().to_f64(), + }); + Ok(()) + } + + pub(super) fn handle_connector_set_scale( + &self, + connector: Connector, + scale: f64, + ) -> Result<(), CphError> { + if scale < 0.1 { + return Err(CphError::ScaleTooSmall(scale)); + } + if scale > 1000.0 { + return Err(CphError::ScaleTooLarge(scale)); + } + let scale = Scale::from_f64(scale); + let connector = self.get_output_node(connector)?; + connector.set_preferred_scale(scale); + Ok(()) + } + + pub(super) fn handle_connector_set_format( + &self, + connector: Connector, + format: ConfigFormat, + ) -> Result<(), CphError> { + let Some(&format) = config_formats().get(&format) else { + return Err(CphError::UnknownFormat(format)); + }; + let connector = self.get_connector(connector)?; + connector + .modify_state(&self.state, |s| s.format = format) + .map_err(CphError::ModifyConnectorState)?; + Ok(()) + } + + pub(super) fn handle_connector_set_colors( + &self, + connector: Connector, + color_space: ColorSpace, + eotf: ConfigEotf, + ) -> Result<(), CphError> { + let bcs = match color_space { + ColorSpace::DEFAULT => BackendColorSpace::Default, + ColorSpace::BT2020 => BackendColorSpace::Bt2020, + _ => return Err(CphError::UnknownColorSpace(color_space)), + }; + let btf = match eotf { + ConfigEotf::DEFAULT => BackendEotfs::Default, + ConfigEotf::PQ => BackendEotfs::Pq, + _ => return Err(CphError::UnknownEotf(eotf)), + }; + let connector = self.get_connector(connector)?; + connector + .modify_state(&self.state, |s| { + s.color_space = bcs; + s.eotf = btf; + }) + .map_err(CphError::ModifyConnectorState)?; + Ok(()) + } + + pub(super) fn handle_connector_set_blend_space( + &self, + connector: Connector, + blend_space: ConfigBlendSpace, + ) -> Result<(), CphError> { + let blend_space = match blend_space { + ConfigBlendSpace::SRGB => BlendSpace::Srgb, + ConfigBlendSpace::LINEAR => BlendSpace::Linear, + _ => return Err(CphError::UnknownBlendSpace(blend_space)), + }; + let connector = self.get_output_node(connector)?; + connector.set_blend_space(blend_space); + Ok(()) + } + + pub(super) fn handle_connector_set_brightness( + &self, + connector: Connector, + brightness: Option, + ) -> Result<(), CphError> { + let connector = self.get_output_node(connector)?; + connector.set_brightness(brightness); + Ok(()) + } + + pub(super) fn handle_connector_set_use_native_gamut( + &self, + connector: Connector, + use_native_gamut: bool, + ) -> Result<(), CphError> { + let connector = self.get_output_node(connector)?; + connector.set_use_native_gamut(use_native_gamut); + Ok(()) + } + + pub(super) fn handle_set_vrr_mode( + &self, + connector: Option, + mode: ConfigVrrMode, + ) -> Result<(), CphError> { + let Some(mode) = VrrMode::from_config(mode) else { + return Err(CphError::UnknownVrrMode(mode)); + }; + match connector { + Some(c) => { + let connector = self.get_output_node(c)?; + connector.set_vrr_mode(mode); + } + _ => self.state.default_vrr_mode.set(*mode), + } + Ok(()) + } + + pub(super) fn handle_set_vrr_cursor_hz( + &self, + connector: Option, + hz: f64, + ) -> Result<(), CphError> { + match connector { + Some(c) => { + let connector = self.get_output_node(c)?; + connector.schedule.set_cursor_hz(hz); + } + _ => { + let Some((hz, _)) = map_cursor_hz(hz) else { + return Err(CphError::InvalidCursorHz(hz)); + }; + self.state.default_vrr_cursor_hz.set(hz) + } + } + Ok(()) + } + + pub(super) fn handle_set_tearing_mode( + &self, + connector: Option, + mode: ConfigTearingMode, + ) -> Result<(), CphError> { + let Some(mode) = TearingMode::from_config(mode) else { + return Err(CphError::UnknownTearingMode(mode)); + }; + match connector { + Some(c) => { + let connector = self.get_output_node(c)?; + connector.set_tearing_mode(mode); + } + _ => self.state.default_tearing_mode.set(*mode), + } + Ok(()) + } + + pub(super) fn handle_connector_set_transform( + &self, + connector: Connector, + transform: Transform, + ) -> Result<(), CphError> { + let connector = self.get_output_node(connector)?; + connector.update_transform(transform.into()); + Ok(()) + } + + pub(super) fn handle_connector_set_position( + &self, + connector: Connector, + x: i32, + y: i32, + ) -> Result<(), CphError> { + let connector = self.get_output_node(connector)?; + if x < 0 || y < 0 || x > MAX_EXTENTS || y > MAX_EXTENTS { + return Err(CphError::InvalidConnectorPosition(x, y)); + } + connector.set_position(x, y); + Ok(()) + } + + pub(super) fn handle_connector_get_position( + &self, + connector: Connector, + ) -> Result<(), CphError> { + let connector = self.get_output_node(connector)?; + let (x, y) = connector.global.pos.get().position(); + self.respond(Response::ConnectorGetPosition { x, y }); + Ok(()) + } + + pub(super) fn handle_connector_set_enabled( + &self, + connector: Connector, + enabled: bool, + ) -> Result<(), CphError> { + let connector = self.get_connector(connector)?; + connector + .modify_state(&self.state, |s| { + s.enabled = enabled; + }) + .map_err(CphError::ModifyConnectorState)?; + Ok(()) + } + + pub(super) fn handle_get_connector( + &self, + ty: jay_config::video::connector_type::ConnectorType, + idx: u32, + ) -> Result<(), CphError> { + let connectors = self.state.connectors.lock(); + let connector = 'get_connector: { + for connector in connectors.values() { + let kid = connector.connector.kernel_id(); + if ty == kid.ty.to_config() && idx == kid.idx { + break 'get_connector Connector(connector.connector.id().raw() as _); + } + } + Connector(0) + }; + self.respond(Response::GetConnector { connector }); + Ok(()) + } + + pub(super) fn handle_get_connector_by_name(&self, name: &str) { + let connector = self + .state + .connectors + .lock() + .values() + .find(|c| *c.name == name) + .map(|c| c.connector.id().raw() as _) + .map(Connector) + .unwrap_or(Connector(0)); + self.respond(Response::GetConnector { connector }); + } + + pub(super) fn handle_create_virtual_output(&self, name: &str) { + self.state.virtual_outputs.get_or_create(&self.state, name); + } + + pub(super) fn handle_remove_virtual_output(&self, name: &str) { + self.state.virtual_outputs.remove_output(&self.state, name); + } + + pub(super) fn handle_get_connector_active_workspace( + &self, + connector: Connector, + ) -> Result<(), CphError> { + let output = self.get_output_node(connector)?; + let workspace = output + .workspace + .get() + .map_or(Workspace(0), |ws| self.get_workspace_by_name(&ws.name)); + self.respond(Response::GetConnectorActiveWorkspace { workspace }); + Ok(()) + } + + pub(super) fn handle_get_connector_workspaces( + &self, + connector: Connector, + ) -> Result<(), CphError> { + let output = self.get_output_node(connector)?; + let workspaces = output + .workspaces + .iter() + .map(|ws| self.get_workspace_by_name(&ws.name)) + .collect::>(); + self.respond(Response::GetConnectorWorkspaces { workspaces }); + Ok(()) + } + + pub(super) fn handle_get_workspace_connector( + &self, + workspace: Workspace, + ) -> Result<(), CphError> { + let connector = self + .get_existing_workspace(workspace)? + .map(|ws| ws.output.get()) + .filter(|o| !o.is_dummy) + .map(|o| Connector(o.global.connector.id.raw() as _)) + .unwrap_or(Connector(0)); + self.respond(Response::GetWorkspaceConnector { connector }); + Ok(()) + } + + pub(super) fn handle_get_connector_in_direction( + &self, + connector: Connector, + direction: Direction, + ) -> Result<(), CphError> { + let source_output = self.get_output_node(connector)?; + let connector = self + .state + .find_output_in_direction(&source_output, direction.into()) + .map(|o| Connector(o.global.connector.id.raw() as u64)) + .unwrap_or(Connector(0)); + self.respond(Response::GetConnectorInDirection { connector }); + Ok(()) + } +} diff --git a/src/config/handler/resources.rs b/src/config/handler/resources.rs new file mode 100644 index 00000000..0f72c0c2 --- /dev/null +++ b/src/config/handler/resources.rs @@ -0,0 +1,137 @@ +use super::*; + +impl ConfigProxyHandler { + fn get_timer(&self, timer: JayTimer) -> Result, CphError> { + match self.timers_by_id.get(&timer.0) { + Some(t) => Ok(t), + _ => Err(CphError::TimerDoesNotExist(timer)), + } + } + + pub(super) fn handle_remove_timer(&self, timer: JayTimer) -> Result<(), CphError> { + let timer = self.get_timer(timer)?; + self.timers_by_id.remove(&timer.id); + self.timers_by_name.remove(&timer.name); + Ok(()) + } + + pub(super) fn handle_program_timer( + &self, + timer: JayTimer, + initial: Option, + periodic: Option, + ) -> Result<(), CphError> { + let timer = self.get_timer(timer)?; + timer.timer.program(initial, periodic)?; + Ok(()) + } + + pub(super) fn handle_get_timer(self: &Rc, name: &str) -> Result<(), CphError> { + let name = Rc::new(name.to_owned()); + if let Some(t) = self.timers_by_name.get(&name) { + self.respond(Response::GetTimer { + timer: JayTimer(t.id), + }); + return Ok(()); + } + let id = self.timer_ids.fetch_add(1); + let timer = TimerFd::new(c::CLOCK_BOOTTIME)?; + let handler = { + let timer = timer.clone(); + let slf = self.clone(); + self.state.eng.spawn("config timer", async move { + loop { + match timer.expired(&slf.state.ring).await { + Ok(_) => slf.send(&ServerMessage::TimerExpired { + timer: JayTimer(id), + }), + Err(e) => { + log::error!("Could not wait for timer expiration: {}", ErrorFmt(e)); + if let Some(timer) = slf.timers_by_id.remove(&id) { + slf.timers_by_name.remove(&timer.name); + } + return; + } + } + } + }) + }; + let td = Rc::new(TimerData { + timer, + id, + name: name.clone(), + _handler: handler, + }); + self.timers_by_name.set(name.clone(), td.clone()); + self.timers_by_id.set(id, td.clone()); + self.respond(Response::GetTimer { + timer: JayTimer(id), + }); + Ok(()) + } + + pub(super) fn handle_add_pollable(self: &Rc, fd: i32) -> Result<(), CphError> { + let fd = match fcntl_dupfd_cloexec(fd, 0).to_os_error() { + Ok(fd) => Rc::new(fd), + Err(e) => { + let err = format!("Could not invoke F_DUPFD_CLOEXEC: {}", ErrorFmt(e)); + log::error!("{}", err); + self.respond(Response::AddPollable { id: Err(err) }); + return Ok(()); + } + }; + let id = self.pollable_id.fetch_add(1); + let id = PollableId(id); + let create = |writable: bool, events: c::c_short| { + let event = Rc::new(AsyncEvent::default()); + let slf = self.clone(); + let trigger = event.clone(); + let fd = fd.clone(); + let future = self.state.eng.spawn("config fd poller", async move { + loop { + trigger.triggered().await; + let res = slf.state.ring.poll(&fd, events).await.merge(); + if let Err(e) = &res { + log::warn!("Could not poll fd: {}", ErrorFmt(e)); + } + let res = res.map_err(|e| ErrorFmt(e).to_string()).map(drop); + slf.send(&ServerMessage::InterestReady { id, writable, res }); + } + }); + (event, future) + }; + let (read_trigger, _read_future) = create(false, c::POLLIN); + let (write_trigger, _write_future) = create(true, c::POLLOUT); + self.pollables.set( + id, + Rc::new(Pollable { + write_trigger, + _write_future, + read_trigger, + _read_future, + }), + ); + self.respond(Response::AddPollable { id: Ok(id) }); + Ok(()) + } + + pub(super) fn handle_remove_pollable(self: &Rc, id: PollableId) { + self.pollables.remove(&id); + } + + pub(super) fn handle_add_interest( + self: &Rc, + id: PollableId, + writable: bool, + ) -> Result<(), CphError> { + let Some(pollable) = self.pollables.get(&id) else { + return Err(CphError::PollableDoesNotExist); + }; + let trigger = match writable { + true => &pollable.write_trigger, + false => &pollable.read_trigger, + }; + trigger.trigger(); + Ok(()) + } +} diff --git a/src/config/handler/runtime.rs b/src/config/handler/runtime.rs new file mode 100644 index 00000000..ebd286df --- /dev/null +++ b/src/config/handler/runtime.rs @@ -0,0 +1,102 @@ +use super::*; + +impl ConfigProxyHandler { + pub(super) fn handle_log_request( + &self, + level: ConfigLogLevel, + msg: &str, + file: Option<&str>, + line: Option, + ) { + let level = match level { + ConfigLogLevel::Error => Level::Error, + ConfigLogLevel::Warn => Level::Warn, + ConfigLogLevel::Info => Level::Info, + ConfigLogLevel::Debug => Level::Debug, + ConfigLogLevel::Trace => Level::Trace, + }; + let debug = fmt::from_fn(|fmt| { + if let Some(file) = file { + write!(fmt, "{}", file)?; + if let Some(line) = line { + write!(fmt, ":{}", line)?; + } + write!(fmt, ": ")?; + } + write!(fmt, "{}", msg)?; + Ok(()) + }); + log::log!(level, "{:?}", debug); + } + + pub(super) fn handle_reload(&self) { + self.state.reload_config(); + } + + pub(super) fn handle_set_status(&self, status: &str) { + self.state.set_status(status); + } + + pub(super) fn handle_set_env(&self, key: &str, val: &str) { + if let Some(f) = self.state.forker.get() { + f.setenv(key.as_bytes(), val.as_bytes()); + } + } + + pub(super) fn handle_unset_env(&self, key: &str) { + if let Some(f) = self.state.forker.get() { + f.unsetenv(key.as_bytes()); + } + } + + pub(super) fn handle_get_config_dir(&self) { + let dir = self.state.config_dir.clone().unwrap_or_default(); + self.respond(Response::GetConfigDir { dir }); + } + + pub(super) fn handle_run( + &self, + prog: &str, + args: Vec, + mut env: Vec<(String, String)>, + fds: Vec<(i32, i32)>, + tag: Option<&str>, + ) -> Result<(), CphError> { + if let Some(tag) = tag { + let display = self + .state + .tagged_acceptors + .get(&self.state, tag) + .map_err(CphError::CreateTaggedAcceptor)?; + env.push((WAYLAND_DISPLAY.to_string(), display.to_string())); + } + let fds: Vec<_> = fds + .into_iter() + .map(|(a, b)| (a, Rc::new(OwnedFd::new(b)))) + .collect(); + let forker = match self.state.forker.get() { + Some(f) => f, + _ => return Err(CphError::NoForker), + }; + let env = env.into_iter().map(|(k, v)| (k, Some(v))).collect(); + forker.spawn(prog.to_string(), args, env, fds); + Ok(()) + } + + pub(super) fn handle_set_log_level(&self, level: ConfigLogLevel) { + self.state.set_log_level(level.into()); + } + + pub(super) fn handle_clean_logs_older_than(&self, time: SystemTime) { + self.state.clean_logs_older_than.set(Some(time)); + } + + pub(super) fn handle_quit(&self) { + log::info!("Quitting"); + self.state.ring.stop(); + } + + pub(super) fn handle_switch_to(&self, vtnr: u32) { + self.state.backend.get().switch_to(vtnr); + } +} diff --git a/src/config/handler/seats.rs b/src/config/handler/seats.rs new file mode 100644 index 00000000..3711a0af --- /dev/null +++ b/src/config/handler/seats.rs @@ -0,0 +1,553 @@ +use super::*; + +impl ConfigProxyHandler { + pub(super) fn handle_get_seat(&self, name: &str) { + for seat in self.state.globals.seats.lock().values() { + if seat.seat_name() == name { + self.respond(Response::GetSeat { + seat: Seat(seat.id().raw() as _), + }); + return; + } + } + let seat = self.state.create_seat(name); + self.respond(Response::GetSeat { + seat: Seat(seat.id().raw() as _), + }); + } + + pub(super) fn handle_get_seat_fullscreen(&self, seat: Seat) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + self.respond(Response::GetFullscreen { + fullscreen: seat.get_fullscreen(), + }); + Ok(()) + } + + pub(super) fn handle_set_seat_fullscreen( + &self, + seat: Seat, + fullscreen: bool, + ) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + seat.set_fullscreen(fullscreen); + Ok(()) + } + + pub(super) fn handle_set_keymap( + &self, + seat: Seat, + keymap: Keymap, + ) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + let keymap = if keymap.is_invalid() { + self.state.default_keymap.clone() + } else { + self.get_keymap(keymap)? + }; + seat.set_seat_keymap(&keymap); + Ok(()) + } + + pub(super) fn handle_set_forward(&self, seat: Seat, forward: bool) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + seat.set_forward(forward); + Ok(()) + } + + pub(super) fn handle_set_focus_follows_mouse_mode( + &self, + seat: Seat, + mode: FocusFollowsMouseMode, + ) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + let focus_follows_mouse = match mode { + FocusFollowsMouseMode::True => true, + FocusFollowsMouseMode::False => false, + }; + seat.set_focus_follows_mouse(focus_follows_mouse); + Ok(()) + } + + pub(super) fn handle_set_fallback_output_mode( + &self, + seat: Seat, + mode: FallbackOutputMode, + ) -> Result<(), CphError> { + let Ok(mode) = mode.try_into() else { + return Err(CphError::UnknownFallbackOutputMode(mode)); + }; + let seat = self.get_seat(seat)?; + seat.set_fallback_output_mode(mode); + Ok(()) + } + + pub(super) fn handle_set_window_management_enabled( + &self, + seat: Seat, + enabled: bool, + ) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + seat.set_window_management_enabled(enabled); + Ok(()) + } + + pub(super) fn handle_seat_close(&self, seat: Seat) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + seat.close(); + Ok(()) + } + + pub(super) fn handle_seat_focus( + &self, + seat: Seat, + direction: Direction, + ) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + seat.move_focus(direction.into()); + Ok(()) + } + + pub(super) fn handle_seat_move( + &self, + seat: Seat, + direction: Direction, + ) -> Result<(), CphError> { + self.state.with_layout_animations(|| { + let seat = self.get_seat(seat)?; + seat.move_focused(direction.into()); + Ok(()) + }) + } + + pub(super) fn handle_seat_send_to_scratchpad( + &self, + seat: Seat, + name: &str, + ) -> Result<(), CphError> { + self.state.with_linear_layout_animations(|| { + let seat = self.get_seat(seat)?; + if let Some(toplevel) = seat.get_keyboard_node().node_toplevel() { + self.state.send_to_scratchpad(name, toplevel); + } + Ok(()) + }) + } + + pub(super) fn handle_seat_toggle_scratchpad( + &self, + seat: Seat, + name: &str, + ) -> Result<(), CphError> { + self.state.with_linear_layout_animations(|| { + let seat = self.get_seat(seat)?; + self.state.toggle_scratchpad(&seat, name); + Ok(()) + }) + } + + pub(super) fn handle_seat_cycle_scratchpad( + &self, + seat: Seat, + name: &str, + ) -> Result<(), CphError> { + self.state.with_linear_layout_animations(|| { + let seat = self.get_seat(seat)?; + self.state.cycle_scratchpad(&seat, name); + Ok(()) + }) + } + + pub(super) fn handle_get_repeat_rate(&self, seat: Seat) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + let (rate, delay) = seat.get_rate(); + self.respond(Response::GetRepeatRate { rate, delay }); + Ok(()) + } + + pub(super) fn handle_set_repeat_rate( + &self, + seat: Seat, + rate: i32, + delay: i32, + ) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + if rate < 0 { + return Err(CphError::NegativeRepeatRate); + } + if delay < 0 { + return Err(CphError::NegativeRepeatDelay); + } + seat.set_rate(rate, delay); + Ok(()) + } + + pub(super) fn handle_set_cursor_size(&self, seat: Seat, size: i32) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + if size < 0 { + return Err(CphError::NegativeCursorSize); + } + seat.cursor_group().set_cursor_size(size as _); + Ok(()) + } + + pub(super) fn handle_disable_pointer_constraint( + &self, + seat: Seat, + ) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + seat.disable_pointer_constraint(); + Ok(()) + } + + pub(super) fn handle_set_use_hardware_cursor( + &self, + seat: Seat, + use_hardware_cursor: bool, + ) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + seat.cursor_group().set_hardware_cursor(use_hardware_cursor); + self.state.refresh_hardware_cursors(); + Ok(()) + } + + pub(super) fn handle_get_seat_float_pinned(&self, seat: Seat) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + self.respond(Response::GetFloatPinned { + pinned: seat.pinned(), + }); + Ok(()) + } + + pub(super) fn handle_set_seat_float_pinned( + &self, + seat: Seat, + pinned: bool, + ) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + seat.set_pinned(pinned); + Ok(()) + } + + pub(super) fn handle_get_seat_mono(&self, seat: Seat) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + self.respond(Response::GetMono { + mono: seat.get_mono().unwrap_or(false), + }); + Ok(()) + } + + pub(super) fn handle_set_seat_mono(&self, seat: Seat, mono: bool) -> Result<(), CphError> { + self.state.with_layout_animations(|| { + let seat = self.get_seat(seat)?; + seat.set_mono(mono); + Ok(()) + }) + } + + pub(super) fn handle_get_seat_split(&self, seat: Seat) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + self.respond(Response::GetSplit { + axis: seat + .get_split() + .unwrap_or(ContainerSplit::Horizontal) + .into(), + }); + Ok(()) + } + + pub(super) fn handle_set_seat_split(&self, seat: Seat, axis: Axis) -> Result<(), CphError> { + self.state.with_layout_animations(|| { + let seat = self.get_seat(seat)?; + seat.set_split(axis.into()); + Ok(()) + }) + } + + pub(super) fn handle_seat_toggle_tab(&self, seat: Seat) -> Result<(), CphError> { + self.state.with_layout_animations(|| { + let seat = self.get_seat(seat)?; + seat.toggle_tab(); + Ok(()) + }) + } + + pub(super) fn handle_seat_make_group( + &self, + seat: Seat, + axis: Axis, + ephemeral: bool, + ) -> Result<(), CphError> { + self.state.with_layout_animations(|| { + let seat = self.get_seat(seat)?; + seat.make_group(axis.into(), ephemeral); + Ok(()) + }) + } + + pub(super) fn handle_seat_change_group_opposite(&self, seat: Seat) -> Result<(), CphError> { + self.state.with_layout_animations(|| { + let seat = self.get_seat(seat)?; + seat.change_group_opposite(); + Ok(()) + }) + } + + pub(super) fn handle_seat_equalize( + &self, + seat: Seat, + recursive: bool, + ) -> Result<(), CphError> { + self.state.with_layout_animations(|| { + let seat = self.get_seat(seat)?; + seat.equalize(recursive); + Ok(()) + }) + } + + pub(super) fn handle_seat_move_tab(&self, seat: Seat, right: bool) -> Result<(), CphError> { + self.state.with_layout_animations(|| { + let seat = self.get_seat(seat)?; + seat.move_tab(right); + Ok(()) + }) + } + + pub(super) fn handle_add_shortcut( + &self, + seat: Seat, + mod_mask: Modifiers, + mods: Modifiers, + sym: KeySym, + ) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + seat.add_shortcut(mod_mask, mods, sym); + Ok(()) + } + + pub(super) fn handle_remove_shortcut( + &self, + seat: Seat, + mods: Modifiers, + sym: KeySym, + ) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + seat.remove_shortcut(mods, sym); + Ok(()) + } + + pub(super) fn handle_get_seats(&self) { + let seats = { + let seats = self.state.globals.seats.lock(); + seats + .values() + .map(|seat| Seat::from_raw(seat.id().raw() as _)) + .collect() + }; + self.respond(Response::GetSeats { seats }); + } + + pub(super) fn handle_create_seat_split( + &self, + seat: Seat, + axis: Axis, + ) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + seat.create_split(axis.into()); + Ok(()) + } + + pub(super) fn handle_focus_seat_parent(&self, seat: Seat) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + seat.focus_parent(); + Ok(()) + } + + pub(super) fn handle_get_seat_floating(&self, seat: Seat) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + self.respond(Response::GetFloating { + floating: seat.get_floating().unwrap_or(false), + }); + Ok(()) + } + + pub(super) fn handle_set_seat_floating( + &self, + seat: Seat, + floating: bool, + ) -> Result<(), CphError> { + self.state.with_linear_layout_animations(|| { + let seat = self.get_seat(seat)?; + seat.set_floating(floating); + Ok(()) + }) + } + + pub(super) fn handle_set_pointer_revert_key( + &self, + seat: Seat, + key: KeySym, + ) -> Result<(), CphError> { + self.get_seat(seat)?.set_pointer_revert_key(key); + Ok(()) + } + + pub(super) fn handle_seat_focus_history( + &self, + seat: Seat, + timeline: Timeline, + ) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + match timeline { + Timeline::Older => seat.focus_prev(), + Timeline::Newer => seat.focus_next(), + } + Ok(()) + } + + pub(super) fn handle_seat_focus_history_set_only_visible( + &self, + seat: Seat, + visible: bool, + ) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + seat.focus_history_set_visible(visible); + Ok(()) + } + + pub(super) fn handle_seat_focus_history_set_same_workspace( + &self, + seat: Seat, + same_workspace: bool, + ) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + seat.focus_history_set_same_workspace(same_workspace); + Ok(()) + } + + pub(super) fn handle_seat_focus_layer_rel( + &self, + seat: Seat, + direction: LayerDirection, + ) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + match direction { + LayerDirection::Below => seat.focus_layer_below(), + LayerDirection::Above => seat.focus_layer_above(), + } + Ok(()) + } + + pub(super) fn handle_seat_focus_tiles(&self, seat: Seat) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + seat.focus_tiles(); + Ok(()) + } + + pub(super) fn handle_seat_focus_floats(&self, seat: Seat) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + seat.focus_floats(); + Ok(()) + } + + pub(super) fn handle_seat_toggle_focus_float_tiled( + &self, + seat: Seat, + ) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + seat.toggle_focus_float_tiled(); + Ok(()) + } + + pub(super) fn handle_set_middle_click_paste_enabled(&self, enabled: bool) { + self.state.set_primary_selection_enabled(enabled); + } + + pub(super) fn handle_seat_create_mark( + &self, + seat: Seat, + kc: Option, + ) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + if let Some(kc) = kc { + seat.create_mark(Keycode::from_evdev(kc)); + } else { + seat.create_mark_interactive(); + } + Ok(()) + } + + pub(super) fn handle_seat_jump_to_mark( + &self, + seat: Seat, + kc: Option, + ) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + if let Some(kc) = kc { + seat.jump_to_mark(Keycode::from_evdev(kc)); + } else { + seat.jump_to_mark_interactive(); + } + Ok(()) + } + + pub(super) fn handle_seat_copy_mark( + &self, + seat: Seat, + src: u32, + dst: u32, + ) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + seat.copy_mark(Keycode::from_evdev(src), Keycode::from_evdev(dst)); + Ok(()) + } + + pub(super) fn handle_seat_set_simple_im_enabled( + &self, + seat: Seat, + enabled: bool, + ) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + seat.set_simple_im_enabled(enabled); + Ok(()) + } + + pub(super) fn handle_seat_get_simple_im_enabled( + &self, + seat: Seat, + ) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + self.respond(Response::SeatGetSimpleImEnabled { + enabled: seat.simple_im_enabled(), + }); + Ok(()) + } + + pub(super) fn handle_seat_reload_simple_im(&self, seat: Seat) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + seat.reload_simple_im(); + Ok(()) + } + + pub(super) fn handle_seat_enable_unicode_input(&self, seat: Seat) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + seat.enable_unicode_input(); + Ok(()) + } + + pub(super) fn handle_seat_warp_mouse_to_focus(&self, seat: Seat) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + seat.schedule_warp_mouse_to_focus(); + Ok(()) + } + + pub(super) fn handle_seat_set_mouse_follows_focus( + &self, + seat: Seat, + enabled: bool, + ) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + seat.set_mouse_follows_focus(enabled); + Ok(()) + } +} diff --git a/src/config/handler/theme.rs b/src/config/handler/theme.rs new file mode 100644 index 00000000..50d7a6e6 --- /dev/null +++ b/src/config/handler/theme.rs @@ -0,0 +1,200 @@ +use super::*; + +impl ConfigProxyHandler { + pub(super) fn handle_set_float_above_fullscreen(&self, above: bool) { + self.state.set_float_above_fullscreen(above); + } + + pub(super) fn handle_get_float_above_fullscreen(&self) { + self.respond(Response::GetFloatAboveFullscreen { + above: self.state.float_above_fullscreen.get(), + }); + } + + pub(super) fn handle_set_show_bar(&self, show: bool) { + self.state.set_show_bar(show); + } + + pub(super) fn handle_get_show_bar(&self) { + self.respond(Response::GetShowBar { + show: self.state.show_bar.get(), + }); + } + + pub(super) fn handle_set_show_titles(&self, _show: bool) { + // no-op: titles have been removed + } + + pub(super) fn handle_get_show_titles(&self) { + self.respond(Response::GetShowTitles { show: false }); + } + + pub(super) fn handle_set_floating_titles(&self, _floating: bool) { + // no-op: titles have been removed + } + + pub(super) fn handle_get_floating_titles(&self) { + self.respond(Response::GetFloatingTitles { floating: false }); + } + + pub(super) fn handle_set_bar_position( + &self, + position: BarPosition, + ) -> Result<(), CphError> { + let Ok(position) = position.try_into() else { + return Err(CphError::UnknownBarPosition(position)); + }; + self.state.set_bar_position(position); + Ok(()) + } + + pub(super) fn handle_get_bar_position(&self) { + self.respond(Response::GetBarPosition { + position: self.state.theme.bar_position.get().into(), + }); + } + + pub(super) fn handle_set_corner_radius(&self, radius: f32) { + use jay_theme::CornerRadius; + let radius = radius.max(0.0).min(1000.0); + self.state + .theme + .corner_radius + .set(CornerRadius::from(radius)); + self.state.damage(self.state.root.extents.get()); + } + + pub(super) fn handle_get_corner_radius(&self) { + self.respond(Response::GetCornerRadius { + radius: self.state.theme.corner_radius.get().top_left, + }); + } + + pub(super) fn handle_set_show_float_pin_icon(&self, _show: bool) { + // no-op: titles have been removed, pin icon was in title bar + } + + fn get_sized(&self, sized: Resizable) -> Result { + use jay_config::theme::sized::*; + let sized = match sized { + TITLE_HEIGHT => ThemeSized::title_height, + BORDER_WIDTH => ThemeSized::border_width, + BAR_HEIGHT => ThemeSized::bar_height, + BAR_SEPARATOR_WIDTH => ThemeSized::bar_separator_width, + GAP => ThemeSized::gap, + TITLE_GAP => ThemeSized::title_gap, + TAB_BAR_HEIGHT => ThemeSized::tab_bar_height, + TAB_BAR_PADDING => ThemeSized::tab_bar_padding, + TAB_BAR_RADIUS => ThemeSized::tab_bar_radius, + TAB_BAR_BORDER_WIDTH => ThemeSized::tab_bar_border_width, + TAB_BAR_TEXT_PADDING => ThemeSized::tab_bar_text_padding, + TAB_BAR_GAP => ThemeSized::tab_bar_gap, + _ => return Err(CphError::UnknownSized(sized.0)), + }; + Ok(sized) + } + + pub(super) fn handle_get_size(&self, sized: Resizable) -> Result<(), CphError> { + let sized = self.get_sized(sized)?; + let size = sized.field(&self.state.theme).val.get(); + self.respond(Response::GetSize { size }); + Ok(()) + } + + pub(super) fn handle_set_size(&self, sized: Resizable, size: i32) -> Result<(), CphError> { + let sized = self.get_sized(sized)?; + if size < sized.min() { + return Err(CphError::InvalidSize(size, sized)); + } + if size > sized.max() { + return Err(CphError::InvalidSize(size, sized)); + } + self.state.set_size(sized, size); + Ok(()) + } + + pub(super) fn handle_reset_colors(&self) { + self.state.reset_colors(); + } + + pub(super) fn handle_reset_sizes(&self) { + self.state.reset_sizes(); + } + + pub(super) fn handle_reset_font(&self) { + self.state.reset_fonts(); + } + + pub(super) fn handle_set_font(&self, font: &str) { + self.state.set_font(font); + } + + pub(super) fn handle_set_bar_font(&self, font: &str) { + self.state.set_bar_font(Some(font)); + } + + pub(super) fn handle_set_title_font(&self, _font: &str) { + // no-op: titles have been removed + } + + pub(super) fn handle_get_font(&self) { + let font = self.state.theme.font.get().to_string(); + self.respond(Response::GetFont { font }); + } + + fn get_color(&self, colorable: Colorable) -> Result { + use jay_config::theme::colors::*; + let colorable = match colorable { + UNFOCUSED_TITLE_BACKGROUND_COLOR => ThemeColor::unfocused_title_background, + FOCUSED_TITLE_BACKGROUND_COLOR => ThemeColor::focused_title_background, + CAPTURED_UNFOCUSED_TITLE_BACKGROUND_COLOR => { + ThemeColor::captured_unfocused_title_background + } + CAPTURED_FOCUSED_TITLE_BACKGROUND_COLOR => { + ThemeColor::captured_focused_title_background + } + FOCUSED_INACTIVE_TITLE_BACKGROUND_COLOR => { + ThemeColor::focused_inactive_title_background + } + BACKGROUND_COLOR => ThemeColor::background, + BAR_BACKGROUND_COLOR => ThemeColor::bar_background, + SEPARATOR_COLOR => ThemeColor::separator, + BORDER_COLOR => ThemeColor::border, + ACTIVE_BORDER_COLOR => ThemeColor::active_border, + UNFOCUSED_TITLE_TEXT_COLOR => ThemeColor::unfocused_title_text, + FOCUSED_TITLE_TEXT_COLOR => ThemeColor::focused_title_text, + FOCUSED_INACTIVE_TITLE_TEXT_COLOR => ThemeColor::focused_inactive_title_text, + BAR_STATUS_TEXT_COLOR => ThemeColor::bar_text, + ATTENTION_REQUESTED_BACKGROUND_COLOR => ThemeColor::attention_requested_background, + HIGHLIGHT_COLOR => ThemeColor::highlight, + TAB_ACTIVE_BACKGROUND_COLOR => ThemeColor::tab_active_background, + TAB_ACTIVE_BORDER_COLOR => ThemeColor::tab_active_border, + TAB_INACTIVE_BACKGROUND_COLOR => ThemeColor::tab_inactive_background, + TAB_INACTIVE_BORDER_COLOR => ThemeColor::tab_inactive_border, + TAB_ACTIVE_TEXT_COLOR => ThemeColor::tab_active_text, + TAB_INACTIVE_TEXT_COLOR => ThemeColor::tab_inactive_text, + TAB_BAR_BACKGROUND_COLOR => ThemeColor::tab_bar_background, + TAB_ATTENTION_BACKGROUND_COLOR => ThemeColor::tab_attention_background, + _ => return Err(CphError::UnknownColor(colorable.0)), + }; + Ok(colorable) + } + + pub(super) fn handle_get_color(&self, colorable: Colorable) -> Result<(), CphError> { + let color = self.get_color(colorable)?.field(&self.state.theme).get(); + let [r, g, b, a] = color.to_array(Eotf::Gamma22); + let color = jay_config::theme::Color::new_f32_premultiplied(r, g, b, a); + self.respond(Response::GetColor { color }); + Ok(()) + } + + pub(super) fn handle_set_color( + &self, + colorable: Colorable, + color: jay_config::theme::Color, + ) -> Result<(), CphError> { + self.state + .set_color(self.get_color(colorable)?, color.into()); + Ok(()) + } +} diff --git a/src/config/handler/windows.rs b/src/config/handler/windows.rs new file mode 100644 index 00000000..829af1b8 --- /dev/null +++ b/src/config/handler/windows.rs @@ -0,0 +1,355 @@ +use super::*; + +impl ConfigProxyHandler { + pub(super) fn tl_to_window(&self, tl: &dyn ToplevelNode) -> Window { + self.tl_id_to_window(tl.tl_data().identifier.get()) + } + + pub(super) fn tl_id_to_window(&self, tl: ToplevelIdentifier) -> Window { + if let Some(win) = self.windows_from_tl_id.get(&tl) { + return win; + } + let id = Window(self.window_ids.fetch_add(1)); + self.windows_from_tl_id.set(tl, id); + self.windows_to_tl_id.set(id, tl); + id + } + + pub(super) fn get_window(&self, window: Window) -> Result, CphError> { + self.windows_to_tl_id + .get(&window) + .and_then(|id| self.state.toplevels.get(&id)) + .and_then(|tl| tl.upgrade()) + .ok_or(CphError::WindowDoesNotExist(window)) + } + pub(super) fn get_client(&self, client: ConfigClient) -> Result, CphError> { + self.state + .clients + .get(ClientId::from_raw(client.0)) + .ok() + .ok_or(CphError::ClientDoesNotExist(client)) + } + + pub(super) fn handle_get_clients(&self) { + let mut clients = vec![]; + for client in self.state.clients.clients.borrow().values() { + clients.push(ConfigClient(client.data.id.raw())); + } + self.respond(Response::GetClients { clients }); + } + + pub(super) fn handle_client_exists(&self, client: ConfigClient) { + self.respond(Response::ClientExists { + exists: self.get_client(client).is_ok(), + }); + } + + pub(super) fn handle_client_is_xwayland(&self, client: ConfigClient) -> Result<(), CphError> { + self.respond(Response::ClientIsXwayland { + is_xwayland: self.get_client(client)?.is_xwayland, + }); + Ok(()) + } + + pub(super) fn handle_client_kill(&self, client: ConfigClient) { + self.state.clients.kill(ClientId::from_raw(client.0)); + } + + pub(super) fn handle_get_workspace_window(&self, ws: Workspace) -> Result<(), CphError> { + let window = self + .get_existing_workspace(ws)? + .and_then(|ws| ws.container.get()) + .map(|c| self.tl_to_window(&*c)) + .unwrap_or(Window(0)); + self.respond(Response::GetWorkspaceWindow { window }); + Ok(()) + } + + pub(super) fn handle_get_seat_keyboard_window(&self, seat: Seat) -> Result<(), CphError> { + let window = self + .get_seat(seat)? + .get_keyboard_node() + .node_toplevel() + .map(|tl| self.tl_to_window(&*tl)) + .unwrap_or(Window(0)); + self.respond(Response::GetSeatKeyboardWindow { window }); + Ok(()) + } + + pub(super) fn handle_seat_focus_window(&self, seat: Seat, window_id: Window) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + let window = self.get_window(window_id)?; + if !window.node_visible() { + return Err(CphError::WindowNotVisible(window_id)); + } + seat.focus_toplevel(window); + seat.maybe_schedule_warp_mouse_to_focus(); + Ok(()) + } + + pub(super) fn handle_get_window_title(&self, window: Window) -> Result<(), CphError> { + let title = self.get_window(window)?.tl_data().title.borrow().clone(); + self.respond(Response::GetWindowTitle { title }); + Ok(()) + } + + pub(super) fn handle_get_window_type(&self, window: Window) -> Result<(), CphError> { + let kind = self.get_window(window)?.tl_data().kind.to_window_type(); + self.respond(Response::GetWindowType { kind }); + Ok(()) + } + + pub(super) fn handle_get_content_type(&self, window: Window) -> Result<(), CphError> { + let kind = self + .get_window(window)? + .tl_data() + .content_type + .get() + .to_config(); + self.respond(Response::GetContentType { kind }); + Ok(()) + } + + pub(super) fn handle_window_resize( + &self, + window: Window, + dx1: i32, + dy1: i32, + dx2: i32, + dy2: i32, + ) -> Result<(), CphError> { + self.state.with_layout_animations(|| { + self.get_window(window)?.tl_resize(dx1, dy1, dx2, dy2); + Ok(()) + }) + } + + pub(super) fn handle_window_send_to_scratchpad( + &self, + window: Window, + name: &str, + ) -> Result<(), CphError> { + self.state.with_linear_layout_animations(|| { + let window = self.get_window(window)?; + self.state.send_to_scratchpad(name, window); + Ok(()) + }) + } + + pub(super) fn handle_window_exists(&self, window: Window) { + self.respond(Response::WindowExists { + exists: self.get_window(window).is_ok(), + }); + } + + pub(super) fn handle_get_window_id(&self, window: Window) -> Result<(), CphError> { + let id = self + .get_window(window)? + .tl_data() + .identifier + .get() + .to_string(); + self.respond(Response::GetWindowId { id: id.to_string() }); + Ok(()) + } + + pub(super) fn handle_get_window_is_visible(&self, window: Window) -> Result<(), CphError> { + let window = self.get_window(window)?; + self.respond(Response::GetWindowIsVisible { + visible: window.node_visible(), + }); + Ok(()) + } + + pub(super) fn handle_get_window_client(&self, window: Window) -> Result<(), CphError> { + let window = self.get_window(window)?; + self.respond(Response::GetWindowClient { + client: window + .tl_data() + .client + .as_ref() + .map(|c| ConfigClient(c.id.raw())) + .unwrap_or(ConfigClient(0)), + }); + Ok(()) + } + + pub(super) fn handle_get_window_parent(&self, window: Window) -> Result<(), CphError> { + let window = self + .get_window(window)? + .tl_data() + .parent + .get() + .and_then(|tl| tl.node_into_toplevel()) + .map(|tl| self.tl_to_window(&*tl)) + .unwrap_or(Window(0)); + self.respond(Response::GetWindowParent { window }); + Ok(()) + } + + pub(super) fn handle_get_window_workspace(&self, window: Window) -> Result<(), CphError> { + let workspace = self + .get_window(window)? + .tl_data() + .workspace + .get() + .map(|ws| self.get_workspace_by_name(&ws.name)) + .unwrap_or(Workspace(0)); + self.respond(Response::GetWindowWorkspace { workspace }); + Ok(()) + } + + pub(super) fn handle_get_window_children(&self, window: Window) -> Result<(), CphError> { + let mut windows = vec![]; + if let Some(c) = self.get_window(window)?.node_into_container() { + for c in c.children.iter() { + windows.push(self.tl_to_window(&*c.node)); + } + } + self.respond(Response::GetWindowChildren { windows }); + Ok(()) + } + + pub(super) fn handle_window_close(&self, window: Window) -> Result<(), CphError> { + let window = self.get_window(window)?; + window.tl_close(); + Ok(()) + } + + pub(super) fn handle_window_move( + &self, + window: Window, + direction: Direction, + ) -> Result<(), CphError> { + self.state.with_layout_animations(|| { + let window = self.get_window(window)?; + if let Some(float) = window.tl_data().float.get() { + float.move_by_direction(direction.into()); + } else if let Some(c) = toplevel_parent_container(&*window) { + c.move_child(window, direction.into()); + } + Ok(()) + }) + } + + pub(super) fn handle_get_window_fullscreen( + &self, + window: Window, + ) -> Result<(), CphError> { + let tl = self.get_window(window)?; + self.respond(Response::GetWindowFullscreen { + fullscreen: tl.tl_data().is_fullscreen.get(), + }); + Ok(()) + } + + pub(super) fn handle_set_window_fullscreen( + &self, + window: Window, + fullscreen: bool, + ) -> Result<(), CphError> { + let tl = self.get_window(window)?; + tl.tl_set_fullscreen(fullscreen, None); + Ok(()) + } + + pub(super) fn handle_get_window_float_pinned( + &self, + window: Window, + ) -> Result<(), CphError> { + let window = self.get_window(window)?; + self.respond(Response::GetWindowFloatPinned { + pinned: window.tl_pinned(), + }); + Ok(()) + } + + pub(super) fn handle_set_window_float_pinned( + &self, + window: Window, + pinned: bool, + ) -> Result<(), CphError> { + let window = self.get_window(window)?; + window.tl_set_pinned(true, pinned); + Ok(()) + } + + pub(super) fn handle_get_window_mono(&self, window: Window) -> Result<(), CphError> { + let window = self.get_window(window)?; + self.respond(Response::GetWindowMono { + mono: toplevel_parent_container(&*window) + .map(|c| c.mono_child.is_some()) + .unwrap_or(false), + }); + Ok(()) + } + + pub(super) fn handle_set_window_mono( + &self, + window: Window, + mono: bool, + ) -> Result<(), CphError> { + self.state.with_layout_animations(|| { + let window = self.get_window(window)?; + if let Some(c) = toplevel_parent_container(&*window) { + c.set_mono(mono.then_some(window.as_ref())); + } + Ok(()) + }) + } + + pub(super) fn handle_get_window_split(&self, window: Window) -> Result<(), CphError> { + let window = self.get_window(window)?; + self.respond(Response::GetWindowSplit { + axis: toplevel_parent_container(&*window) + .map(|c| c.split.get()) + .unwrap_or(ContainerSplit::Horizontal) + .into(), + }); + Ok(()) + } + + pub(super) fn handle_set_window_split( + &self, + window: Window, + axis: Axis, + ) -> Result<(), CphError> { + self.state.with_layout_animations(|| { + let window = self.get_window(window)?; + if let Some(c) = toplevel_parent_container(&*window) { + c.set_split(axis.into()); + } + Ok(()) + }) + } + + pub(super) fn handle_create_window_split( + &self, + window: Window, + axis: Axis, + ) -> Result<(), CphError> { + let window = self.get_window(window)?; + toplevel_create_split(&self.state, window, axis.into()); + Ok(()) + } + + pub(super) fn handle_get_window_floating(&self, window: Window) -> Result<(), CphError> { + let window = self.get_window(window)?; + self.respond(Response::GetWindowFloating { + floating: window.tl_data().parent_is_float.get(), + }); + Ok(()) + } + + pub(super) fn handle_set_window_floating( + &self, + window: Window, + floating: bool, + ) -> Result<(), CphError> { + self.state.with_linear_layout_animations(|| { + let window = self.get_window(window)?; + toplevel_set_floating(&self.state, window, floating); + Ok(()) + }) + } +} diff --git a/src/config/handler/workspaces.rs b/src/config/handler/workspaces.rs new file mode 100644 index 00000000..814bd016 --- /dev/null +++ b/src/config/handler/workspaces.rs @@ -0,0 +1,186 @@ +use super::*; + +impl ConfigProxyHandler { + pub(super) fn get_workspace_by_name(&self, name: &String) -> Workspace { + let id = match self.workspaces_by_name.get(name) { + None => { + let id = self.workspace_ids.fetch_add(1); + let name = Rc::new(name.clone()); + self.workspaces_by_name.set(name.clone(), id); + self.workspaces_by_id.set(id, name); + id + } + Some(id) => id, + }; + Workspace(id) + } + + pub(super) fn get_workspace(&self, ws: Workspace) -> Result, CphError> { + match self.workspaces_by_id.get(&ws.0) { + Some(ws) => Ok(ws), + _ => Err(CphError::WorkspaceDoesNotExist(ws)), + } + } + + pub(super) fn get_existing_workspace( + &self, + ws: Workspace, + ) -> Result>, CphError> { + self.get_workspace(ws).map(|name| { + self.state + .workspaces + .lock() + .values() + .find(|ws| ws.name.as_str() == name.as_str()) + .cloned() + }) + } + + pub(super) fn handle_get_workspaces(&self) { + let mut workspaces = vec![]; + for ws in self.state.workspaces.lock().values() { + workspaces.push(self.get_workspace_by_name(&ws.name)); + } + self.respond(Response::GetWorkspaces { workspaces }); + } + + pub(super) fn handle_get_workspace(&self, name: &str) { + self.respond(Response::GetWorkspace { + workspace: self.get_workspace_by_name(&name.to_owned()), + }); + } + + pub(super) fn handle_get_workspace_capture( + &self, + workspace: Workspace, + ) -> Result<(), CphError> { + let ws = self.get_existing_workspace(workspace)?; + let capture = match ws { + Some(ws) => ws.may_capture.get(), + None => self.state.default_workspace_capture.get(), + }; + self.respond(Response::GetWorkspaceCapture { capture }); + Ok(()) + } + + pub(super) fn handle_set_workspace_capture( + &self, + workspace: Workspace, + capture: bool, + ) -> Result<(), CphError> { + if let Some(ws) = self.get_existing_workspace(workspace)? { + ws.may_capture.set(capture); + ws.update_has_captures(); + } + Ok(()) + } + + pub(super) fn handle_get_default_workspace_capture(&self) { + self.respond(Response::GetDefaultWorkspaceCapture { + capture: self.state.default_workspace_capture.get(), + }); + } + + pub(super) fn handle_set_default_workspace_capture(&self, capture: bool) { + self.state.default_workspace_capture.set(capture); + } + + pub(super) fn handle_get_seat_cursor_workspace(&self, seat: Seat) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + let output = seat.get_cursor_output(); + let mut workspace = Workspace(0); + if !output.is_dummy + && let Some(ws) = output.workspace.get() + { + workspace = self.get_workspace_by_name(&ws.name); + } + self.respond(Response::GetSeatCursorWorkspace { workspace }); + Ok(()) + } + + pub(super) fn handle_get_seat_keyboard_workspace(&self, seat: Seat) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + let mut workspace = Workspace(0); + if let Some(output) = seat.get_keyboard_output() + && !output.is_dummy + && let Some(ws) = output.workspace.get() + { + workspace = self.get_workspace_by_name(&ws.name); + } + self.respond(Response::GetSeatKeyboardWorkspace { workspace }); + Ok(()) + } + + pub(super) fn handle_show_workspace( + &self, + seat: Seat, + ws: Workspace, + output: Option, + ) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + let name = self.get_workspace(ws)?; + let output = output.map(|o| self.get_output_node(o)).transpose()?; + self.state.show_workspace(&seat, &name, output); + Ok(()) + } + + pub(super) fn handle_set_seat_workspace( + &self, + seat: Seat, + ws: Workspace, + ) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + let name = self.get_workspace(ws)?; + let output = seat.get_fallback_output(); + let workspace = match output.find_workspace(name.deref()) { + Some(ws) => ws, + _ => output.create_workspace(name.deref()), + }; + seat.set_workspace(&workspace); + Ok(()) + } + + pub(super) fn handle_set_window_workspace( + &self, + window: Window, + ws: Workspace, + ) -> Result<(), CphError> { + let window = self.get_window(window)?; + let name = self.get_workspace(ws)?; + let Some(output) = window.node_output() else { + return Ok(()); + }; + let workspace = match output.find_workspace(name.deref()) { + Some(ws) => ws, + _ => output.create_workspace(name.deref()), + }; + toplevel_set_workspace(&self.state, window, &workspace); + Ok(()) + } + + pub(super) fn handle_move_to_output( + &self, + workspace: WorkspaceSource, + connector: Connector, + ) -> Result<(), CphError> { + let output = self.get_output_node(connector)?; + let ws = match workspace { + WorkspaceSource::Explicit(ws) => match self.get_existing_workspace(ws)? { + Some(ws) => ws, + _ => return Ok(()), + }, + WorkspaceSource::Seat(s) => { + match self.get_seat(s)?.get_fallback_output().workspace.get() { + Some(ws) => ws, + _ => return Ok(()), + } + } + }; + self.state.move_ws_to_output(&ws, &output); + Ok(()) + } + + pub(super) fn handle_set_workspace_display_order(&self, order: WorkspaceDisplayOrder) { + self.state.set_workspace_display_order(order.into()); + } +} diff --git a/src/copy_device.rs b/src/copy_device.rs index a3aee40f..cce9c3c3 100644 --- a/src/copy_device.rs +++ b/src/copy_device.rs @@ -1,14 +1,15 @@ +use jay_io_uring::IoUring; +use jay_eventfd_cache::EventfdCache; +use jay_async_engine::{AsyncEngine, SpawnedFuture}; use { crate::{ - async_engine::{AsyncEngine, SpawnedFuture}, - eventfd_cache::EventfdCache, + + format::{FORMATS, Format}, gfx_api::FdSync, - io_uring::IoUring, - rect::{Rect, Region}, + utils::{ clonecell::CloneCell, - copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, oserror::{OsError, OsErrorExt2}, @@ -22,12 +23,10 @@ use { }, vulkan_core::{ self, VULKAN_API_VERSION, VulkanCoreError, VulkanCoreInstance, device::VulkanDeviceInf, - map_extension_properties, sync::VulkanDeviceSyncExt, - timeline_semaphore::VulkanDeviceTimelineSemaphoreExt, + map_extension_properties, timeline_semaphore::VulkanDeviceTimelineSemaphoreExt, }, }, - ahash::{AHashMap, AHashSet}, - arrayvec::ArrayVec, + ahash::AHashMap, ash::{ Device, ext::{ @@ -36,12 +35,9 @@ use { }, khr::{external_fence_fd, external_memory_fd, external_semaphore_fd}, vk::{ - self, AccessFlags2, BindImageMemoryInfo, BindImagePlaneMemoryInfo, BlitImageInfo2, - BufferCopy2, BufferCreateInfo, BufferImageCopy2, BufferMemoryBarrier2, - BufferUsageFlags, CommandBuffer, CommandBufferAllocateInfo, CommandBufferBeginInfo, - CommandBufferSubmitInfo, CommandBufferUsageFlags, CommandPoolCreateFlags, - CommandPoolCreateInfo, CopyBufferInfo2, CopyBufferToImageInfo2, CopyImageInfo2, - CopyImageToBufferInfo2, DependencyInfo, DeviceCreateInfo, DeviceMemory, + self, BindImageMemoryInfo, BindImagePlaneMemoryInfo, BufferCopy2, BufferCreateInfo, + BufferImageCopy2, BufferUsageFlags, CommandBuffer, CommandBufferAllocateInfo, + CommandPoolCreateFlags, CommandPoolCreateInfo, DeviceCreateInfo, DeviceMemory, DeviceQueueCreateInfo, DrmFormatModifierPropertiesEXT, DrmFormatModifierPropertiesListEXT, ExportMemoryAllocateInfo, Extent3D, ExternalBufferProperties, ExternalFenceFeatureFlags, ExternalFenceHandleTypeFlags, @@ -49,28 +45,26 @@ use { ExternalMemoryBufferCreateInfo, ExternalMemoryBufferCreateInfoKHR, ExternalMemoryFeatureFlags, ExternalMemoryHandleTypeFlags, ExternalMemoryImageCreateInfo, ExternalSemaphoreFeatureFlags, - ExternalSemaphoreHandleTypeFlags, ExternalSemaphoreProperties, Filter, - FormatFeatureFlags, FormatProperties2, ImageAspectFlags, ImageBlit2, ImageCopy2, - ImageCreateFlags, ImageCreateInfo, ImageDrmFormatModifierExplicitCreateInfoEXT, - ImageFormatProperties2, ImageLayout, ImageMemoryBarrier2, ImageMemoryRequirementsInfo2, - ImagePlaneMemoryRequirementsInfo, ImageSubresourceLayers, ImageSubresourceRange, - ImageTiling, ImageType, ImageUsageFlags, ImportMemoryFdInfoKHR, - ImportSemaphoreFdInfoKHR, MemoryAllocateInfo, MemoryDedicatedAllocateInfo, - MemoryFdPropertiesKHR, MemoryGetFdInfoKHR, MemoryPropertyFlags, MemoryRequirements2, - MemoryType, Offset3D, PhysicalDevice, PhysicalDeviceDrmPropertiesEXT, + ExternalSemaphoreHandleTypeFlags, ExternalSemaphoreProperties, FormatFeatureFlags, + FormatProperties2, ImageAspectFlags, ImageBlit2, ImageCopy2, ImageCreateFlags, + ImageCreateInfo, ImageDrmFormatModifierExplicitCreateInfoEXT, ImageFormatProperties2, + ImageLayout, ImageMemoryRequirementsInfo2, ImagePlaneMemoryRequirementsInfo, + ImageTiling, ImageType, ImageUsageFlags, ImportMemoryFdInfoKHR, ImportSemaphoreFdInfoKHR, + MemoryAllocateInfo, MemoryDedicatedAllocateInfo, MemoryFdPropertiesKHR, + MemoryGetFdInfoKHR, MemoryPropertyFlags, MemoryRequirements2, MemoryType, + PhysicalDevice, PhysicalDeviceDrmPropertiesEXT, PhysicalDeviceExternalBufferInfo, PhysicalDeviceExternalFenceInfo, PhysicalDeviceExternalImageFormatInfoKHR, PhysicalDeviceExternalSemaphoreInfo, PhysicalDeviceFeatures2, PhysicalDeviceImageDrmFormatModifierInfoEXT, PhysicalDeviceImageFormatInfo2, PhysicalDeviceProperties2, PhysicalDeviceSynchronization2Features, PhysicalDeviceTimelineSemaphoreFeatures, - PipelineStageFlags2, QUEUE_FAMILY_FOREIGN_EXT, Queue, QueueFlags, SampleCountFlags, - SemaphoreCreateInfo, SemaphoreImportFlags, SemaphoreSubmitInfo, SharingMode, - SubmitInfo2, SubresourceLayout, WHOLE_SIZE, + Queue, QueueFlags, SampleCountFlags, SemaphoreCreateInfo, SemaphoreImportFlags, + SharingMode, SubresourceLayout, }, }, bstr::ByteSlice, isnt::std_1::collections::IsntHashMapExt, - linearize::{Linearize, LinearizeExt, StaticCopyMap, StaticMap, static_copy_map, static_map}, + linearize::{Linearize, LinearizeExt, StaticCopyMap, StaticMap, static_map}, log::Level, run_on_drop::on_drop, std::{ @@ -86,6 +80,14 @@ use { vk::{Buffer, CommandPool, Image, Semaphore}, }; +mod execute; +mod queue_allocation; +mod registry; + +pub use registry::CopyDeviceRegistry; + +use queue_allocation::{QueueIndex, QueueToAllocate, allocate_queues}; + #[derive(Debug, Error)] pub enum CopyDeviceError { #[error(transparent)] @@ -197,20 +199,6 @@ pub struct PhysicalCopyDevice { image_blit_2: RefCell>>, } -#[derive(Debug)] -struct QueueToAllocate { - family: u32, - num: usize, -} - -#[derive(Copy, Clone, Default, Debug)] -struct QueueIndex { - allocate_idx: usize, - family: u32, - idx_within_family: u32, - transfer_granularity_mask: (u32, u32), -} - pub struct CopyDevice { _tasks: Vec>, dev: Rc, @@ -349,13 +337,6 @@ struct ClassifiedDmabuf<'a> { format: &'a CopyDeviceSupport, } -pub struct CopyDeviceRegistry { - ring: Rc, - eng: Rc, - eventfd_cache: Rc, - devs: CopyHashMap>>, -} - const DEVICE_EXTENSIONS: [&CStr; 6] = [ external_semaphore_fd::NAME, external_fence_fd::NAME, @@ -1290,429 +1271,6 @@ impl CopyDeviceInner { } } -impl CopyDeviceCopy { - fn ensure_not_busy(&self) -> Result<(), CopyDeviceError> { - let slf = &*self.inner; - if let Some(sync) = slf.busy.get() - && sync.is_unsignaled() - { - return Err(CopyDeviceError::Busy); - } - slf.busy.take(); - Ok(()) - } - - pub fn execute( - &self, - sync: Option<&FdSync>, - region: Option<&Region>, - ) -> Result, CopyDeviceError> { - self.ensure_not_busy()?; - let slf = &*self.inner; - let tt = slf.tt; - let dev = &slf.dev.dev; - let cmd = slf.command_buffer; - let queue_family = slf.dev.phy.queues[tt].family; - let region_buf; - let width = slf.width; - let height = slf.height; - let region = match region { - Some(r) => r, - _ => { - region_buf = Region::new(Rect::new_saturating(0, 0, width as i32, height as i32)); - ®ion_buf - } - }; - let (x_mask, y_mask) = slf.dev.phy.queues[tt].transfer_granularity_mask; - let rects = &mut *slf.dev.phy.rects.borrow_mut(); - rects.clear(); - for rect in region.iter() { - let x1 = (rect.x1().max(0) as u32 & !x_mask).min(width); - let y1 = (rect.y1().max(0) as u32 & !y_mask).min(height); - let x2 = ((rect.x2().max(0) as u32 + x_mask) & !x_mask).min(width); - let y2 = ((rect.y2().max(0) as u32 + y_mask) & !y_mask).min(height); - let width = x2 - x1; - let height = y2 - y1; - if width == 0 || height == 0 { - continue; - } - rects.push((x1 as i32, y1 as i32, width, height)); - } - if rects.is_empty() { - return Ok(None); - } - let begin_info = - CommandBufferBeginInfo::default().flags(CommandBufferUsageFlags::ONE_TIME_SUBMIT); - unsafe { - dev.begin_command_buffer(cmd, &begin_info) - .map_err(CopyDeviceError::BeginCommandBuffer)?; - } - macro_rules! initial_buffer_barriers { - ($($buf:expr, $access:expr;)*) => { - [$( - BufferMemoryBarrier2::default() - .dst_stage_mask(PipelineStageFlags2::TRANSFER) - .dst_access_mask($access) - .src_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) - .dst_queue_family_index(queue_family) - .buffer($buf.buf) - .size(WHOLE_SIZE), - )*] - }; - } - macro_rules! final_buffer_barriers { - ($($buf:expr, $access:expr;)*) => { - [$( - BufferMemoryBarrier2::default() - .src_stage_mask(PipelineStageFlags2::TRANSFER) - .src_access_mask($access) - .src_queue_family_index(queue_family) - .dst_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) - .buffer($buf.buf) - .size(WHOLE_SIZE), - )*] - }; - } - let image_subresource_range = ImageSubresourceRange { - aspect_mask: ImageAspectFlags::COLOR, - base_mip_level: 0, - level_count: 1, - base_array_layer: 0, - layer_count: 1, - }; - let image_subresource = ImageSubresourceLayers { - aspect_mask: ImageAspectFlags::COLOR, - mip_level: 0, - base_array_layer: 0, - layer_count: 1, - }; - macro_rules! initial_image_barriers { - ($($img:expr, $layout:expr, $access:expr;)*) => { - [$( - ImageMemoryBarrier2::default() - .dst_stage_mask(PipelineStageFlags2::TRANSFER) - .dst_access_mask($access) - .old_layout(ImageLayout::GENERAL) - .new_layout(ImageLayout::GENERAL) - .src_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) - .dst_queue_family_index(queue_family) - .image($img.img) - .subresource_range(image_subresource_range), - ImageMemoryBarrier2::default() - .src_stage_mask(PipelineStageFlags2::TRANSFER) - .src_access_mask($access) - .dst_stage_mask(PipelineStageFlags2::TRANSFER) - .dst_access_mask($access) - .old_layout(ImageLayout::GENERAL) - .new_layout($layout) - .src_queue_family_index(queue_family) - .dst_queue_family_index(queue_family) - .image($img.img) - .subresource_range(image_subresource_range), - )*] - }; - } - macro_rules! final_image_barriers { - ($($img:expr, $layout:expr, $access:expr;)*) => { - [$( - ImageMemoryBarrier2::default() - .src_stage_mask(PipelineStageFlags2::TRANSFER) - .src_access_mask($access) - .dst_stage_mask(PipelineStageFlags2::TRANSFER) - .dst_access_mask($access) - .old_layout($layout) - .new_layout(ImageLayout::GENERAL) - .src_queue_family_index(queue_family) - .dst_queue_family_index(queue_family) - .image($img.img) - .subresource_range(image_subresource_range), - ImageMemoryBarrier2::default() - .src_stage_mask(PipelineStageFlags2::TRANSFER) - .src_access_mask($access) - .old_layout(ImageLayout::GENERAL) - .new_layout(ImageLayout::GENERAL) - .src_queue_family_index(queue_family) - .dst_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) - .image($img.img) - .subresource_range(image_subresource_range), - )*] - }; - } - match &slf.ty { - CopyDeviceCopyType::BufferToBuffer { - src, - dst, - stride, - bpp, - } => { - let regions = &mut *slf.dev.phy.buffer_copy_2.borrow_mut(); - regions.clear(); - let stride = *stride as u64; - let bpp = *bpp as u64; - for &mut (x, y, width, height) in rects { - let lo = y as u64 * stride + x as u64 * bpp; - let size = (height as u64 - 1) * stride + width as u64 * bpp; - let region = BufferCopy2::default() - .src_offset(lo) - .dst_offset(lo) - .size(size); - regions.push(region); - } - use AccessFlags2 as A; - let initial_barriers = initial_buffer_barriers![ - src, A::TRANSFER_READ; - dst, A::TRANSFER_WRITE; - ]; - let final_barriers = final_buffer_barriers![ - src, A::TRANSFER_READ; - dst, A::TRANSFER_WRITE; - ]; - let initial_dependency_info = - DependencyInfo::default().buffer_memory_barriers(&initial_barriers); - let final_dependency_info = - DependencyInfo::default().buffer_memory_barriers(&final_barriers); - let copy_buffer_info = CopyBufferInfo2::default() - .src_buffer(src.buf) - .dst_buffer(dst.buf) - .regions(regions); - unsafe { - dev.cmd_pipeline_barrier2(cmd, &initial_dependency_info); - dev.cmd_copy_buffer2(cmd, ©_buffer_info); - dev.cmd_pipeline_barrier2(cmd, &final_dependency_info); - } - } - CopyDeviceCopyType::BufferToImage { - buf, - buf_format, - buf_stride, - img, - } - | CopyDeviceCopyType::ImageToBuffer { - img, - buf, - buf_format, - buf_stride, - } => { - let regions = &mut *slf.dev.phy.buffer_image_copy_2.borrow_mut(); - regions.clear(); - for &mut (x, y, width, height) in rects { - let offset = y as u64 * *buf_stride as u64 + x as u64 * buf_format.bpp as u64; - let region = BufferImageCopy2::default() - .buffer_offset(offset) - .buffer_row_length(*buf_stride / buf_format.bpp) - .buffer_image_height(slf.height) - .image_subresource(image_subresource) - .image_offset(Offset3D { x, y, z: 0 }) - .image_extent(Extent3D { - width, - height, - depth: 1, - }); - regions.push(region); - } - let buffer_to_image = match &slf.ty { - CopyDeviceCopyType::BufferToImage { .. } => true, - CopyDeviceCopyType::ImageToBuffer { .. } => false, - _ => unreachable!(), - }; - let image_access_mask; - let image_layout; - let buffer_access_mask; - match buffer_to_image { - true => { - image_access_mask = AccessFlags2::TRANSFER_WRITE; - image_layout = ImageLayout::TRANSFER_DST_OPTIMAL; - buffer_access_mask = AccessFlags2::TRANSFER_READ; - } - false => { - image_access_mask = AccessFlags2::TRANSFER_READ; - image_layout = ImageLayout::TRANSFER_SRC_OPTIMAL; - buffer_access_mask = AccessFlags2::TRANSFER_WRITE; - } - } - let initial_image_barriers = initial_image_barriers![ - img, image_layout, image_access_mask; - ]; - let final_image_barriers = final_image_barriers![ - img, image_layout, image_access_mask; - ]; - let initial_buffer_barriers = initial_buffer_barriers![ - buf, buffer_access_mask; - ]; - let final_buffer_barriers = final_buffer_barriers![ - buf, buffer_access_mask; - ]; - let initial_dependency_info = DependencyInfo::default() - .buffer_memory_barriers(&initial_buffer_barriers) - .image_memory_barriers(&initial_image_barriers); - let final_dependency_info = DependencyInfo::default() - .buffer_memory_barriers(&final_buffer_barriers) - .image_memory_barriers(&final_image_barriers); - unsafe { - dev.cmd_pipeline_barrier2(cmd, &initial_dependency_info); - match buffer_to_image { - true => { - let copy = CopyBufferToImageInfo2::default() - .src_buffer(buf.buf) - .dst_image(img.img) - .dst_image_layout(image_layout) - .regions(®ions); - dev.cmd_copy_buffer_to_image2(cmd, ©); - } - false => { - let copy = CopyImageToBufferInfo2::default() - .src_image(img.img) - .src_image_layout(image_layout) - .dst_buffer(buf.buf) - .regions(®ions); - dev.cmd_copy_image_to_buffer2(cmd, ©); - } - } - dev.cmd_pipeline_barrier2(cmd, &final_dependency_info); - } - } - CopyDeviceCopyType::ImageToImage { src, dst } => { - let regions = &mut *slf.dev.phy.image_copy_2.borrow_mut(); - regions.clear(); - for &mut (x, y, width, height) in rects { - let region = ImageCopy2::default() - .src_subresource(image_subresource) - .src_offset(Offset3D { x, y, z: 0 }) - .dst_subresource(image_subresource) - .dst_offset(Offset3D { x, y, z: 0 }) - .extent(Extent3D { - width, - height, - depth: 1, - }); - regions.push(region); - } - use {AccessFlags2 as A, ImageLayout as L}; - let initial_barriers = initial_image_barriers![ - src, L::TRANSFER_SRC_OPTIMAL, A::TRANSFER_READ; - dst, L::TRANSFER_DST_OPTIMAL, A::TRANSFER_WRITE; - ]; - let final_barriers = final_image_barriers![ - src, L::TRANSFER_SRC_OPTIMAL, A::TRANSFER_READ; - dst, L::TRANSFER_DST_OPTIMAL, A::TRANSFER_WRITE; - ]; - let initial_dependency_info = - DependencyInfo::default().image_memory_barriers(&initial_barriers); - let final_dependency_info = - DependencyInfo::default().image_memory_barriers(&final_barriers); - let copy_image_info = CopyImageInfo2::default() - .src_image(src.img) - .src_image_layout(L::TRANSFER_SRC_OPTIMAL) - .dst_image(dst.img) - .dst_image_layout(L::TRANSFER_DST_OPTIMAL) - .regions(regions); - unsafe { - dev.cmd_pipeline_barrier2(cmd, &initial_dependency_info); - dev.cmd_copy_image2(cmd, ©_image_info); - dev.cmd_pipeline_barrier2(cmd, &final_dependency_info); - } - } - CopyDeviceCopyType::Blit { src, dst } => { - let regions = &mut *slf.dev.phy.image_blit_2.borrow_mut(); - regions.clear(); - for &mut (x, y, width, height) in rects { - let x1 = x; - let y1 = y; - let x2 = x1 + width as i32; - let y2 = y1 + height as i32; - let offsets = [ - Offset3D { x: x1, y: y1, z: 0 }, - Offset3D { x: x2, y: y2, z: 1 }, - ]; - let region = ImageBlit2::default() - .src_subresource(image_subresource) - .src_offsets(offsets) - .dst_subresource(image_subresource) - .dst_offsets(offsets); - regions.push(region); - } - use {AccessFlags2 as A, ImageLayout as L}; - let initial_barriers = initial_image_barriers![ - src, L::TRANSFER_SRC_OPTIMAL, A::TRANSFER_READ; - dst, L::TRANSFER_DST_OPTIMAL, A::TRANSFER_WRITE; - ]; - let final_barriers = final_image_barriers![ - src, L::TRANSFER_SRC_OPTIMAL, A::TRANSFER_READ; - dst, L::TRANSFER_DST_OPTIMAL, A::TRANSFER_WRITE; - ]; - let initial_dependency_info = - DependencyInfo::default().image_memory_barriers(&initial_barriers); - let final_dependency_info = - DependencyInfo::default().image_memory_barriers(&final_barriers); - let blit_image_info = BlitImageInfo2::default() - .src_image(src.img) - .src_image_layout(L::TRANSFER_SRC_OPTIMAL) - .dst_image(dst.img) - .dst_image_layout(L::TRANSFER_DST_OPTIMAL) - .regions(regions) - .filter(Filter::NEAREST); - unsafe { - dev.cmd_pipeline_barrier2(cmd, &initial_dependency_info); - dev.cmd_blit_image2(cmd, &blit_image_info); - dev.cmd_pipeline_barrier2(cmd, &final_dependency_info); - } - } - }; - unsafe { - dev.end_command_buffer(cmd) - .map_err(CopyDeviceError::EndCommandBuffer)?; - } - let mut wait_semaphore = None; - let mut wait_semaphores = ArrayVec::<_, 1>::new(); - if let Some(sync) = sync - && let Some(sync_file) = sync.get_sync_file() - { - let semaphore = match slf.dev.semaphores.pop() { - Some(s) => s, - _ => slf.dev.create_semaphore()?, - }; - semaphore.import(sync_file)?; - let info = SemaphoreSubmitInfo::default() - .semaphore(semaphore.semaphore) - .stage_mask(PipelineStageFlags2::TRANSFER); - wait_semaphores.push(info); - wait_semaphore = Some(semaphore); - } - let command_buffer_info = CommandBufferSubmitInfo::default().command_buffer(cmd); - let mut semaphore_submit_info = SemaphoreSubmitInfo::default(); - let mut submit_info = SubmitInfo2::default() - .command_buffer_infos(slice::from_ref(&command_buffer_info)) - .wait_semaphore_infos(&wait_semaphores); - let vulkan_sync = slf.dev.create_sync( - self.dev.timeline_semaphore.as_ref(), - &mut semaphore_submit_info, - &mut submit_info, - )?; - unsafe { - slf.dev - .dev - .queue_submit2( - slf.dev.queues[tt], - slice::from_ref(&submit_info), - vulkan_sync.fence(), - ) - .map_err(CopyDeviceError::SubmitCopy)?; - } - let sync = vulkan_sync.to_sync(|| slf.dev.wait_idle()); - slf.busy.set(sync.clone()); - let pending = Pending { - dev: slf.dev.clone(), - busy_id: slf.busy_id.add_fetch(1), - sync: sync.clone(), - copy: self.inner.clone(), - semaphore: wait_semaphore, - vulkan_sync, - }; - slf.dev.submissions[tt].pending.push(pending); - Ok(sync) - } -} - impl VulkanSemaphore { fn import(&self, sync_file: &OwnedFd) -> Result<(), CopyDeviceError> { let fd = uapi::fcntl_dupfd_cloexec(sync_file.raw(), 0) @@ -1733,47 +1291,6 @@ impl VulkanSemaphore { } } -impl CopyDeviceRegistry { - pub fn new( - ring: &Rc, - eng: &Rc, - eventfd_cache: &Rc, - ) -> Self { - Self { - ring: ring.clone(), - eng: eng.clone(), - eventfd_cache: eventfd_cache.clone(), - devs: Default::default(), - } - } - - pub fn remove(&self, dev: c::dev_t) { - self.devs.remove(&dev); - } - - pub fn get(&self, dev: c::dev_t) -> Option> { - if let Some(dev) = self.devs.get(&dev) { - return dev; - } - match PhysicalCopyDevice::new(&self.ring, &self.eng, &self.eventfd_cache, dev).map(Some) { - Ok(cd) => { - self.devs.set(dev, cd.clone()); - cd - } - Err(e) => { - let maj = uapi::major(dev); - let min = uapi::minor(dev); - log::warn!( - "Could not create physical copy device for {maj}:{min}: {}", - ErrorFmt(e), - ); - self.devs.set(dev, None); - None - } - } - } -} - impl Drop for VulkanSemaphore { fn drop(&mut self) { unsafe { @@ -1898,125 +1415,6 @@ impl Deref for CopyDevice { } } -type QueueInfo = (u32, (u32, u32), u32); - -fn allocate_queues( - gfx: QueueInfo, - compute_only: Option, - transfer_only: Option, -) -> (Vec, KeyedCopy) { - let intra = compute_only.unwrap_or(gfx); - let cross = transfer_only.unwrap_or(intra); - let mut distinct_families = AHashSet::default(); - distinct_families.insert(cross); - distinct_families.insert(intra); - distinct_families.insert(gfx); - let mut queues_to_allocate = vec![]; - macro_rules! index { - ($qi:expr, $within:expr) => { - QueueIndex { - allocate_idx: queues_to_allocate.len(), - family: $qi.0, - idx_within_family: $within as u32, - transfer_granularity_mask: $qi.1, - } - }; - } - macro_rules! alloc { - ($qi:expr, $num:expr) => { - QueueToAllocate { - family: $qi.0, - num: $num as usize, - } - }; - } - let (blit, intra_idx, download, upload); - if distinct_families.len() == 3 { - let num_cross = cross.2.min(2) as usize; - blit = index!(gfx, 0); - queues_to_allocate.push(alloc!(gfx, 1)); - intra_idx = index!(intra, 0); - queues_to_allocate.push(alloc!(intra, 1)); - download = index!(cross, 0); - upload = index!(cross, num_cross - 1); - queues_to_allocate.push(alloc!(cross, num_cross)); - } else if distinct_families.len() == 1 { - let qi = cross; - let num = qi.2.min(4); - match num { - 1 => { - blit = index!(qi, 0); - intra_idx = index!(qi, 0); - download = index!(qi, 0); - upload = index!(qi, 0); - } - 2 => { - blit = index!(qi, 0); - intra_idx = index!(qi, 0); - download = index!(qi, 0); - upload = index!(qi, 1); - } - 3 => { - blit = index!(qi, 0); - intra_idx = index!(qi, 0); - download = index!(qi, 1); - upload = index!(qi, 2); - } - 4 => { - blit = index!(qi, 0); - intra_idx = index!(qi, 1); - download = index!(qi, 2); - upload = index!(qi, 3); - } - _ => unreachable!(), - } - queues_to_allocate.push(alloc!(qi, num)); - } else { - if gfx == intra { - let num_gfx = gfx.2.min(2); - blit = index!(gfx, 0); - intra_idx = index!(gfx, num_gfx - 1); - queues_to_allocate.push(alloc!(gfx, num_gfx)); - let num_cross = cross.2.min(2); - download = index!(cross, 0); - upload = index!(cross, num_cross - 1); - queues_to_allocate.push(alloc!(cross, num_cross)); - } else { - // if cross == gfx then intra == gfx - assert_eq!(intra, cross); - blit = index!(gfx, 0); - queues_to_allocate.push(alloc!(gfx, 1)); - let num_intra = intra.2.min(3); - match num_intra { - 1 => { - intra_idx = index!(intra, 0); - download = index!(intra, 0); - upload = index!(intra, 0); - } - 2 => { - intra_idx = index!(intra, 0); - download = index!(intra, 0); - upload = index!(intra, 1); - } - 3 => { - intra_idx = index!(intra, 0); - download = index!(intra, 1); - upload = index!(intra, 2); - } - _ => unreachable!(), - } - queues_to_allocate.push(alloc!(intra, num_intra)); - } - } - let queue_indices = static_copy_map! { - TransferType::Blit => blit, - TransferType::Intra => intra_idx, - TransferType::Download => download, - TransferType::Upload => upload, - }; - (queues_to_allocate, queue_indices) -} - impl VulkanDeviceInf for CopyDeviceInner { fn instance(&self) -> &VulkanCoreInstance { &self.phy.instance diff --git a/src/copy_device/execute.rs b/src/copy_device/execute.rs new file mode 100644 index 00000000..2211940c --- /dev/null +++ b/src/copy_device/execute.rs @@ -0,0 +1,443 @@ +use jay_geometry::{Rect, Region}; +use { + super::{CopyDeviceCopy, CopyDeviceCopyType, CopyDeviceError, Pending}, + crate::{ + gfx_api::FdSync, + + vulkan_core::sync::VulkanDeviceSyncExt, + }, + arrayvec::ArrayVec, + ash::vk::{ + AccessFlags2, BlitImageInfo2, BufferCopy2, BufferImageCopy2, BufferMemoryBarrier2, + CommandBufferBeginInfo, CommandBufferSubmitInfo, CommandBufferUsageFlags, + CopyBufferInfo2, CopyBufferToImageInfo2, CopyImageInfo2, CopyImageToBufferInfo2, + DependencyInfo, Extent3D, Filter, ImageAspectFlags, ImageBlit2, ImageCopy2, ImageLayout, + ImageMemoryBarrier2, ImageSubresourceLayers, ImageSubresourceRange, Offset3D, + PipelineStageFlags2, QUEUE_FAMILY_FOREIGN_EXT, SemaphoreSubmitInfo, SubmitInfo2, + WHOLE_SIZE, + }, + std::slice, +}; + +impl CopyDeviceCopy { + fn ensure_not_busy(&self) -> Result<(), CopyDeviceError> { + let slf = &*self.inner; + if let Some(sync) = slf.busy.get() + && sync.is_unsignaled() + { + return Err(CopyDeviceError::Busy); + } + slf.busy.take(); + Ok(()) + } + + pub fn execute( + &self, + sync: Option<&FdSync>, + region: Option<&Region>, + ) -> Result, CopyDeviceError> { + self.ensure_not_busy()?; + let slf = &*self.inner; + let tt = slf.tt; + let dev = &slf.dev.dev; + let cmd = slf.command_buffer; + let queue_family = slf.dev.phy.queues[tt].family; + let region_buf; + let width = slf.width; + let height = slf.height; + let region = match region { + Some(r) => r, + _ => { + region_buf = Region::new(Rect::new_saturating(0, 0, width as i32, height as i32)); + ®ion_buf + } + }; + let (x_mask, y_mask) = slf.dev.phy.queues[tt].transfer_granularity_mask; + let rects = &mut *slf.dev.phy.rects.borrow_mut(); + rects.clear(); + for rect in region.iter() { + let x1 = (rect.x1().max(0) as u32 & !x_mask).min(width); + let y1 = (rect.y1().max(0) as u32 & !y_mask).min(height); + let x2 = ((rect.x2().max(0) as u32 + x_mask) & !x_mask).min(width); + let y2 = ((rect.y2().max(0) as u32 + y_mask) & !y_mask).min(height); + let width = x2 - x1; + let height = y2 - y1; + if width == 0 || height == 0 { + continue; + } + rects.push((x1 as i32, y1 as i32, width, height)); + } + if rects.is_empty() { + return Ok(None); + } + let begin_info = + CommandBufferBeginInfo::default().flags(CommandBufferUsageFlags::ONE_TIME_SUBMIT); + unsafe { + dev.begin_command_buffer(cmd, &begin_info) + .map_err(CopyDeviceError::BeginCommandBuffer)?; + } + macro_rules! initial_buffer_barriers { + ($($buf:expr, $access:expr;)*) => { + [$( + BufferMemoryBarrier2::default() + .dst_stage_mask(PipelineStageFlags2::TRANSFER) + .dst_access_mask($access) + .src_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) + .dst_queue_family_index(queue_family) + .buffer($buf.buf) + .size(WHOLE_SIZE), + )*] + }; + } + macro_rules! final_buffer_barriers { + ($($buf:expr, $access:expr;)*) => { + [$( + BufferMemoryBarrier2::default() + .src_stage_mask(PipelineStageFlags2::TRANSFER) + .src_access_mask($access) + .src_queue_family_index(queue_family) + .dst_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) + .buffer($buf.buf) + .size(WHOLE_SIZE), + )*] + }; + } + let image_subresource_range = ImageSubresourceRange { + aspect_mask: ImageAspectFlags::COLOR, + base_mip_level: 0, + level_count: 1, + base_array_layer: 0, + layer_count: 1, + }; + let image_subresource = ImageSubresourceLayers { + aspect_mask: ImageAspectFlags::COLOR, + mip_level: 0, + base_array_layer: 0, + layer_count: 1, + }; + macro_rules! initial_image_barriers { + ($($img:expr, $layout:expr, $access:expr;)*) => { + [$( + ImageMemoryBarrier2::default() + .dst_stage_mask(PipelineStageFlags2::TRANSFER) + .dst_access_mask($access) + .old_layout(ImageLayout::GENERAL) + .new_layout(ImageLayout::GENERAL) + .src_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) + .dst_queue_family_index(queue_family) + .image($img.img) + .subresource_range(image_subresource_range), + ImageMemoryBarrier2::default() + .src_stage_mask(PipelineStageFlags2::TRANSFER) + .src_access_mask($access) + .dst_stage_mask(PipelineStageFlags2::TRANSFER) + .dst_access_mask($access) + .old_layout(ImageLayout::GENERAL) + .new_layout($layout) + .src_queue_family_index(queue_family) + .dst_queue_family_index(queue_family) + .image($img.img) + .subresource_range(image_subresource_range), + )*] + }; + } + macro_rules! final_image_barriers { + ($($img:expr, $layout:expr, $access:expr;)*) => { + [$( + ImageMemoryBarrier2::default() + .src_stage_mask(PipelineStageFlags2::TRANSFER) + .src_access_mask($access) + .dst_stage_mask(PipelineStageFlags2::TRANSFER) + .dst_access_mask($access) + .old_layout($layout) + .new_layout(ImageLayout::GENERAL) + .src_queue_family_index(queue_family) + .dst_queue_family_index(queue_family) + .image($img.img) + .subresource_range(image_subresource_range), + ImageMemoryBarrier2::default() + .src_stage_mask(PipelineStageFlags2::TRANSFER) + .src_access_mask($access) + .old_layout(ImageLayout::GENERAL) + .new_layout(ImageLayout::GENERAL) + .src_queue_family_index(queue_family) + .dst_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) + .image($img.img) + .subresource_range(image_subresource_range), + )*] + }; + } + match &slf.ty { + CopyDeviceCopyType::BufferToBuffer { + src, + dst, + stride, + bpp, + } => { + let regions = &mut *slf.dev.phy.buffer_copy_2.borrow_mut(); + regions.clear(); + let stride = *stride as u64; + let bpp = *bpp as u64; + for &mut (x, y, width, height) in rects { + let lo = y as u64 * stride + x as u64 * bpp; + let size = (height as u64 - 1) * stride + width as u64 * bpp; + let region = BufferCopy2::default() + .src_offset(lo) + .dst_offset(lo) + .size(size); + regions.push(region); + } + use AccessFlags2 as A; + let initial_barriers = initial_buffer_barriers![ + src, A::TRANSFER_READ; + dst, A::TRANSFER_WRITE; + ]; + let final_barriers = final_buffer_barriers![ + src, A::TRANSFER_READ; + dst, A::TRANSFER_WRITE; + ]; + let initial_dependency_info = + DependencyInfo::default().buffer_memory_barriers(&initial_barriers); + let final_dependency_info = + DependencyInfo::default().buffer_memory_barriers(&final_barriers); + let copy_buffer_info = CopyBufferInfo2::default() + .src_buffer(src.buf) + .dst_buffer(dst.buf) + .regions(regions); + unsafe { + dev.cmd_pipeline_barrier2(cmd, &initial_dependency_info); + dev.cmd_copy_buffer2(cmd, ©_buffer_info); + dev.cmd_pipeline_barrier2(cmd, &final_dependency_info); + } + } + CopyDeviceCopyType::BufferToImage { + buf, + buf_format, + buf_stride, + img, + } + | CopyDeviceCopyType::ImageToBuffer { + img, + buf, + buf_format, + buf_stride, + } => { + let regions = &mut *slf.dev.phy.buffer_image_copy_2.borrow_mut(); + regions.clear(); + for &mut (x, y, width, height) in rects { + let offset = y as u64 * *buf_stride as u64 + x as u64 * buf_format.bpp as u64; + let region = BufferImageCopy2::default() + .buffer_offset(offset) + .buffer_row_length(*buf_stride / buf_format.bpp) + .buffer_image_height(slf.height) + .image_subresource(image_subresource) + .image_offset(Offset3D { x, y, z: 0 }) + .image_extent(Extent3D { + width, + height, + depth: 1, + }); + regions.push(region); + } + let buffer_to_image = match &slf.ty { + CopyDeviceCopyType::BufferToImage { .. } => true, + CopyDeviceCopyType::ImageToBuffer { .. } => false, + _ => unreachable!(), + }; + let image_access_mask; + let image_layout; + let buffer_access_mask; + match buffer_to_image { + true => { + image_access_mask = AccessFlags2::TRANSFER_WRITE; + image_layout = ImageLayout::TRANSFER_DST_OPTIMAL; + buffer_access_mask = AccessFlags2::TRANSFER_READ; + } + false => { + image_access_mask = AccessFlags2::TRANSFER_READ; + image_layout = ImageLayout::TRANSFER_SRC_OPTIMAL; + buffer_access_mask = AccessFlags2::TRANSFER_WRITE; + } + } + let initial_image_barriers = initial_image_barriers![ + img, image_layout, image_access_mask; + ]; + let final_image_barriers = final_image_barriers![ + img, image_layout, image_access_mask; + ]; + let initial_buffer_barriers = initial_buffer_barriers![ + buf, buffer_access_mask; + ]; + let final_buffer_barriers = final_buffer_barriers![ + buf, buffer_access_mask; + ]; + let initial_dependency_info = DependencyInfo::default() + .buffer_memory_barriers(&initial_buffer_barriers) + .image_memory_barriers(&initial_image_barriers); + let final_dependency_info = DependencyInfo::default() + .buffer_memory_barriers(&final_buffer_barriers) + .image_memory_barriers(&final_image_barriers); + unsafe { + dev.cmd_pipeline_barrier2(cmd, &initial_dependency_info); + match buffer_to_image { + true => { + let copy = CopyBufferToImageInfo2::default() + .src_buffer(buf.buf) + .dst_image(img.img) + .dst_image_layout(image_layout) + .regions(®ions); + dev.cmd_copy_buffer_to_image2(cmd, ©); + } + false => { + let copy = CopyImageToBufferInfo2::default() + .src_image(img.img) + .src_image_layout(image_layout) + .dst_buffer(buf.buf) + .regions(®ions); + dev.cmd_copy_image_to_buffer2(cmd, ©); + } + } + dev.cmd_pipeline_barrier2(cmd, &final_dependency_info); + } + } + CopyDeviceCopyType::ImageToImage { src, dst } => { + let regions = &mut *slf.dev.phy.image_copy_2.borrow_mut(); + regions.clear(); + for &mut (x, y, width, height) in rects { + let region = ImageCopy2::default() + .src_subresource(image_subresource) + .src_offset(Offset3D { x, y, z: 0 }) + .dst_subresource(image_subresource) + .dst_offset(Offset3D { x, y, z: 0 }) + .extent(Extent3D { + width, + height, + depth: 1, + }); + regions.push(region); + } + use {AccessFlags2 as A, ImageLayout as L}; + let initial_barriers = initial_image_barriers![ + src, L::TRANSFER_SRC_OPTIMAL, A::TRANSFER_READ; + dst, L::TRANSFER_DST_OPTIMAL, A::TRANSFER_WRITE; + ]; + let final_barriers = final_image_barriers![ + src, L::TRANSFER_SRC_OPTIMAL, A::TRANSFER_READ; + dst, L::TRANSFER_DST_OPTIMAL, A::TRANSFER_WRITE; + ]; + let initial_dependency_info = + DependencyInfo::default().image_memory_barriers(&initial_barriers); + let final_dependency_info = + DependencyInfo::default().image_memory_barriers(&final_barriers); + let copy_image_info = CopyImageInfo2::default() + .src_image(src.img) + .src_image_layout(L::TRANSFER_SRC_OPTIMAL) + .dst_image(dst.img) + .dst_image_layout(L::TRANSFER_DST_OPTIMAL) + .regions(regions); + unsafe { + dev.cmd_pipeline_barrier2(cmd, &initial_dependency_info); + dev.cmd_copy_image2(cmd, ©_image_info); + dev.cmd_pipeline_barrier2(cmd, &final_dependency_info); + } + } + CopyDeviceCopyType::Blit { src, dst } => { + let regions = &mut *slf.dev.phy.image_blit_2.borrow_mut(); + regions.clear(); + for &mut (x, y, width, height) in rects { + let x1 = x; + let y1 = y; + let x2 = x1 + width as i32; + let y2 = y1 + height as i32; + let offsets = [ + Offset3D { x: x1, y: y1, z: 0 }, + Offset3D { x: x2, y: y2, z: 1 }, + ]; + let region = ImageBlit2::default() + .src_subresource(image_subresource) + .src_offsets(offsets) + .dst_subresource(image_subresource) + .dst_offsets(offsets); + regions.push(region); + } + use {AccessFlags2 as A, ImageLayout as L}; + let initial_barriers = initial_image_barriers![ + src, L::TRANSFER_SRC_OPTIMAL, A::TRANSFER_READ; + dst, L::TRANSFER_DST_OPTIMAL, A::TRANSFER_WRITE; + ]; + let final_barriers = final_image_barriers![ + src, L::TRANSFER_SRC_OPTIMAL, A::TRANSFER_READ; + dst, L::TRANSFER_DST_OPTIMAL, A::TRANSFER_WRITE; + ]; + let initial_dependency_info = + DependencyInfo::default().image_memory_barriers(&initial_barriers); + let final_dependency_info = + DependencyInfo::default().image_memory_barriers(&final_barriers); + let blit_image_info = BlitImageInfo2::default() + .src_image(src.img) + .src_image_layout(L::TRANSFER_SRC_OPTIMAL) + .dst_image(dst.img) + .dst_image_layout(L::TRANSFER_DST_OPTIMAL) + .regions(regions) + .filter(Filter::NEAREST); + unsafe { + dev.cmd_pipeline_barrier2(cmd, &initial_dependency_info); + dev.cmd_blit_image2(cmd, &blit_image_info); + dev.cmd_pipeline_barrier2(cmd, &final_dependency_info); + } + } + }; + unsafe { + dev.end_command_buffer(cmd) + .map_err(CopyDeviceError::EndCommandBuffer)?; + } + let mut wait_semaphore = None; + let mut wait_semaphores = ArrayVec::<_, 1>::new(); + if let Some(sync) = sync + && let Some(sync_file) = sync.get_sync_file() + { + let semaphore = match slf.dev.semaphores.pop() { + Some(s) => s, + _ => slf.dev.create_semaphore()?, + }; + semaphore.import(sync_file)?; + let info = SemaphoreSubmitInfo::default() + .semaphore(semaphore.semaphore) + .stage_mask(PipelineStageFlags2::TRANSFER); + wait_semaphores.push(info); + wait_semaphore = Some(semaphore); + } + let command_buffer_info = CommandBufferSubmitInfo::default().command_buffer(cmd); + let mut semaphore_submit_info = SemaphoreSubmitInfo::default(); + let mut submit_info = SubmitInfo2::default() + .command_buffer_infos(slice::from_ref(&command_buffer_info)) + .wait_semaphore_infos(&wait_semaphores); + let vulkan_sync = slf.dev.create_sync( + self.dev.timeline_semaphore.as_ref(), + &mut semaphore_submit_info, + &mut submit_info, + )?; + unsafe { + slf.dev + .dev + .queue_submit2( + slf.dev.queues[tt], + slice::from_ref(&submit_info), + vulkan_sync.fence(), + ) + .map_err(CopyDeviceError::SubmitCopy)?; + } + let sync = vulkan_sync.to_sync(|| slf.dev.wait_idle()); + slf.busy.set(sync.clone()); + let pending = Pending { + dev: slf.dev.clone(), + busy_id: slf.busy_id.add_fetch(1), + sync: sync.clone(), + copy: self.inner.clone(), + semaphore: wait_semaphore, + vulkan_sync, + }; + slf.dev.submissions[tt].pending.push(pending); + Ok(sync) + } +} diff --git a/src/copy_device/queue_allocation.rs b/src/copy_device/queue_allocation.rs new file mode 100644 index 00000000..8d3d91d9 --- /dev/null +++ b/src/copy_device/queue_allocation.rs @@ -0,0 +1,138 @@ +use { + super::{KeyedCopy, TransferType}, + ahash::AHashSet, + linearize::static_copy_map, +}; + +#[derive(Debug)] +pub(super) struct QueueToAllocate { + pub(super) family: u32, + pub(super) num: usize, +} + +#[derive(Copy, Clone, Default, Debug)] +pub(super) struct QueueIndex { + pub(super) allocate_idx: usize, + pub(super) family: u32, + pub(super) idx_within_family: u32, + pub(super) transfer_granularity_mask: (u32, u32), +} + +type QueueInfo = (u32, (u32, u32), u32); + +pub(super) fn allocate_queues( + gfx: QueueInfo, + compute_only: Option, + transfer_only: Option, +) -> (Vec, KeyedCopy) { + let intra = compute_only.unwrap_or(gfx); + let cross = transfer_only.unwrap_or(intra); + let mut distinct_families = AHashSet::default(); + distinct_families.insert(cross); + distinct_families.insert(intra); + distinct_families.insert(gfx); + let mut queues_to_allocate = vec![]; + macro_rules! index { + ($qi:expr, $within:expr) => { + QueueIndex { + allocate_idx: queues_to_allocate.len(), + family: $qi.0, + idx_within_family: $within as u32, + transfer_granularity_mask: $qi.1, + } + }; + } + macro_rules! alloc { + ($qi:expr, $num:expr) => { + QueueToAllocate { + family: $qi.0, + num: $num as usize, + } + }; + } + let (blit, intra_idx, download, upload); + if distinct_families.len() == 3 { + let num_cross = cross.2.min(2) as usize; + blit = index!(gfx, 0); + queues_to_allocate.push(alloc!(gfx, 1)); + intra_idx = index!(intra, 0); + queues_to_allocate.push(alloc!(intra, 1)); + download = index!(cross, 0); + upload = index!(cross, num_cross - 1); + queues_to_allocate.push(alloc!(cross, num_cross)); + } else if distinct_families.len() == 1 { + let qi = cross; + let num = qi.2.min(4); + match num { + 1 => { + blit = index!(qi, 0); + intra_idx = index!(qi, 0); + download = index!(qi, 0); + upload = index!(qi, 0); + } + 2 => { + blit = index!(qi, 0); + intra_idx = index!(qi, 0); + download = index!(qi, 0); + upload = index!(qi, 1); + } + 3 => { + blit = index!(qi, 0); + intra_idx = index!(qi, 0); + download = index!(qi, 1); + upload = index!(qi, 2); + } + 4 => { + blit = index!(qi, 0); + intra_idx = index!(qi, 1); + download = index!(qi, 2); + upload = index!(qi, 3); + } + _ => unreachable!(), + } + queues_to_allocate.push(alloc!(qi, num)); + } else { + if gfx == intra { + let num_gfx = gfx.2.min(2); + blit = index!(gfx, 0); + intra_idx = index!(gfx, num_gfx - 1); + queues_to_allocate.push(alloc!(gfx, num_gfx)); + let num_cross = cross.2.min(2); + download = index!(cross, 0); + upload = index!(cross, num_cross - 1); + queues_to_allocate.push(alloc!(cross, num_cross)); + } else { + // if cross == gfx then intra == gfx + assert_eq!(intra, cross); + blit = index!(gfx, 0); + queues_to_allocate.push(alloc!(gfx, 1)); + let num_intra = intra.2.min(3); + match num_intra { + 1 => { + intra_idx = index!(intra, 0); + download = index!(intra, 0); + upload = index!(intra, 0); + } + 2 => { + intra_idx = index!(intra, 0); + download = index!(intra, 0); + upload = index!(intra, 1); + } + 3 => { + intra_idx = index!(intra, 0); + download = index!(intra, 1); + upload = index!(intra, 2); + } + _ => unreachable!(), + } + queues_to_allocate.push(alloc!(intra, num_intra)); + } + } + let queue_indices = static_copy_map! { + TransferType::Blit => blit, + TransferType::Intra => intra_idx, + TransferType::Download => download, + TransferType::Upload => upload, + }; + (queues_to_allocate, queue_indices) +} diff --git a/src/copy_device/registry.rs b/src/copy_device/registry.rs new file mode 100644 index 00000000..43bb9761 --- /dev/null +++ b/src/copy_device/registry.rs @@ -0,0 +1,62 @@ +use jay_io_uring::IoUring; +use jay_eventfd_cache::EventfdCache; +use jay_async_engine::AsyncEngine; +use { + super::PhysicalCopyDevice, + crate::{ + + + + utils::{copyhashmap::CopyHashMap, errorfmt::ErrorFmt}, + }, + std::rc::Rc, + uapi::c, +}; + +pub struct CopyDeviceRegistry { + ring: Rc, + eng: Rc, + eventfd_cache: Rc, + devs: CopyHashMap>>, +} + +impl CopyDeviceRegistry { + pub fn new( + ring: &Rc, + eng: &Rc, + eventfd_cache: &Rc, + ) -> Self { + Self { + ring: ring.clone(), + eng: eng.clone(), + eventfd_cache: eventfd_cache.clone(), + devs: Default::default(), + } + } + + pub fn remove(&self, dev: c::dev_t) { + self.devs.remove(&dev); + } + + pub fn get(&self, dev: c::dev_t) -> Option> { + if let Some(dev) = self.devs.get(&dev) { + return dev; + } + match PhysicalCopyDevice::new(&self.ring, &self.eng, &self.eventfd_cache, dev).map(Some) { + Ok(cd) => { + self.devs.set(dev, cd.clone()); + cd + } + Err(e) => { + let maj = uapi::major(dev); + let min = uapi::minor(dev); + log::warn!( + "Could not create physical copy device for {maj}:{min}: {}", + ErrorFmt(e), + ); + self.devs.set(dev, None); + None + } + } + } +} diff --git a/src/criteria.rs b/src/criteria.rs index cf8125f2..4d67b849 100644 --- a/src/criteria.rs +++ b/src/criteria.rs @@ -1,96 +1,2 @@ pub mod clm; -mod crit_graph; -pub mod crit_leaf; -mod crit_matchers; -mod crit_per_target_data; pub mod tlm; - -use { - crate::{ - criteria::{ - crit_graph::{CritMgr, CritMiddle, CritRoot, CritRootCriterion, CritRootFixed}, - crit_leaf::CritLeafMatcher, - crit_matchers::{critm_any_or_all::CritMatchAnyOrAll, critm_exactly::CritMatchExactly}, - }, - utils::copyhashmap::CopyHashMap, - }, - linearize::StaticMap, - regex::Regex, - std::rc::{Rc, Weak}, -}; -pub use { - crit_graph::{CritTarget, CritUpstreamNode}, - crit_per_target_data::CritDestroyListener, -}; - -linear_ids!(CritMatcherIds, CritMatcherId, u64); - -type RootMatcherMap = CopyHashMap>>; -type FixedRootMatcher = StaticMap>>>; - -#[derive(Clone)] -pub enum CritLiteralOrRegex { - Literal(String), - Regex(Regex), -} - -impl CritLiteralOrRegex { - fn matches(&self, string: &str) -> bool { - match self { - CritLiteralOrRegex::Literal(p) => string == p, - CritLiteralOrRegex::Regex(r) => r.is_match(string), - } - } -} - -pub trait CritMgrExt: CritMgr { - fn list( - &self, - upstream: &[Rc>], - all: bool, - ) -> Rc> { - if upstream.is_empty() { - return self.match_constant()[all].clone(); - } - CritMiddle::new(self, upstream, CritMatchAnyOrAll::new(upstream, all)) - } - - fn exactly( - &self, - upstream: &[Rc>], - num: usize, - ) -> Rc> { - if num > upstream.len() { - return self.match_constant()[false].clone(); - } - if num == 0 { - let upstream: Vec<_> = upstream.iter().map(|u| u.not(self)).collect(); - return self.list(&upstream, true); - } - CritMiddle::new(self, upstream, CritMatchExactly::new(upstream, num)) - } - - fn leaf( - &self, - upstream: &Rc>, - on_match: impl Fn(::LeafData) -> Box + 'static, - ) -> Rc> { - CritLeafMatcher::new(self, upstream, on_match) - } - - fn not( - &self, - upstream: &Rc>, - ) -> Rc> { - upstream.not(self) - } - - fn root(&self, criterion: T) -> Rc> - where - T: CritRootCriterion, - { - CritRoot::new(self.roots(), self.id(), criterion) - } -} - -impl CritMgrExt for T where T: CritMgr {} diff --git a/src/criteria/clm.rs b/src/criteria/clm.rs index 96e78a03..1081debe 100644 --- a/src/criteria/clm.rs +++ b/src/criteria/clm.rs @@ -3,29 +3,29 @@ pub mod clm_matchers; use { crate::{ client::{Client, ClientId}, - criteria::{ - CritDestroyListener, CritLiteralOrRegex, CritMatcherId, CritMatcherIds, CritMgrExt, - CritUpstreamNode, FixedRootMatcher, RootMatcherMap, - clm::clm_matchers::{ - clmm_id::ClmMatchId, - clmm_is_xwayland::ClmMatchIsXwayland, - clmm_pid::ClmMatchPid, - clmm_sandboxed::ClmMatchSandboxed, - clmm_string::{ - ClmMatchComm, ClmMatchExe, ClmMatchSandboxAppId, ClmMatchSandboxEngine, - ClmMatchSandboxInstanceId, ClmMatchTag, - }, - clmm_uid::ClmMatchUid, + criteria::clm::clm_matchers::{ + clmm_id::ClmMatchId, + clmm_is_xwayland::ClmMatchIsXwayland, + clmm_pid::ClmMatchPid, + clmm_sandboxed::ClmMatchSandboxed, + clmm_string::{ + ClmMatchComm, ClmMatchExe, ClmMatchSandboxAppId, ClmMatchSandboxEngine, + ClmMatchSandboxInstanceId, ClmMatchTag, }, - crit_graph::{ - CritMgr, CritRoot, CritRootFixed, CritTarget, CritTargetOwner, WeakCritTargetOwner, - }, - crit_leaf::{CritLeafEvent, CritLeafMatcher}, - crit_matchers::critm_constant::CritMatchConstant, + clmm_uid::ClmMatchUid, }, state::State, utils::{copyhashmap::CopyHashMap, hash_map_ext::HashMapExt, queue::AsyncQueue}, }, + jay_criteria::{ + CritDestroyListener, CritLiteralOrRegex, CritMatcherId, CritMatcherIds, CritMgrExt, + CritUpstreamNode, FixedRootMatcher, RootMatcherMap, + crit_graph::{ + CritMgr, CritRoot, CritRootFixed, CritTarget, CritTargetOwner, WeakCritTargetOwner, + }, + crit_leaf::{CritLeafEvent, CritLeafMatcher}, + crit_matchers::critm_constant::CritMatchConstant, + }, linearize::static_map, std::{ marker::PhantomData, @@ -39,19 +39,19 @@ bitflags! { CL_CHANGED_NEW, } -type ClmFixedRootMatcher = FixedRootMatcher, T>; +type ClmFixedRootMatcher = FixedRootMatcher; pub struct ClMatcherManager { ids: Rc, changes: AsyncQueue>, - leaf_events: Rc>>>, - constant: ClmFixedRootMatcher>>, + leaf_events: Rc>>, + constant: ClmFixedRootMatcher>, sandboxed: ClmFixedRootMatcher, is_xwayland: ClmFixedRootMatcher, matchers: Rc, } -type ClmRootMatcherMap = RootMatcherMap, T>; +type ClmRootMatcherMap = RootMatcherMap; #[derive(Default)] pub struct RootMatchers { @@ -98,8 +98,8 @@ pub async fn handle_cl_leaf_events(state: Rc) { } } -pub type ClmUpstreamNode = dyn CritUpstreamNode>; -pub type ClmLeafMatcher = CritLeafMatcher>; +pub type ClmUpstreamNode = dyn CritUpstreamNode; +pub type ClmLeafMatcher = CritLeafMatcher; impl ClMatcherManager { pub fn new(ids: &Rc) -> Self { @@ -146,6 +146,7 @@ impl ClMatcherManager { } fn update_matches(&self, data: &Rc) { + let data = data.as_ref(); let mut changed = data.changed_properties.take(); if changed.contains(CL_CHANGED_DESTROYED) { for destroyed in data.destroyed.lock().drain_values() { @@ -235,15 +236,27 @@ impl ClMatcherManager { } } -impl CritTarget for Rc { +pub struct ClientTargetOwner { + client: Rc, +} + +pub struct WeakClientTargetOwner { + state: Weak, + id: ClientId, +} + +impl CritTarget for Client { type Id = ClientId; type Mgr = ClMatcherManager; type RootMatchers = RootMatchers; type LeafData = ClientId; - type Owner = Weak; + type Owner = WeakClientTargetOwner; fn owner(&self) -> Self::Owner { - Rc::downgrade(self) + WeakClientTargetOwner { + state: Rc::downgrade(&self.state), + id: self.id, + } } fn id(&self) -> Self::Id { @@ -259,25 +272,27 @@ impl CritTarget for Rc { } } -impl CritTargetOwner for Rc { - type Target = Rc; +impl CritTargetOwner for ClientTargetOwner { + type Target = Client; fn data(&self) -> &Self::Target { - self + &self.client } } -impl WeakCritTargetOwner for Weak { - type Target = Rc; - type Owner = Rc; +impl WeakCritTargetOwner for WeakClientTargetOwner { + type Target = Client; + type Owner = ClientTargetOwner; fn upgrade(&self) -> Option { - self.upgrade() + let state = self.state.upgrade()?; + let client = state.clients.get(self.id).ok()?; + Some(ClientTargetOwner { client }) } } impl CritMgr for ClMatcherManager { - type Target = Rc; + type Target = Client; fn id(&self) -> CritMatcherId { self.ids.next() diff --git a/src/criteria/clm/clm_matchers.rs b/src/criteria/clm/clm_matchers.rs index f39837fc..1013ec59 100644 --- a/src/criteria/clm/clm_matchers.rs +++ b/src/criteria/clm/clm_matchers.rs @@ -1,6 +1,6 @@ macro_rules! fixed_root_criterion { ($ty:ty, $field:ident) => { - impl crate::criteria::crit_graph::CritFixedRootCriterionBase> + impl jay_criteria::crit_graph::CritFixedRootCriterionBase for $ty { fn constant(&self) -> bool { @@ -10,7 +10,7 @@ macro_rules! fixed_root_criterion { fn not<'a>( &self, mgr: &'a crate::criteria::clm::ClMatcherManager, - ) -> &'a crate::criteria::FixedRootMatcher, Self> { + ) -> &'a jay_criteria::FixedRootMatcher { &mgr.$field } } diff --git a/src/criteria/clm/clm_matchers/clmm_id.rs b/src/criteria/clm/clm_matchers/clmm_id.rs index 1cb347af..12742126 100644 --- a/src/criteria/clm/clm_matchers/clmm_id.rs +++ b/src/criteria/clm/clm_matchers/clmm_id.rs @@ -1,19 +1,19 @@ use { crate::{ client::{Client, ClientId}, - criteria::{RootMatcherMap, clm::RootMatchers, crit_graph::CritRootCriterion}, + criteria::clm::RootMatchers, }, - std::rc::Rc, + jay_criteria::{RootMatcherMap, crit_graph::CritRootCriterion}, }; pub struct ClmMatchId(pub ClientId); -impl CritRootCriterion> for ClmMatchId { - fn matches(&self, data: &Rc) -> bool { +impl CritRootCriterion for ClmMatchId { + fn matches(&self, data: &Client) -> bool { data.id == self.0 } - fn nodes(roots: &RootMatchers) -> Option<&RootMatcherMap, Self>> { + fn nodes(roots: &RootMatchers) -> Option<&RootMatcherMap> { Some(&roots.id) } } diff --git a/src/criteria/clm/clm_matchers/clmm_is_xwayland.rs b/src/criteria/clm/clm_matchers/clmm_is_xwayland.rs index 4f71c47f..9360d974 100644 --- a/src/criteria/clm/clm_matchers/clmm_is_xwayland.rs +++ b/src/criteria/clm/clm_matchers/clmm_is_xwayland.rs @@ -1,14 +1,11 @@ -use { - crate::{client::Client, criteria::crit_graph::CritFixedRootCriterion}, - std::rc::Rc, -}; +use {crate::client::Client, jay_criteria::crit_graph::CritFixedRootCriterion}; pub struct ClmMatchIsXwayland(pub bool); fixed_root_criterion!(ClmMatchIsXwayland, is_xwayland); -impl CritFixedRootCriterion> for ClmMatchIsXwayland { - fn matches(&self, data: &Rc) -> bool { +impl CritFixedRootCriterion for ClmMatchIsXwayland { + fn matches(&self, data: &Client) -> bool { data.is_xwayland } } diff --git a/src/criteria/clm/clm_matchers/clmm_pid.rs b/src/criteria/clm/clm_matchers/clmm_pid.rs index fc7ae8dc..bd89c248 100644 --- a/src/criteria/clm/clm_matchers/clmm_pid.rs +++ b/src/criteria/clm/clm_matchers/clmm_pid.rs @@ -1,20 +1,17 @@ use { - crate::{ - client::Client, - criteria::{RootMatcherMap, clm::RootMatchers, crit_graph::CritRootCriterion}, - }, - std::rc::Rc, + crate::{client::Client, criteria::clm::RootMatchers}, + jay_criteria::{RootMatcherMap, crit_graph::CritRootCriterion}, uapi::c, }; pub struct ClmMatchPid(pub c::pid_t); -impl CritRootCriterion> for ClmMatchPid { - fn matches(&self, data: &Rc) -> bool { +impl CritRootCriterion for ClmMatchPid { + fn matches(&self, data: &Client) -> bool { data.pid_info.pid == self.0 } - fn nodes(roots: &RootMatchers) -> Option<&RootMatcherMap, Self>> { + fn nodes(roots: &RootMatchers) -> Option<&RootMatcherMap> { Some(&roots.pid) } } diff --git a/src/criteria/clm/clm_matchers/clmm_sandboxed.rs b/src/criteria/clm/clm_matchers/clmm_sandboxed.rs index 4988e9b1..a32623cf 100644 --- a/src/criteria/clm/clm_matchers/clmm_sandboxed.rs +++ b/src/criteria/clm/clm_matchers/clmm_sandboxed.rs @@ -1,14 +1,11 @@ -use { - crate::{client::Client, criteria::crit_graph::CritFixedRootCriterion}, - std::rc::Rc, -}; +use {crate::client::Client, jay_criteria::crit_graph::CritFixedRootCriterion}; pub struct ClmMatchSandboxed(pub bool); fixed_root_criterion!(ClmMatchSandboxed, sandboxed); -impl CritFixedRootCriterion> for ClmMatchSandboxed { - fn matches(&self, data: &Rc) -> bool { - data.acceptor.sandboxed +impl CritFixedRootCriterion for ClmMatchSandboxed { + fn matches(&self, data: &Client) -> bool { + data.metadata.sandboxed } } diff --git a/src/criteria/clm/clm_matchers/clmm_string.rs b/src/criteria/clm/clm_matchers/clmm_string.rs index a5877adc..7ca368d1 100644 --- a/src/criteria/clm/clm_matchers/clmm_string.rs +++ b/src/criteria/clm/clm_matchers/clmm_string.rs @@ -1,33 +1,30 @@ use { crate::{ - client::Client, - criteria::{ - clm::{ClmRootMatcherMap, RootMatchers}, - crit_matchers::critm_string::{CritMatchString, StringAccess}, - }, - security_context_acceptor::AcceptorMetadata, + client::{Client, ClientMetadata}, + criteria::clm::{ClmRootMatcherMap, RootMatchers}, }, - std::{marker::PhantomData, rc::Rc}, + jay_criteria::crit_matchers::critm_string::{CritMatchString, StringAccess}, + std::marker::PhantomData, }; -pub type ClmMatchString = CritMatchString, T>; +pub type ClmMatchString = CritMatchString; -pub type ClmMatchSandboxEngine = ClmMatchString>; -pub type ClmMatchSandboxAppId = ClmMatchString>; -pub type ClmMatchSandboxInstanceId = ClmMatchString>; -pub type ClmMatchTag = ClmMatchString>; +pub type ClmMatchSandboxEngine = ClmMatchString>; +pub type ClmMatchSandboxAppId = ClmMatchString>; +pub type ClmMatchSandboxInstanceId = ClmMatchString>; +pub type ClmMatchTag = ClmMatchString>; pub type ClmMatchComm = ClmMatchString; pub type ClmMatchExe = ClmMatchString; -pub struct AcceptorMetadataAccess(PhantomData); +pub struct ClientMetadataAccess(PhantomData); pub struct CommAccess; pub struct ExeAccess; -trait AcceptorMetadataField: Sized + 'static { - fn field(meta: &AcceptorMetadata) -> &Option; +trait ClientMetadataField: Sized + 'static { + fn field(meta: &ClientMetadata) -> &Option; fn nodes( roots: &RootMatchers, - ) -> &ClmRootMatcherMap>>; + ) -> &ClmRootMatcherMap>>; } pub struct SandboxEngineField; @@ -35,12 +32,12 @@ pub struct SandboxAppIdField; pub struct SandboxInstanceIdField; pub struct TagField; -impl StringAccess> for AcceptorMetadataAccess +impl StringAccess for ClientMetadataAccess where - T: AcceptorMetadataField, + T: ClientMetadataField, { - fn with_string(data: &Rc, f: impl FnOnce(&str) -> bool) -> bool { - f(T::field(&data.acceptor).as_deref().unwrap_or_default()) + fn with_string(data: &Client, f: impl FnOnce(&str) -> bool) -> bool { + f(T::field(&data.metadata).as_deref().unwrap_or_default()) } fn nodes(roots: &RootMatchers) -> &ClmRootMatcherMap> { @@ -48,56 +45,56 @@ where } } -impl AcceptorMetadataField for SandboxEngineField { - fn field(meta: &AcceptorMetadata) -> &Option { +impl ClientMetadataField for SandboxEngineField { + fn field(meta: &ClientMetadata) -> &Option { &meta.sandbox_engine } fn nodes( roots: &RootMatchers, - ) -> &ClmRootMatcherMap>> { + ) -> &ClmRootMatcherMap>> { &roots.sandbox_engine } } -impl AcceptorMetadataField for SandboxAppIdField { - fn field(meta: &AcceptorMetadata) -> &Option { +impl ClientMetadataField for SandboxAppIdField { + fn field(meta: &ClientMetadata) -> &Option { &meta.app_id } fn nodes( roots: &RootMatchers, - ) -> &ClmRootMatcherMap>> { + ) -> &ClmRootMatcherMap>> { &roots.sandbox_app_id } } -impl AcceptorMetadataField for SandboxInstanceIdField { - fn field(meta: &AcceptorMetadata) -> &Option { +impl ClientMetadataField for SandboxInstanceIdField { + fn field(meta: &ClientMetadata) -> &Option { &meta.instance_id } fn nodes( roots: &RootMatchers, - ) -> &ClmRootMatcherMap>> { + ) -> &ClmRootMatcherMap>> { &roots.sandbox_instance_id } } -impl AcceptorMetadataField for TagField { - fn field(meta: &AcceptorMetadata) -> &Option { +impl ClientMetadataField for TagField { + fn field(meta: &ClientMetadata) -> &Option { &meta.tag } fn nodes( roots: &RootMatchers, - ) -> &ClmRootMatcherMap>> { + ) -> &ClmRootMatcherMap>> { &roots.tag } } -impl StringAccess> for CommAccess { - fn with_string(data: &Rc, f: impl FnOnce(&str) -> bool) -> bool { +impl StringAccess for CommAccess { + fn with_string(data: &Client, f: impl FnOnce(&str) -> bool) -> bool { f(&data.pid_info.comm) } @@ -106,8 +103,8 @@ impl StringAccess> for CommAccess { } } -impl StringAccess> for ExeAccess { - fn with_string(data: &Rc, f: impl FnOnce(&str) -> bool) -> bool { +impl StringAccess for ExeAccess { + fn with_string(data: &Client, f: impl FnOnce(&str) -> bool) -> bool { f(&data.pid_info.exe) } diff --git a/src/criteria/clm/clm_matchers/clmm_uid.rs b/src/criteria/clm/clm_matchers/clmm_uid.rs index 6056b955..63f8cbf3 100644 --- a/src/criteria/clm/clm_matchers/clmm_uid.rs +++ b/src/criteria/clm/clm_matchers/clmm_uid.rs @@ -1,20 +1,17 @@ use { - crate::{ - client::Client, - criteria::{RootMatcherMap, clm::RootMatchers, crit_graph::CritRootCriterion}, - }, - std::rc::Rc, + crate::{client::Client, criteria::clm::RootMatchers}, + jay_criteria::{RootMatcherMap, crit_graph::CritRootCriterion}, uapi::c, }; pub struct ClmMatchUid(pub c::uid_t); -impl CritRootCriterion> for ClmMatchUid { - fn matches(&self, data: &Rc) -> bool { +impl CritRootCriterion for ClmMatchUid { + fn matches(&self, data: &Client) -> bool { data.pid_info.uid == self.0 } - fn nodes(roots: &RootMatchers) -> Option<&RootMatcherMap, Self>> { + fn nodes(roots: &RootMatchers) -> Option<&RootMatcherMap> { Some(&roots.uid) } } diff --git a/src/criteria/tlm.rs b/src/criteria/tlm.rs index d7b5bece..d474e920 100644 --- a/src/criteria/tlm.rs +++ b/src/criteria/tlm.rs @@ -3,14 +3,7 @@ pub mod tlm_matchers; use { crate::{ criteria::{ - CritDestroyListener, CritLiteralOrRegex, CritMatcherId, CritMatcherIds, CritMgrExt, - CritUpstreamNode, FixedRootMatcher, RootMatcherMap, clm::ClmUpstreamNode, - crit_graph::{ - CritMgr, CritRoot, CritRootFixed, CritTarget, CritTargetOwner, WeakCritTargetOwner, - }, - crit_leaf::{CritLeafEvent, CritLeafMatcher}, - crit_matchers::critm_constant::CritMatchConstant, tlm::tlm_matchers::{ tlmm_client::TlmMatchClient, tlmm_content_type::TlmMatchContentType, @@ -33,6 +26,15 @@ use { utils::{copyhashmap::CopyHashMap, hash_map_ext::HashMapExt, queue::AsyncQueue}, }, jay_config::window::{ContentType, WindowType}, + jay_criteria::{ + CritDestroyListener, CritLiteralOrRegex, CritMatcherId, CritMatcherIds, CritMgrExt, + CritUpstreamNode, FixedRootMatcher, RootMatcherMap, + crit_graph::{ + CritMgr, CritRoot, CritRootFixed, CritTarget, CritTargetOwner, WeakCritTargetOwner, + }, + crit_leaf::{CritLeafEvent, CritLeafMatcher}, + crit_matchers::critm_constant::CritMatchConstant, + }, linearize::static_map, std::{ marker::PhantomData, @@ -408,10 +410,10 @@ impl CritTarget for ToplevelData { type Mgr = TlMatcherManager; type RootMatchers = RootMatchers; type LeafData = ToplevelIdentifier; - type Owner = Weak; + type Owner = WeakToplevelTargetOwner; fn owner(&self) -> Self::Owner { - self.slf.clone() + WeakToplevelTargetOwner(self.slf.clone()) } fn id(&self) -> Self::Id { @@ -427,20 +429,24 @@ impl CritTarget for ToplevelData { } } -impl CritTargetOwner for Rc { +pub struct ToplevelTargetOwner(Rc); + +pub struct WeakToplevelTargetOwner(Weak); + +impl CritTargetOwner for ToplevelTargetOwner { type Target = ToplevelData; fn data(&self) -> &Self::Target { - self.tl_data() + self.0.tl_data() } } -impl WeakCritTargetOwner for Weak { +impl WeakCritTargetOwner for WeakToplevelTargetOwner { type Target = ToplevelData; - type Owner = Rc; + type Owner = ToplevelTargetOwner; fn upgrade(&self) -> Option { - self.upgrade() + self.0.upgrade().map(ToplevelTargetOwner) } } diff --git a/src/criteria/tlm/tlm_matchers.rs b/src/criteria/tlm/tlm_matchers.rs index ccf42900..6b833abf 100644 --- a/src/criteria/tlm/tlm_matchers.rs +++ b/src/criteria/tlm/tlm_matchers.rs @@ -1,6 +1,6 @@ macro_rules! fixed_root_criterion { ($ty:ty, $field:ident) => { - impl crate::criteria::crit_graph::CritFixedRootCriterionBase + impl jay_criteria::crit_graph::CritFixedRootCriterionBase for $ty { fn constant(&self) -> bool { @@ -10,7 +10,7 @@ macro_rules! fixed_root_criterion { fn not<'a>( &self, mgr: &'a crate::criteria::tlm::TlMatcherManager, - ) -> &'a crate::criteria::FixedRootMatcher { + ) -> &'a jay_criteria::FixedRootMatcher { &mgr.$field } } diff --git a/src/criteria/tlm/tlm_matchers/tlmm_client.rs b/src/criteria/tlm/tlm_matchers/tlmm_client.rs index 13fd3a5c..564106c1 100644 --- a/src/criteria/tlm/tlm_matchers/tlmm_client.rs +++ b/src/criteria/tlm/tlm_matchers/tlmm_client.rs @@ -2,18 +2,20 @@ use { crate::{ client::Client, criteria::{ - CritMatcherId, CritUpstreamNode, clm::ClmUpstreamNode, - crit_graph::{ - CritDownstream, CritDownstreamData, CritMgr, CritUpstreamData, - CritUpstreamNodeBase, CritUpstreamNodeData, - }, - crit_per_target_data::{CritDestroyListenerBase, CritPerTargetData}, tlm::TlMatcherManager, }, state::State, tree::{ToplevelData, ToplevelNodeBase}, }, + jay_criteria::{ + CritMatcherId, CritUpstreamNode, + crit_graph::{ + CritDownstream, CritDownstreamData, CritMgr, CritUpstreamData, CritUpstreamNodeBase, + CritUpstreamNodeData, + }, + crit_per_target_data::{CritDestroyListenerBase, CritPerTargetData}, + }, std::{rc::Rc, slice}, }; @@ -21,7 +23,7 @@ pub struct TlmMatchClient { id: CritMatcherId, state: Rc, node: Rc, - upstream: CritDownstreamData>, + upstream: CritDownstreamData, downstream: CritUpstreamData, } @@ -73,8 +75,8 @@ impl CritUpstreamNodeBase for TlmMatchClient { } } -impl CritDownstream> for TlmMatchClient { - fn update_matched(self: Rc, target: &Rc, matched: bool) { +impl CritDownstream for TlmMatchClient { + fn update_matched(self: Rc, target: &Client, matched: bool) { let handle = |data: &ToplevelData| { let node = match matched { true => self.downstream.get_or_create(data), diff --git a/src/criteria/tlm/tlm_matchers/tlmm_content_type.rs b/src/criteria/tlm/tlm_matchers/tlmm_content_type.rs index a7878b6d..08b9b2c7 100644 --- a/src/criteria/tlm/tlm_matchers/tlmm_content_type.rs +++ b/src/criteria/tlm/tlm_matchers/tlmm_content_type.rs @@ -1,14 +1,12 @@ use { crate::{ - criteria::{ - crit_graph::CritRootCriterion, - tlm::{RootMatchers, TlmRootMatcherMap}, - }, + criteria::tlm::{RootMatchers, TlmRootMatcherMap}, ifs::wp_content_type_v1::ContentTypeExt, tree::ToplevelData, utils::bitflags::BitflagsExt, }, jay_config::window::ContentType, + jay_criteria::crit_graph::CritRootCriterion, }; pub struct TlmMatchContentType { diff --git a/src/criteria/tlm/tlm_matchers/tlmm_floating.rs b/src/criteria/tlm/tlm_matchers/tlmm_floating.rs index 8cb2dc5e..5d70f0df 100644 --- a/src/criteria/tlm/tlm_matchers/tlmm_floating.rs +++ b/src/criteria/tlm/tlm_matchers/tlmm_floating.rs @@ -1,4 +1,4 @@ -use crate::{criteria::crit_graph::CritFixedRootCriterion, tree::ToplevelData}; +use {crate::tree::ToplevelData, jay_criteria::crit_graph::CritFixedRootCriterion}; pub struct TlmMatchFloating(pub bool); diff --git a/src/criteria/tlm/tlm_matchers/tlmm_fullscreen.rs b/src/criteria/tlm/tlm_matchers/tlmm_fullscreen.rs index 5ed21689..ad0aa22d 100644 --- a/src/criteria/tlm/tlm_matchers/tlmm_fullscreen.rs +++ b/src/criteria/tlm/tlm_matchers/tlmm_fullscreen.rs @@ -1,4 +1,4 @@ -use crate::{criteria::crit_graph::CritFixedRootCriterion, tree::ToplevelData}; +use {crate::tree::ToplevelData, jay_criteria::crit_graph::CritFixedRootCriterion}; pub struct TlmMatchFullscreen(pub bool); diff --git a/src/criteria/tlm/tlm_matchers/tlmm_just_mapped.rs b/src/criteria/tlm/tlm_matchers/tlmm_just_mapped.rs index e21b5675..87b0a363 100644 --- a/src/criteria/tlm/tlm_matchers/tlmm_just_mapped.rs +++ b/src/criteria/tlm/tlm_matchers/tlmm_just_mapped.rs @@ -1,4 +1,4 @@ -use crate::{criteria::crit_graph::CritFixedRootCriterion, tree::ToplevelData}; +use {crate::tree::ToplevelData, jay_criteria::crit_graph::CritFixedRootCriterion}; pub struct TlmMatchJustMapped(pub bool); diff --git a/src/criteria/tlm/tlm_matchers/tlmm_kind.rs b/src/criteria/tlm/tlm_matchers/tlmm_kind.rs index 8c332877..4c045d8c 100644 --- a/src/criteria/tlm/tlm_matchers/tlmm_kind.rs +++ b/src/criteria/tlm/tlm_matchers/tlmm_kind.rs @@ -1,13 +1,11 @@ use { crate::{ - criteria::{ - crit_graph::CritRootCriterion, - tlm::{RootMatchers, TlmRootMatcherMap}, - }, + criteria::tlm::{RootMatchers, TlmRootMatcherMap}, tree::ToplevelData, utils::bitflags::BitflagsExt, }, jay_config::window::WindowType, + jay_criteria::crit_graph::CritRootCriterion, }; pub struct TlmMatchKind { diff --git a/src/criteria/tlm/tlm_matchers/tlmm_seat_focus.rs b/src/criteria/tlm/tlm_matchers/tlmm_seat_focus.rs index 34e75562..b97aa7b2 100644 --- a/src/criteria/tlm/tlm_matchers/tlmm_seat_focus.rs +++ b/src/criteria/tlm/tlm_matchers/tlmm_seat_focus.rs @@ -1,11 +1,9 @@ use crate::{ - criteria::{ - crit_graph::CritRootCriterion, - tlm::{RootMatchers, TlmRootMatcherMap}, - }, + criteria::tlm::{RootMatchers, TlmRootMatcherMap}, ifs::wl_seat::SeatId, tree::ToplevelData, }; +use jay_criteria::crit_graph::CritRootCriterion; pub struct TlmMatchSeatFocus { id: SeatId, diff --git a/src/criteria/tlm/tlm_matchers/tlmm_string.rs b/src/criteria/tlm/tlm_matchers/tlmm_string.rs index 62413481..7256eb2f 100644 --- a/src/criteria/tlm/tlm_matchers/tlmm_string.rs +++ b/src/criteria/tlm/tlm_matchers/tlmm_string.rs @@ -1,10 +1,8 @@ use crate::{ - criteria::{ - crit_matchers::critm_string::{CritMatchString, StringAccess}, - tlm::{RootMatchers, TlmRootMatcherMap}, - }, + criteria::tlm::{RootMatchers, TlmRootMatcherMap}, tree::{ToplevelData, ToplevelType}, }; +use jay_criteria::crit_matchers::critm_string::{CritMatchString, StringAccess}; pub type TlmMatchString = CritMatchString; diff --git a/src/criteria/tlm/tlm_matchers/tlmm_urgent.rs b/src/criteria/tlm/tlm_matchers/tlmm_urgent.rs index 157135ad..266d797c 100644 --- a/src/criteria/tlm/tlm_matchers/tlmm_urgent.rs +++ b/src/criteria/tlm/tlm_matchers/tlmm_urgent.rs @@ -1,4 +1,4 @@ -use crate::{criteria::crit_graph::CritFixedRootCriterion, tree::ToplevelData}; +use {crate::tree::ToplevelData, jay_criteria::crit_graph::CritFixedRootCriterion}; pub struct TlmMatchUrgent(pub bool); diff --git a/src/criteria/tlm/tlm_matchers/tlmm_visible.rs b/src/criteria/tlm/tlm_matchers/tlmm_visible.rs index 1bdea414..d4412c59 100644 --- a/src/criteria/tlm/tlm_matchers/tlmm_visible.rs +++ b/src/criteria/tlm/tlm_matchers/tlmm_visible.rs @@ -1,4 +1,4 @@ -use crate::{criteria::crit_graph::CritFixedRootCriterion, tree::ToplevelData}; +use {crate::tree::ToplevelData, jay_criteria::crit_graph::CritFixedRootCriterion}; pub struct TlmMatchVisible(pub bool); diff --git a/src/cursor.rs b/src/cursor.rs index 82ef8188..d1b53fdf 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -1,15 +1,19 @@ +use jay_time::Time; +use std::time::Duration; +use jay_geometry::Rect; +use jay_async_engine::AsyncEngine; +use jay_units::fixed::Fixed; +use jay_units::scale::Scale; use { crate::{ - async_engine::AsyncEngine, + cmm::cmm_render_intent::RenderIntent, - fixed::Fixed, format::ARGB8888, gfx_api::{AcquireSync, AlphaMode, GfxContext, GfxError, GfxTexture, ReleaseSync}, - rect::Rect, + renderer::Renderer, - scale::Scale, state::State, - time::Time, + tree::OutputNode, utils::{errorfmt::ErrorFmt, numcell::NumCell, smallmap::SmallMapMut}, }, @@ -29,7 +33,7 @@ use { rc::Rc, slice, str, sync::LazyLock, - time::Duration, + }, thiserror::Error, uapi::Bytes, diff --git a/src/cursor_user.rs b/src/cursor_user.rs index 6d911048..dc3df528 100644 --- a/src/cursor_user.rs +++ b/src/cursor_user.rs @@ -1,11 +1,12 @@ +use jay_geometry::Rect; +use jay_units::fixed::Fixed; +use jay_units::scale::Scale; use { crate::{ backend::HardwareCursorUpdate, cursor::{Cursor, DEFAULT_CURSOR_SIZE, KnownCursor}, - fixed::Fixed, gfx_api::{AcquireSync, ReleaseSync}, - rect::Rect, - scale::Scale, + state::State, tree::OutputNode, utils::{ diff --git a/src/damage.rs b/src/damage.rs index da38856a..7940ef69 100644 --- a/src/damage.rs +++ b/src/damage.rs @@ -1,15 +1,18 @@ +use jay_time::Time; +use std::time::Duration; +use jay_theme::Color; +use jay_geometry::{Rect, Region}; +use jay_async_engine::AsyncEngine; use { crate::{ - async_engine::AsyncEngine, + cmm::{cmm_manager::ColorManager, cmm_render_intent::RenderIntent}, - fixed::Fixed, ifs::wl_output::WlOutputGlobal, - rect::{Rect, Region}, + renderer::renderer_base::RendererBase, state::State, - theme::Color, - time::Time, - tree::Transform, + + utils::{asyncevent::AsyncEvent, errorfmt::ErrorFmt, timer::TimerFd}, }, isnt::std_1::primitive::IsntSliceExt, @@ -17,11 +20,13 @@ use { cell::{Cell, RefCell}, collections::VecDeque, rc::Rc, - time::Duration, + }, uapi::c::CLOCK_MONOTONIC, }; +pub use jay_damage::*; + pub async fn visualize_damage(state: Rc) { let timer = match TimerFd::new(CLOCK_MONOTONIC) { Ok(t) => t, @@ -196,114 +201,3 @@ impl DamageVisualizer { } } } - -#[derive(Copy, Clone, Debug)] -pub struct DamageMatrix { - transform: Transform, - mx: f64, - my: f64, - dx: f64, - dy: f64, - smear: i32, -} - -impl Default for DamageMatrix { - fn default() -> Self { - Self { - transform: Default::default(), - mx: 1.0, - my: 1.0, - dx: 0.0, - dy: 0.0, - smear: 0, - } - } -} - -impl DamageMatrix { - pub fn apply(&self, dx: i32, dy: i32, rect: Rect) -> Rect { - let x1 = rect.x1() - self.smear; - let x2 = rect.x2() + self.smear; - let y1 = rect.y1() - self.smear; - let y2 = rect.y2() + self.smear; - let [x1, y1, x2, y2] = match self.transform { - Transform::None => [x1, y1, x2, y2], - Transform::Rotate90 => [-y2, x1, -y1, x2], - Transform::Rotate180 => [-x2, -y2, -x1, -y1], - Transform::Rotate270 => [y1, -x2, y2, -x1], - Transform::Flip => [-x2, y1, -x1, y2], - Transform::FlipRotate90 => [y1, x1, y2, x2], - Transform::FlipRotate180 => [x1, -y2, x2, -y1], - Transform::FlipRotate270 => [-y2, -x2, -y1, -x1], - }; - let x1 = (x1 as f64 * self.mx + self.dx).floor() as i32 + dx; - let y1 = (y1 as f64 * self.my + self.dy).floor() as i32 + dy; - let x2 = (x2 as f64 * self.mx + self.dx).ceil() as i32 + dx; - let y2 = (y2 as f64 * self.my + self.dy).ceil() as i32 + dy; - Rect::new_saturating(x1, y1, x2, y2) - } - - pub fn new( - transform: Transform, - legacy_scale: i32, - buffer_width: i32, - buffer_height: i32, - viewport: Option<[Fixed; 4]>, - dst_width: i32, - dst_height: i32, - ) -> DamageMatrix { - let mut buffer_width = buffer_width as f64; - let mut buffer_height = buffer_height as f64; - let dst_width = dst_width as f64; - let dst_height = dst_height as f64; - - let mut mx = 1.0; - let mut my = 1.0; - if legacy_scale != 1 { - let scale_inv = 1.0 / (legacy_scale as f64); - mx = scale_inv; - my = scale_inv; - buffer_width *= scale_inv; - buffer_height *= scale_inv; - } - let (mut buffer_width, mut buffer_height) = - transform.maybe_swap((buffer_width, buffer_height)); - let (mut dx, mut dy) = match transform { - Transform::None => (0.0, 0.0), - Transform::Rotate90 => (buffer_width, 0.0), - Transform::Rotate180 => (buffer_width, buffer_height), - Transform::Rotate270 => (0.0, buffer_height), - Transform::Flip => (buffer_width, 0.0), - Transform::FlipRotate90 => (0.0, 0.0), - Transform::FlipRotate180 => (0.0, buffer_height), - Transform::FlipRotate270 => (buffer_width, buffer_height), - }; - if let Some([x, y, w, h]) = viewport { - dx -= x.to_f64(); - dy -= y.to_f64(); - buffer_width = w.to_f64(); - buffer_height = h.to_f64(); - } - let mut smear = false; - if dst_width != buffer_width { - let scale = dst_width / buffer_width; - mx *= scale; - dx *= scale; - smear |= dst_width > buffer_width; - } - if dst_height != buffer_height { - let scale = dst_height / buffer_height; - my *= scale; - dy *= scale; - smear |= dst_height > buffer_height; - } - DamageMatrix { - transform, - mx, - my, - dx, - dy, - smear: smear as _, - } - } -} diff --git a/src/dbus.rs b/src/dbus.rs index c99d4f7a..6b125c0b 100644 --- a/src/dbus.rs +++ b/src/dbus.rs @@ -1,34 +1,26 @@ -pub use types::*; +pub use jay_dbus_core::*; +use jay_io_uring::IoUring; +use jay_async_engine::{AsyncEngine, SpawnedFuture}; use { crate::{ - async_engine::{AsyncEngine, SpawnedFuture}, - dbus::{ - property::{Get, GetReply}, - types::{ObjectPath, Signature, Variant}, - }, - io_uring::{IoUring, IoUringError}, + + utils::{ - buf::DynamicBuf, - bufio::{BufIo, BufIoError}, + bufio::BufIo, clonecell::CloneCell, copyhashmap::CopyHashMap, numcell::NumCell, - oserror::OsError, run_toplevel::RunToplevel, stack::Stack, vecstorage::VecStorage, xrd::{XRD, xrd}, }, - wire_dbus::{ - org, - org::freedesktop::dbus::properties::{GetAll, GetAllReply, PropertiesChanged}, - }, }, ahash::AHashMap, std::{ borrow::{Borrow, Cow}, cell::{Cell, RefCell}, - fmt::{Debug, Display}, + fmt::Debug, future::Future, marker::PhantomData, mem, @@ -37,109 +29,14 @@ use { rc::Rc, task::{Context, Poll, Waker}, }, - thiserror::Error, uapi::OwnedFd, }; mod auth; -mod dynamic_type; -mod formatter; mod holder; mod incoming; mod outgoing; -mod parser; -mod property; mod socket; -mod types; - -#[derive(Debug)] -pub struct CallError { - pub name: String, - pub msg: Option, -} - -impl Display for CallError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if let Some(msg) = &self.msg { - write!(f, "{}: {}", self.name, msg) - } else { - write!(f, "{}", self.name) - } - } -} - -#[derive(Debug, Error)] -pub enum DbusError { - #[error("Encountered an unknown type in a signature")] - UnknownType, - #[error("Function call reply does not contain a reply serial")] - NoReplySerial, - #[error("Signal message contains no interface or member or path")] - MissingSignalHeaders, - #[error("Method call message contains no interface or member or path")] - MissingMethodCallHeaders, - #[error("Error has no error name")] - NoErrorName, - #[error("The socket was killed")] - Killed, - #[error("{0}")] - CallError(CallError), - #[error("FD index is out of bounds")] - OobFds, - #[error("Variant has an invalid type")] - InvalidVariantType, - #[error("Could not create a socket")] - Socket(#[source] OsError), - #[error("Could not connect")] - Connect(#[source] IoUringError), - #[error("Could not write to the dbus socket")] - WriteError(#[source] IoUringError), - #[error("Could not read from the dbus socket")] - ReadError(#[source] IoUringError), - #[error("timeout")] - IoUringError(#[source] Box), - #[error("Server did not send auth challenge")] - NoChallenge, - #[error("Server did not accept our authentication")] - Auth, - #[error("Array length is not a multiple of the element size")] - PodArrayLength, - #[error("Peer did not send enough fds")] - TooFewFds, - #[error("Variant signature is not a single type")] - TrailingVariantSignature, - #[error("Dict signature does not contain a terminating '}}'")] - UnterminatedDict, - #[error("Struct signature does not contain a terminating '}}'")] - UnterminatedStruct, - #[error("Dict signature contains trailing types")] - DictTrailing, - #[error("String does not contain valid UTF-8")] - InvalidUtf8, - #[error("Unexpected end of message")] - UnexpectedEof, - #[error("Boolean value was not 0 or 1")] - InvalidBoolValue, - #[error("Signature is empty")] - EmptySignature, - #[error("The session bus address is not set")] - SessionBusAddressNotSet, - #[error("Server does not support FD passing")] - UnixFd, - #[error("Server message has a different endianess than ourselves")] - InvalidEndianess, - #[error("Server speaks an unexpected protocol version")] - InvalidProtocol, - #[error("Signature contains an invalid type")] - InvalidSignatureType, - #[error("The signal already has a handler")] - AlreadyHandled, - #[error(transparent)] - BufIoError(#[from] BufIoError), - #[error(transparent)] - DbusError(Rc), -} -efrom!(DbusError, IoUringError); const DBUS_SESSION_BUS_ADDRESS: &str = "DBUS_SESSION_BUS_ADDRESS"; @@ -300,22 +197,6 @@ impl<'a> Borrow> for MemberHandlerOwnedKey { } } -const TY_BYTE: u8 = b'y'; -const TY_BOOLEAN: u8 = b'b'; -const TY_INT16: u8 = b'n'; -const TY_UINT16: u8 = b'q'; -const TY_INT32: u8 = b'i'; -const TY_UINT32: u8 = b'u'; -const TY_INT64: u8 = b'x'; -const TY_UINT64: u8 = b't'; -const TY_DOUBLE: u8 = b'd'; -const TY_STRING: u8 = b's'; -const TY_OBJECT_PATH: u8 = b'o'; -const TY_SIGNATURE: u8 = b'g'; -const TY_ARRAY: u8 = b'a'; -const TY_VARIANT: u8 = b'v'; -const TY_UNIX_FD: u8 = b'h'; - const HDR_PATH: u8 = 1; const HDR_INTERFACE: u8 = 2; const HDR_MEMBER: u8 = 3; @@ -397,102 +278,6 @@ impl Drop for DbusHolder { } } -#[derive(Clone, Debug)] -pub enum DynamicType { - U8, - Bool, - I16, - U16, - I32, - U32, - I64, - U64, - F64, - String, - ObjectPath, - Signature, - Variant, - Fd, - Array(Box), - DictEntry(Box, Box), - Struct(Vec), -} - -pub struct Parser<'a> { - buf: &'a [u8], - pos: usize, - fds: &'a [Rc], -} - -pub struct Formatter<'a> { - fds: &'a mut Vec>, - buf: &'a mut DynamicBuf, -} - -pub unsafe trait Message<'a>: Sized + 'a { - const SIGNATURE: &'static str; - const INTERFACE: &'static str; - const MEMBER: &'static str; - type Generic<'b>: Message<'b>; - - fn marshal(&self, w: &mut Formatter); - fn unmarshal(p: &mut Parser<'a>) -> Result; - fn num_fds(&self) -> u32; -} - -pub struct ErrorMessage<'a> { - pub msg: Cow<'a, str>, -} - -unsafe impl<'a> Message<'a> for ErrorMessage<'a> { - const SIGNATURE: &'static str = "s"; - const INTERFACE: &'static str = ""; - const MEMBER: &'static str = ""; - type Generic<'b> = ErrorMessage<'b>; - - fn marshal(&self, w: &mut Formatter) { - self.msg.marshal(w) - } - - fn unmarshal(p: &mut Parser<'a>) -> Result { - Ok(Self { - msg: p.unmarshal()?, - }) - } - - fn num_fds(&self) -> u32 { - 0 - } -} - -pub trait Property { - const INTERFACE: &'static str; - const PROPERTY: &'static str; - type Type: DbusType<'static>; -} - -pub trait Signal<'a>: Message<'a> {} - -pub trait MethodCall<'a>: Message<'a> { - type Reply: Message<'static>; -} - -pub unsafe trait DbusType<'a>: Clone + 'a { - const ALIGNMENT: usize; - const IS_POD: bool; - type Generic<'b>: DbusType<'b> + 'b; - - fn consume_signature(s: &mut &[u8]) -> Result<(), DbusError>; - #[allow(clippy::allow_attributes, dead_code)] - fn write_signature(w: &mut Vec); - fn marshal(&self, fmt: &mut Formatter); - fn unmarshal(parser: &mut Parser<'a>) -> Result; - - fn num_fds(&self) -> u32 { - 0 - } -} - pub struct Reply> { socket: Rc, buf: Vec, @@ -690,14 +475,16 @@ impl DbusObject { where T: Property + 'static, { - self.emit_signal(&PropertiesChanged { - interface_name: T::INTERFACE.into(), - changed_properties: Cow::Borrowed(&[DictEntry { - key: T::PROPERTY.into(), - value: value.borrow(), - }]), - invalidated_properties: Default::default(), - }); + self.emit_signal( + &crate::wire_dbus::org::freedesktop::dbus::properties::PropertiesChanged { + interface_name: T::INTERFACE.into(), + changed_properties: Cow::Borrowed(&[DictEntry { + key: T::PROPERTY.into(), + value: borrow_variant(&value), + }]), + invalidated_properties: Default::default(), + }, + ); let phd = Rc::new(PropertyHandlerData:: { data: value, _phantom: Default::default(), @@ -744,7 +531,33 @@ where } fn value<'a>(&'a self) -> Variant<'a> { - self.data.borrow() + borrow_variant(&self.data) + } +} + +fn borrow_variant<'a>(value: &'a Variant<'static>) -> Variant<'a> { + match value { + Variant::U8(v) => Variant::U8(*v), + Variant::Bool(v) => Variant::Bool(*v), + Variant::I16(v) => Variant::I16(*v), + Variant::U16(v) => Variant::U16(*v), + Variant::I32(v) => Variant::I32(*v), + Variant::U32(v) => Variant::U32(*v), + Variant::I64(v) => Variant::I64(*v), + Variant::U64(v) => Variant::U64(*v), + Variant::F64(v) => Variant::F64(*v), + Variant::String(v) => Variant::String(Cow::Borrowed(v.as_ref())), + Variant::ObjectPath(v) => Variant::ObjectPath(ObjectPath(Cow::Borrowed(v.0.as_ref()))), + Variant::Signature(v) => Variant::Signature(Signature(Cow::Borrowed(v.0.as_ref()))), + Variant::Variant(v) => Variant::Variant(Box::new(borrow_variant(v))), + Variant::Fd(v) => Variant::Fd(v.clone()), + Variant::Array(ty, values) => { + Variant::Array(ty.clone(), values.iter().map(borrow_variant).collect()) + } + Variant::DictEntry(k, v) => { + Variant::DictEntry(Box::new(borrow_variant(k)), Box::new(borrow_variant(v))) + } + Variant::Struct(values) => Variant::Struct(values.iter().map(borrow_variant).collect()), } } @@ -841,7 +654,7 @@ struct PropertyGetHandlerProxy; impl MethodHandlerApi for PropertyGetHandlerProxy { fn signature(&self) -> &'static str { - Get::::SIGNATURE + crate::wire_dbus::org::freedesktop::dbus::properties::Get::SIGNATURE } fn handle<'a>( @@ -856,7 +669,7 @@ impl MethodHandlerApi for PropertyGetHandlerProxy { if !reply_expected { return Ok(()); } - let msg = org::freedesktop::dbus::properties::Get::unmarshal(parser)?; + let msg = crate::wire_dbus::org::freedesktop::dbus::properties::Get::unmarshal(parser)?; let key = MemberHandlerKey { interface: msg.interface_name.deref(), member: msg.property_name.deref(), @@ -865,7 +678,9 @@ impl MethodHandlerApi for PropertyGetHandlerProxy { Some(h) => socket.send_reply( dest, serial, - &org::freedesktop::dbus::properties::GetReply { value: h.value() }, + &crate::wire_dbus::org::freedesktop::dbus::properties::GetReply { + value: h.value(), + }, ), _ => socket.send_error(dest, serial, "Property does not exist"), }; @@ -877,7 +692,7 @@ struct PropertyGetAllHandlerProxy; impl MethodHandlerApi for PropertyGetAllHandlerProxy { fn signature(&self) -> &'static str { - GetAll::SIGNATURE + crate::wire_dbus::org::freedesktop::dbus::properties::GetAll::SIGNATURE } fn handle<'a>( @@ -892,7 +707,7 @@ impl MethodHandlerApi for PropertyGetAllHandlerProxy { if !reply_expected { return Ok(()); } - let msg = GetAll::unmarshal(parser)?; + let msg = crate::wire_dbus::org::freedesktop::dbus::properties::GetAll::unmarshal(parser)?; let all_props = object.properties.lock(); let mut props = vec![]; for property in all_props.values() { @@ -906,7 +721,7 @@ impl MethodHandlerApi for PropertyGetAllHandlerProxy { socket.send_reply( dest, serial, - &GetAllReply { + &crate::wire_dbus::org::freedesktop::dbus::properties::GetAllReply { props: props.into(), }, ); diff --git a/src/dbus/holder.rs b/src/dbus/holder.rs index aa0ce1ba..828176f6 100644 --- a/src/dbus/holder.rs +++ b/src/dbus/holder.rs @@ -1,8 +1,10 @@ +use jay_io_uring::IoUring; +use jay_async_engine::AsyncEngine; use { crate::{ - async_engine::AsyncEngine, + dbus::{DbusError, DbusHolder, DbusSocket, auth::handle_auth}, - io_uring::IoUring, + utils::{ bufio::BufIo, errorfmt::ErrorFmt, numcell::NumCell, oserror::OsErrorExt2, run_toplevel::RunToplevel, diff --git a/src/dbus/incoming.rs b/src/dbus/incoming.rs index 43052b63..764f2164 100644 --- a/src/dbus/incoming.rs +++ b/src/dbus/incoming.rs @@ -6,16 +6,13 @@ use { crate::{ dbus::{ CallError, DbusError, DbusSocket, Headers, MSG_ERROR, MSG_METHOD_CALL, - MSG_METHOD_RETURN, MSG_SIGNAL, MemberHandlerKey, Message, MethodHandlerApi, - NO_REPLY_EXPECTED, Parser, PropertyGetAllHandlerProxy, PropertyGetHandlerProxy, + MSG_METHOD_RETURN, MSG_SIGNAL, MemberHandlerKey, NO_REPLY_EXPECTED, Parser, }, utils::{ - bitflags::BitflagsExt, bufio::BufIoIncoming, errorfmt::ErrorFmt, ptr_ext::{MutPtrExt, PtrExt}, }, - wire_dbus::org::freedesktop::dbus::properties::{Get, GetAll}, }, std::{cell::UnsafeCell, ops::Deref, rc::Rc}, }; @@ -87,11 +84,7 @@ impl Incoming { return Err(DbusError::TooFewFds); } let fds: Vec<_> = self.incoming.fds.drain(..unix_fds).collect(); - let mut parser = Parser { - buf: msg_buf, - pos: FIXED_HEADER_SIZE + dyn_header_len as usize, - fds: &fds, - }; + let mut parser = Parser::new_at(msg_buf, FIXED_HEADER_SIZE + dyn_header_len as usize, &fds); match msg_ty { MSG_METHOD_CALL => { let (sender, interface, member, path) = match ( @@ -104,23 +97,11 @@ impl Incoming { _ => return Err(DbusError::MissingMethodCallHeaders), }; if let Some(object) = self.socket.objects.get(path.deref()) { - let method_handler; - let handler: Option<&dyn MethodHandlerApi> = - if (interface.deref(), member.deref()) == (Get::INTERFACE, Get::MEMBER) { - Some(&PropertyGetHandlerProxy) - } else if (interface.deref(), member.deref()) - == (GetAll::INTERFACE, GetAll::MEMBER) - { - Some(&PropertyGetAllHandlerProxy) - } else { - let key = MemberHandlerKey { - interface: interface.deref(), - member: member.deref(), - }; - method_handler = object.methods.get(&key); - method_handler.as_deref() - }; - if let Some(handler) = handler { + let key = MemberHandlerKey { + interface: interface.deref(), + member: member.deref(), + }; + if let Some(handler) = object.methods.get(&key) { let sig = headers.signature.as_deref().unwrap_or(""); if sig != handler.signature() { let msg = format!( @@ -130,7 +111,7 @@ impl Incoming { ); self.socket.send_error(sender.deref(), serial, &msg); } else { - let reply_expected = !flags.contains(NO_REPLY_EXPECTED); + let reply_expected = flags & NO_REPLY_EXPECTED == 0; if let Err(e) = handler.handle( &object, &self.socket, diff --git a/src/dbus/socket.rs b/src/dbus/socket.rs index 9b688b0a..2c733ec4 100644 --- a/src/dbus/socket.rs +++ b/src/dbus/socket.rs @@ -6,8 +6,8 @@ use { HDR_ERROR_NAME, HDR_INTERFACE, HDR_MEMBER, HDR_PATH, HDR_REPLY_SERIAL, HDR_SIGNATURE, HDR_UNIX_FDS, Headers, InterfaceSignalHandlers, MSG_ERROR, MSG_METHOD_CALL, MSG_METHOD_RETURN, MSG_SIGNAL, Message, MethodCall, NO_REPLY_EXPECTED, Parser, - Property, Reply, ReplyHandler, Signal, SignalHandler, SignalHandlerApi, - SignalHandlerData, + Property, PropertyGetAllHandlerProxy, PropertyGetHandlerProxy, Reply, ReplyHandler, + Signal, SignalHandler, SignalHandlerApi, SignalHandlerData, property::Get, types::{ObjectPath, Signature, Variant}, }, @@ -28,7 +28,6 @@ impl DbusSocket { self.outgoing_.take(); self.reply_handlers.clear(); self.signal_handlers.borrow_mut().clear(); - self.objects.clear(); } pub(super) fn kill(self: &Rc) { @@ -148,6 +147,24 @@ impl DbusSocket { Entry::Occupied(_) => Err(DbusError::AlreadyHandled), Entry::Vacant(v) => { v.insert(data.clone()); + data.methods.set( + crate::dbus::MemberHandlerOwnedKey { + key: crate::dbus::MemberHandlerKey { + interface: org::freedesktop::dbus::properties::Get::INTERFACE, + member: org::freedesktop::dbus::properties::Get::MEMBER, + }, + }, + Rc::new(PropertyGetHandlerProxy), + ); + data.methods.set( + crate::dbus::MemberHandlerOwnedKey { + key: crate::dbus::MemberHandlerKey { + interface: org::freedesktop::dbus::properties::GetAll::INTERFACE, + member: org::freedesktop::dbus::properties::GetAll::MEMBER, + }, + }, + Rc::new(PropertyGetAllHandlerProxy), + ); Ok(DbusObject { socket: self.clone(), data, @@ -268,14 +285,14 @@ impl DbusSocket { ); } - pub fn emit_signal<'a, T: Signal<'a>>(&self, path: &str, msg: &T) -> u32 { - let (msg, serial) = self.format_signal(path, msg); + pub fn send_error(&self, destination: &str, reply_serial: u32, msg: &str) -> u32 { + let (msg, serial) = self.format_error(destination, reply_serial, msg); self.bufio.send(msg); serial } - pub fn send_error(&self, destination: &str, reply_serial: u32, msg: &str) -> u32 { - let (msg, serial) = self.format_error(destination, reply_serial, msg); + pub fn emit_signal<'a, T: Signal<'a>>(&self, path: &str, msg: &T) -> u32 { + let (msg, serial) = self.format_signal(path, msg); self.bufio.send(msg); serial } @@ -303,10 +320,6 @@ impl DbusSocket { serial } - fn format_signal<'a, T: Signal<'a>>(&self, path: &str, msg: &T) -> (BufIoMessage, u32) { - self.format_generic(MSG_SIGNAL, Some(path), None, None, 0, msg, None, true, true) - } - fn format_error(&self, destination: &str, reply_serial: u32, msg: &str) -> (BufIoMessage, u32) { let em = ErrorMessage { msg: msg.into() }; self.format_generic( @@ -322,6 +335,10 @@ impl DbusSocket { ) } + fn format_signal<'a, T: Signal<'a>>(&self, path: &str, msg: &T) -> (BufIoMessage, u32) { + self.format_generic(MSG_SIGNAL, Some(path), None, None, 0, msg, None, true, true) + } + fn format_reply<'a, T: Message<'a>>( &self, destination: &str, diff --git a/src/drm_feedback.rs b/src/drm_feedback.rs index 7ac9b0ee..df936d41 100644 --- a/src/drm_feedback.rs +++ b/src/drm_feedback.rs @@ -1,124 +1,17 @@ -use { - crate::{gfx_api::GfxContext, utils::oserror::OsError, video::Modifier}, - ahash::AHashMap, - byteorder::{NativeEndian, WriteBytesExt}, - std::{io::Write, rc::Rc}, - thiserror::Error, - uapi::{OwnedFd, c}, -}; +pub use jay_drm_feedback::*; -linear_ids!(DrmFeedbackIds, DrmFeedbackId); +use crate::{gfx_api::GfxContext, video::Modifier}; -#[derive(Debug)] -pub struct DrmFeedbackShared { - pub fd: Rc, - pub size: usize, - pub main_device: c::dev_t, - pub indices: AHashMap<(u32, Modifier), u16>, -} - -#[derive(Debug)] -pub struct DrmFeedback { - pub id: DrmFeedbackId, - pub shared: Rc, - pub tranches: Vec, -} - -#[derive(Clone, Debug)] -pub struct DrmFeedbackTranche { - pub device: c::dev_t, - pub indices: Vec, - pub scanout: bool, -} - -impl DrmFeedback { - pub fn new( - ids: &DrmFeedbackIds, - render_ctx: &dyn GfxContext, - ) -> Result { - let main_device = match render_ctx.allocator().drm() { - Some(drm) => drm.dev(), - _ => return Err(DrmFeedbackError::NoDrmDevice), - }; - let (data, index_map) = create_fd_data(render_ctx); - let mut memfd = - uapi::memfd_create("drm_feedback", c::MFD_CLOEXEC | c::MFD_ALLOW_SEALING).unwrap(); - memfd.write_all(&data).unwrap(); - uapi::lseek(memfd.raw(), 0, c::SEEK_SET).unwrap(); - uapi::fcntl_add_seals( - memfd.raw(), - c::F_SEAL_SEAL | c::F_SEAL_GROW | c::F_SEAL_SHRINK | c::F_SEAL_WRITE, - ) - .unwrap(); - Ok(Self { - id: ids.next(), - tranches: vec![DrmFeedbackTranche { - device: main_device, - indices: (0..index_map.len()).map(|v| v as u16).collect(), - scanout: false, - }], - shared: Rc::new(DrmFeedbackShared { - fd: Rc::new(memfd), - size: data.len(), - main_device, - indices: index_map, - }), - }) +impl DrmFeedbackContext for dyn GfxContext + '_ { + fn main_device(&self) -> Option { + self.allocator().drm().map(|drm| drm.dev()) } - pub fn for_scanout( - &self, - ids: &DrmFeedbackIds, - devnum: c::dev_t, - formats: &[(u32, Modifier)], - ) -> Result, DrmFeedbackError> { - let mut tranches = vec![]; - { - let mut indices = vec![]; - for (format, modifier) in formats { - if let Some(idx) = self.shared.indices.get(&(*format, *modifier)) { - indices.push(*idx); - } - } - if indices.len() > 0 { - tranches.push(DrmFeedbackTranche { - device: devnum, - indices, - scanout: true, - }); - } else { - return Ok(None); + fn for_each_read_format(&self, f: &mut dyn FnMut(u32, Modifier)) { + for (format, info) in &**self.formats() { + for modifier in &info.read_modifiers { + f(*format, *modifier); } } - tranches.extend(self.tranches.iter().cloned()); - Ok(Some(Self { - id: ids.next(), - shared: self.shared.clone(), - tranches, - })) } } - -fn create_fd_data(ctx: &dyn GfxContext) -> (Vec, AHashMap<(u32, Modifier), u16>) { - let mut vec = vec![]; - let mut map = AHashMap::new(); - let mut pos = 0; - for (format, info) in &**ctx.formats() { - for modifier in &info.read_modifiers { - vec.write_u32::(*format).unwrap(); - vec.write_u32::(0).unwrap(); - vec.write_u64::(*modifier).unwrap(); - map.insert((*format, *modifier), pos); - pos += 1; - } - } - (vec, map) -} - -#[derive(Debug, Error)] -pub enum DrmFeedbackError { - #[error("Could not stat drm device")] - Stat(#[from] OsError), - #[error("Graphics API does not have a DRM device")] - NoDrmDevice, -} diff --git a/src/ei/ei_acceptor.rs b/src/ei/ei_acceptor.rs index 51d037b9..771d04b4 100644 --- a/src/ei/ei_acceptor.rs +++ b/src/ei/ei_acceptor.rs @@ -1,6 +1,7 @@ +use jay_async_engine::SpawnedFuture; use { crate::{ - async_engine::SpawnedFuture, + state::State, utils::{ errorfmt::ErrorFmt, diff --git a/src/ei/ei_client.rs b/src/ei/ei_client.rs index c5c1d85c..76eeb00b 100644 --- a/src/ei/ei_client.rs +++ b/src/ei/ei_client.rs @@ -1,7 +1,8 @@ pub use crate::ei::ei_client::ei_error::{EiClientError, EiParserError}; +use jay_async_engine::SpawnedFuture; use { crate::{ - async_engine::SpawnedFuture, + client::ClientId, ei::{ EiContext, EiInterfaceVersion, diff --git a/src/ei/ei_client/ei_tasks.rs b/src/ei/ei_client/ei_tasks.rs index 3e28b729..61bf076e 100644 --- a/src/ei/ei_client/ei_tasks.rs +++ b/src/ei/ei_client/ei_tasks.rs @@ -1,6 +1,7 @@ +use jay_async_engine::Phase; use { crate::{ - async_engine::Phase, + ei::{ ei_client::{EiClient, ei_error::EiClientError}, ei_object::EiObjectId, diff --git a/src/ei/ei_ifs/ei_device.rs b/src/ei/ei_ifs/ei_device.rs index 37ff2b45..16114f62 100644 --- a/src/ei/ei_ifs/ei_device.rs +++ b/src/ei/ei_ifs/ei_device.rs @@ -1,3 +1,6 @@ +use jay_geometry::Rect; +use jay_units::fixed::Fixed; +use jay_units::scale::Scale; use { crate::{ backend::{ButtonState, KeyState, ScrollAxis}, @@ -6,11 +9,9 @@ use { ei_ifs::{ei_seat::EiSeat, ei_touchscreen::TouchChange}, ei_object::{EiObject, EiVersion}, }, - fixed::Fixed, ifs::wl_seat::{CursorPositionType, PX_PER_SCROLL}, leaks::Tracker, - rect::Rect, - scale::Scale, + utils::{copyhashmap::CopyHashMap, syncqueue::SyncQueue}, wire_ei::{ EiDeviceId, diff --git a/src/ei/ei_ifs/ei_keyboard.rs b/src/ei/ei_ifs/ei_keyboard.rs index 271f3ed8..d8d92396 100644 --- a/src/ei/ei_ifs/ei_keyboard.rs +++ b/src/ei/ei_ifs/ei_keyboard.rs @@ -1,3 +1,4 @@ +use jay_keyboard::KeyboardState; use { crate::{ backend::KeyState, @@ -6,7 +7,6 @@ use { ei_ifs::ei_device::{EiDevice, EiDeviceInterface}, ei_object::{EiObject, EiVersion}, }, - keyboard::KeyboardState, leaks::Tracker, wire_ei::{ EiKeyboardId, diff --git a/src/ei/ei_ifs/ei_pointer.rs b/src/ei/ei_ifs/ei_pointer.rs index 99eee09e..f8dc14c2 100644 --- a/src/ei/ei_ifs/ei_pointer.rs +++ b/src/ei/ei_ifs/ei_pointer.rs @@ -1,3 +1,4 @@ +use jay_units::fixed::Fixed; use { crate::{ ei::{ @@ -5,7 +6,6 @@ use { ei_ifs::ei_device::{EiDevice, EiDeviceInterface}, ei_object::{EiObject, EiVersion}, }, - fixed::Fixed, leaks::Tracker, wire_ei::{ EiPointerId, diff --git a/src/ei/ei_ifs/ei_pointer_absolute.rs b/src/ei/ei_ifs/ei_pointer_absolute.rs index e07e6b83..51fb779e 100644 --- a/src/ei/ei_ifs/ei_pointer_absolute.rs +++ b/src/ei/ei_ifs/ei_pointer_absolute.rs @@ -1,3 +1,4 @@ +use jay_units::fixed::Fixed; use { crate::{ ei::{ @@ -5,7 +6,6 @@ use { ei_ifs::ei_device::{EiDevice, EiDeviceInterface}, ei_object::{EiObject, EiVersion}, }, - fixed::Fixed, leaks::Tracker, wire_ei::{ EiPointerAbsoluteId, diff --git a/src/ei/ei_ifs/ei_scroll.rs b/src/ei/ei_ifs/ei_scroll.rs index aa0efdd1..fcd0dd65 100644 --- a/src/ei/ei_ifs/ei_scroll.rs +++ b/src/ei/ei_ifs/ei_scroll.rs @@ -1,3 +1,4 @@ +use jay_units::fixed::Fixed; use { crate::{ ei::{ @@ -5,7 +6,6 @@ use { ei_ifs::ei_device::{EiDevice, EiDeviceInterface}, ei_object::{EiObject, EiVersion}, }, - fixed::Fixed, ifs::wl_seat::wl_pointer::{HORIZONTAL_SCROLL, VERTICAL_SCROLL}, leaks::Tracker, wire_ei::{ diff --git a/src/ei/ei_ifs/ei_seat.rs b/src/ei/ei_ifs/ei_seat.rs index 89a3f84d..b8a7ebdf 100644 --- a/src/ei/ei_ifs/ei_seat.rs +++ b/src/ei/ei_ifs/ei_seat.rs @@ -1,3 +1,5 @@ +use jay_units::fixed::Fixed; +use jay_keyboard::{DynKeyboardState, KeyboardState, KeyboardStateId}; use { crate::{ backend::{ButtonState, KeyState}, @@ -15,12 +17,10 @@ use { }, ei_object::{EiInterface, EiObject, EiVersion}, }, - fixed::Fixed, ifs::wl_seat::{ PhysicalKeyboardId, WlSeatGlobal, wl_pointer::{HORIZONTAL_SCROLL, PendingScroll, VERTICAL_SCROLL}, }, - keyboard::{DynKeyboardState, KeyboardState, KeyboardStateId}, leaks::Tracker, tree::Node, utils::{array, bitflags::BitflagsExt, clonecell::CloneCell}, diff --git a/src/ei/ei_ifs/ei_touchscreen.rs b/src/ei/ei_ifs/ei_touchscreen.rs index 3b9a63a5..2a527c6e 100644 --- a/src/ei/ei_ifs/ei_touchscreen.rs +++ b/src/ei/ei_ifs/ei_touchscreen.rs @@ -1,3 +1,4 @@ +use jay_units::fixed::Fixed; use { crate::{ ei::{ @@ -5,7 +6,6 @@ use { ei_ifs::ei_device::{EiDevice, EiDeviceInterface}, ei_object::{EiObject, EiVersion}, }, - fixed::Fixed, leaks::Tracker, utils::clonecell::UnsafeCellCloneSafe, wire_ei::{ diff --git a/src/ei/ei_object.rs b/src/ei/ei_object.rs index c79c78a3..701ff3dd 100644 --- a/src/ei/ei_object.rs +++ b/src/ei/ei_object.rs @@ -9,41 +9,13 @@ use { }, std::{ cmp::Ordering, - fmt::{Display, Formatter, LowerHex}, rc::Rc, }, }; +pub use jay_wire_types::EiObjectId; pub const EI_HANDSHAKE_ID: EiHandshakeId = EiHandshakeId::from_raw(0); -#[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)] -pub struct EiObjectId(u64); - -impl EiObjectId { - #[expect(dead_code)] - pub const NONE: Self = EiObjectId(0); - - pub fn from_raw(raw: u64) -> Self { - Self(raw) - } - - pub fn raw(self) -> u64 { - self.0 - } -} - -impl Display for EiObjectId { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - Display::fmt(&self.0, f) - } -} - -impl LowerHex for EiObjectId { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - LowerHex::fmt(&self.0, f) - } -} - pub trait EiObjectBase { fn id(&self) -> EiObjectId; fn version(&self) -> EiVersion; diff --git a/src/forker.rs b/src/forker.rs index 9938a1d1..b4d38cba 100644 --- a/src/forker.rs +++ b/src/forker.rs @@ -1,607 +1,10 @@ +mod error; mod io; +mod protocol; +mod proxy; +mod worker; -use { - crate::{ - async_engine::{AsyncEngine, SpawnedFuture}, - compositor::{DISPLAY, LIBEI_SOCKET, WAYLAND_DISPLAY}, - forker::io::{IoIn, IoOut}, - io_uring::IoUring, - state::State, - utils::{ - buffd::BufFdError, - clone3::{Forked, double_fork, fork_with_pidfd}, - copyhashmap::CopyHashMap, - errorfmt::ErrorFmt, - numcell::NumCell, - oserror::OsErrorExt2, - pipe::{Pipe, pipe}, - process_name::set_process_name, - queue::AsyncQueue, - }, - xwayland, - }, - ahash::AHashMap, - bincode::Options, - jay_config::_private::bincode_ops, - log::Level, - serde::{Deserialize, Serialize}, - std::{ - cell::{Cell, RefCell}, - env, - ffi::OsStr, - io::{Read, Write}, - os::unix::ffi::OsStrExt, - rc::{Rc, Weak}, - task::{Poll, Waker}, - }, - thiserror::Error, - uapi::{Errno, Fd, IntoUstr, OwnedFd, UstrPtr, c}, +pub use { + error::ForkerError, + proxy::ForkerProxy, }; - -pub struct ForkerProxy { - pidfd: Rc, - socket: Rc, - task_in: Cell>>, - task_out: Cell>>, - task_proc: Cell>>, - outgoing: AsyncQueue, - next_id: NumCell, - pending_pidfds: CopyHashMap>, - fds: RefCell>>, -} - -struct PidfdHandoff { - pidfd: Cell, c::pid_t), ForkerError>>>, - waiter: Cell>, -} - -#[derive(Debug, Error)] -pub enum ForkerError { - #[error("Could not create a socketpair")] - Socketpair(#[source] crate::utils::oserror::OsError), - #[error("Could not fork")] - Fork(#[source] crate::utils::oserror::OsError), - #[error("Could not read the next message")] - ReadFailed(#[source] BufFdError), - #[error("Could not write the next message")] - WriteFailed(#[source] BufFdError), - #[error("Could not decode the next message")] - DecodeFailed(#[source] bincode::Error), - #[error("Could not encode the next message")] - EncodeFailed(#[source] bincode::Error), - #[error("Could not fork")] - PidfdForkFailed, - #[error("Could not receive pidfd from child")] - RecvPidfd(#[source] crate::utils::oserror::OsError), - #[error("Could not read cmsg")] - CmsgRead(#[source] crate::utils::oserror::OsError), - #[error("Cmsg has an unexpected form")] - InvalidCmsg, -} - -impl ForkerProxy { - pub fn clear(&self) { - self.task_in.take(); - self.task_out.take(); - self.task_proc.take(); - self.outgoing.clear(); - } - - pub fn create(reaper_pid: c::pid_t) -> Result { - let (parent, child) = uapi::socketpair(c::AF_UNIX, c::SOCK_STREAM | c::SOCK_CLOEXEC, 0) - .map_os_err(ForkerError::Socketpair)?; - match double_fork()? { - Some(pidfd) => Ok(ForkerProxy { - pidfd: Rc::new(pidfd), - socket: Rc::new(parent), - task_in: Cell::new(None), - task_out: Cell::new(None), - task_proc: Cell::new(None), - outgoing: Default::default(), - next_id: Default::default(), - pending_pidfds: Default::default(), - fds: Default::default(), - }), - None => { - drop(parent); - Forker::handle(reaper_pid, child) - } - } - } - - pub fn install(self: &Rc, state: &Rc) { - state.forker.set(Some(self.clone())); - self.task_proc.set(Some(state.eng.spawn( - "forker check process", - self.clone().check_process(state.clone()), - ))); - self.task_in.set(Some( - state - .eng - .spawn("forker incoming", self.clone().incoming(state.clone())), - )); - self.task_out.set(Some( - state - .eng - .spawn("forker outgoing", self.clone().outgoing(state.clone())), - )); - } - - pub fn setenv(&self, key: &[u8], val: &[u8]) { - self.outgoing.push(ServerMessage::SetEnv { - var: key.to_vec(), - val: Some(val.to_vec()), - }) - } - - pub fn unsetenv(&self, key: &[u8]) { - self.outgoing.push(ServerMessage::SetEnv { - var: key.to_vec(), - val: None, - }) - } - - async fn pidfd(&self, id: u32) -> Result<(Rc, c::pid_t), ForkerError> { - let handoff = Rc::new(PidfdHandoff { - pidfd: Cell::new(None), - waiter: Cell::new(None), - }); - self.pending_pidfds.set(id, Rc::downgrade(&handoff)); - futures_util::future::poll_fn(|ctx| { - if let Some(pidfd) = handoff.pidfd.take() { - Poll::Ready(pidfd) - } else { - handoff.waiter.set(Some(ctx.waker().clone())); - Poll::Pending - } - }) - .await - } - - pub async fn xwayland( - &self, - state: &State, - stderr: Rc, - dfd: Rc, - listenfd: Rc, - wmfd: Rc, - waylandfd: Rc, - ) -> Result<(Rc, c::pid_t), ForkerError> { - let (prog, args) = xwayland::build_args(state, self).await; - let env = vec![ - ("WAYLAND_SOCKET".to_string(), Some("6".to_string())), - (LIBEI_SOCKET.to_string(), None), - ]; - let fds = vec![ - (2, stderr), - (3, dfd), - (4, listenfd), - (5, wmfd), - (6, waylandfd), - ]; - let pidfd_id = self.next_id.fetch_add(1); - self.spawn_(prog, args, env, fds, Some(pidfd_id)); - self.pidfd(pidfd_id).await - } - - pub fn spawn( - &self, - prog: String, - args: Vec, - env: Vec<(String, Option)>, - fds: Vec<(i32, Rc)>, - ) { - self.spawn_(prog, args, env, fds, None) - } - - fn spawn_( - &self, - prog: String, - args: Vec, - env: Vec<(String, Option)>, - fds: Vec<(i32, Rc)>, - pidfd_id: Option, - ) { - for (_, fd) in &fds { - self.fds.borrow_mut().push(fd.clone()); - } - let fds = fds.into_iter().map(|(a, _)| a).collect(); - self.outgoing.push(ServerMessage::Spawn { - prog, - args, - env, - fds, - pidfd_id, - }) - } - - async fn incoming(self: Rc, state: Rc) { - let mut io = IoIn::new(&self.socket, &state.ring); - loop { - let msg = match io.read_msg().await { - Ok(msg) => msg, - Err(e) => { - log::error!("Could not read from the ol' forker: {}", ErrorFmt(e)); - self.task_in.take(); - return; - } - }; - self.handle_msg(msg, &mut io); - } - } - - fn handle_msg(&self, msg: ForkerMessage, io: &mut IoIn) { - match msg { - ForkerMessage::Log { level, msg } => self.handle_log(level, &msg), - ForkerMessage::PidFd { id, success, pid } => self.handle_pidfd(id, success, io, pid), - } - } - - fn handle_pidfd(&self, id: u32, success: bool, io: &mut IoIn, pid: c::pid_t) { - let res = match success { - true => Ok((io.pop_fd().unwrap(), pid)), - _ => Err(ForkerError::PidfdForkFailed), - }; - if let Some(handoff) = self.pending_pidfds.remove(&id) - && let Some(handoff) = handoff.upgrade() - { - handoff.pidfd.set(Some(res)); - if let Some(w) = handoff.waiter.take() { - w.wake(); - } - } - } - - fn handle_log(&self, level: usize, msg: &str) { - let level = match level { - 1 => Level::Error, - 2 => Level::Warn, - 3 => Level::Info, - 4 => Level::Debug, - 5 => Level::Trace, - _ => Level::Error, - }; - log::log!(level, "{}", msg); - } - - async fn outgoing(self: Rc, state: Rc) { - let mut io = IoOut::new(&self.socket, &state.ring); - loop { - let msg = self.outgoing.pop().await; - for fd in self.fds.borrow_mut().drain(..) { - io.push_fd(fd); - } - if let Err(e) = io.write_msg(msg).await { - log::error!("Could not write to the ol' forker: {}", ErrorFmt(e)); - self.clear(); - state.forker.set(None); - return; - } - } - } - - async fn check_process(self: Rc, state: Rc) { - if let Err(e) = state.ring.readable(&self.pidfd).await { - log::error!( - "Cannot wait for the forker pidfd to become readable: {}", - ErrorFmt(e) - ); - } - log::error!("The ol' forker died. Cannot spawn further processes."); - self.clear(); - state.forker.set(None); - } -} - -#[derive(Serialize, Deserialize)] -enum ServerMessage { - SetEnv { - var: Vec, - val: Option>, - }, - Spawn { - prog: String, - args: Vec, - env: Vec<(String, Option)>, - fds: Vec, - pidfd_id: Option, - }, -} - -#[derive(Serialize, Deserialize)] -enum ForkerMessage { - Log { - level: usize, - msg: String, - }, - PidFd { - id: u32, - success: bool, - pid: c::pid_t, - }, -} - -struct Forker { - socket: Rc, - ae: Rc, - ring: Rc, - fds: RefCell>>, - outgoing: AsyncQueue, - pending_spawns: CopyHashMap>, -} - -impl Forker { - fn handle(ppid: c::pid_t, socket: OwnedFd) -> ! { - unsafe { - env::set_var("XDG_SESSION_TYPE", "wayland"); - env::remove_var(DISPLAY); - env::remove_var(WAYLAND_DISPLAY); - } - set_process_name("the ol' forker"); - setup_deathsig(ppid); - unsafe { - c::signal(c::SIGCHLD, c::SIG_IGN); - } - let socket = Rc::new(setup_fds(socket)); - std::panic::set_hook({ - let socket = socket.raw(); - Box::new(move |pi| { - let msg = ForkerMessage::Log { - level: log::Level::Error as _, - msg: format!("The ol' forker panicked: {}", pi), - }; - let msg = bincode_ops().serialize(&msg).unwrap(); - let _ = Fd::new(socket).write_all(&msg); - }) - }); - let ae = AsyncEngine::new(); - let ring = IoUring::new(&ae, 32).unwrap(); - let forker = Rc::new(Forker { - socket, - ae: ae.clone(), - ring: ring.clone(), - fds: RefCell::new(vec![]), - outgoing: Default::default(), - pending_spawns: Default::default(), - }); - let _f1 = ae.spawn("forker incoming", forker.clone().incoming()); - let _f2 = ae.spawn("forker outgoing", forker.clone().outgoing()); - let _ = ring.run(); - std::process::exit(1); - } - - async fn outgoing(self: Rc) { - let mut io = IoOut::new(&self.socket, &self.ring); - loop { - let msg = self.outgoing.pop().await; - for fd in self.fds.borrow_mut().drain(..) { - io.push_fd(fd); - } - if io.write_msg(msg).await.is_err() { - self.ring.stop(); - return; - } - } - } - - async fn incoming(self: Rc) { - let mut io = IoIn::new(&self.socket, &self.ring); - loop { - let msg = match io.read_msg().await { - Ok(m) => m, - _ => { - self.ring.stop(); - return; - } - }; - self.handle_msg(msg, &mut io); - } - } - - fn handle_msg(self: &Rc, msg: ServerMessage, io: &mut IoIn) { - match msg { - ServerMessage::SetEnv { var, val } => self.handle_set_env(&var, val), - ServerMessage::Spawn { - prog, - args, - env, - fds, - pidfd_id, - } => self.handle_spawn(prog, args, env, fds, io, pidfd_id), - } - } - - fn handle_set_env(self: &Rc, var: &[u8], val: Option>) { - let var = OsStr::from_bytes(var); - unsafe { - match val { - Some(val) => env::set_var(var, OsStr::from_bytes(&val)), - _ => env::remove_var(var), - } - } - } - - fn handle_spawn( - self: &Rc, - prog: String, - args: Vec, - env: Vec<(String, Option)>, - fds: Vec, - io: &mut IoIn, - pidfd_id: Option, - ) { - let fds = fds - .into_iter() - .map(|a| (a, Rc::try_unwrap(io.pop_fd().unwrap()).unwrap())) - .collect(); - self.spawn(prog, args, env, fds, pidfd_id) - } - - fn spawn( - self: &Rc, - prog: String, - args: Vec, - env: Vec<(String, Option)>, - fds: Vec<(i32, OwnedFd)>, - pidfd_id: Option, - ) { - let Pipe { read, mut write } = pipe().unwrap(); - let res = match fork_with_pidfd(false) { - Ok(o) => o, - Err(e) => { - self.fail_pidfd(pidfd_id); - self.outgoing.push(ForkerMessage::Log { - level: log::Level::Error as usize, - msg: ErrorFmt(e).to_string(), - }); - return; - } - }; - match res { - Forked::Parent { pid, pidfd } => { - drop(write); - let slf = self.clone(); - let spawn = self.ae.spawn("await spawn", async move { - let read = Rc::new(read); - if let Err(e) = slf.ring.readable(&read).await { - log::error!( - "Cannot wait for the child fd to become readable: {}", - ErrorFmt(e) - ); - slf.fail_pidfd(pidfd_id); - } else { - let mut s = String::new(); - let _ = Fd::new(read.raw()).read_to_string(&mut s); - if s.len() > 0 { - slf.outgoing.push(ForkerMessage::Log { - level: log::Level::Error as _, - msg: format!("Could not spawn `{}`: {}", prog, s), - }); - slf.fail_pidfd(pidfd_id); - } else { - if let Some(id) = pidfd_id { - slf.fds.borrow_mut().push(Rc::new(pidfd)); - slf.outgoing.push(ForkerMessage::PidFd { - id, - success: true, - pid, - }); - } - } - } - slf.pending_spawns.remove(&pid); - }); - self.pending_spawns.set(pid, spawn); - } - Forked::Child { .. } => { - let err: Result<(), SpawnError> = (|| { - if let Some(max_desired) = fds.iter().map(|v| v.0).max() { - write = uapi::fcntl_dupfd_cloexec(write.raw(), max_desired.wrapping_add(1)) - .map_os_err(SpawnError::Dupfd)?; - } - let fds = map_fds(fds)?; - for fd in fds { - let fd = fd.unwrap(); - let res: Result<_, Errno> = (|| { - uapi::fcntl_setfd(fd, uapi::fcntl_getfd(fd)? & !c::FD_CLOEXEC)?; - Ok(()) - })(); - res.map_os_err(SpawnError::Cloexec)?; - } - unsafe { - c::signal(c::SIGCHLD, c::SIG_DFL); - } - for (key, val) in env { - unsafe { - match val { - None => env::remove_var(&key), - Some(val) => env::set_var(&key, &val), - } - } - } - let prog = prog.into_ustr(); - let mut argsnt = UstrPtr::new(); - argsnt.push(&prog); - for arg in args { - argsnt.push(arg); - } - uapi::execvp(&prog, &argsnt).map_os_err(SpawnError::Exec)?; - Ok(()) - })(); - if let Err(e) = err { - let _ = write.write_all(ErrorFmt(e).to_string().as_bytes()); - } - std::process::exit(1); - } - } - } - - fn fail_pidfd(&self, pidfd_id: Option) { - if let Some(id) = pidfd_id { - self.outgoing.push(ForkerMessage::PidFd { - id, - success: false, - pid: 0, - }); - } - } -} - -#[derive(Debug, Error)] -enum SpawnError { - #[error("exec failed")] - Exec(#[source] crate::utils::oserror::OsError), - #[error("Could not unset cloexec flag")] - Cloexec(#[source] crate::utils::oserror::OsError), - #[error("dupfd faild")] - Dupfd(#[source] crate::utils::oserror::OsError), -} - -fn setup_fds(mut socket: OwnedFd) -> OwnedFd { - if socket.raw() != 0 { - uapi::dup3(socket.unwrap(), 0, 0).unwrap(); - socket = OwnedFd::new(0); - } - uapi::close_range(1, c::c_uint::MAX, 0).unwrap(); - uapi::dup3(socket.raw(), 3, c::O_CLOEXEC).unwrap(); - socket = OwnedFd::new(3); - let fd = uapi::open("/dev/null", c::O_RDWR, 0).unwrap().unwrap(); - assert!(fd == 0); - uapi::dup2(0, 1).unwrap(); - uapi::dup2(0, 2).unwrap(); - socket -} - -fn setup_deathsig(ppid: c::pid_t) { - unsafe { - let res = c::prctl(c::PR_SET_PDEATHSIG, c::SIGKILL as c::c_ulong); - uapi::map_err!(res).unwrap(); - if ppid != uapi::getppid() { - std::process::exit(0); - } - } -} - -fn map_fds(fds: Vec<(i32, OwnedFd)>) -> Result, SpawnError> { - let mut desired: Vec<_> = fds.iter().map(|v| v.0).collect(); - desired.sort_by(|a, b| b.cmp(a)); - let mut existing_to_desired: AHashMap<_, _> = fds.iter().map(|v| (v.1.raw(), v.0)).collect(); - let mut desired_to_existing: AHashMap<_, _> = fds.into_iter().map(|v| (v.0, v.1)).collect(); - for desired in desired { - let existing = desired_to_existing.get(&desired).unwrap().raw(); - if existing == desired { - continue; - } - if let Some(conflict_desired) = existing_to_desired.get(&desired).copied() { - let new = uapi::fcntl_dupfd_cloexec(desired, 0).map_os_err(SpawnError::Dupfd)?; - existing_to_desired.remove(&desired); - existing_to_desired.insert(new.raw(), conflict_desired); - desired_to_existing.insert(conflict_desired, new); - } - uapi::dup3(existing, desired, c::O_CLOEXEC).map_os_err(SpawnError::Dupfd)?; - existing_to_desired.remove(&existing); - existing_to_desired.insert(desired, desired); - desired_to_existing.insert(desired, OwnedFd::new(desired)); - } - Ok(desired_to_existing.into_values().collect()) -} diff --git a/src/forker/error.rs b/src/forker/error.rs new file mode 100644 index 00000000..6eadab71 --- /dev/null +++ b/src/forker/error.rs @@ -0,0 +1,28 @@ +use { + crate::utils::buffd::BufFdError, + thiserror::Error, +}; + +#[derive(Debug, Error)] +pub enum ForkerError { + #[error("Could not create a socketpair")] + Socketpair(#[source] crate::utils::oserror::OsError), + #[error("Could not fork")] + Fork(#[source] crate::utils::oserror::OsError), + #[error("Could not read the next message")] + ReadFailed(#[source] BufFdError), + #[error("Could not write the next message")] + WriteFailed(#[source] BufFdError), + #[error("Could not decode the next message")] + DecodeFailed(#[source] bincode::Error), + #[error("Could not encode the next message")] + EncodeFailed(#[source] bincode::Error), + #[error("Could not fork")] + PidfdForkFailed, + #[error("Could not receive pidfd from child")] + RecvPidfd(#[source] crate::utils::oserror::OsError), + #[error("Could not read cmsg")] + CmsgRead(#[source] crate::utils::oserror::OsError), + #[error("Cmsg has an unexpected form")] + InvalidCmsg, +} diff --git a/src/forker/io.rs b/src/forker/io.rs index 6018b19a..03b7cc2c 100644 --- a/src/forker/io.rs +++ b/src/forker/io.rs @@ -1,3 +1,4 @@ +use jay_io_uring::IoUring; use { bincode::Options, serde::{Serialize, de::DeserializeOwned}, @@ -6,15 +7,14 @@ use { use { crate::{ - forker::ForkerError, - io_uring::IoUring, + forker::{ForkerError, protocol::bincode_ops}, + utils::{ buf::DynamicBuf, buffd::{BufFdIn, BufFdOut}, vec_ext::VecExt, }, }, - jay_config::_private::bincode_ops, uapi::OwnedFd, }; diff --git a/src/forker/protocol.rs b/src/forker/protocol.rs new file mode 100644 index 00000000..9e28e7bc --- /dev/null +++ b/src/forker/protocol.rs @@ -0,0 +1,40 @@ +use { + bincode::Options, + serde::{Deserialize, Serialize}, + uapi::c, +}; + +pub(super) fn bincode_ops() -> impl Options { + bincode::DefaultOptions::new() + .with_fixint_encoding() + .with_little_endian() + .with_no_limit() +} + +#[derive(Serialize, Deserialize)] +pub(super) enum ServerMessage { + SetEnv { + var: Vec, + val: Option>, + }, + Spawn { + prog: String, + args: Vec, + env: Vec<(String, Option)>, + fds: Vec, + pidfd_id: Option, + }, +} + +#[derive(Serialize, Deserialize)] +pub(super) enum ForkerMessage { + Log { + level: usize, + msg: String, + }, + PidFd { + id: u32, + success: bool, + pid: c::pid_t, + }, +} diff --git a/src/forker/proxy.rs b/src/forker/proxy.rs new file mode 100644 index 00000000..26632c01 --- /dev/null +++ b/src/forker/proxy.rs @@ -0,0 +1,261 @@ +use jay_async_engine::SpawnedFuture; +use { + crate::{ + + compositor::LIBEI_SOCKET, + forker::{ + ForkerError, + io::{IoIn, IoOut}, + protocol::{ForkerMessage, ServerMessage}, + worker::Forker, + }, + state::State, + utils::{ + clone3::double_fork, + copyhashmap::CopyHashMap, + errorfmt::ErrorFmt, + numcell::NumCell, + oserror::OsErrorExt2, + queue::AsyncQueue, + }, + xwayland, + }, + log::Level, + std::{ + cell::{Cell, RefCell}, + rc::{Rc, Weak}, + task::{Poll, Waker}, + }, + uapi::{OwnedFd, c}, +}; + +pub struct ForkerProxy { + pidfd: Rc, + socket: Rc, + task_in: Cell>>, + task_out: Cell>>, + task_proc: Cell>>, + outgoing: AsyncQueue, + next_id: NumCell, + pending_pidfds: CopyHashMap>, + fds: RefCell>>, +} + +struct PidfdHandoff { + pidfd: Cell, c::pid_t), ForkerError>>>, + waiter: Cell>, +} + +impl ForkerProxy { + pub fn clear(&self) { + self.task_in.take(); + self.task_out.take(); + self.task_proc.take(); + self.outgoing.clear(); + } + + pub fn create(reaper_pid: c::pid_t) -> Result { + let (parent, child) = uapi::socketpair(c::AF_UNIX, c::SOCK_STREAM | c::SOCK_CLOEXEC, 0) + .map_os_err(ForkerError::Socketpair)?; + match double_fork()? { + Some(pidfd) => Ok(ForkerProxy { + pidfd: Rc::new(pidfd), + socket: Rc::new(parent), + task_in: Cell::new(None), + task_out: Cell::new(None), + task_proc: Cell::new(None), + outgoing: Default::default(), + next_id: Default::default(), + pending_pidfds: Default::default(), + fds: Default::default(), + }), + None => { + drop(parent); + Forker::handle(reaper_pid, child) + } + } + } + + pub fn install(self: &Rc, state: &Rc) { + state.forker.set(Some(self.clone())); + self.task_proc.set(Some(state.eng.spawn( + "forker check process", + self.clone().check_process(state.clone()), + ))); + self.task_in.set(Some( + state + .eng + .spawn("forker incoming", self.clone().incoming(state.clone())), + )); + self.task_out.set(Some( + state + .eng + .spawn("forker outgoing", self.clone().outgoing(state.clone())), + )); + } + + pub fn setenv(&self, key: &[u8], val: &[u8]) { + self.outgoing.push(ServerMessage::SetEnv { + var: key.to_vec(), + val: Some(val.to_vec()), + }) + } + + pub fn unsetenv(&self, key: &[u8]) { + self.outgoing.push(ServerMessage::SetEnv { + var: key.to_vec(), + val: None, + }) + } + + async fn pidfd(&self, id: u32) -> Result<(Rc, c::pid_t), ForkerError> { + let handoff = Rc::new(PidfdHandoff { + pidfd: Cell::new(None), + waiter: Cell::new(None), + }); + self.pending_pidfds.set(id, Rc::downgrade(&handoff)); + futures_util::future::poll_fn(|ctx| { + if let Some(pidfd) = handoff.pidfd.take() { + Poll::Ready(pidfd) + } else { + handoff.waiter.set(Some(ctx.waker().clone())); + Poll::Pending + } + }) + .await + } + + pub async fn xwayland( + &self, + state: &State, + stderr: Rc, + dfd: Rc, + listenfd: Rc, + wmfd: Rc, + waylandfd: Rc, + ) -> Result<(Rc, c::pid_t), ForkerError> { + let (prog, args) = xwayland::build_args(state, self).await; + let env = vec![ + ("WAYLAND_SOCKET".to_string(), Some("6".to_string())), + (LIBEI_SOCKET.to_string(), None), + ]; + let fds = vec![ + (2, stderr), + (3, dfd), + (4, listenfd), + (5, wmfd), + (6, waylandfd), + ]; + let pidfd_id = self.next_id.fetch_add(1); + self.spawn_(prog, args, env, fds, Some(pidfd_id)); + self.pidfd(pidfd_id).await + } + + pub fn spawn( + &self, + prog: String, + args: Vec, + env: Vec<(String, Option)>, + fds: Vec<(i32, Rc)>, + ) { + self.spawn_(prog, args, env, fds, None) + } + + fn spawn_( + &self, + prog: String, + args: Vec, + env: Vec<(String, Option)>, + fds: Vec<(i32, Rc)>, + pidfd_id: Option, + ) { + for (_, fd) in &fds { + self.fds.borrow_mut().push(fd.clone()); + } + let fds = fds.into_iter().map(|(a, _)| a).collect(); + self.outgoing.push(ServerMessage::Spawn { + prog, + args, + env, + fds, + pidfd_id, + }) + } + + async fn incoming(self: Rc, state: Rc) { + let mut io = IoIn::new(&self.socket, &state.ring); + loop { + let msg = match io.read_msg().await { + Ok(msg) => msg, + Err(e) => { + log::error!("Could not read from the ol' forker: {}", ErrorFmt(e)); + self.task_in.take(); + return; + } + }; + self.handle_msg(msg, &mut io); + } + } + + fn handle_msg(&self, msg: ForkerMessage, io: &mut IoIn) { + match msg { + ForkerMessage::Log { level, msg } => self.handle_log(level, &msg), + ForkerMessage::PidFd { id, success, pid } => self.handle_pidfd(id, success, io, pid), + } + } + + fn handle_pidfd(&self, id: u32, success: bool, io: &mut IoIn, pid: c::pid_t) { + let res = match success { + true => Ok((io.pop_fd().unwrap(), pid)), + _ => Err(ForkerError::PidfdForkFailed), + }; + if let Some(handoff) = self.pending_pidfds.remove(&id) + && let Some(handoff) = handoff.upgrade() + { + handoff.pidfd.set(Some(res)); + if let Some(w) = handoff.waiter.take() { + w.wake(); + } + } + } + + fn handle_log(&self, level: usize, msg: &str) { + let level = match level { + 1 => Level::Error, + 2 => Level::Warn, + 3 => Level::Info, + 4 => Level::Debug, + 5 => Level::Trace, + _ => Level::Error, + }; + log::log!(level, "{}", msg); + } + + async fn outgoing(self: Rc, state: Rc) { + let mut io = IoOut::new(&self.socket, &state.ring); + loop { + let msg = self.outgoing.pop().await; + for fd in self.fds.borrow_mut().drain(..) { + io.push_fd(fd); + } + if let Err(e) = io.write_msg(msg).await { + log::error!("Could not write to the ol' forker: {}", ErrorFmt(e)); + self.clear(); + state.forker.set(None); + return; + } + } + } + + async fn check_process(self: Rc, state: Rc) { + if let Err(e) = state.ring.readable(&self.pidfd).await { + log::error!( + "Cannot wait for the forker pidfd to become readable: {}", + ErrorFmt(e) + ); + } + log::error!("The ol' forker died. Cannot spawn further processes."); + self.clear(); + state.forker.set(None); + } +} diff --git a/src/forker/worker.rs b/src/forker/worker.rs new file mode 100644 index 00000000..6d226a74 --- /dev/null +++ b/src/forker/worker.rs @@ -0,0 +1,317 @@ +use jay_io_uring::IoUring; +use jay_async_engine::{AsyncEngine, SpawnedFuture}; +use { + crate::{ + + compositor::{DISPLAY, WAYLAND_DISPLAY}, + forker::{ + io::{IoIn, IoOut}, + protocol::{ForkerMessage, ServerMessage, bincode_ops}, + }, + + utils::{ + clone3::{Forked, fork_with_pidfd}, + copyhashmap::CopyHashMap, + errorfmt::ErrorFmt, + oserror::OsErrorExt2, + pipe::{Pipe, pipe}, + process_name::set_process_name, + queue::AsyncQueue, + }, + }, + ahash::AHashMap, + bincode::Options, + std::{ + cell::RefCell, + env, + ffi::OsStr, + io::{Read, Write}, + os::unix::ffi::OsStrExt, + rc::Rc, + }, + thiserror::Error, + uapi::{Errno, Fd, IntoUstr, OwnedFd, UstrPtr, c}, +}; + +pub(super) struct Forker { + socket: Rc, + ae: Rc, + ring: Rc, + fds: RefCell>>, + outgoing: AsyncQueue, + pending_spawns: CopyHashMap>, +} + +impl Forker { + pub(super) fn handle(ppid: c::pid_t, socket: OwnedFd) -> ! { + unsafe { + env::set_var("XDG_SESSION_TYPE", "wayland"); + env::remove_var(DISPLAY); + env::remove_var(WAYLAND_DISPLAY); + } + set_process_name("the ol' forker"); + setup_deathsig(ppid); + unsafe { + c::signal(c::SIGCHLD, c::SIG_IGN); + } + let socket = Rc::new(setup_fds(socket)); + std::panic::set_hook({ + let socket = socket.raw(); + Box::new(move |pi| { + let msg = ForkerMessage::Log { + level: log::Level::Error as _, + msg: format!("The ol' forker panicked: {}", pi), + }; + let msg = bincode_ops().serialize(&msg).unwrap(); + let _ = Fd::new(socket).write_all(&msg); + }) + }); + let ae = AsyncEngine::new(); + let ring = IoUring::new(&ae, 32).unwrap(); + let forker = Rc::new(Forker { + socket, + ae: ae.clone(), + ring: ring.clone(), + fds: RefCell::new(vec![]), + outgoing: Default::default(), + pending_spawns: Default::default(), + }); + let _f1 = ae.spawn("forker incoming", forker.clone().incoming()); + let _f2 = ae.spawn("forker outgoing", forker.clone().outgoing()); + let _ = ring.run(); + std::process::exit(1); + } + + async fn outgoing(self: Rc) { + let mut io = IoOut::new(&self.socket, &self.ring); + loop { + let msg = self.outgoing.pop().await; + for fd in self.fds.borrow_mut().drain(..) { + io.push_fd(fd); + } + if io.write_msg(msg).await.is_err() { + self.ring.stop(); + return; + } + } + } + + async fn incoming(self: Rc) { + let mut io = IoIn::new(&self.socket, &self.ring); + loop { + let msg = match io.read_msg().await { + Ok(m) => m, + _ => { + self.ring.stop(); + return; + } + }; + self.handle_msg(msg, &mut io); + } + } + + fn handle_msg(self: &Rc, msg: ServerMessage, io: &mut IoIn) { + match msg { + ServerMessage::SetEnv { var, val } => self.handle_set_env(&var, val), + ServerMessage::Spawn { + prog, + args, + env, + fds, + pidfd_id, + } => self.handle_spawn(prog, args, env, fds, io, pidfd_id), + } + } + + fn handle_set_env(self: &Rc, var: &[u8], val: Option>) { + let var = OsStr::from_bytes(var); + unsafe { + match val { + Some(val) => env::set_var(var, OsStr::from_bytes(&val)), + _ => env::remove_var(var), + } + } + } + + fn handle_spawn( + self: &Rc, + prog: String, + args: Vec, + env: Vec<(String, Option)>, + fds: Vec, + io: &mut IoIn, + pidfd_id: Option, + ) { + let fds = fds + .into_iter() + .map(|a| (a, Rc::try_unwrap(io.pop_fd().unwrap()).unwrap())) + .collect(); + self.spawn(prog, args, env, fds, pidfd_id) + } + + fn spawn( + self: &Rc, + prog: String, + args: Vec, + env: Vec<(String, Option)>, + fds: Vec<(i32, OwnedFd)>, + pidfd_id: Option, + ) { + let Pipe { read, mut write } = pipe().unwrap(); + let res = match fork_with_pidfd(false) { + Ok(o) => o, + Err(e) => { + self.fail_pidfd(pidfd_id); + self.outgoing.push(ForkerMessage::Log { + level: log::Level::Error as usize, + msg: ErrorFmt(e).to_string(), + }); + return; + } + }; + match res { + Forked::Parent { pid, pidfd } => { + drop(write); + let slf = self.clone(); + let spawn = self.ae.spawn("await spawn", async move { + let read = Rc::new(read); + if let Err(e) = slf.ring.readable(&read).await { + log::error!( + "Cannot wait for the child fd to become readable: {}", + ErrorFmt(e) + ); + slf.fail_pidfd(pidfd_id); + } else { + let mut s = String::new(); + let _ = Fd::new(read.raw()).read_to_string(&mut s); + if s.len() > 0 { + slf.outgoing.push(ForkerMessage::Log { + level: log::Level::Error as _, + msg: format!("Could not spawn `{}`: {}", prog, s), + }); + slf.fail_pidfd(pidfd_id); + } else if let Some(id) = pidfd_id { + slf.fds.borrow_mut().push(Rc::new(pidfd)); + slf.outgoing.push(ForkerMessage::PidFd { + id, + success: true, + pid, + }); + } + } + slf.pending_spawns.remove(&pid); + }); + self.pending_spawns.set(pid, spawn); + } + Forked::Child { .. } => { + let err: Result<(), SpawnError> = (|| { + if let Some(max_desired) = fds.iter().map(|v| v.0).max() { + write = uapi::fcntl_dupfd_cloexec(write.raw(), max_desired.wrapping_add(1)) + .map_os_err(SpawnError::Dupfd)?; + } + let fds = map_fds(fds)?; + for fd in fds { + let fd = fd.unwrap(); + let res: Result<_, Errno> = (|| { + uapi::fcntl_setfd(fd, uapi::fcntl_getfd(fd)? & !c::FD_CLOEXEC)?; + Ok(()) + })(); + res.map_os_err(SpawnError::Cloexec)?; + } + unsafe { + c::signal(c::SIGCHLD, c::SIG_DFL); + } + for (key, val) in env { + unsafe { + match val { + None => env::remove_var(&key), + Some(val) => env::set_var(&key, &val), + } + } + } + let prog = prog.into_ustr(); + let mut argsnt = UstrPtr::new(); + argsnt.push(&prog); + for arg in args { + argsnt.push(arg); + } + uapi::execvp(&prog, &argsnt).map_os_err(SpawnError::Exec)?; + Ok(()) + })(); + if let Err(e) = err { + let _ = write.write_all(ErrorFmt(e).to_string().as_bytes()); + } + std::process::exit(1); + } + } + } + + fn fail_pidfd(&self, pidfd_id: Option) { + if let Some(id) = pidfd_id { + self.outgoing.push(ForkerMessage::PidFd { + id, + success: false, + pid: 0, + }); + } + } +} + +#[derive(Debug, Error)] +enum SpawnError { + #[error("exec failed")] + Exec(#[source] crate::utils::oserror::OsError), + #[error("Could not unset cloexec flag")] + Cloexec(#[source] crate::utils::oserror::OsError), + #[error("dupfd faild")] + Dupfd(#[source] crate::utils::oserror::OsError), +} + +fn setup_fds(mut socket: OwnedFd) -> OwnedFd { + if socket.raw() != 0 { + uapi::dup3(socket.unwrap(), 0, 0).unwrap(); + socket = OwnedFd::new(0); + } + uapi::close_range(1, c::c_uint::MAX, 0).unwrap(); + uapi::dup3(socket.raw(), 3, c::O_CLOEXEC).unwrap(); + socket = OwnedFd::new(3); + let fd = uapi::open("/dev/null", c::O_RDWR, 0).unwrap().unwrap(); + assert!(fd == 0); + uapi::dup2(0, 1).unwrap(); + uapi::dup2(0, 2).unwrap(); + socket +} + +fn setup_deathsig(ppid: c::pid_t) { + unsafe { + let res = c::prctl(c::PR_SET_PDEATHSIG, c::SIGKILL as c::c_ulong); + uapi::map_err!(res).unwrap(); + if ppid != uapi::getppid() { + std::process::exit(0); + } + } +} + +fn map_fds(fds: Vec<(i32, OwnedFd)>) -> Result, SpawnError> { + let mut desired: Vec<_> = fds.iter().map(|v| v.0).collect(); + desired.sort_by(|a, b| b.cmp(a)); + let mut existing_to_desired: AHashMap<_, _> = fds.iter().map(|v| (v.1.raw(), v.0)).collect(); + let mut desired_to_existing: AHashMap<_, _> = fds.into_iter().map(|v| (v.0, v.1)).collect(); + for desired in desired { + let existing = desired_to_existing.get(&desired).unwrap().raw(); + if existing == desired { + continue; + } + if let Some(conflict_desired) = existing_to_desired.get(&desired).copied() { + let new = uapi::fcntl_dupfd_cloexec(desired, 0).map_os_err(SpawnError::Dupfd)?; + existing_to_desired.remove(&desired); + existing_to_desired.insert(new.raw(), conflict_desired); + desired_to_existing.insert(conflict_desired, new); + } + uapi::dup3(existing, desired, c::O_CLOEXEC).map_os_err(SpawnError::Dupfd)?; + existing_to_desired.remove(&existing); + existing_to_desired.insert(desired, desired); + desired_to_existing.insert(desired, OwnedFd::new(desired)); + } + Ok(desired_to_existing.into_values().collect()) +} diff --git a/src/format.rs b/src/format.rs index f5751a45..5f812a1c 100644 --- a/src/format.rs +++ b/src/format.rs @@ -1,583 +1,57 @@ +pub use jay_formats::*; + use { - crate::{ - gfx_apis::gl::sys::{GL_BGRA_EXT, GL_RGBA, GL_RGBA8, GL_UNSIGNED_BYTE, GLenum, GLint}, - pipewire::pw_pod::{ - SPA_VIDEO_FORMAT_ABGR_210LE, SPA_VIDEO_FORMAT_ARGB_210LE, SPA_VIDEO_FORMAT_BGR, - SPA_VIDEO_FORMAT_BGR15, SPA_VIDEO_FORMAT_BGR16, SPA_VIDEO_FORMAT_BGRA, - SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_GRAY8, SPA_VIDEO_FORMAT_RGB, - SPA_VIDEO_FORMAT_RGB16, SPA_VIDEO_FORMAT_RGBA, SPA_VIDEO_FORMAT_RGBx, - SPA_VIDEO_FORMAT_UNKNOWN, SPA_VIDEO_FORMAT_xBGR_210LE, SPA_VIDEO_FORMAT_xRGB_210LE, - SpaVideoFormat, - }, + crate::pipewire::pw_pod::{ + SPA_VIDEO_FORMAT_ABGR_210LE, SPA_VIDEO_FORMAT_ARGB_210LE, SPA_VIDEO_FORMAT_BGR, + SPA_VIDEO_FORMAT_BGR15, SPA_VIDEO_FORMAT_BGR16, SPA_VIDEO_FORMAT_BGRA, + SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_GRAY8, SPA_VIDEO_FORMAT_RGB, + SPA_VIDEO_FORMAT_RGB16, SPA_VIDEO_FORMAT_RGBA, SPA_VIDEO_FORMAT_RGBx, + SPA_VIDEO_FORMAT_UNKNOWN, SPA_VIDEO_FORMAT_xBGR_210LE, SPA_VIDEO_FORMAT_xRGB_210LE, + SpaVideoFormat, }, ahash::AHashMap, - ash::vk, - jay_config::video::Format as ConfigFormat, - std::{ - fmt::{self, Debug, Write}, - sync::LazyLock, - }, + std::sync::LazyLock, }; -#[derive(Copy, Clone, Debug)] -pub struct FormatShmInfo { - pub gl_format: GLint, - pub gl_internal_format: GLenum, - pub gl_type: GLint, -} - -#[derive(Copy, Clone, Debug)] -pub struct Format { - pub name: &'static str, - pub vk_format: vk::Format, - pub drm: u32, - pub wl_id: Option, - pub external_only_guess: bool, - pub has_alpha: bool, - pub pipewire: SpaVideoFormat, - pub opaque: Option<&'static Format>, - pub shm_info: Option, - pub config: ConfigFormat, - pub bpp: u32, -} - -const fn default(config: ConfigFormat) -> Format { - Format { - name: "", - vk_format: vk::Format::UNDEFINED, - drm: 0, - wl_id: None, - external_only_guess: false, - has_alpha: false, - pipewire: SPA_VIDEO_FORMAT_UNKNOWN, - opaque: None, - shm_info: None, - config, - bpp: 4, - } -} - -impl PartialEq for Format { - fn eq(&self, other: &Self) -> bool { - self.drm == other.drm - } -} - -impl Eq for Format {} - -static FORMATS_MAP: LazyLock> = LazyLock::new(|| { - let mut map = AHashMap::new(); - for format in FORMATS { - assert!(map.insert(format.drm, format).is_none()); - } - map -}); - static PW_FORMATS_MAP: LazyLock> = LazyLock::new(|| { let mut map = AHashMap::new(); for format in FORMATS { - if format.pipewire != SPA_VIDEO_FORMAT_UNKNOWN { - assert!(map.insert(format.pipewire, format).is_none()); + let pw = pw_format(format); + if pw != SPA_VIDEO_FORMAT_UNKNOWN { + assert!(map.insert(pw, format).is_none()); } } map }); -static FORMATS_REFS: LazyLock> = LazyLock::new(|| FORMATS.iter().collect()); - -static FORMATS_NAMES: LazyLock> = LazyLock::new(|| { - let mut map = AHashMap::new(); - for format in FORMATS { - assert!(map.insert(format.name, format).is_none()); - } - map -}); - -static FORMATS_CONFIG: LazyLock> = LazyLock::new(|| { - let mut map = AHashMap::new(); - for format in FORMATS { - assert!(map.insert(format.config, format).is_none()); - } - map -}); - -#[test] -fn formats_dont_panic() { - formats(); - pw_formats(); - named_formats(); - config_formats(); -} - -pub fn formats() -> &'static AHashMap { - &FORMATS_MAP -} - pub fn pw_formats() -> &'static AHashMap { &PW_FORMATS_MAP } -pub fn ref_formats() -> &'static [&'static Format] { - &FORMATS_REFS -} - -pub fn named_formats() -> &'static AHashMap<&'static str, &'static Format> { - &FORMATS_NAMES -} - -pub fn config_formats() -> &'static AHashMap { - &FORMATS_CONFIG +pub fn pipewire_format(format: &'static Format) -> SpaVideoFormat { + pw_format(format) } const fn fourcc_code(a: char, b: char, c: char, d: char) -> u32 { (a as u32) | ((b as u32) << 8) | ((c as u32) << 16) | ((d as u32) << 24) } -#[expect(dead_code)] -pub fn debug(fourcc: u32) -> impl Debug { - fmt::from_fn(move |fmt| { - fmt.write_char(fourcc as u8 as char)?; - fmt.write_char((fourcc >> 8) as u8 as char)?; - fmt.write_char((fourcc >> 16) as u8 as char)?; - fmt.write_char((fourcc >> 24) as u8 as char)?; - Ok(()) - }) -} - -const ARGB8888_ID: u32 = 0; -const ARGB8888_DRM: u32 = fourcc_code('A', 'R', '2', '4'); - -const XRGB8888_ID: u32 = 1; -const XRGB8888_DRM: u32 = fourcc_code('X', 'R', '2', '4'); - -pub fn map_wayland_format_id(id: u32) -> u32 { - match id { - ARGB8888_ID => ARGB8888_DRM, - XRGB8888_ID => XRGB8888_DRM, - _ => id, +fn pw_format(format: &Format) -> SpaVideoFormat { + match format.drm { + drm if drm == fourcc_code('A', 'R', '2', '4') => SPA_VIDEO_FORMAT_BGRA, + drm if drm == fourcc_code('X', 'R', '2', '4') => SPA_VIDEO_FORMAT_BGRx, + drm if drm == fourcc_code('A', 'B', '2', '4') => SPA_VIDEO_FORMAT_RGBA, + drm if drm == fourcc_code('X', 'B', '2', '4') => SPA_VIDEO_FORMAT_RGBx, + drm if drm == fourcc_code('R', '8', ' ', ' ') => SPA_VIDEO_FORMAT_GRAY8, + drm if drm == fourcc_code('R', 'G', '2', '4') => SPA_VIDEO_FORMAT_BGR, + drm if drm == fourcc_code('B', 'G', '2', '4') => SPA_VIDEO_FORMAT_RGB, + drm if drm == fourcc_code('R', 'G', '1', '6') => SPA_VIDEO_FORMAT_BGR16, + drm if drm == fourcc_code('B', 'G', '1', '6') => SPA_VIDEO_FORMAT_RGB16, + drm if drm == fourcc_code('X', 'R', '1', '5') => SPA_VIDEO_FORMAT_BGR15, + drm if drm == fourcc_code('A', 'R', '3', '0') => SPA_VIDEO_FORMAT_ARGB_210LE, + drm if drm == fourcc_code('X', 'R', '3', '0') => SPA_VIDEO_FORMAT_xRGB_210LE, + drm if drm == fourcc_code('A', 'B', '3', '0') => SPA_VIDEO_FORMAT_ABGR_210LE, + drm if drm == fourcc_code('X', 'B', '3', '0') => SPA_VIDEO_FORMAT_xBGR_210LE, + _ => SPA_VIDEO_FORMAT_UNKNOWN, } } - -pub static ARGB8888: &Format = &Format { - name: "argb8888", - shm_info: Some(FormatShmInfo { - gl_format: GL_BGRA_EXT, - gl_internal_format: GL_RGBA8, - gl_type: GL_UNSIGNED_BYTE, - }), - vk_format: vk::Format::B8G8R8A8_UNORM, - bpp: 4, - drm: ARGB8888_DRM, - wl_id: Some(ARGB8888_ID), - external_only_guess: false, - has_alpha: true, - pipewire: SPA_VIDEO_FORMAT_BGRA, - opaque: Some(XRGB8888), - config: ConfigFormat::ARGB8888, -}; - -pub static XRGB8888: &Format = &Format { - name: "xrgb8888", - shm_info: Some(FormatShmInfo { - gl_format: GL_BGRA_EXT, - gl_internal_format: GL_RGBA8, - gl_type: GL_UNSIGNED_BYTE, - }), - vk_format: vk::Format::B8G8R8A8_UNORM, - bpp: 4, - drm: XRGB8888_DRM, - wl_id: Some(XRGB8888_ID), - external_only_guess: false, - has_alpha: false, - pipewire: SPA_VIDEO_FORMAT_BGRx, - opaque: None, - config: ConfigFormat::XRGB8888, -}; - -static ABGR8888: &Format = &Format { - name: "abgr8888", - shm_info: Some(FormatShmInfo { - gl_format: GL_RGBA, - gl_internal_format: GL_RGBA8, - gl_type: GL_UNSIGNED_BYTE, - }), - vk_format: vk::Format::R8G8B8A8_UNORM, - bpp: 4, - drm: fourcc_code('A', 'B', '2', '4'), - wl_id: None, - external_only_guess: false, - has_alpha: true, - pipewire: SPA_VIDEO_FORMAT_RGBA, - opaque: Some(XBGR8888), - config: ConfigFormat::ABGR8888, -}; - -static XBGR8888: &Format = &Format { - name: "xbgr8888", - shm_info: Some(FormatShmInfo { - gl_format: GL_RGBA, - gl_internal_format: GL_RGBA8, - gl_type: GL_UNSIGNED_BYTE, - }), - vk_format: vk::Format::R8G8B8A8_UNORM, - bpp: 4, - drm: fourcc_code('X', 'B', '2', '4'), - wl_id: None, - external_only_guess: false, - has_alpha: false, - pipewire: SPA_VIDEO_FORMAT_RGBx, - opaque: None, - config: ConfigFormat::XBGR8888, -}; - -static R8: &Format = &Format { - name: "r8", - vk_format: vk::Format::R8_UNORM, - bpp: 1, - drm: fourcc_code('R', '8', ' ', ' '), - pipewire: SPA_VIDEO_FORMAT_GRAY8, - ..default(ConfigFormat::R8) -}; - -static GR88: &Format = &Format { - name: "gr88", - vk_format: vk::Format::R8G8_UNORM, - bpp: 2, - drm: fourcc_code('G', 'R', '8', '8'), - ..default(ConfigFormat::GR88) -}; - -static RGB888: &Format = &Format { - name: "rgb888", - vk_format: vk::Format::B8G8R8_UNORM, - bpp: 3, - drm: fourcc_code('R', 'G', '2', '4'), - pipewire: SPA_VIDEO_FORMAT_BGR, - ..default(ConfigFormat::RGB888) -}; - -static BGR888: &Format = &Format { - name: "bgr888", - vk_format: vk::Format::R8G8B8_UNORM, - bpp: 3, - drm: fourcc_code('B', 'G', '2', '4'), - pipewire: SPA_VIDEO_FORMAT_RGB, - ..default(ConfigFormat::BGR888) -}; - -static RGBA4444: &Format = &Format { - name: "rgba4444", - vk_format: vk::Format::R4G4B4A4_UNORM_PACK16, - bpp: 2, - drm: fourcc_code('R', 'A', '1', '2'), - has_alpha: true, - opaque: Some(RGBX4444), - ..default(ConfigFormat::RGBA4444) -}; - -static RGBX4444: &Format = &Format { - name: "rgbx4444", - vk_format: vk::Format::R4G4B4A4_UNORM_PACK16, - bpp: 2, - drm: fourcc_code('R', 'X', '1', '2'), - ..default(ConfigFormat::RGBX4444) -}; - -static BGRA4444: &Format = &Format { - name: "bgra4444", - vk_format: vk::Format::B4G4R4A4_UNORM_PACK16, - bpp: 2, - drm: fourcc_code('B', 'A', '1', '2'), - has_alpha: true, - opaque: Some(BGRX4444), - ..default(ConfigFormat::BGRA4444) -}; - -static BGRX4444: &Format = &Format { - name: "bgrx4444", - vk_format: vk::Format::B4G4R4A4_UNORM_PACK16, - bpp: 2, - drm: fourcc_code('B', 'X', '1', '2'), - ..default(ConfigFormat::BGRX4444) -}; - -static RGB565: &Format = &Format { - name: "rgb565", - vk_format: vk::Format::R5G6B5_UNORM_PACK16, - bpp: 2, - drm: fourcc_code('R', 'G', '1', '6'), - pipewire: SPA_VIDEO_FORMAT_BGR16, - ..default(ConfigFormat::RGB565) -}; - -static BGR565: &Format = &Format { - name: "bgr565", - vk_format: vk::Format::B5G6R5_UNORM_PACK16, - bpp: 2, - drm: fourcc_code('B', 'G', '1', '6'), - pipewire: SPA_VIDEO_FORMAT_RGB16, - ..default(ConfigFormat::BGR565) -}; - -static RGBA5551: &Format = &Format { - name: "rgba5551", - vk_format: vk::Format::R5G5B5A1_UNORM_PACK16, - bpp: 2, - drm: fourcc_code('R', 'A', '1', '5'), - has_alpha: true, - opaque: Some(RGBX5551), - ..default(ConfigFormat::RGBA5551) -}; - -static RGBX5551: &Format = &Format { - name: "rgbx5551", - vk_format: vk::Format::R5G5B5A1_UNORM_PACK16, - bpp: 2, - drm: fourcc_code('R', 'X', '1', '5'), - ..default(ConfigFormat::RGBX5551) -}; - -static BGRA5551: &Format = &Format { - name: "bgra5551", - vk_format: vk::Format::B5G5R5A1_UNORM_PACK16, - bpp: 2, - drm: fourcc_code('B', 'A', '1', '5'), - has_alpha: true, - opaque: Some(BGRX5551), - ..default(ConfigFormat::BGRA5551) -}; - -static BGRX5551: &Format = &Format { - name: "bgrx5551", - vk_format: vk::Format::B5G5R5A1_UNORM_PACK16, - bpp: 2, - drm: fourcc_code('B', 'X', '1', '5'), - ..default(ConfigFormat::BGRX5551) -}; - -static ARGB1555: &Format = &Format { - name: "argb1555", - vk_format: vk::Format::A1R5G5B5_UNORM_PACK16, - bpp: 2, - drm: fourcc_code('A', 'R', '1', '5'), - has_alpha: true, - opaque: Some(XRGB1555), - ..default(ConfigFormat::ARGB1555) -}; - -static XRGB1555: &Format = &Format { - name: "xrgb1555", - vk_format: vk::Format::A1R5G5B5_UNORM_PACK16, - bpp: 2, - drm: fourcc_code('X', 'R', '1', '5'), - pipewire: SPA_VIDEO_FORMAT_BGR15, - ..default(ConfigFormat::XRGB1555) -}; - -static ARGB2101010: &Format = &Format { - name: "argb2101010", - vk_format: vk::Format::A2R10G10B10_UNORM_PACK32, - bpp: 4, - drm: fourcc_code('A', 'R', '3', '0'), - has_alpha: true, - opaque: Some(XRGB2101010), - pipewire: SPA_VIDEO_FORMAT_ARGB_210LE, - ..default(ConfigFormat::ARGB2101010) -}; - -static XRGB2101010: &Format = &Format { - name: "xrgb2101010", - vk_format: vk::Format::A2R10G10B10_UNORM_PACK32, - bpp: 4, - drm: fourcc_code('X', 'R', '3', '0'), - pipewire: SPA_VIDEO_FORMAT_xRGB_210LE, - ..default(ConfigFormat::XRGB2101010) -}; - -static ABGR2101010: &Format = &Format { - name: "abgr2101010", - vk_format: vk::Format::A2B10G10R10_UNORM_PACK32, - bpp: 4, - drm: fourcc_code('A', 'B', '3', '0'), - has_alpha: true, - opaque: Some(XBGR2101010), - pipewire: SPA_VIDEO_FORMAT_ABGR_210LE, - ..default(ConfigFormat::ABGR2101010) -}; - -static XBGR2101010: &Format = &Format { - name: "xbgr2101010", - vk_format: vk::Format::A2B10G10R10_UNORM_PACK32, - bpp: 4, - drm: fourcc_code('X', 'B', '3', '0'), - pipewire: SPA_VIDEO_FORMAT_xBGR_210LE, - ..default(ConfigFormat::XBGR2101010) -}; - -static ABGR16161616: &Format = &Format { - name: "abgr16161616", - vk_format: vk::Format::R16G16B16A16_UNORM, - bpp: 8, - drm: fourcc_code('A', 'B', '4', '8'), - has_alpha: true, - opaque: Some(XBGR16161616), - ..default(ConfigFormat::ABGR16161616) -}; - -static XBGR16161616: &Format = &Format { - name: "xbgr16161616", - vk_format: vk::Format::R16G16B16A16_UNORM, - bpp: 8, - drm: fourcc_code('X', 'B', '4', '8'), - ..default(ConfigFormat::XBGR16161616) -}; - -pub static ABGR16161616F: &Format = &Format { - name: "abgr16161616f", - vk_format: vk::Format::R16G16B16A16_SFLOAT, - bpp: 8, - drm: fourcc_code('A', 'B', '4', 'H'), - has_alpha: true, - opaque: Some(XBGR16161616F), - ..default(ConfigFormat::ABGR16161616F) -}; - -static XBGR16161616F: &Format = &Format { - name: "xbgr16161616f", - vk_format: vk::Format::R16G16B16A16_SFLOAT, - bpp: 8, - drm: fourcc_code('X', 'B', '4', 'H'), - ..default(ConfigFormat::XBGR16161616F) -}; - -static BGR161616: &Format = &Format { - name: "bgr161616", - vk_format: vk::Format::R16G16B16_UNORM, - bpp: 6, - drm: fourcc_code('B', 'G', '4', '8'), - ..default(ConfigFormat::BGR161616) -}; - -static R16F: &Format = &Format { - name: "r16f", - vk_format: vk::Format::R16_SFLOAT, - bpp: 2, - drm: fourcc_code('R', ' ', ' ', 'H'), - ..default(ConfigFormat::R16F) -}; - -static GR1616F: &Format = &Format { - name: "gr1616f", - vk_format: vk::Format::R16G16_SFLOAT, - bpp: 4, - drm: fourcc_code('G', 'R', ' ', 'H'), - ..default(ConfigFormat::GR1616F) -}; - -static BGR161616F: &Format = &Format { - name: "bgr161616f", - vk_format: vk::Format::R16G16B16_SFLOAT, - bpp: 6, - drm: fourcc_code('B', 'G', 'R', 'H'), - ..default(ConfigFormat::BGR161616F) -}; - -static R32F: &Format = &Format { - name: "r32f", - vk_format: vk::Format::R32_SFLOAT, - bpp: 4, - drm: fourcc_code('R', ' ', ' ', 'F'), - ..default(ConfigFormat::R32F) -}; - -static GR3232F: &Format = &Format { - name: "gr3232f", - vk_format: vk::Format::R32G32_SFLOAT, - bpp: 8, - drm: fourcc_code('G', 'R', ' ', 'F'), - ..default(ConfigFormat::GR3232F) -}; - -static BGR323232F: &Format = &Format { - name: "bgr323232f", - vk_format: vk::Format::R32G32B32_SFLOAT, - bpp: 12, - drm: fourcc_code('B', 'G', 'R', 'F'), - ..default(ConfigFormat::BGR323232F) -}; - -static ABGR32323232F: &Format = &Format { - name: "abgr32323232f", - vk_format: vk::Format::R32G32B32A32_SFLOAT, - bpp: 16, - drm: fourcc_code('A', 'B', '8', 'F'), - has_alpha: true, - ..default(ConfigFormat::ABGR32323232F) -}; - -pub static FORMATS: &[Format] = &[ - *ARGB8888, - *XRGB8888, - *ABGR8888, - *XBGR8888, - *R8, - *GR88, - *RGB888, - *BGR888, - #[cfg(target_endian = "little")] - *RGBA4444, - #[cfg(target_endian = "little")] - *RGBX4444, - #[cfg(target_endian = "little")] - *BGRA4444, - #[cfg(target_endian = "little")] - *BGRX4444, - #[cfg(target_endian = "little")] - *RGB565, - #[cfg(target_endian = "little")] - *BGR565, - #[cfg(target_endian = "little")] - *RGBA5551, - #[cfg(target_endian = "little")] - *RGBX5551, - #[cfg(target_endian = "little")] - *BGRA5551, - #[cfg(target_endian = "little")] - *BGRX5551, - #[cfg(target_endian = "little")] - *ARGB1555, - #[cfg(target_endian = "little")] - *XRGB1555, - #[cfg(target_endian = "little")] - *ARGB2101010, - #[cfg(target_endian = "little")] - *XRGB2101010, - #[cfg(target_endian = "little")] - *ABGR2101010, - #[cfg(target_endian = "little")] - *XBGR2101010, - #[cfg(target_endian = "little")] - *ABGR16161616, - #[cfg(target_endian = "little")] - *XBGR16161616, - #[cfg(target_endian = "little")] - *ABGR16161616F, - #[cfg(target_endian = "little")] - *XBGR16161616F, - #[cfg(target_endian = "little")] - *BGR161616, - #[cfg(target_endian = "little")] - *R16F, - #[cfg(target_endian = "little")] - *GR1616F, - #[cfg(target_endian = "little")] - *BGR161616F, - #[cfg(target_endian = "little")] - *R32F, - #[cfg(target_endian = "little")] - *GR3232F, - #[cfg(target_endian = "little")] - *BGR323232F, - #[cfg(target_endian = "little")] - *ABGR32323232F, -]; diff --git a/src/gfx_api.rs b/src/gfx_api.rs index d4c0f19b..a7898377 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -1,3 +1,10 @@ +use jay_theme::Color; +use jay_io_uring::{IoUring, IoUringError}; +use jay_geometry::{Rect, Region}; +use jay_eventfd_cache::Eventfd; +use jay_cpu_worker::CpuWorker; +use jay_units::fixed::Fixed; +use jay_units::scale::Scale; use { crate::{ allocator::Allocator, @@ -5,18 +12,16 @@ use { cmm_description::{ColorDescription, LinearColorDescription}, cmm_render_intent::RenderIntent, }, - cpu_worker::CpuWorker, + cursor::Cursor, damage::DamageVisualizer, - eventfd_cache::Eventfd, - fixed::Fixed, + format::Format, - io_uring::{IoUring, IoUringError}, - rect::{Rect, Region}, + + renderer::{Renderer, renderer_base::RendererBase}, - scale::Scale, state::State, - theme::Color, + tree::{Node, OutputNode, Transform}, utils::{ clonecell::UnsafeCellCloneSafe, errorfmt::ErrorFmt, oserror::OsErrorExt, @@ -47,6 +52,8 @@ use { uapi::{OwnedFd, c}, }; +pub use jay_gfx_types::{AlphaMode, ShmMemory, ShmMemoryBacking}; + #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Linearize)] pub enum GfxApi { OpenGl, @@ -409,14 +416,6 @@ pub enum ResetStatus { Other(u32), } -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)] -pub enum AlphaMode { - #[default] - PremultipliedElectrical, - PremultipliedOptical, - Straight, -} - pub trait GfxBlendBuffer: Any + Debug {} pub trait GfxFramebuffer: Debug { @@ -804,32 +803,6 @@ pub struct PendingShmTransfer { id: u64, } -pub trait ShmMemory { - fn len(&self) -> usize; - fn safe_access(&self) -> ShmMemoryBacking; - fn access(&self, f: &mut dyn FnMut(&[Cell])) -> Result<(), Box>; -} - -pub enum ShmMemoryBacking { - Ptr(*const [Cell]), - Fd(Rc, usize), -} - -impl ShmMemory for Vec> { - fn len(&self) -> usize { - self.len() - } - - fn safe_access(&self) -> ShmMemoryBacking { - ShmMemoryBacking::Ptr(&**self) - } - - fn access(&self, f: &mut dyn FnMut(&[Cell])) -> Result<(), Box> { - f(self); - Ok(()) - } -} - pub trait AsyncShmGfxTexture: GfxTexture { fn staging_size(&self) -> usize { 0 @@ -989,32 +962,22 @@ pub struct GfxFormat { pub supports_shm: bool, } -#[derive(Error)] -#[error(transparent)] -pub struct GfxError(pub Box); - -impl Debug for GfxError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - Debug::fmt(&self.0, f) - } -} - impl GfxFormat { pub fn cross_intersect(&self, other: &GfxFormat) -> GfxFormat { assert_eq!(self.format, other.format); + let other_write_modifiers: IndexSet<_> = other.write_modifiers.keys().copied().collect(); GfxFormat { format: self.format, read_modifiers: self .read_modifiers - .iter() + .intersection(&other_write_modifiers) .copied() - .filter(|m| other.write_modifiers.contains_key(m)) .collect(), write_modifiers: self .write_modifiers .iter() - .map(|(m, v)| (*m, v.clone())) - .filter(|(m, _)| other.read_modifiers.contains(m)) + .filter(|(m, _)| other.read_modifiers.contains(*m)) + .map(|(&k, v)| (k, v.clone())) .collect(), supports_shm: self.supports_shm && other.supports_shm, } @@ -1029,15 +992,24 @@ pub fn cross_intersect_formats( for lf in local.values() { if let Some(rf) = remote.get(&lf.format.drm) { let f = lf.cross_intersect(rf); - if f.read_modifiers.is_empty() && f.write_modifiers.is_empty() { - continue; + if !f.read_modifiers.is_empty() || !f.write_modifiers.is_empty() || f.supports_shm { + res.insert(f.format.drm, f); } - res.insert(f.format.drm, f); } } res } +#[derive(Error)] +#[error(transparent)] +pub struct GfxError(pub Box); + +impl Debug for GfxError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Debug::fmt(&self.0, f) + } +} + impl PendingShmTransfer { pub fn new(cancel: Rc, id: u64) -> Self { Self { cancel, id } diff --git a/src/gfx_apis.rs b/src/gfx_apis.rs index bb9b48a9..d03c46f2 100644 --- a/src/gfx_apis.rs +++ b/src/gfx_apis.rs @@ -1,11 +1,15 @@ pub use vulkan::create_vulkan_allocator; +use jay_pr_caps::PrCapsThread; +use jay_io_uring::IoUring; +use jay_eventfd_cache::EventfdCache; +use jay_async_engine::AsyncEngine; use { crate::{ - async_engine::AsyncEngine, - eventfd_cache::EventfdCache, + + gfx_api::{GfxApi, GfxContext, GfxError}, - io_uring::IoUring, - pr_caps::PrCapsThread, + + utils::errorfmt::ErrorFmt, video::drm::Drm, }, diff --git a/src/gfx_apis/gl.rs b/src/gfx_apis/gl.rs index dc43a79c..ee8c4cee 100644 --- a/src/gfx_apis/gl.rs +++ b/src/gfx_apis/gl.rs @@ -18,6 +18,7 @@ macro_rules! egl_transparent { }; } +use jay_theme::Color; use { crate::{ cmm::cmm_eotf::Eotf, @@ -38,7 +39,7 @@ use { GL_TRIANGLE_STRIP, GL_TRIANGLES, }, }, - theme::Color, + utils::{errorfmt::ErrorFmt, rc_eq::rc_eq, vecstorage::VecStorage}, video::{ dmabuf::DMA_BUF_SYNC_READ, diff --git a/src/gfx_apis/gl/gl/sys.rs b/src/gfx_apis/gl/gl/sys.rs index 5d79cba8..aae348df 100644 --- a/src/gfx_apis/gl/gl/sys.rs +++ b/src/gfx_apis/gl/gl/sys.rs @@ -13,9 +13,6 @@ pub type GLuint = c::c_uint; egl_transparent!(GLeglImageOES); -pub const GL_RGBA: GLint = 0x1908; -pub const GL_RGBA8: GLenum = 0x8058; -pub const GL_BGRA_EXT: GLint = 0x80E1; pub const GL_CLAMP_TO_EDGE: GLint = 0x812F; pub const GL_COLOR_ATTACHMENT0: GLenum = 0x8CE0; pub const GL_COLOR_BUFFER_BIT: GLbitfield = 0x00004000; @@ -40,7 +37,6 @@ pub const GL_TEXTURE_WRAP_T: GLenum = 0x2803; pub const GL_TRIANGLE_STRIP: GLenum = 0x0005; pub const GL_TRIANGLES: GLenum = 0x0004; pub const GL_UNPACK_ROW_LENGTH_EXT: GLenum = 0x0CF2; -pub const GL_UNSIGNED_BYTE: GLint = 0x1401; pub const GL_VERTEX_SHADER: GLenum = 0x8B31; pub const GL_BLEND: GLenum = 0x0BE2; pub const GL_ONE: GLenum = 1; diff --git a/src/gfx_apis/gl/renderer/context.rs b/src/gfx_apis/gl/renderer/context.rs index 00bfe9c2..6aaf9279 100644 --- a/src/gfx_apis/gl/renderer/context.rs +++ b/src/gfx_apis/gl/renderer/context.rs @@ -1,7 +1,9 @@ +use jay_geometry::Rect; +use jay_cpu_worker::CpuWorker; use { crate::{ allocator::Allocator, - cpu_worker::CpuWorker, + format::{Format, XRGB8888}, gfx_api::{ AsyncShmGfxTexture, BufferResvUser, GfxApi, GfxBlendBuffer, GfxContext, GfxError, @@ -17,7 +19,7 @@ use { }, renderer::{framebuffer::Framebuffer, image::Image}, }, - rect::Rect, + video::{ dmabuf::DmaBuf, drm::{Drm, syncobj::SyncobjCtx}, diff --git a/src/gfx_apis/gl/renderer/framebuffer.rs b/src/gfx_apis/gl/renderer/framebuffer.rs index 57d12729..c8b9c4fc 100644 --- a/src/gfx_apis/gl/renderer/framebuffer.rs +++ b/src/gfx_apis/gl/renderer/framebuffer.rs @@ -1,3 +1,5 @@ +use jay_theme::Color; +use jay_geometry::Region; use { crate::{ cmm::{ @@ -21,8 +23,8 @@ use { run_ops, sys::{GL_ONE, GL_ONE_MINUS_SRC_ALPHA}, }, - rect::Region, - theme::Color, + + }, std::{ cell::Cell, diff --git a/src/gfx_apis/gl/renderer/texture.rs b/src/gfx_apis/gl/renderer/texture.rs index b1170329..1f34b85a 100644 --- a/src/gfx_apis/gl/renderer/texture.rs +++ b/src/gfx_apis/gl/renderer/texture.rs @@ -1,3 +1,4 @@ +use jay_geometry::Region; use { crate::{ format::Format, @@ -14,7 +15,7 @@ use { GL_UNPACK_ROW_LENGTH_EXT, GLint, }, }, - rect::Region, + video::dmabuf::DmaBuf, }, std::{ diff --git a/src/gfx_apis/vulkan.rs b/src/gfx_apis/vulkan.rs index 47e1af43..b03c507b 100644 --- a/src/gfx_apis/vulkan.rs +++ b/src/gfx_apis/vulkan.rs @@ -21,12 +21,18 @@ mod shm_image; mod staging; mod transfer; +use jay_pr_caps::PrCapsThread; +use jay_io_uring::IoUring; +use jay_geometry::Rect; +use jay_eventfd_cache::EventfdCache; +use jay_cpu_worker::{CpuWorker, jobs::read_write::ReadWriteJobError}; +use jay_async_engine::AsyncEngine; use { crate::{ allocator::{Allocator, AllocatorError}, - async_engine::AsyncEngine, - cpu_worker::{CpuWorker, jobs::read_write::ReadWriteJobError}, - eventfd_cache::EventfdCache, + + + format::Format, gfx_api::{ AsyncShmGfxTexture, GfxApi, GfxBlendBuffer, GfxBuffer, GfxContext, GfxError, GfxFormat, @@ -37,9 +43,9 @@ use { device::VulkanDevice, image::VulkanImageMemory, instance::VulkanInstance, renderer::VulkanRenderer, }, - io_uring::IoUring, - pr_caps::PrCapsThread, - rect::Rect, + + + utils::{errorfmt::ErrorFmt, oserror::OsError}, video::{ dmabuf::DmaBuf, diff --git a/src/gfx_apis/vulkan/allocator.rs b/src/gfx_apis/vulkan/allocator.rs index 0cdffd0c..9352160a 100644 --- a/src/gfx_apis/vulkan/allocator.rs +++ b/src/gfx_apis/vulkan/allocator.rs @@ -1,6 +1,7 @@ +use jay_cpu_worker::{AsyncCpuWork, CpuJob, CpuWork, CpuWorker}; use { crate::{ - cpu_worker::{AsyncCpuWork, CpuJob, CpuWork, CpuWorker}, + gfx_apis::vulkan::{VulkanError, device::VulkanDevice, renderer::VulkanRenderer}, utils::{numcell::NumCell, page_size::page_size, ptr_ext::MutPtrExt}, vulkan_core::gpu_alloc_ash::{self, AshMemoryDevice}, diff --git a/src/gfx_apis/vulkan/alpha_modes.rs b/src/gfx_apis/vulkan/alpha_modes.rs index 9480c2b0..98c458f3 100644 --- a/src/gfx_apis/vulkan/alpha_modes.rs +++ b/src/gfx_apis/vulkan/alpha_modes.rs @@ -4,8 +4,12 @@ pub const AM_PREMULTIPLIED_ELECTRICAL: u32 = 0; pub const AM_PREMULTIPLIED_OPTICAL: u32 = 1; pub const AM_STRAIGHT: u32 = 2; -impl AlphaMode { - pub fn to_vulkan(self) -> u32 { +pub trait AlphaModeExt { + fn to_vulkan(self) -> u32; +} + +impl AlphaModeExt for AlphaMode { + fn to_vulkan(self) -> u32 { match self { AlphaMode::PremultipliedElectrical => AM_PREMULTIPLIED_ELECTRICAL, AlphaMode::PremultipliedOptical => AM_PREMULTIPLIED_OPTICAL, diff --git a/src/gfx_apis/vulkan/bo_allocator.rs b/src/gfx_apis/vulkan/bo_allocator.rs index 6a7ffcfa..a619534f 100644 --- a/src/gfx_apis/vulkan/bo_allocator.rs +++ b/src/gfx_apis/vulkan/bo_allocator.rs @@ -1,8 +1,8 @@ use { crate::{ allocator::{ - Allocator, AllocatorError, BO_USE_RENDERING, BO_USE_WRITE, BufferObject, BufferUsage, - MappedBuffer, + Allocator, AllocatorDrm, AllocatorError, BO_USE_RENDERING, BO_USE_WRITE, BufferObject, + BufferUsage, MappedBuffer, }, format::Format, gfx_apis::vulkan::{ @@ -429,7 +429,7 @@ impl VulkanBoAllocator { } impl Allocator for VulkanBoAllocator { - fn drm(&self) -> Option<&Drm> { + fn drm(&self) -> Option<&dyn AllocatorDrm> { Some(&self.data.drm) } diff --git a/src/gfx_apis/vulkan/device.rs b/src/gfx_apis/vulkan/device.rs index b6b9c412..82cd6238 100644 --- a/src/gfx_apis/vulkan/device.rs +++ b/src/gfx_apis/vulkan/device.rs @@ -1,7 +1,8 @@ +use jay_eventfd_cache::EventfdCache; use { crate::{ allocator::BufferObject, - eventfd_cache::EventfdCache, + format::XRGB8888, gfx_apis::vulkan::{ VulkanError, diff --git a/src/gfx_apis/vulkan/image.rs b/src/gfx_apis/vulkan/image.rs index d023838b..8d46da76 100644 --- a/src/gfx_apis/vulkan/image.rs +++ b/src/gfx_apis/vulkan/image.rs @@ -1,3 +1,5 @@ +use jay_theme::Color; +use jay_geometry::Region; use { crate::{ cmm::cmm_description::{ColorDescription, LinearColorDescription}, @@ -13,8 +15,8 @@ use { format::VulkanModifierLimits, renderer::VulkanRenderer, shm_image::VulkanShmImage, transfer::TransferType, }, - rect::Region, - theme::Color, + + utils::oserror::OsErrorExt2, video::dmabuf::{DmaBuf, PlaneVec}, }, diff --git a/src/gfx_apis/vulkan/pipeline.rs b/src/gfx_apis/vulkan/pipeline.rs index 5351a483..6d670e26 100644 --- a/src/gfx_apis/vulkan/pipeline.rs +++ b/src/gfx_apis/vulkan/pipeline.rs @@ -2,8 +2,8 @@ use { crate::{ gfx_api::AlphaMode, gfx_apis::vulkan::{ - VulkanError, descriptor::VulkanDescriptorSetLayout, device::VulkanDevice, - shaders::VulkanShader, + VulkanError, alpha_modes::AlphaModeExt, descriptor::VulkanDescriptorSetLayout, + device::VulkanDevice, shaders::VulkanShader, }, }, arrayvec::ArrayVec, diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index d0a48d26..b67e72a3 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -1,16 +1,32 @@ +mod color; +mod op; +mod pipeline_cache; +mod pipelines; +mod paint_region; + +use jay_theme::Color; +use jay_io_uring::IoUring; +use jay_geometry::{Rect, Region}; +use jay_cpu_worker::PendingJob; +use jay_async_engine::{AsyncEngine, SpawnedFuture}; use { + color::{ColorTransforms, EotfArgsCache}, + op::{ + TexCopyType, TexSourceType, VulkanFillOp, VulkanOp, VulkanRoundedFillOp, + VulkanRoundedTexOp, VulkanTexOp, + }, + paint_region::{PaintRegion, Point, constrain_to_fb}, + pipeline_cache::{FillPipelines, OutPipelineKey, TexPipelines}, crate::{ - async_engine::{AsyncEngine, SpawnedFuture}, + cmm::{ - cmm_description::{ColorDescription, LinearColorDescription, LinearColorDescriptionId}, - cmm_eotf::{Eotf, EotfPow, bt1886_eotf_args, bt1886_inv_eotf_args}, + cmm_description::{ColorDescription, LinearColorDescription}, cmm_render_intent::RenderIntent, - cmm_transform::ColorMatrix, }, - cpu_worker::PendingJob, + gfx_api::{ - AcquireSync, AlphaMode, BufferResv, BufferResvUser, FdSync, GfxApiOpt, GfxBlendBuffer, - GfxFormat, GfxTexture, GfxWriteModifier, ReleaseSync, + AcquireSync, BufferResv, BufferResvUser, FdSync, GfxApiOpt, GfxBlendBuffer, GfxFormat, + GfxTexture, GfxWriteModifier, ReleaseSync, }, gfx_apis::vulkan::{ VulkanError, VulkanSync, VulkanTimelineSemaphore, @@ -20,9 +36,9 @@ use { descriptor::VulkanDescriptorSetLayout, descriptor_buffer::VulkanDescriptorBufferWriter, device::VulkanDevice, - eotfs::{EOTF_LINEAR, EotfExt, VulkanEotf}, + eotfs::VulkanEotf, image::{QueueFamily, QueueState, QueueTransfer, VulkanImage, VulkanImageMemory}, - pipeline::{PipelineCreateInfo, VulkanPipeline}, + pipeline::VulkanPipeline, sampler::VulkanSampler, semaphore::VulkanSemaphore, shaders::{ @@ -37,12 +53,12 @@ use { VulkanShader, }, }, - io_uring::IoUring, - rect::{Rect, Region}, - theme::Color, + + + utils::{ - copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, ordered_float::F32, - oserror::OsErrorExt2, stack::Stack, + copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, oserror::OsErrorExt2, + stack::Stack, }, video::dmabuf::{DMA_BUF_SYNC_READ, DMA_BUF_SYNC_WRITE, dma_buf_export_sync_file}, vulkan_core::{ @@ -68,17 +84,13 @@ use { }, }, isnt::std_1::{collections::IsntHashMapExt, primitive::IsntSliceExt}, - jay_algorithms::rect::Tag, - linearize::{Linearize, LinearizeExt, StaticMap, static_map}, + linearize::{Linearize, LinearizeExt, StaticMap}, std::{ any::Any, borrow::Cow, cell::{Cell, LazyCell, RefCell}, - collections::hash_map::Entry, fmt::{Debug, Formatter}, - mem, - ops::Range, - ptr, + mem, ptr, rc::{Rc, Weak}, slice, }, @@ -159,18 +171,6 @@ pub(super) struct UsedTexture { release_sync: ReleaseSync, } -#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Linearize)] -pub(super) enum TexCopyType { - Identity, - Multiply, -} - -#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Linearize)] -pub(super) enum TexSourceType { - Opaque, - HasAlpha, -} - #[derive(Default)] pub(super) struct Memory { dmabuf_sample: Vec>, @@ -209,90 +209,12 @@ pub(super) struct Memory { fb_inv_eotf_args_address: Option, } -type Point = [[f32; 2]; 4]; - -enum VulkanOp { - Fill(VulkanFillOp), - Tex(VulkanTexOp), - RoundedFill(VulkanRoundedFillOp), - RoundedTex(VulkanRoundedTexOp), -} - -struct VulkanTexOp { - tex: Rc, - index: usize, - range: Range, - buffer_resv: Option>, - acquire_sync: Option, - release_sync: ReleaseSync, - alpha: f32, - source_type: TexSourceType, - copy_type: TexCopyType, - alpha_mode: AlphaMode, - range_address: DeviceAddress, - instances: u32, - tex_cd: Rc, - color_management_data_address: Option, - eotf_args_address: Option, - resource_descriptor_buffer_offset: DeviceAddress, -} - -struct VulkanFillOp { - range: Range, - color: [f32; 4], - source_type: TexSourceType, - range_address: DeviceAddress, - instances: u32, -} - -struct VulkanRoundedFillOp { - target: Point, - color: [f32; 4], - source_type: TexSourceType, - size: [f32; 2], - corner_radius: [f32; 4], - border_width: f32, - scale: f32, - range_address: DeviceAddress, - z_order: u32, -} - -struct VulkanRoundedTexOp { - tex: Rc, - index: usize, - target: Point, - source: Point, - buffer_resv: Option>, - acquire_sync: Option, - release_sync: ReleaseSync, - alpha: f32, - source_type: TexSourceType, - copy_type: TexCopyType, - alpha_mode: AlphaMode, - tex_cd: Rc, - color_management_data_address: Option, - eotf_args_address: Option, - resource_descriptor_buffer_offset: DeviceAddress, - size: [f32; 2], - corner_radius: [f32; 4], - scale: f32, - range_address: DeviceAddress, -} - #[derive(Copy, Clone, Debug, Linearize, Eq, PartialEq)] pub(super) enum RenderPass { BlendBuffer, FrameBuffer, } -#[derive(Copy, Clone)] -struct PaintRegion { - x1: f32, - y1: f32, - x2: f32, - y2: f32, -} - pub(super) struct PendingFrame { point: u64, renderer: Rc, @@ -306,30 +228,6 @@ pub(super) struct PendingFrame { _used_buffers: ArrayVec, } -type FillPipelines = Rc>>; - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -struct TexPipelineKey { - tex_copy_type: TexCopyType, - tex_source_type: TexSourceType, - tex_alpha_mode: AlphaMode, - eotf: VulkanEotf, - has_color_management_data: bool, -} - -pub(super) struct TexPipelines { - format: vk::Format, - eotf: VulkanEotf, - pipelines: CopyHashMap>, -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub(super) struct OutPipelineKey { - format: vk::Format, - eotf: VulkanEotf, - has_color_management_data: bool, -} - impl VulkanDevice { pub fn create_renderer( self: &Rc, @@ -486,261 +384,6 @@ impl VulkanDevice { } impl VulkanRenderer { - fn get_or_create_fill_pipelines( - &self, - format: vk::Format, - ) -> Result { - if let Some(pl) = self.fill_pipelines.get(&format) { - return Ok(pl); - } - let create_fill_pipeline = |src_has_alpha| { - let push_size = if self.device.descriptor_buffer.is_some() { - size_of::() - } else { - size_of::() - }; - let info = PipelineCreateInfo { - format, - vert: self.fill_vert_shader.clone(), - frag: self.fill_frag_shader.clone(), - blend: src_has_alpha, - src_has_alpha, - has_alpha_mult: false, - alpha_mode: AlphaMode::PremultipliedOptical, - // all transformations are applied in the compositor - eotf: EOTF_LINEAR, - inv_eotf: EOTF_LINEAR, - descriptor_set_layouts: Default::default(), - has_color_management_data: false, - }; - self.device.create_pipeline2(info, push_size) - }; - let fill_pipelines = Rc::new(static_map! { - TexSourceType::HasAlpha => create_fill_pipeline(true)?, - TexSourceType::Opaque => create_fill_pipeline(false)?, - }); - self.fill_pipelines.set(format, fill_pipelines.clone()); - Ok(fill_pipelines) - } - - fn get_or_create_rounded_fill_pipelines( - &self, - format: vk::Format, - ) -> Result { - if let Some(pl) = self.rounded_fill_pipelines.get(&format) { - return Ok(pl); - } - let create_pipeline = |src_has_alpha| { - let push_size = if self.device.descriptor_buffer.is_some() { - size_of::() - } else { - size_of::() - }; - let info = PipelineCreateInfo { - format, - vert: self.rounded_fill_vert_shader.clone(), - frag: self.rounded_fill_frag_shader.clone(), - blend: src_has_alpha, - src_has_alpha, - has_alpha_mult: false, - alpha_mode: AlphaMode::PremultipliedOptical, - eotf: EOTF_LINEAR, - inv_eotf: EOTF_LINEAR, - descriptor_set_layouts: Default::default(), - has_color_management_data: false, - }; - self.device.create_pipeline2(info, push_size) - }; - let pipelines = Rc::new(static_map! { - TexSourceType::HasAlpha => create_pipeline(true)?, - TexSourceType::Opaque => create_pipeline(false)?, - }); - self.rounded_fill_pipelines.set(format, pipelines.clone()); - Ok(pipelines) - } - - fn get_or_create_rounded_tex_pipelines( - &self, - format: vk::Format, - target_cd: &ColorDescription, - ) -> Rc { - let eotf = target_cd.eotf.to_vulkan(); - let pipelines = &self.rounded_tex_pipelines[eotf]; - match pipelines.get(&format) { - Some(pl) => pl, - _ => { - let pl = Rc::new(TexPipelines { - format, - eotf, - pipelines: Default::default(), - }); - pipelines.set(format, pl.clone()); - pl - } - } - } - - fn get_or_create_rounded_tex_pipeline( - &self, - pipelines: &TexPipelines, - tex_cd: &ColorDescription, - tex_copy_type: TexCopyType, - tex_source_type: TexSourceType, - mut tex_alpha_mode: AlphaMode, - has_color_management_data: bool, - ) -> Result, VulkanError> { - if tex_source_type == TexSourceType::Opaque { - tex_alpha_mode = AlphaMode::PremultipliedElectrical; - } - let key = TexPipelineKey { - tex_copy_type, - tex_source_type, - tex_alpha_mode, - eotf: tex_cd.eotf.to_vulkan(), - has_color_management_data, - }; - if let Some(pl) = pipelines.pipelines.get(&key) { - return Ok(pl); - } - let has_alpha_mult = match tex_copy_type { - TexCopyType::Identity => false, - TexCopyType::Multiply => true, - }; - let push_size = if self.device.descriptor_buffer.is_some() { - size_of::() - } else { - size_of::() - }; - let info = PipelineCreateInfo { - format: pipelines.format, - vert: self.rounded_tex_vert_shader.clone(), - frag: self.rounded_tex_frag_shader.clone(), - blend: true, // always blend since corners are transparent - src_has_alpha: true, // rounding makes everything have alpha - has_alpha_mult, - alpha_mode: key.tex_alpha_mode, - eotf: key.eotf.to_vulkan(), - inv_eotf: pipelines.eotf.to_vulkan(), - descriptor_set_layouts: self.tex_descriptor_set_layouts.clone(), - has_color_management_data, - }; - let pl = self.device.create_pipeline2(info, push_size)?; - pipelines.pipelines.set(key, pl.clone()); - Ok(pl) - } - - fn get_or_create_tex_pipelines( - &self, - format: vk::Format, - target_cd: &ColorDescription, - ) -> Rc { - let eotf = target_cd.eotf.to_vulkan(); - let pipelines = &self.tex_pipelines[eotf]; - match pipelines.get(&format) { - Some(pl) => pl, - _ => { - let pl = Rc::new(TexPipelines { - format, - eotf, - pipelines: Default::default(), - }); - pipelines.set(format, pl.clone()); - pl - } - } - } - - fn get_or_create_tex_pipeline( - &self, - pipelines: &TexPipelines, - tex_cd: &ColorDescription, - tex_copy_type: TexCopyType, - tex_source_type: TexSourceType, - mut tex_alpha_mode: AlphaMode, - has_color_management_data: bool, - ) -> Result, VulkanError> { - if tex_source_type == TexSourceType::Opaque { - tex_alpha_mode = AlphaMode::PremultipliedElectrical; - } - let key = TexPipelineKey { - tex_copy_type, - tex_source_type, - tex_alpha_mode, - eotf: tex_cd.eotf.to_vulkan(), - has_color_management_data, - }; - if let Some(pl) = pipelines.pipelines.get(&key) { - return Ok(pl); - } - let src_has_alpha = match tex_source_type { - TexSourceType::Opaque => false, - TexSourceType::HasAlpha => true, - }; - let has_alpha_mult = match tex_copy_type { - TexCopyType::Identity => false, - TexCopyType::Multiply => true, - }; - let push_size = if self.device.descriptor_buffer.is_some() { - size_of::() - } else { - size_of::() - }; - let info = PipelineCreateInfo { - format: pipelines.format, - vert: self.tex_vert_shader.clone(), - frag: self.tex_frag_shader.clone(), - blend: src_has_alpha || has_alpha_mult, - src_has_alpha, - has_alpha_mult, - alpha_mode: key.tex_alpha_mode, - eotf: key.eotf.to_vulkan(), - inv_eotf: pipelines.eotf.to_vulkan(), - descriptor_set_layouts: self.tex_descriptor_set_layouts.clone(), - has_color_management_data, - }; - let pl = self.device.create_pipeline2(info, push_size)?; - pipelines.pipelines.set(key, pl.clone()); - Ok(pl) - } - - fn get_or_create_out_pipeline( - &self, - format: vk::Format, - bb_cd: &ColorDescription, - fb_cd: &ColorDescription, - has_color_management_data: bool, - ) -> Result, VulkanError> { - let key = OutPipelineKey { - format, - eotf: bb_cd.eotf.to_vulkan(), - has_color_management_data, - }; - let fb_eotf = fb_cd.eotf.to_vulkan(); - let pipelines = &self.out_pipelines[fb_eotf]; - if let Some(pl) = pipelines.get(&key) { - return Ok(pl); - } - let mut descriptor_set_layouts = ArrayVec::new(); - descriptor_set_layouts.push(self.out_descriptor_set_layout.clone().unwrap()); - let out = self - .device - .create_pipeline::(PipelineCreateInfo { - format: key.format, - vert: self.out_vert_shader.clone().unwrap(), - frag: self.out_frag_shader.clone().unwrap(), - blend: false, - src_has_alpha: true, - has_alpha_mult: false, - alpha_mode: AlphaMode::PremultipliedElectrical, - eotf: key.eotf.to_vulkan(), - inv_eotf: fb_eotf.to_vulkan(), - descriptor_set_layouts, - has_color_management_data, - })?; - pipelines.set(key, out.clone()); - Ok(out) - } - pub(super) fn allocate_point(&self) -> u64 { self.last_point.fetch_add(1) + 1 } @@ -914,8 +557,8 @@ impl VulkanRenderer { memory.fill_targets.clear(); memory.data_buffer.clear(); memory.uniform_buffer_writer.clear(); - memory.color_transforms.map.clear(); - memory.eotf_args_cache.map.clear(); + memory.color_transforms.clear(); + memory.eotf_args_cache.clear(); let sync = |memory: &mut Memory| { for pass in RenderPass::variants() { let ops = &mut memory.ops_tmp[pass]; @@ -2722,246 +2365,3 @@ async fn await_release( } renderer.pending_frames.remove(&frame.point); } - -impl PaintRegion { - fn intersects(&self, pos: &Point) -> bool { - let mut p = *pos; - for [x, y] in &mut p { - *x = x.clamp(self.x1, self.x2); - *y = y.clamp(self.y1, self.y2); - } - if p[0] == p[1] && p[2] == p[3] { - return false; - } - if p[0] == p[2] && p[1] == p[3] { - return false; - } - true - } - - fn union(&self, other: &Self) -> Self { - Self { - x1: self.x1.min(other.x1), - y1: self.y1.min(other.y1), - x2: self.x2.max(other.x2), - y2: self.y2.max(other.y2), - } - } - - fn to_scissor(&self, target: &VulkanImage) -> Rect2D { - let from_norm = |c: f32, max: u32| ((c + 1.0) * 0.5 * max as f32).round() as i32; - let x1 = from_norm(self.x1, target.width).max(0); - let y1 = from_norm(self.y1, target.height).max(0); - let x2 = from_norm(self.x2, target.width).min(target.width as i32); - let y2 = from_norm(self.y2, target.height).min(target.height as i32); - Rect2D { - offset: Offset2D { x: x1, y: y1 }, - extent: Extent2D { - width: (x2 - x1).max(0) as u32, - height: (y2 - y1).max(0) as u32, - }, - } - } - - fn constrain(&self, pos: &mut Point, tex_pos: Option<&mut Point>) -> bool { - zone!("constrain"); - let mut npos = *pos; - for [x, y] in &mut npos { - *x = x.clamp(self.x1, self.x2); - *y = y.clamp(self.y1, self.y2); - } - if npos == *pos { - return true; - } - if npos[0] == npos[1] && npos[2] == npos[3] { - return false; - } - if npos[0] == npos[2] && npos[1] == npos[3] { - return false; - } - if let Some(tp) = tex_pos { - let mut ntp = *tp; - for i in 0..4 { - if npos[i] == pos[i] { - continue; - } - macro_rules! sub { - ($l:expr, $r:expr) => { - [$l[0] - $r[0], $l[1] - $r[1]] - }; - } - let dx = sub!(npos[i], pos[i]); - let dy = sub!(pos[(i + 1) & 3], pos[i]); - let dz = sub!(pos[(i + 2) & 3], pos[i]); - let det = 1.0 / (dy[0] * dz[1] - dy[1] * dz[0]); - let alpha = [ - (dx[0] * dz[1] - dx[1] * dz[0]) * det, - (dx[1] * dy[0] - dx[0] * dy[1]) * det, - ]; - let dy = sub!(tp[(i + 1) & 3], tp[i]); - let dz = sub!(tp[(i + 2) & 3], tp[i]); - ntp[i][0] += alpha[0] * dy[0] + alpha[1] * dz[0]; - ntp[i][1] += alpha[0] * dy[1] + alpha[1] * dz[1]; - } - *tp = ntp; - } - *pos = npos; - true - } -} - -fn constrain_to_fb(fb: &VulkanImage, rect: &Rect) -> Option<[i32; 4]> -where - T: Tag, -{ - let x1 = rect.x1().max(0); - let y1 = rect.y1().max(0); - let x2 = rect.x2(); - let y2 = rect.y2(); - if x1 as u32 > fb.width || y1 as u32 > fb.height || x2 <= 0 || y2 <= 0 { - return None; - } - let x2 = x2.min(fb.width as i32); - let y2 = y2.min(fb.height as i32); - if x1 == x2 || y1 == y2 { - return None; - } - Some([x1, y1, x2, y2]) -} - -#[derive(Default)] -struct ColorTransforms { - map: AHashMap<([LinearColorDescriptionId; 2], RenderIntent), ColorTransform>, -} - -struct ColorTransform { - matrix: ColorMatrix, - offset: Option, -} - -impl ColorTransforms { - fn get_or_create( - &mut self, - src: &LinearColorDescription, - dst: &ColorDescription, - intent: RenderIntent, - ) -> Option<&mut ColorTransform> { - if src.embeds_into(&dst.linear) { - return None; - } - let ct = match self.map.entry(([src.id, dst.linear.id], intent)) { - Entry::Occupied(o) => o.into_mut(), - Entry::Vacant(e) => { - let matrix = src.color_transform(&dst.linear, intent); - let ct = ColorTransform { - matrix, - offset: None, - }; - e.insert(ct) - } - }; - Some(ct) - } - - fn apply_to_color( - &mut self, - src: &LinearColorDescription, - dst: &ColorDescription, - intent: RenderIntent, - mut color: Color, - ) -> Color { - if let Some(ct) = self.get_or_create(src, dst, intent) { - color = ct.matrix * color; - }; - color - } - - fn get_offset( - &mut self, - src: &LinearColorDescription, - dst: &ColorDescription, - intent: RenderIntent, - uniform_buffer_offset_mask: DeviceSize, - writer: &mut GenericBufferWriter, - ) -> Option { - let ct = self.get_or_create(src, dst, intent)?; - if ct.offset.is_none() { - let data = ColorManagementData { - matrix: ct.matrix.to_f32(), - }; - let offset = writer.write(uniform_buffer_offset_mask, &data); - ct.offset = Some(offset); - } - ct.offset - } -} - -#[derive(Default)] -struct EotfArgsCache { - map: AHashMap<(EotfCacheKey, bool), EotfArg>, -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -enum EotfCacheKey { - Pow(EotfPow), - Bt1886(F32), -} - -struct EotfArg { - offset: DeviceSize, -} - -impl EotfArgsCache { - fn get_offset( - &mut self, - desc: &ColorDescription, - inv: bool, - uniform_buffer_offset_mask: DeviceSize, - writer: &mut GenericBufferWriter, - ) -> Option { - let key = match desc.eotf { - Eotf::Bt1886(c) => EotfCacheKey::Bt1886(c), - Eotf::Pow(pow) => EotfCacheKey::Pow(pow), - _ => return None, - }; - let ct = match self.map.entry((key, inv)) { - Entry::Occupied(o) => o.into_mut(), - Entry::Vacant(e) => { - #[expect(unused_assignments)] - let [mut arg1, mut arg2, mut arg3, mut arg4] = [0.0; 4]; - if inv { - match key { - EotfCacheKey::Pow(pow) => arg1 = pow.inv_eotf_f32(), - EotfCacheKey::Bt1886(c) => { - [arg1, arg2, arg3, arg4] = bt1886_inv_eotf_args(c); - } - } - let data = InvEotfArgs { - arg1, - arg2, - arg3, - arg4, - }; - let offset = writer.write(uniform_buffer_offset_mask, &data); - e.insert(EotfArg { offset }) - } else { - match key { - EotfCacheKey::Pow(pow) => arg1 = pow.eotf_f32(), - EotfCacheKey::Bt1886(c) => { - [arg1, arg2, arg3, arg4] = bt1886_eotf_args(c); - } - } - let data = EotfArgs { - arg1, - arg2, - arg3, - arg4, - }; - let offset = writer.write(uniform_buffer_offset_mask, &data); - e.insert(EotfArg { offset }) - } - } - }; - Some(ct.offset) - } -} diff --git a/src/gfx_apis/vulkan/renderer/color.rs b/src/gfx_apis/vulkan/renderer/color.rs new file mode 100644 index 00000000..9fe7f832 --- /dev/null +++ b/src/gfx_apis/vulkan/renderer/color.rs @@ -0,0 +1,185 @@ +use jay_theme::Color; +use { + crate::{ + cmm::{ + cmm_description::{ColorDescription, LinearColorDescription, LinearColorDescriptionId}, + cmm_eotf::{Eotf, EotfPow, bt1886_eotf_args, bt1886_inv_eotf_args}, + cmm_render_intent::RenderIntent, + cmm_transform::ColorMatrix, + }, + gfx_api::AlphaMode, + gfx_apis::vulkan::{ + buffer_cache::GenericBufferWriter, + shaders::{ColorManagementData, EotfArgs, InvEotfArgs}, + }, + + utils::ordered_float::F32, + }, + ahash::AHashMap, + ash::vk::DeviceSize, + std::collections::hash_map::Entry, +}; + +#[derive(Default)] +pub(super) struct ColorTransforms { + map: AHashMap<([LinearColorDescriptionId; 2], RenderIntent), ColorTransform>, +} + +struct ColorTransform { + matrix: ColorMatrix, + offset: Option, +} + +impl ColorTransforms { + pub(super) fn clear(&mut self) { + self.map.clear(); + } + + fn get_or_create( + &mut self, + src: &LinearColorDescription, + dst: &ColorDescription, + intent: RenderIntent, + ) -> Option<&mut ColorTransform> { + if src.embeds_into(&dst.linear) { + return None; + } + let ct = match self.map.entry(([src.id, dst.linear.id], intent)) { + Entry::Occupied(o) => o.into_mut(), + Entry::Vacant(e) => { + let matrix = src.color_transform(&dst.linear, intent); + let ct = ColorTransform { + matrix, + offset: None, + }; + e.insert(ct) + } + }; + Some(ct) + } + + pub(super) fn apply_to_color( + &mut self, + src: &LinearColorDescription, + dst: &ColorDescription, + intent: RenderIntent, + mut color: Color, + ) -> Color { + if let Some(ct) = self.get_or_create(src, dst, intent) { + color = apply_color_matrix(ct.matrix, color); + }; + color + } + + pub(super) fn get_offset( + &mut self, + src: &LinearColorDescription, + dst: &ColorDescription, + intent: RenderIntent, + uniform_buffer_offset_mask: DeviceSize, + writer: &mut GenericBufferWriter, + ) -> Option { + let ct = self.get_or_create(src, dst, intent)?; + if ct.offset.is_none() { + let data = ColorManagementData { + matrix: ct.matrix.to_f32(), + }; + let offset = writer.write(uniform_buffer_offset_mask, &data); + ct.offset = Some(offset); + } + ct.offset + } +} + +fn apply_color_matrix(matrix: ColorMatrix, color: Color) -> Color { + let mut rgba = color.to_array(Eotf::Linear); + let a = rgba[3]; + if a < 1.0 && a > 0.0 { + for c in &mut rgba[..3] { + *c /= a; + } + } + let [r, g, b] = matrix * [rgba[0] as f64, rgba[1] as f64, rgba[2] as f64]; + Color::new( + Eotf::Linear, + AlphaMode::Straight, + r as f32, + g as f32, + b as f32, + a, + ) +} + +#[derive(Default)] +pub(super) struct EotfArgsCache { + map: AHashMap<(EotfCacheKey, bool), EotfArg>, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +enum EotfCacheKey { + Pow(EotfPow), + Bt1886(F32), +} + +struct EotfArg { + offset: DeviceSize, +} + +impl EotfArgsCache { + pub(super) fn clear(&mut self) { + self.map.clear(); + } + + pub(super) fn get_offset( + &mut self, + desc: &ColorDescription, + inv: bool, + uniform_buffer_offset_mask: DeviceSize, + writer: &mut GenericBufferWriter, + ) -> Option { + let key = match desc.eotf { + Eotf::Bt1886(c) => EotfCacheKey::Bt1886(c), + Eotf::Pow(pow) => EotfCacheKey::Pow(pow), + _ => return None, + }; + let ct = match self.map.entry((key, inv)) { + Entry::Occupied(o) => o.into_mut(), + Entry::Vacant(e) => { + #[expect(unused_assignments)] + let [mut arg1, mut arg2, mut arg3, mut arg4] = [0.0; 4]; + if inv { + match key { + EotfCacheKey::Pow(pow) => arg1 = pow.inv_eotf_f32(), + EotfCacheKey::Bt1886(c) => { + [arg1, arg2, arg3, arg4] = bt1886_inv_eotf_args(c); + } + } + let data = InvEotfArgs { + arg1, + arg2, + arg3, + arg4, + }; + let offset = writer.write(uniform_buffer_offset_mask, &data); + e.insert(EotfArg { offset }) + } else { + match key { + EotfCacheKey::Pow(pow) => arg1 = pow.eotf_f32(), + EotfCacheKey::Bt1886(c) => { + [arg1, arg2, arg3, arg4] = bt1886_eotf_args(c); + } + } + let data = EotfArgs { + arg1, + arg2, + arg3, + arg4, + }; + let offset = writer.write(uniform_buffer_offset_mask, &data); + e.insert(EotfArg { offset }) + } + } + }; + Some(ct.offset) + } +} diff --git a/src/gfx_apis/vulkan/renderer/op.rs b/src/gfx_apis/vulkan/renderer/op.rs new file mode 100644 index 00000000..f4dbbb03 --- /dev/null +++ b/src/gfx_apis/vulkan/renderer/op.rs @@ -0,0 +1,91 @@ +use { + super::paint_region::Point, + crate::{ + cmm::cmm_description::ColorDescription, + gfx_api::{AcquireSync, AlphaMode, BufferResv, ReleaseSync}, + gfx_apis::vulkan::image::VulkanImage, + }, + ash::vk::DeviceAddress, + linearize::Linearize, + std::{ops::Range, rc::Rc}, +}; + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Linearize)] +pub(in crate::gfx_apis::vulkan) enum TexCopyType { + Identity, + Multiply, +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Linearize)] +pub(in crate::gfx_apis::vulkan) enum TexSourceType { + Opaque, + HasAlpha, +} + +pub(super) enum VulkanOp { + Fill(VulkanFillOp), + Tex(VulkanTexOp), + RoundedFill(VulkanRoundedFillOp), + RoundedTex(VulkanRoundedTexOp), +} + +pub(super) struct VulkanTexOp { + pub(super) tex: Rc, + pub(super) index: usize, + pub(super) range: Range, + pub(super) buffer_resv: Option>, + pub(super) acquire_sync: Option, + pub(super) release_sync: ReleaseSync, + pub(super) alpha: f32, + pub(super) source_type: TexSourceType, + pub(super) copy_type: TexCopyType, + pub(super) alpha_mode: AlphaMode, + pub(super) range_address: DeviceAddress, + pub(super) instances: u32, + pub(super) tex_cd: Rc, + pub(super) color_management_data_address: Option, + pub(super) eotf_args_address: Option, + pub(super) resource_descriptor_buffer_offset: DeviceAddress, +} + +pub(super) struct VulkanFillOp { + pub(super) range: Range, + pub(super) color: [f32; 4], + pub(super) source_type: TexSourceType, + pub(super) range_address: DeviceAddress, + pub(super) instances: u32, +} + +pub(super) struct VulkanRoundedFillOp { + pub(super) target: Point, + pub(super) color: [f32; 4], + pub(super) source_type: TexSourceType, + pub(super) size: [f32; 2], + pub(super) corner_radius: [f32; 4], + pub(super) border_width: f32, + pub(super) scale: f32, + pub(super) range_address: DeviceAddress, + pub(super) z_order: u32, +} + +pub(super) struct VulkanRoundedTexOp { + pub(super) tex: Rc, + pub(super) index: usize, + pub(super) target: Point, + pub(super) source: Point, + pub(super) buffer_resv: Option>, + pub(super) acquire_sync: Option, + pub(super) release_sync: ReleaseSync, + pub(super) alpha: f32, + pub(super) source_type: TexSourceType, + pub(super) copy_type: TexCopyType, + pub(super) alpha_mode: AlphaMode, + pub(super) tex_cd: Rc, + pub(super) color_management_data_address: Option, + pub(super) eotf_args_address: Option, + pub(super) resource_descriptor_buffer_offset: DeviceAddress, + pub(super) size: [f32; 2], + pub(super) corner_radius: [f32; 4], + pub(super) scale: f32, + pub(super) range_address: DeviceAddress, +} diff --git a/src/gfx_apis/vulkan/renderer/paint_region.rs b/src/gfx_apis/vulkan/renderer/paint_region.rs new file mode 100644 index 00000000..0bd8820b --- /dev/null +++ b/src/gfx_apis/vulkan/renderer/paint_region.rs @@ -0,0 +1,122 @@ +use { + crate::gfx_apis::vulkan::image::VulkanImage, + ash::vk::{Extent2D, Offset2D, Rect2D}, + jay_algorithms::rect::Tag, + jay_geometry::Rect, +}; + +pub(super) type Point = [[f32; 2]; 4]; + +#[derive(Copy, Clone)] +pub(super) struct PaintRegion { + pub(super) x1: f32, + pub(super) y1: f32, + pub(super) x2: f32, + pub(super) y2: f32, +} + +impl PaintRegion { + pub(super) fn intersects(&self, pos: &Point) -> bool { + let mut p = *pos; + for [x, y] in &mut p { + *x = x.clamp(self.x1, self.x2); + *y = y.clamp(self.y1, self.y2); + } + if p[0] == p[1] && p[2] == p[3] { + return false; + } + if p[0] == p[2] && p[1] == p[3] { + return false; + } + true + } + + pub(super) fn union(&self, other: &Self) -> Self { + Self { + x1: self.x1.min(other.x1), + y1: self.y1.min(other.y1), + x2: self.x2.max(other.x2), + y2: self.y2.max(other.y2), + } + } + + pub(super) fn to_scissor(&self, target: &VulkanImage) -> Rect2D { + let from_norm = |c: f32, max: u32| ((c + 1.0) * 0.5 * max as f32).round() as i32; + let x1 = from_norm(self.x1, target.width).max(0); + let y1 = from_norm(self.y1, target.height).max(0); + let x2 = from_norm(self.x2, target.width).min(target.width as i32); + let y2 = from_norm(self.y2, target.height).min(target.height as i32); + Rect2D { + offset: Offset2D { x: x1, y: y1 }, + extent: Extent2D { + width: (x2 - x1).max(0) as u32, + height: (y2 - y1).max(0) as u32, + }, + } + } + + pub(super) fn constrain(&self, pos: &mut Point, tex_pos: Option<&mut Point>) -> bool { + zone!("constrain"); + let mut npos = *pos; + for [x, y] in &mut npos { + *x = x.clamp(self.x1, self.x2); + *y = y.clamp(self.y1, self.y2); + } + if npos == *pos { + return true; + } + if npos[0] == npos[1] && npos[2] == npos[3] { + return false; + } + if npos[0] == npos[2] && npos[1] == npos[3] { + return false; + } + if let Some(tp) = tex_pos { + let mut ntp = *tp; + for i in 0..4 { + if npos[i] == pos[i] { + continue; + } + macro_rules! sub { + ($l:expr, $r:expr) => { + [$l[0] - $r[0], $l[1] - $r[1]] + }; + } + let dx = sub!(npos[i], pos[i]); + let dy = sub!(pos[(i + 1) & 3], pos[i]); + let dz = sub!(pos[(i + 2) & 3], pos[i]); + let det = 1.0 / (dy[0] * dz[1] - dy[1] * dz[0]); + let alpha = [ + (dx[0] * dz[1] - dx[1] * dz[0]) * det, + (dx[1] * dy[0] - dx[0] * dy[1]) * det, + ]; + let dy = sub!(tp[(i + 1) & 3], tp[i]); + let dz = sub!(tp[(i + 2) & 3], tp[i]); + ntp[i][0] += alpha[0] * dy[0] + alpha[1] * dz[0]; + ntp[i][1] += alpha[0] * dy[1] + alpha[1] * dz[1]; + } + *tp = ntp; + } + *pos = npos; + true + } +} + +pub(super) fn constrain_to_fb(fb: &VulkanImage, rect: &Rect) -> Option<[i32; 4]> +where + T: Tag, +{ + let x1 = rect.x1().max(0); + let y1 = rect.y1().max(0); + let x2 = rect.x2(); + let y2 = rect.y2(); + if x1 as u32 > fb.width || y1 as u32 > fb.height || x2 <= 0 || y2 <= 0 { + return None; + } + let x2 = x2.min(fb.width as i32); + let y2 = y2.min(fb.height as i32); + if x1 == x2 || y1 == y2 { + return None; + } + Some([x1, y1, x2, y2]) +} diff --git a/src/gfx_apis/vulkan/renderer/pipeline_cache.rs b/src/gfx_apis/vulkan/renderer/pipeline_cache.rs new file mode 100644 index 00000000..cca90c18 --- /dev/null +++ b/src/gfx_apis/vulkan/renderer/pipeline_cache.rs @@ -0,0 +1,36 @@ +use { + super::op::{TexCopyType, TexSourceType}, + crate::{ + gfx_api::AlphaMode, + gfx_apis::vulkan::{eotfs::VulkanEotf, pipeline::VulkanPipeline}, + utils::copyhashmap::CopyHashMap, + }, + ash::vk, + linearize::StaticMap, + std::rc::Rc, +}; + +pub(in crate::gfx_apis::vulkan) type FillPipelines = + Rc>>; + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub(super) struct TexPipelineKey { + pub(super) tex_copy_type: TexCopyType, + pub(super) tex_source_type: TexSourceType, + pub(super) tex_alpha_mode: AlphaMode, + pub(super) eotf: VulkanEotf, + pub(super) has_color_management_data: bool, +} + +pub(in crate::gfx_apis::vulkan) struct TexPipelines { + pub(super) format: vk::Format, + pub(super) eotf: VulkanEotf, + pub(super) pipelines: CopyHashMap>, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub(in crate::gfx_apis::vulkan) struct OutPipelineKey { + pub(super) format: vk::Format, + pub(super) eotf: VulkanEotf, + pub(super) has_color_management_data: bool, +} diff --git a/src/gfx_apis/vulkan/renderer/pipelines.rs b/src/gfx_apis/vulkan/renderer/pipelines.rs new file mode 100644 index 00000000..29b7429a --- /dev/null +++ b/src/gfx_apis/vulkan/renderer/pipelines.rs @@ -0,0 +1,282 @@ +use { + super::{ + VulkanRenderer, + op::{TexCopyType, TexSourceType}, + pipeline_cache::{FillPipelines, OutPipelineKey, TexPipelineKey, TexPipelines}, + }, + crate::{ + cmm::cmm_description::ColorDescription, + gfx_api::AlphaMode, + gfx_apis::vulkan::{ + VulkanError, + eotfs::{EOTF_LINEAR, EotfExt}, + pipeline::{PipelineCreateInfo, VulkanPipeline}, + shaders::{ + FillPushConstants, LegacyFillPushConstants, LegacyRoundedFillPushConstants, + LegacyRoundedTexPushConstants, LegacyTexPushConstants, OutPushConstants, + RoundedFillPushConstants, RoundedTexPushConstants, TexPushConstants, + }, + }, + }, + arrayvec::ArrayVec, + ash::vk, + linearize::static_map, + std::{mem::size_of, rc::Rc}, +}; + +impl VulkanRenderer { + pub(super) fn get_or_create_fill_pipelines( + &self, + format: vk::Format, + ) -> Result { + if let Some(pl) = self.fill_pipelines.get(&format) { + return Ok(pl); + } + let create_fill_pipeline = |src_has_alpha| { + let push_size = if self.device.descriptor_buffer.is_some() { + size_of::() + } else { + size_of::() + }; + let info = PipelineCreateInfo { + format, + vert: self.fill_vert_shader.clone(), + frag: self.fill_frag_shader.clone(), + blend: src_has_alpha, + src_has_alpha, + has_alpha_mult: false, + alpha_mode: AlphaMode::PremultipliedOptical, + // all transformations are applied in the compositor + eotf: EOTF_LINEAR, + inv_eotf: EOTF_LINEAR, + descriptor_set_layouts: Default::default(), + has_color_management_data: false, + }; + self.device.create_pipeline2(info, push_size) + }; + let fill_pipelines = Rc::new(static_map! { + TexSourceType::HasAlpha => create_fill_pipeline(true)?, + TexSourceType::Opaque => create_fill_pipeline(false)?, + }); + self.fill_pipelines.set(format, fill_pipelines.clone()); + Ok(fill_pipelines) + } + + pub(super) fn get_or_create_rounded_fill_pipelines( + &self, + format: vk::Format, + ) -> Result { + if let Some(pl) = self.rounded_fill_pipelines.get(&format) { + return Ok(pl); + } + let create_pipeline = |src_has_alpha| { + let push_size = if self.device.descriptor_buffer.is_some() { + size_of::() + } else { + size_of::() + }; + let info = PipelineCreateInfo { + format, + vert: self.rounded_fill_vert_shader.clone(), + frag: self.rounded_fill_frag_shader.clone(), + blend: src_has_alpha, + src_has_alpha, + has_alpha_mult: false, + alpha_mode: AlphaMode::PremultipliedOptical, + eotf: EOTF_LINEAR, + inv_eotf: EOTF_LINEAR, + descriptor_set_layouts: Default::default(), + has_color_management_data: false, + }; + self.device.create_pipeline2(info, push_size) + }; + let pipelines = Rc::new(static_map! { + TexSourceType::HasAlpha => create_pipeline(true)?, + TexSourceType::Opaque => create_pipeline(false)?, + }); + self.rounded_fill_pipelines.set(format, pipelines.clone()); + Ok(pipelines) + } + + pub(super) fn get_or_create_rounded_tex_pipelines( + &self, + format: vk::Format, + target_cd: &ColorDescription, + ) -> Rc { + let eotf = target_cd.eotf.to_vulkan(); + let pipelines = &self.rounded_tex_pipelines[eotf]; + match pipelines.get(&format) { + Some(pl) => pl, + _ => { + let pl = Rc::new(TexPipelines { + format, + eotf, + pipelines: Default::default(), + }); + pipelines.set(format, pl.clone()); + pl + } + } + } + + pub(super) fn get_or_create_rounded_tex_pipeline( + &self, + pipelines: &TexPipelines, + tex_cd: &ColorDescription, + tex_copy_type: TexCopyType, + tex_source_type: TexSourceType, + mut tex_alpha_mode: AlphaMode, + has_color_management_data: bool, + ) -> Result, VulkanError> { + if tex_source_type == TexSourceType::Opaque { + tex_alpha_mode = AlphaMode::PremultipliedElectrical; + } + let key = TexPipelineKey { + tex_copy_type, + tex_source_type, + tex_alpha_mode, + eotf: tex_cd.eotf.to_vulkan(), + has_color_management_data, + }; + if let Some(pl) = pipelines.pipelines.get(&key) { + return Ok(pl); + } + let has_alpha_mult = match tex_copy_type { + TexCopyType::Identity => false, + TexCopyType::Multiply => true, + }; + let push_size = if self.device.descriptor_buffer.is_some() { + size_of::() + } else { + size_of::() + }; + let info = PipelineCreateInfo { + format: pipelines.format, + vert: self.rounded_tex_vert_shader.clone(), + frag: self.rounded_tex_frag_shader.clone(), + blend: true, + src_has_alpha: true, + has_alpha_mult, + alpha_mode: key.tex_alpha_mode, + eotf: key.eotf.to_vulkan(), + inv_eotf: pipelines.eotf.to_vulkan(), + descriptor_set_layouts: self.tex_descriptor_set_layouts.clone(), + has_color_management_data, + }; + let pl = self.device.create_pipeline2(info, push_size)?; + pipelines.pipelines.set(key, pl.clone()); + Ok(pl) + } + + pub(super) fn get_or_create_tex_pipelines( + &self, + format: vk::Format, + target_cd: &ColorDescription, + ) -> Rc { + let eotf = target_cd.eotf.to_vulkan(); + let pipelines = &self.tex_pipelines[eotf]; + match pipelines.get(&format) { + Some(pl) => pl, + _ => { + let pl = Rc::new(TexPipelines { + format, + eotf, + pipelines: Default::default(), + }); + pipelines.set(format, pl.clone()); + pl + } + } + } + + pub(super) fn get_or_create_tex_pipeline( + &self, + pipelines: &TexPipelines, + tex_cd: &ColorDescription, + tex_copy_type: TexCopyType, + tex_source_type: TexSourceType, + mut tex_alpha_mode: AlphaMode, + has_color_management_data: bool, + ) -> Result, VulkanError> { + if tex_source_type == TexSourceType::Opaque { + tex_alpha_mode = AlphaMode::PremultipliedElectrical; + } + let key = TexPipelineKey { + tex_copy_type, + tex_source_type, + tex_alpha_mode, + eotf: tex_cd.eotf.to_vulkan(), + has_color_management_data, + }; + if let Some(pl) = pipelines.pipelines.get(&key) { + return Ok(pl); + } + let src_has_alpha = match tex_source_type { + TexSourceType::Opaque => false, + TexSourceType::HasAlpha => true, + }; + let has_alpha_mult = match tex_copy_type { + TexCopyType::Identity => false, + TexCopyType::Multiply => true, + }; + let push_size = if self.device.descriptor_buffer.is_some() { + size_of::() + } else { + size_of::() + }; + let info = PipelineCreateInfo { + format: pipelines.format, + vert: self.tex_vert_shader.clone(), + frag: self.tex_frag_shader.clone(), + blend: src_has_alpha || has_alpha_mult, + src_has_alpha, + has_alpha_mult, + alpha_mode: key.tex_alpha_mode, + eotf: key.eotf.to_vulkan(), + inv_eotf: pipelines.eotf.to_vulkan(), + descriptor_set_layouts: self.tex_descriptor_set_layouts.clone(), + has_color_management_data, + }; + let pl = self.device.create_pipeline2(info, push_size)?; + pipelines.pipelines.set(key, pl.clone()); + Ok(pl) + } + + pub(super) fn get_or_create_out_pipeline( + &self, + format: vk::Format, + bb_cd: &ColorDescription, + fb_cd: &ColorDescription, + has_color_management_data: bool, + ) -> Result, VulkanError> { + let key = OutPipelineKey { + format, + eotf: bb_cd.eotf.to_vulkan(), + has_color_management_data, + }; + let fb_eotf = fb_cd.eotf.to_vulkan(); + let pipelines = &self.out_pipelines[fb_eotf]; + if let Some(pl) = pipelines.get(&key) { + return Ok(pl); + } + let mut descriptor_set_layouts = ArrayVec::new(); + descriptor_set_layouts.push(self.out_descriptor_set_layout.clone().unwrap()); + let out = self + .device + .create_pipeline::(PipelineCreateInfo { + format: key.format, + vert: self.out_vert_shader.clone().unwrap(), + frag: self.out_frag_shader.clone().unwrap(), + blend: false, + src_has_alpha: true, + has_alpha_mult: false, + alpha_mode: AlphaMode::PremultipliedElectrical, + eotf: key.eotf.to_vulkan(), + inv_eotf: fb_eotf.to_vulkan(), + descriptor_set_layouts, + has_color_management_data, + })?; + pipelines.set(key, out.clone()); + Ok(out) + } +} diff --git a/src/gfx_apis/vulkan/shm_image.rs b/src/gfx_apis/vulkan/shm_image.rs index 0d816fef..fa421b90 100644 --- a/src/gfx_apis/vulkan/shm_image.rs +++ b/src/gfx_apis/vulkan/shm_image.rs @@ -1,6 +1,8 @@ +use jay_geometry::Rect; +use jay_cpu_worker::CpuWorker; use { crate::{ - cpu_worker::CpuWorker, + format::Format, gfx_api::FdSync, gfx_apis::vulkan::{ @@ -12,7 +14,7 @@ use { staging::VulkanStagingBuffer, transfer::{TransferType, VulkanShmImageAsyncData}, }, - rect::Rect, + utils::errorfmt::ErrorFmt, vulkan_core::sync::VulkanDeviceSyncExt, }, diff --git a/src/gfx_apis/vulkan/staging.rs b/src/gfx_apis/vulkan/staging.rs index 9b342ffc..dc2bd3df 100644 --- a/src/gfx_apis/vulkan/staging.rs +++ b/src/gfx_apis/vulkan/staging.rs @@ -1,6 +1,7 @@ +use jay_cpu_worker::CpuWorker; use { crate::{ - cpu_worker::CpuWorker, + gfx_api::GfxStagingBuffer, gfx_apis::vulkan::{ VulkanError, diff --git a/src/gfx_apis/vulkan/transfer.rs b/src/gfx_apis/vulkan/transfer.rs index e108bb5a..3d371b4e 100644 --- a/src/gfx_apis/vulkan/transfer.rs +++ b/src/gfx_apis/vulkan/transfer.rs @@ -1,12 +1,13 @@ +use jay_geometry::{Rect, Region}; +use jay_cpu_worker::{ + CpuJob, CpuWork, CpuWorker, + jobs::{ + img_copy::ImgCopyWork, + read_write::{ReadWriteJobError, ReadWriteWork}, + }, +}; use { crate::{ - cpu_worker::{ - CpuJob, CpuWork, CpuWorker, - jobs::{ - img_copy::ImgCopyWork, - read_write::{ReadWriteJobError, ReadWriteWork}, - }, - }, gfx_api::{ AsyncShmGfxTextureCallback, FdSync, PendingShmTransfer, ShmMemory, ShmMemoryBacking, }, @@ -19,7 +20,7 @@ use { shm_image::VulkanShmImage, staging::{VulkanStagingBuffer, VulkanStagingShell}, }, - rect::{Rect, Region}, + utils::{clonecell::CloneCell, errorfmt::ErrorFmt}, vulkan_core::sync::VulkanDeviceSyncExt, }, diff --git a/src/globals.rs b/src/globals.rs index dbc5a650..ca56d39d 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -1,6 +1,6 @@ use { crate::{ - client::{Client, ClientCaps}, + client::Client, ifs::{ color_management::wp_color_manager_v1::WpColorManagerV1Global, ext_foreign_toplevel_image_capture_source_manager_v1::ExtForeignToplevelImageCaptureSourceManagerV1Global, @@ -11,7 +11,7 @@ use { ext_session_lock_manager_v1::ExtSessionLockManagerV1Global, head_management::jay_head_manager_v1::JayHeadManagerV1Global, hyprland_focus_grab_manager_v1::HyprlandFocusGrabManagerV1Global, - ipc::{ + data_transfer::{ data_control::{ ext_data_control_manager_v1::ExtDataControlManagerV1Global, zwlr_data_control_manager_v1::ZwlrDataControlManagerV1Global, @@ -56,7 +56,6 @@ use { wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1Global, wp_linux_drm_syncobj_manager_v1::WpLinuxDrmSyncobjManagerV1Global, wp_presentation::WpPresentationGlobal, - wp_security_context_manager_v1::WpSecurityContextManagerV1Global, wp_single_pixel_buffer_manager_v1::WpSinglePixelBufferManagerV1Global, wp_tearing_control_manager_v1::WpTearingControlManagerV1Global, wp_viewporter::WpViewporterGlobal, @@ -85,14 +84,10 @@ use { }, arrayvec::ArrayVec, linearize::{Linearize, StaticMap}, - std::{ - cell::Cell, - error::Error, - fmt::{Display, Formatter}, - rc::Rc, - }, + std::{cell::Cell, error::Error, rc::Rc}, thiserror::Error, }; +pub use jay_wire_types::GlobalName; #[derive(Debug, Error)] pub enum GlobalsError { @@ -112,25 +107,6 @@ pub struct GlobalError { pub error: Box, } -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub struct GlobalName(u32); - -impl GlobalName { - pub fn from_raw(id: u32) -> Self { - Self(id) - } - - pub fn raw(self) -> u32 { - self.0 - } -} - -impl Display for GlobalName { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - Display::fmt(&self.0, f) - } -} - pub trait GlobalBase { fn name(&self) -> GlobalName; fn bind<'a>( @@ -145,9 +121,6 @@ pub trait GlobalBase { pub trait Global: GlobalBase { fn version(&self) -> u32; - fn required_caps(&self) -> ClientCaps { - ClientCaps::none() - } fn xwayland_only(&self) -> bool { false } @@ -155,11 +128,11 @@ pub trait Global: GlobalBase { let _ = state; true } - fn permitted(&self, caps: ClientCaps, xwayland: bool) -> bool { - caps.contains(self.required_caps()) && (xwayland || !self.xwayland_only()) + fn permitted(&self, xwayland: bool) -> bool { + xwayland || !self.xwayland_only() } - fn not_permitted(&self, caps: ClientCaps, xwayland: bool) -> bool { - !self.permitted(caps, xwayland) + fn not_permitted(&self, xwayland: bool) -> bool { + !self.permitted(xwayland) } } @@ -227,7 +200,6 @@ singletons! { ZwpVirtualKeyboardManagerV1, ZwpInputMethodManagerV2, ZwpTextInputManagerV3, - WpSecurityContextManagerV1, XdgWmDialogV1, ExtTransientSeatManagerV1, ZwpPointerGesturesV1, @@ -274,7 +246,7 @@ impl Globals { removed: CopyHashMap::new(), outputs: Default::default(), seats: Default::default(), - singletons: StaticMap::from_fn(|_| GlobalName(0)), + singletons: StaticMap::from_fn(|_| GlobalName::from_raw(0)), exposed: Default::default(), }; add_singletons(&mut slf); @@ -292,7 +264,7 @@ impl Globals { if id == 0 { panic!("Global names overflowed"); } - GlobalName(id) + GlobalName::from_raw(id) } fn insert_no_broadcast<'a>(&'a self, global: Rc) { @@ -305,19 +277,16 @@ impl Globals { fn insert(&self, state: &State, global: Rc) { self.insert_no_broadcast_(&global); - self.broadcast(state, global.required_caps(), global.xwayland_only(), |r| { - r.handle_global(&global) - }); + self.broadcast(state, global.xwayland_only(), |r| r.handle_global(&global)); } pub fn get( &self, name: GlobalName, - client_caps: ClientCaps, allow_xwayland_only: bool, ) -> Result, GlobalsError> { let global = self.take(name, false)?; - if global.not_permitted(client_caps, allow_xwayland_only) { + if global.not_permitted(allow_xwayland_only) { return Err(GlobalsError::GlobalDoesNotExist(name)); } Ok(global) @@ -334,7 +303,7 @@ impl Globals { assert_eq!(global.name(), replacement.name()); assert_eq!(global.interface().0, replacement.interface().0); self.removed.set(global.name(), replacement); - self.broadcast(state, global.required_caps(), global.xwayland_only(), |r| { + self.broadcast(state, global.xwayland_only(), |r| { r.handle_global_removed(&**global) }); Ok(()) @@ -345,7 +314,6 @@ impl Globals { } pub fn notify_all(&self, registry: &Rc) { - let caps = registry.client.effective_caps.get(); let xwayland = registry.client.is_xwayland; let globals = self.registry.lock(); macro_rules! emit { @@ -353,7 +321,7 @@ impl Globals { for global in globals.values() { if global.singleton().is_some() == $singleton { if global.exposed(®istry.client.state) - && global.permitted(caps, xwayland) + && global.permitted(xwayland) { registry.handle_global(global); } @@ -368,11 +336,10 @@ impl Globals { fn broadcast)>( &self, state: &State, - required_caps: ClientCaps, xwayland_only: bool, f: F, ) { - state.clients.broadcast(required_caps, xwayland_only, |c| { + state.clients.broadcast(xwayland_only, |c| { let registries = c.lock_registries(); for registry in registries.values() { f(registry); @@ -429,10 +396,9 @@ impl Globals { } for client in state.clients.clients.borrow().values() { let client = &client.data; - let caps = client.effective_caps.get(); let xwayland = client.is_xwayland; for global in &singletons { - if global.permitted(caps, xwayland) { + if global.permitted(xwayland) { for registry in client.objects.registries.lock().values() { registry.handle_global(global); } diff --git a/src/icons.rs b/src/icons.rs index f4537352..0ec56137 100644 --- a/src/icons.rs +++ b/src/icons.rs @@ -1,14 +1,15 @@ #![allow(clippy::excessive_precision)] #![allow(dead_code)] +use jay_theme::Theme; +use jay_units::scale::Scale; use { crate::{ cmm::cmm_eotf::Eotf, format::ARGB8888, gfx_api::{GfxContext, GfxError, GfxTexture}, - scale::Scale, state::State, - theme::Theme, + utils::{copyhashmap::CopyHashMap, windows::WindowsExt}, }, ahash::AHashSet, @@ -100,7 +101,7 @@ pub fn create_icons( } let size = size as u32; - let create_pins = |color: crate::theme::Color| { + let create_pins = |color: jay_theme::Color| { let create_pin = |color: Color| { let mut paint = Paint::default(); paint.set_color(color); @@ -222,19 +223,7 @@ impl PathBuilderExt for PathBuilder { } } -impl From for Color { - fn from(v: crate::theme::Color) -> Self { - let [r, g, b, a] = v.to_array(Eotf::Gamma22); - let mut c = Self::TRANSPARENT; - c.set_red(r / a); - c.set_green(g / a); - c.set_blue(b / a); - c.set_alpha(a); - c - } -} - -fn calculate_accents(srgb: crate::theme::Color) -> [Color; 2] { +fn calculate_accents(srgb: jay_theme::Color) -> [Color; 2] { let [l, a, b, alpha] = srgb_to_lab(srgb); let l2 = if l < 0.65 { 0.9 } else { l - 0.4 }; let l1 = (l2 + l) / 2.0; @@ -244,7 +233,7 @@ fn calculate_accents(srgb: crate::theme::Color) -> [Color; 2] { ] } -fn srgb_to_lab(srgb: crate::theme::Color) -> [f32; 4] { +fn srgb_to_lab(srgb: jay_theme::Color) -> [f32; 4] { let [mut r, mut g, mut b, alpha] = srgb.to_array(Eotf::Gamma22); if alpha < 1.0 { r /= alpha; diff --git a/src/ifs.rs b/src/ifs.rs index f29b0d67..85993cf3 100644 --- a/src/ifs.rs +++ b/src/ifs.rs @@ -1,4 +1,5 @@ pub mod color_management; +pub mod data_transfer; pub mod ext_foreign_toplevel_handle_v1; pub mod ext_foreign_toplevel_image_capture_source_manager_v1; pub mod ext_foreign_toplevel_list_v1; @@ -12,8 +13,6 @@ pub mod ext_session_lock_v1; pub mod head_management; pub mod hyprland_focus_grab_manager_v1; pub mod hyprland_focus_grab_v1; -pub mod ipc; -pub mod jay_acceptor_request; pub mod jay_client_query; pub mod jay_color_management; pub mod jay_compositor; @@ -75,8 +74,6 @@ pub mod wp_linux_drm_syncobj_manager_v1; pub mod wp_linux_drm_syncobj_timeline_v1; pub mod wp_presentation; pub mod wp_presentation_feedback; -pub mod wp_security_context_manager_v1; -pub mod wp_security_context_v1; pub mod wp_single_pixel_buffer_manager_v1; pub mod wp_tearing_control_manager_v1; pub mod wp_viewporter; diff --git a/src/ifs/ipc.rs b/src/ifs/data_transfer.rs similarity index 89% rename from src/ifs/ipc.rs rename to src/ifs/data_transfer.rs index 58c4adaf..3ef84d0c 100644 --- a/src/ifs/ipc.rs +++ b/src/ifs/data_transfer.rs @@ -1,8 +1,8 @@ +use jay_units::fixed::Fixed; use { crate::{ client::{Client, ClientError, ClientId}, - fixed::Fixed, - ifs::{ipc::x_data_device::XIpcDevice, wl_seat::WlSeatGlobal}, + ifs::{data_transfer::x_data_device::XTransferDevice, wl_seat::WlSeatGlobal}, utils::{ bitflags::BitflagsExt, cell_ext::CellExt, clonecell::CloneCell, numcell::NumCell, smallmap::SmallMap, @@ -38,7 +38,7 @@ linear_ids!(DataSourceIds, DataSourceId, u64); linear_ids!(DataOfferIds, DataOfferId, u64); #[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum IpcLocation { +pub enum TransferLocation { Clipboard, PrimarySelection, } @@ -56,7 +56,7 @@ pub trait DataSource: DynDataSource { pub trait DynDataSource: 'static { fn source_data(&self) -> &SourceData; fn send_send(&self, mime_type: &str, fd: Rc); - fn offer_to_x(self: Rc, dd: &Rc); + fn offer_to_x(self: Rc, dd: &Rc); fn detach_seat(&self, seat: &Rc); fn cancel_unprivileged_offers(&self); @@ -123,13 +123,13 @@ pub trait DynDataOffer: 'static { } } -pub trait IterableIpcVtable: IpcVtable { +pub trait IterableTransferVtable: TransferVtable { fn for_each_device(seat: &WlSeatGlobal, client: ClientId, f: C) where C: FnMut(&Rc); } -pub trait IpcVtable: Sized { +pub trait TransferVtable: Sized { type Device; type Source: DataSource; type Offer: DataOffer; @@ -167,7 +167,7 @@ pub struct OfferData { } #[derive(Debug, Error)] -pub enum IpcError { +pub enum TransferError { #[error("The data source is already attached")] AlreadyAttached, #[error("The data source does not have drag-and-drop actions set")] @@ -248,20 +248,20 @@ pub fn attach_seat( src: &S, seat: &Rc, role: Role, -) -> Result<(), IpcError> { +) -> Result<(), TransferError> { let data = src.source_data(); let mut state = data.state.get(); if state.contains(SOURCE_STATE_USED) { - return Err(IpcError::AlreadyAttached); + return Err(TransferError::AlreadyAttached); } state |= SOURCE_STATE_USED; if role == Role::Dnd { if data.actions.is_none() { - return Err(IpcError::ActionsNotSet); + return Err(TransferError::ActionsNotSet); } } else { if data.actions.is_some() { - return Err(IpcError::ActionsSet); + return Err(TransferError::ActionsSet); } } data.state.set(state); @@ -283,7 +283,7 @@ pub fn cancel_offers(src: &S, cancel_privileged: bool) { data.offers.replace(offers); } -pub fn cancel_offer(offer: &T::Offer) { +pub fn cancel_offer(offer: &T::Offer) { let data = offer.offer_data(); data.source.take(); destroy_data_offer::(&offer); @@ -299,7 +299,7 @@ pub fn detach_seat(src: &S, seat: &Rc) { // data.client.flush(); } -fn offer_source_to_device( +fn offer_source_to_device( src: &Rc, dd: &Rc, data: &SourceData, @@ -335,9 +335,9 @@ fn offer_source_to_device( } } -fn offer_source_to_x(src: Rc, dd: &Rc) +fn offer_source_to_x(src: Rc, dd: &Rc) where - T: IpcVtable, + T: TransferVtable, { let data = src.source_data(); src.cancel_unprivileged_offers(); @@ -348,7 +348,7 @@ where pub fn offer_source_to_data_control_device(src: Rc, dd: &Rc) where - T: IpcVtable, + T: TransferVtable, { let data = src.source_data(); let shared = data.shared.get(); @@ -356,7 +356,7 @@ where offer_source_to_device::(&src, dd, data, shared); } -pub fn offer_source_to_regular_client( +pub fn offer_source_to_regular_client( src: Rc, client: &Rc, ) { @@ -376,7 +376,7 @@ pub fn offer_source_to_regular_client( }); } -pub fn add_data_source_mime_type(src: &T::Source, mime_type: &str) { +pub fn add_data_source_mime_type(src: &T::Source, mime_type: &str) { let data = src.source_data(); if data.mime_types.borrow_mut().insert(mime_type.to_string()) { for (_, offer) in &data.offers { @@ -387,14 +387,14 @@ pub fn add_data_source_mime_type(src: &T::Source, mime_type: &str) } } -pub fn destroy_data_source(src: &T::Source) { +pub fn destroy_data_source(src: &T::Source) { let data = src.source_data(); if let Some(seat) = data.seat.take() { T::unset(&seat, data.role.get()); } } -pub fn destroy_data_offer(offer: &T::Offer) { +pub fn destroy_data_offer(offer: &T::Offer) { let data = offer.offer_data(); if let Some(device) = data.device.take() { let device_data = T::get_device_data(&device); @@ -421,7 +421,7 @@ pub fn destroy_data_offer(offer: &T::Offer) { } } -pub fn destroy_data_device(dd: &T::Device) { +pub fn destroy_data_device(dd: &T::Device) { let data = T::get_device_data(dd); let offers = [data.selection.take(), data.dnd.take()]; for offer in offers.into_iter().flat_map(|o| o.into_iter()) { @@ -430,7 +430,7 @@ pub fn destroy_data_device(dd: &T::Device) { } } -fn break_source_loops(src: &T::Source) { +fn break_source_loops(src: &T::Source) { let data = src.source_data(); let mut remove = SmallVec::<[DataOfferId; 1]>::new(); for (id, offer) in &data.offers { @@ -444,20 +444,20 @@ fn break_source_loops(src: &T::Source) { destroy_data_source::(src); } -fn break_offer_loops(offer: &T::Offer) { +fn break_offer_loops(offer: &T::Offer) { let data = offer.offer_data(); data.device.set(None); destroy_data_offer::(offer); } -fn break_device_loops(dd: &T::Device) { +fn break_device_loops(dd: &T::Device) { let data = T::get_device_data(dd); data.selection.take(); data.dnd.take(); destroy_data_device::(dd); } -pub fn receive_data_offer(offer: &T::Offer, mime_type: &str, fd: Rc) { +pub fn receive_data_offer(offer: &T::Offer, mime_type: &str, fd: Rc) { let data = offer.offer_data(); if let Some(src) = data.source.get() { src.send_send(mime_type, fd); diff --git a/src/ifs/ipc/data_control.rs b/src/ifs/data_transfer/data_control.rs similarity index 84% rename from src/ifs/ipc/data_control.rs rename to src/ifs/data_transfer/data_control.rs index 26fe691c..2ea54734 100644 --- a/src/ifs/ipc/data_control.rs +++ b/src/ifs/data_transfer/data_control.rs @@ -1,5 +1,5 @@ use { - crate::ifs::ipc::{DynDataSource, IpcLocation}, + crate::ifs::data_transfer::{DynDataSource, TransferLocation}, std::rc::Rc, }; @@ -20,7 +20,7 @@ pub trait DynDataControlDevice { fn handle_new_source( self: Rc, - location: IpcLocation, + location: TransferLocation, source: Option>, ); } diff --git a/src/ifs/ipc/data_control/ext_data_control_device_v1.rs b/src/ifs/data_transfer/data_control/ext_data_control_device_v1.rs similarity index 86% rename from src/ifs/ipc/data_control/ext_data_control_device_v1.rs rename to src/ifs/data_transfer/data_control/ext_data_control_device_v1.rs index 2765e45c..3c5d31b1 100644 --- a/src/ifs/ipc/data_control/ext_data_control_device_v1.rs +++ b/src/ifs/data_transfer/data_control/ext_data_control_device_v1.rs @@ -2,11 +2,11 @@ use { crate::{ client::Client, ifs::{ - ipc::data_control::{ + data_transfer::data_control::{ ext_data_control_offer_v1::ExtDataControlOfferV1, ext_data_control_source_v1::ExtDataControlSourceV1, private::{ - DataControlDevice, DataControlDeviceData, DataControlIpc, DataControlOfferData, + DataControlDevice, DataControlDeviceData, DataControlProtocol, DataControlOfferData, logic::{self, DataControlError}, }, }, @@ -25,7 +25,7 @@ use { pub struct ExtDataControlDeviceV1 { pub id: ExtDataControlDeviceV1Id, - pub data: DataControlDeviceData, + pub data: DataControlDeviceData, pub tracker: Tracker, } @@ -97,9 +97,9 @@ impl ExtDataControlDeviceV1RequestHandler for ExtDataControlDeviceV1 { } } -pub struct ExtDataControlIpc; +pub struct ExtDataControlProtocol; -impl DataControlIpc for ExtDataControlIpc { +impl DataControlProtocol for ExtDataControlProtocol { const PRIMARY_SELECTION_SINCE: Version = Version(1); type Device = ExtDataControlDeviceV1; type OfferId = ExtDataControlOfferV1Id; @@ -119,21 +119,21 @@ impl DataControlIpc for ExtDataControlIpc { } impl DataControlDevice for ExtDataControlDeviceV1 { - type Ipc = ExtDataControlIpc; + type Protocol = ExtDataControlProtocol; - fn data(&self) -> &DataControlDeviceData { + fn data(&self) -> &DataControlDeviceData { &self.data } - fn send_data_offer(&self, offer: &Rc<::Offer>) { + fn send_data_offer(&self, offer: &Rc<::Offer>) { self.send_data_offer(offer) } - fn send_selection(&self, offer: Option<&Rc<::Offer>>) { + fn send_selection(&self, offer: Option<&Rc<::Offer>>) { self.send_selection(offer) } - fn send_primary_selection(&self, offer: Option<&Rc<::Offer>>) { + fn send_primary_selection(&self, offer: Option<&Rc<::Offer>>) { self.send_primary_selection(offer) } } diff --git a/src/ifs/ipc/data_control/ext_data_control_manager_v1.rs b/src/ifs/data_transfer/data_control/ext_data_control_manager_v1.rs similarity index 90% rename from src/ifs/ipc/data_control/ext_data_control_manager_v1.rs rename to src/ifs/data_transfer/data_control/ext_data_control_manager_v1.rs index a1d60588..f7ba13bc 100644 --- a/src/ifs/ipc/data_control/ext_data_control_manager_v1.rs +++ b/src/ifs/data_transfer/data_control/ext_data_control_manager_v1.rs @@ -1,9 +1,9 @@ use { crate::{ - client::{CAP_DATA_CONTROL_MANAGER, Client, ClientCaps, ClientError}, + client::{Client, ClientError}, globals::{Global, GlobalName}, - ifs::ipc::{ - IpcLocation, + ifs::data_transfer::{ + TransferLocation, data_control::{ DynDataControlDevice, ext_data_control_device_v1::ExtDataControlDeviceV1, ext_data_control_source_v1::ExtDataControlSourceV1, @@ -81,9 +81,9 @@ impl ExtDataControlManagerV1RequestHandler for ExtDataControlManagerV1 { seat.global.add_data_control_device(dev.clone()); self.client.add_client_obj(&dev)?; dev.clone() - .handle_new_source(IpcLocation::Clipboard, seat.global.get_selection()); + .handle_new_source(TransferLocation::Clipboard, seat.global.get_selection()); dev.clone().handle_new_source( - IpcLocation::PrimarySelection, + TransferLocation::PrimarySelection, seat.global.get_primary_selection(), ); Ok(()) @@ -105,10 +105,6 @@ impl Global for ExtDataControlManagerV1Global { fn version(&self) -> u32 { 1 } - - fn required_caps(&self) -> ClientCaps { - CAP_DATA_CONTROL_MANAGER - } } simple_add_global!(ExtDataControlManagerV1Global); diff --git a/src/ifs/ipc/data_control/ext_data_control_offer_v1.rs b/src/ifs/data_transfer/data_control/ext_data_control_offer_v1.rs similarity index 85% rename from src/ifs/ipc/data_control/ext_data_control_offer_v1.rs rename to src/ifs/data_transfer/data_control/ext_data_control_offer_v1.rs index 66985730..1d83ac93 100644 --- a/src/ifs/ipc/data_control/ext_data_control_offer_v1.rs +++ b/src/ifs/data_transfer/data_control/ext_data_control_offer_v1.rs @@ -1,7 +1,7 @@ use { crate::{ - ifs::ipc::data_control::{ - ext_data_control_device_v1::ExtDataControlIpc, + ifs::data_transfer::data_control::{ + ext_data_control_device_v1::ExtDataControlProtocol, private::{ DataControlOffer, DataControlOfferData, logic::{self, DataControlError}, @@ -17,14 +17,14 @@ use { pub struct ExtDataControlOfferV1 { pub id: ExtDataControlOfferV1Id, - pub data: DataControlOfferData, + pub data: DataControlOfferData, pub tracker: Tracker, } impl DataControlOffer for ExtDataControlOfferV1 { - type Ipc = ExtDataControlIpc; + type Protocol = ExtDataControlProtocol; - fn data(&self) -> &DataControlOfferData { + fn data(&self) -> &DataControlOfferData { &self.data } diff --git a/src/ifs/ipc/data_control/ext_data_control_source_v1.rs b/src/ifs/data_transfer/data_control/ext_data_control_source_v1.rs similarity index 90% rename from src/ifs/ipc/data_control/ext_data_control_source_v1.rs rename to src/ifs/data_transfer/data_control/ext_data_control_source_v1.rs index 50ea99d5..13c7e468 100644 --- a/src/ifs/ipc/data_control/ext_data_control_source_v1.rs +++ b/src/ifs/data_transfer/data_control/ext_data_control_source_v1.rs @@ -1,10 +1,10 @@ use { crate::{ client::Client, - ifs::ipc::{ - IpcLocation, SourceData, + ifs::data_transfer::{ + TransferLocation, SourceData, data_control::{ - ext_data_control_device_v1::ExtDataControlIpc, + ext_data_control_device_v1::ExtDataControlProtocol, private::{ DataControlSource, DataControlSourceData, logic::{self, DataControlError}, @@ -27,7 +27,7 @@ pub struct ExtDataControlSourceV1 { } impl DataControlSource for ExtDataControlSourceV1 { - type Ipc = ExtDataControlIpc; + type Protocol = ExtDataControlProtocol; fn data(&self) -> &DataControlSourceData { &self.data @@ -49,7 +49,7 @@ impl ExtDataControlSourceV1 { data: DataControlSourceData { data: SourceData::new(client), version, - location: Cell::new(IpcLocation::Clipboard), + location: Cell::new(TransferLocation::Clipboard), used: Cell::new(false), }, tracker: Default::default(), diff --git a/src/ifs/ipc/data_control/private.rs b/src/ifs/data_transfer/data_control/private.rs similarity index 60% rename from src/ifs/ipc/data_control/private.rs rename to src/ifs/data_transfer/data_control/private.rs index bb5aeb47..0b475dd1 100644 --- a/src/ifs/ipc/data_control/private.rs +++ b/src/ifs/data_transfer/data_control/private.rs @@ -2,12 +2,12 @@ use { crate::{ client::{Client, ClientError, ClientId, WaylandObject, WaylandObjectLookup}, ifs::{ - ipc::{ + data_transfer::{ DataOffer, DataOfferId, DataSource, DeviceData, DynDataOffer, DynDataSource, - IpcLocation, IpcVtable, OfferData, Role, SourceData, cancel_offer, cancel_offers, + TransferLocation, TransferVtable, OfferData, Role, SourceData, cancel_offer, cancel_offers, data_control::{DataControlDeviceId, DynDataControlDevice}, detach_seat, offer_source_to_data_control_device, offer_source_to_x, - x_data_device::{XClipboardIpc, XIpcDevice, XPrimarySelectionIpc}, + x_data_device::{XClipboardTransfer, XTransferDevice, XPrimarySelectionTransfer}, }, wl_seat::WlSeatGlobal, }, @@ -19,26 +19,26 @@ use { struct ClipboardCore(PhantomData); struct PrimarySelectionCore(PhantomData); -struct DataControlIpcImpl(PhantomData); +struct DataControlProtocolImpl(PhantomData); -type Device = ::Device; -type Offer = ::Offer; -type Source = ::Source; -type SourceId = ::SourceId; +type Device = ::Device; +type Offer = ::Offer; +type Source = ::Source; +type SourceId = ::SourceId; -pub trait DataControlIpc: Sized + 'static { +pub trait DataControlProtocol: Sized + 'static { const PRIMARY_SELECTION_SINCE: Version; - type Device: DataControlDevice; + type Device: DataControlDevice; type OfferId: From; - type Offer: DataControlOffer; + type Offer: DataControlOffer; type SourceId: WaylandObjectLookup; - type Source: DataControlSource; + type Source: DataControlSource; fn create_offer(id: Self::OfferId, data: DataControlOfferData) -> Rc; } -pub struct DataControlDeviceData { +pub struct DataControlDeviceData { pub data_control_device_id: DataControlDeviceId, pub client: Rc, pub version: Version, @@ -48,29 +48,29 @@ pub struct DataControlDeviceData { } pub trait DataControlDevice: WaylandObject { - type Ipc: DataControlIpc; + type Protocol: DataControlProtocol; - fn data(&self) -> &DataControlDeviceData; + fn data(&self) -> &DataControlDeviceData; - fn send_data_offer(&self, offer: &Rc>); + fn send_data_offer(&self, offer: &Rc>); - fn send_selection(&self, offer: Option<&Rc>>); + fn send_selection(&self, offer: Option<&Rc>>); - fn send_primary_selection(&self, offer: Option<&Rc>>); + fn send_primary_selection(&self, offer: Option<&Rc>>); } -pub struct DataControlOfferData { +pub struct DataControlOfferData { pub offer_id: DataOfferId, pub client: Rc, pub device: Rc, pub data: OfferData, - pub location: IpcLocation, + pub location: TransferLocation, } pub trait DataControlOffer: WaylandObject { - type Ipc: DataControlIpc; + type Protocol: DataControlProtocol; - fn data(&self) -> &DataControlOfferData; + fn data(&self) -> &DataControlOfferData; fn send_offer(&self, mime_type: &str); } @@ -78,12 +78,12 @@ pub trait DataControlOffer: WaylandObject { pub struct DataControlSourceData { pub data: SourceData, pub version: Version, - pub location: Cell, + pub location: Cell, pub used: Cell, } pub trait DataControlSource: WaylandObject { - type Ipc: DataControlIpc; + type Protocol: DataControlProtocol; fn data(&self) -> &DataControlSourceData; @@ -99,24 +99,24 @@ impl DynDataControlDevice for T { fn handle_new_source( self: Rc, - location: IpcLocation, + location: TransferLocation, source: Option>, ) { - if location == IpcLocation::PrimarySelection - && self.data().version < T::Ipc::PRIMARY_SELECTION_SINCE + if location == TransferLocation::PrimarySelection + && self.data().version < T::Protocol::PRIMARY_SELECTION_SINCE { return; } match location { - IpcLocation::Clipboard => match source { + TransferLocation::Clipboard => match source { Some(src) => { - offer_source_to_data_control_device::>(src, &self); + offer_source_to_data_control_device::>(src, &self); } _ => self.send_selection(None), }, - IpcLocation::PrimarySelection => match source { + TransferLocation::PrimarySelection => match source { Some(src) => { - offer_source_to_data_control_device::>(src, &self); + offer_source_to_data_control_device::>(src, &self); } _ => self.send_primary_selection(None), }, @@ -124,29 +124,29 @@ impl DynDataControlDevice for T { } } -type Clipboard = DataControlIpcImpl>; -type PrimarySelection = DataControlIpcImpl>; +type Clipboard = DataControlProtocolImpl>; +type PrimarySelection = DataControlProtocolImpl>; -pub trait DataControlLocationIpc { - type Ipc: DataControlIpc; - const LOCATION: IpcLocation; +pub trait DataControlLocation { + type Protocol: DataControlProtocol; + const LOCATION: TransferLocation; - fn loc_get_device_data(dd: &Device) -> &DeviceData>; + fn loc_get_device_data(dd: &Device) -> &DeviceData>; - fn loc_send_selection(dd: &Device, offer: Option<&Rc>>); + fn loc_send_selection(dd: &Device, offer: Option<&Rc>>); fn loc_unset(seat: &Rc); } -impl DataControlLocationIpc for ClipboardCore { - type Ipc = T; - const LOCATION: IpcLocation = IpcLocation::Clipboard; +impl DataControlLocation for ClipboardCore { + type Protocol = T; + const LOCATION: TransferLocation = TransferLocation::Clipboard; - fn loc_get_device_data(dd: &Device) -> &DeviceData> { + fn loc_get_device_data(dd: &Device) -> &DeviceData> { &dd.data().clipboard_data } - fn loc_send_selection(dd: &Device, offer: Option<&Rc>>) { + fn loc_send_selection(dd: &Device, offer: Option<&Rc>>) { dd.send_selection(offer) } @@ -155,15 +155,15 @@ impl DataControlLocationIpc for ClipboardCore { } } -impl DataControlLocationIpc for PrimarySelectionCore { - type Ipc = T; - const LOCATION: IpcLocation = IpcLocation::PrimarySelection; +impl DataControlLocation for PrimarySelectionCore { + type Protocol = T; + const LOCATION: TransferLocation = TransferLocation::PrimarySelection; - fn loc_get_device_data(dd: &Device) -> &DeviceData> { + fn loc_get_device_data(dd: &Device) -> &DeviceData> { &dd.data().primary_selection_data } - fn loc_send_selection(dd: &Device, offer: Option<&Rc>>) { + fn loc_send_selection(dd: &Device, offer: Option<&Rc>>) { dd.send_primary_selection(offer) } @@ -172,10 +172,10 @@ impl DataControlLocationIpc for PrimarySelectionCore { } } -impl IpcVtable for DataControlIpcImpl { - type Device = Device; - type Source = Source; - type Offer = Offer; +impl TransferVtable for DataControlProtocolImpl { + type Device = Device; + type Source = Source; + type Offer = Offer; fn get_device_data(dd: &Self::Device) -> &DeviceData { T::loc_get_device_data(dd) @@ -197,7 +197,7 @@ impl IpcVtable for DataControlIpcImpl { data: offer_data, location: T::LOCATION, }; - let rc = T::Ipc::create_offer(data.client.new_id()?, offer); + let rc = T::Protocol::create_offer(data.client.new_id()?, offer); data.client.add_server_obj(&rc); Ok(rc) } @@ -234,10 +234,10 @@ impl DynDataSource for T { self.send_send(mime_type, fd); } - fn offer_to_x(self: Rc, dd: &Rc) { + fn offer_to_x(self: Rc, dd: &Rc) { match self.data().location.get() { - IpcLocation::Clipboard => offer_source_to_x::(self, dd), - IpcLocation::PrimarySelection => offer_source_to_x::(self, dd), + TransferLocation::Clipboard => offer_source_to_x::(self, dd), + TransferLocation::PrimarySelection => offer_source_to_x::(self, dd), } } @@ -251,7 +251,7 @@ impl DynDataSource for T { } impl DataOffer for T { - type Device = Device; + type Device = Device; fn offer_data(&self) -> &OfferData { &self.data().data @@ -273,8 +273,8 @@ impl DynDataOffer for T { fn cancel(&self) { match self.data().location { - IpcLocation::Clipboard => cancel_offer::>(self), - IpcLocation::PrimarySelection => cancel_offer::>(self), + TransferLocation::Clipboard => cancel_offer::>(self), + TransferLocation::PrimarySelection => cancel_offer::>(self), } } @@ -292,8 +292,8 @@ pub mod logic { crate::{ client::ClientError, ifs::{ - ipc::{ - IpcLocation, add_data_source_mime_type, break_device_loops, break_offer_loops, + data_transfer::{ + TransferLocation, add_data_source_mime_type, break_device_loops, break_offer_loops, break_source_loops, data_control::private::{ Clipboard, DataControlDevice, DataControlOffer, DataControlSource, @@ -311,16 +311,16 @@ pub mod logic { }; pub fn data_device_break_loops(d: &D) { - break_device_loops::>(d); - break_device_loops::>(d); + break_device_loops::>(d); + break_device_loops::>(d); d.data().seat.remove_data_control_device(d); } fn use_source( device: &D, - source: Option>, - location: IpcLocation, - ) -> Result>>, DataControlError> { + source: Option>, + location: TransferLocation, + ) -> Result>>, DataControlError> { if let Some(source) = source { let src = device.data().client.lookup(source)?; if src.data().used.replace(true) { @@ -335,16 +335,16 @@ pub mod logic { pub fn device_set_selection( d: &D, - source: Option>, + source: Option>, ) -> Result<(), DataControlError> { - let src = use_source(d, source, IpcLocation::Clipboard)?; + let src = use_source(d, source, TransferLocation::Clipboard)?; d.data().seat.set_selection(src)?; Ok(()) } pub fn device_destroy(d: &D) -> Result<(), DataControlError> { - destroy_data_device::>(d); - destroy_data_device::>(d); + destroy_data_device::>(d); + destroy_data_device::>(d); d.data().seat.remove_data_control_device(d); d.data().client.remove_obj(d)?; Ok(()) @@ -352,9 +352,9 @@ pub mod logic { pub fn device_set_primary_selection( d: &D, - source: Option>, + source: Option>, ) -> Result<(), DataControlError> { - let src = use_source(d, source, IpcLocation::PrimarySelection)?; + let src = use_source(d, source, TransferLocation::PrimarySelection)?; d.data().seat.set_primary_selection(src)?; Ok(()) } @@ -366,14 +366,14 @@ pub mod logic { if s.data().used.get() { return Err(DataControlError::AlreadyUsed); } - add_data_source_mime_type::>(s, mime_type); + add_data_source_mime_type::>(s, mime_type); Ok(()) } pub fn data_source_destroy(s: &S) -> Result<(), DataControlError> { match s.data().location.get() { - IpcLocation::Clipboard => destroy_data_source::>(s), - IpcLocation::PrimarySelection => destroy_data_source::>(s), + TransferLocation::Clipboard => destroy_data_source::>(s), + TransferLocation::PrimarySelection => destroy_data_source::>(s), } s.data().data.client.remove_obj(s)?; Ok(()) @@ -381,24 +381,24 @@ pub mod logic { pub fn data_source_break_loops(s: &S) { match s.data().location.get() { - IpcLocation::Clipboard => break_source_loops::>(s), - IpcLocation::PrimarySelection => break_source_loops::>(s), + TransferLocation::Clipboard => break_source_loops::>(s), + TransferLocation::PrimarySelection => break_source_loops::>(s), } } pub fn data_offer_receive(o: &O, mime_type: &str, fd: Rc) { match o.data().location { - IpcLocation::Clipboard => receive_data_offer::>(o, mime_type, fd), - IpcLocation::PrimarySelection => { - receive_data_offer::>(o, mime_type, fd) + TransferLocation::Clipboard => receive_data_offer::>(o, mime_type, fd), + TransferLocation::PrimarySelection => { + receive_data_offer::>(o, mime_type, fd) } } } pub fn data_offer_destroy(o: &O) -> Result<(), DataControlError> { match o.data().location { - IpcLocation::Clipboard => destroy_data_offer::>(o), - IpcLocation::PrimarySelection => destroy_data_offer::>(o), + TransferLocation::Clipboard => destroy_data_offer::>(o), + TransferLocation::PrimarySelection => destroy_data_offer::>(o), } o.data().client.remove_obj(o)?; Ok(()) @@ -406,8 +406,8 @@ pub mod logic { pub fn data_offer_break_loops(o: &O) { match o.data().location { - IpcLocation::Clipboard => break_offer_loops::>(o), - IpcLocation::PrimarySelection => break_offer_loops::>(o), + TransferLocation::Clipboard => break_offer_loops::>(o), + TransferLocation::PrimarySelection => break_offer_loops::>(o), } } diff --git a/src/ifs/ipc/data_control/zwlr_data_control_device_v1.rs b/src/ifs/data_transfer/data_control/zwlr_data_control_device_v1.rs similarity index 86% rename from src/ifs/ipc/data_control/zwlr_data_control_device_v1.rs rename to src/ifs/data_transfer/data_control/zwlr_data_control_device_v1.rs index f4683217..db2e8a96 100644 --- a/src/ifs/ipc/data_control/zwlr_data_control_device_v1.rs +++ b/src/ifs/data_transfer/data_control/zwlr_data_control_device_v1.rs @@ -2,9 +2,9 @@ use { crate::{ client::Client, ifs::{ - ipc::data_control::{ + data_transfer::data_control::{ private::{ - DataControlDevice, DataControlDeviceData, DataControlIpc, DataControlOfferData, + DataControlDevice, DataControlDeviceData, DataControlProtocol, DataControlOfferData, logic::{self, DataControlError}, }, zwlr_data_control_offer_v1::ZwlrDataControlOfferV1, @@ -27,7 +27,7 @@ pub const PRIMARY_SELECTION_SINCE: Version = Version(2); pub struct ZwlrDataControlDeviceV1 { pub id: ZwlrDataControlDeviceV1Id, - pub data: DataControlDeviceData, + pub data: DataControlDeviceData, pub tracker: Tracker, } @@ -103,9 +103,9 @@ impl ZwlrDataControlDeviceV1RequestHandler for ZwlrDataControlDeviceV1 { } } -pub struct WlrDataControlIpc; +pub struct WlrDataControlProtocol; -impl DataControlIpc for WlrDataControlIpc { +impl DataControlProtocol for WlrDataControlProtocol { const PRIMARY_SELECTION_SINCE: Version = PRIMARY_SELECTION_SINCE; type Device = ZwlrDataControlDeviceV1; type OfferId = ZwlrDataControlOfferV1Id; @@ -125,21 +125,21 @@ impl DataControlIpc for WlrDataControlIpc { } impl DataControlDevice for ZwlrDataControlDeviceV1 { - type Ipc = WlrDataControlIpc; + type Protocol = WlrDataControlProtocol; - fn data(&self) -> &DataControlDeviceData { + fn data(&self) -> &DataControlDeviceData { &self.data } - fn send_data_offer(&self, offer: &Rc<::Offer>) { + fn send_data_offer(&self, offer: &Rc<::Offer>) { self.send_data_offer(offer) } - fn send_selection(&self, offer: Option<&Rc<::Offer>>) { + fn send_selection(&self, offer: Option<&Rc<::Offer>>) { self.send_selection(offer) } - fn send_primary_selection(&self, offer: Option<&Rc<::Offer>>) { + fn send_primary_selection(&self, offer: Option<&Rc<::Offer>>) { self.send_primary_selection(offer) } } diff --git a/src/ifs/ipc/data_control/zwlr_data_control_manager_v1.rs b/src/ifs/data_transfer/data_control/zwlr_data_control_manager_v1.rs similarity index 90% rename from src/ifs/ipc/data_control/zwlr_data_control_manager_v1.rs rename to src/ifs/data_transfer/data_control/zwlr_data_control_manager_v1.rs index b79b4a0f..c737b248 100644 --- a/src/ifs/ipc/data_control/zwlr_data_control_manager_v1.rs +++ b/src/ifs/data_transfer/data_control/zwlr_data_control_manager_v1.rs @@ -1,9 +1,9 @@ use { crate::{ - client::{CAP_DATA_CONTROL_MANAGER, Client, ClientCaps, ClientError}, + client::{Client, ClientError}, globals::{Global, GlobalName}, - ifs::ipc::{ - IpcLocation, + ifs::data_transfer::{ + TransferLocation, data_control::{ DynDataControlDevice, zwlr_data_control_device_v1::ZwlrDataControlDeviceV1, zwlr_data_control_source_v1::ZwlrDataControlSourceV1, @@ -81,9 +81,9 @@ impl ZwlrDataControlManagerV1RequestHandler for ZwlrDataControlManagerV1 { seat.global.add_data_control_device(dev.clone()); self.client.add_client_obj(&dev)?; dev.clone() - .handle_new_source(IpcLocation::Clipboard, seat.global.get_selection()); + .handle_new_source(TransferLocation::Clipboard, seat.global.get_selection()); dev.clone().handle_new_source( - IpcLocation::PrimarySelection, + TransferLocation::PrimarySelection, seat.global.get_primary_selection(), ); Ok(()) @@ -105,10 +105,6 @@ impl Global for ZwlrDataControlManagerV1Global { fn version(&self) -> u32 { 2 } - - fn required_caps(&self) -> ClientCaps { - CAP_DATA_CONTROL_MANAGER - } } simple_add_global!(ZwlrDataControlManagerV1Global); diff --git a/src/ifs/ipc/data_control/zwlr_data_control_offer_v1.rs b/src/ifs/data_transfer/data_control/zwlr_data_control_offer_v1.rs similarity index 85% rename from src/ifs/ipc/data_control/zwlr_data_control_offer_v1.rs rename to src/ifs/data_transfer/data_control/zwlr_data_control_offer_v1.rs index f806576f..51457f14 100644 --- a/src/ifs/ipc/data_control/zwlr_data_control_offer_v1.rs +++ b/src/ifs/data_transfer/data_control/zwlr_data_control_offer_v1.rs @@ -1,11 +1,11 @@ use { crate::{ - ifs::ipc::data_control::{ + ifs::data_transfer::data_control::{ private::{ DataControlOffer, DataControlOfferData, logic::{self, DataControlError}, }, - zwlr_data_control_device_v1::WlrDataControlIpc, + zwlr_data_control_device_v1::WlrDataControlProtocol, }, leaks::Tracker, object::Object, @@ -17,14 +17,14 @@ use { pub struct ZwlrDataControlOfferV1 { pub id: ZwlrDataControlOfferV1Id, - pub data: DataControlOfferData, + pub data: DataControlOfferData, pub tracker: Tracker, } impl DataControlOffer for ZwlrDataControlOfferV1 { - type Ipc = WlrDataControlIpc; + type Protocol = WlrDataControlProtocol; - fn data(&self) -> &DataControlOfferData { + fn data(&self) -> &DataControlOfferData { &self.data } diff --git a/src/ifs/ipc/data_control/zwlr_data_control_source_v1.rs b/src/ifs/data_transfer/data_control/zwlr_data_control_source_v1.rs similarity index 90% rename from src/ifs/ipc/data_control/zwlr_data_control_source_v1.rs rename to src/ifs/data_transfer/data_control/zwlr_data_control_source_v1.rs index 13beb7a9..4a8b81c2 100644 --- a/src/ifs/ipc/data_control/zwlr_data_control_source_v1.rs +++ b/src/ifs/data_transfer/data_control/zwlr_data_control_source_v1.rs @@ -1,14 +1,14 @@ use { crate::{ client::Client, - ifs::ipc::{ - IpcLocation, SourceData, + ifs::data_transfer::{ + TransferLocation, SourceData, data_control::{ private::{ DataControlSource, DataControlSourceData, logic::{self, DataControlError}, }, - zwlr_data_control_device_v1::WlrDataControlIpc, + zwlr_data_control_device_v1::WlrDataControlProtocol, }, }, leaks::Tracker, @@ -27,7 +27,7 @@ pub struct ZwlrDataControlSourceV1 { } impl DataControlSource for ZwlrDataControlSourceV1 { - type Ipc = WlrDataControlIpc; + type Protocol = WlrDataControlProtocol; fn data(&self) -> &DataControlSourceData { &self.data @@ -49,7 +49,7 @@ impl ZwlrDataControlSourceV1 { data: DataControlSourceData { data: SourceData::new(client), version, - location: Cell::new(IpcLocation::Clipboard), + location: Cell::new(TransferLocation::Clipboard), used: Cell::new(false), }, tracker: Default::default(), diff --git a/src/ifs/ipc/wl_data_device.rs b/src/ifs/data_transfer/wl_data_device.rs similarity index 92% rename from src/ifs/ipc/wl_data_device.rs rename to src/ifs/data_transfer/wl_data_device.rs index 67f75540..f42bfb3d 100644 --- a/src/ifs/ipc/wl_data_device.rs +++ b/src/ifs/data_transfer/wl_data_device.rs @@ -1,11 +1,12 @@ +use jay_units::fixed::Fixed; use { crate::{ client::{Client, ClientError, ClientId}, - fixed::Fixed, ifs::{ - ipc::{ - DeviceData, IpcVtable, IterableIpcVtable, OfferData, Role, break_device_loops, - destroy_data_device, wl_data_offer::WlDataOffer, wl_data_source::WlDataSource, + data_transfer::{ + DeviceData, IterableTransferVtable, OfferData, Role, TransferVtable, + break_device_loops, destroy_data_device, wl_data_offer::WlDataOffer, + wl_data_source::WlDataSource, }, wl_seat::{WlSeatError, WlSeatGlobal}, wl_surface::WlSurfaceError, @@ -141,16 +142,16 @@ impl WlDataDeviceRequestHandler for WlDataDevice { } fn release(&self, _req: Release, _slf: &Rc) -> Result<(), Self::Error> { - destroy_data_device::(self); + destroy_data_device::(self); self.seat.remove_data_device(self); self.client.remove_obj(self)?; Ok(()) } } -pub struct ClipboardIpc; +pub struct ClipboardTransfer; -impl IterableIpcVtable for ClipboardIpc { +impl IterableTransferVtable for ClipboardTransfer { fn for_each_device(seat: &WlSeatGlobal, client: ClientId, f: C) where C: FnMut(&Rc), @@ -159,7 +160,7 @@ impl IterableIpcVtable for ClipboardIpc { } } -impl IpcVtable for ClipboardIpc { +impl TransferVtable for ClipboardTransfer { type Device = WlDataDevice; type Source = WlDataSource; type Offer = WlDataOffer; @@ -216,7 +217,7 @@ object_base! { impl Object for WlDataDevice { fn break_loops(&self) { - break_device_loops::(self); + break_device_loops::(self); self.seat.remove_data_device(self); } } diff --git a/src/ifs/ipc/wl_data_device_manager.rs b/src/ifs/data_transfer/wl_data_device_manager.rs similarity index 96% rename from src/ifs/ipc/wl_data_device_manager.rs rename to src/ifs/data_transfer/wl_data_device_manager.rs index 922629f0..30e68ef7 100644 --- a/src/ifs/ipc/wl_data_device_manager.rs +++ b/src/ifs/data_transfer/wl_data_device_manager.rs @@ -2,7 +2,7 @@ use { crate::{ client::{Client, ClientError}, globals::{Global, GlobalName}, - ifs::ipc::{wl_data_device::WlDataDevice, wl_data_source::WlDataSource}, + ifs::data_transfer::{wl_data_device::WlDataDevice, wl_data_source::WlDataSource}, leaks::Tracker, object::{Object, Version}, wire::{WlDataDeviceManagerId, wl_data_device_manager::*}, diff --git a/src/ifs/ipc/wl_data_offer.rs b/src/ifs/data_transfer/wl_data_offer.rs similarity index 94% rename from src/ifs/ipc/wl_data_offer.rs rename to src/ifs/data_transfer/wl_data_offer.rs index d043708c..6f835c4b 100644 --- a/src/ifs/ipc/wl_data_offer.rs +++ b/src/ifs/data_transfer/wl_data_offer.rs @@ -1,13 +1,13 @@ +use jay_units::fixed::Fixed; use { crate::{ client::{Client, ClientError, ClientId}, - fixed::Fixed, ifs::{ - ipc::{ + data_transfer::{ DataOffer, DataOfferId, DynDataOffer, OFFER_STATE_ACCEPTED, OFFER_STATE_DROPPED, OFFER_STATE_FINISHED, OfferData, Role, SOURCE_STATE_FINISHED, break_offer_loops, cancel_offer, destroy_data_offer, receive_data_offer, - wl_data_device::{ClipboardIpc, WlDataDevice}, + wl_data_device::{ClipboardTransfer, WlDataDevice}, wl_data_device_manager::DND_ALL, }, wl_seat::WlSeatGlobal, @@ -65,7 +65,7 @@ impl DynDataOffer for WlDataOffer { } fn cancel(&self) { - cancel_offer::(self); + cancel_offer::(self); } fn send_enter(&self, surface: WlSurfaceId, x: Fixed, y: Fixed, serial: u64) { @@ -133,12 +133,12 @@ impl WlDataOfferRequestHandler for WlDataOffer { if self.data.shared.state.get().contains(OFFER_STATE_FINISHED) { return Err(WlDataOfferError::AlreadyFinished); } - receive_data_offer::(self, req.mime_type, req.fd); + receive_data_offer::(self, req.mime_type, req.fd); Ok(()) } fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { - destroy_data_offer::(self); + destroy_data_offer::(self); self.client.remove_obj(self)?; Ok(()) } @@ -198,7 +198,7 @@ object_base! { impl Object for WlDataOffer { fn break_loops(&self) { - break_offer_loops::(self); + break_offer_loops::(self); } } diff --git a/src/ifs/ipc/wl_data_source.rs b/src/ifs/data_transfer/wl_data_source.rs similarity index 93% rename from src/ifs/ipc/wl_data_source.rs rename to src/ifs/data_transfer/wl_data_source.rs index 0693527b..351b8162 100644 --- a/src/ifs/ipc/wl_data_source.rs +++ b/src/ifs/data_transfer/wl_data_source.rs @@ -2,14 +2,14 @@ use { crate::{ client::{Client, ClientError}, ifs::{ - ipc::{ + data_transfer::{ DataSource, DynDataOffer, DynDataSource, OFFER_STATE_ACCEPTED, OFFER_STATE_DROPPED, SOURCE_STATE_CANCELLED, SOURCE_STATE_DROPPED, SharedState, SourceData, add_data_source_mime_type, break_source_loops, cancel_offers, destroy_data_source, detach_seat, offer_source_to_x, - wl_data_device::ClipboardIpc, + wl_data_device::ClipboardTransfer, wl_data_device_manager::{DND_ALL, DND_NONE}, - x_data_device::{XClipboardIpc, XIpcDevice}, + x_data_device::{XClipboardTransfer, XTransferDevice}, }, wl_seat::WlSeatGlobal, xdg_toplevel_drag_v1::XdgToplevelDragV1, @@ -52,8 +52,8 @@ impl DynDataSource for WlDataSource { WlDataSource::send_send(self, mime_type, fd); } - fn offer_to_x(self: Rc, dd: &Rc) { - offer_source_to_x::(self, dd); + fn offer_to_x(self: Rc, dd: &Rc) { + offer_source_to_x::(self, dd); } fn detach_seat(&self, seat: &Rc) { @@ -201,12 +201,12 @@ impl WlDataSourceRequestHandler for WlDataSource { type Error = WlDataSourceError; fn offer(&self, req: Offer, _slf: &Rc) -> Result<(), Self::Error> { - add_data_source_mime_type::(self, req.mime_type); + add_data_source_mime_type::(self, req.mime_type); Ok(()) } fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { - destroy_data_source::(self); + destroy_data_source::(self); self.data.client.remove_obj(self)?; Ok(()) } @@ -230,7 +230,7 @@ object_base! { impl Object for WlDataSource { fn break_loops(&self) { - break_source_loops::(self); + break_source_loops::(self); self.toplevel_drag.take(); } } diff --git a/src/ifs/ipc/x_data_device.rs b/src/ifs/data_transfer/x_data_device.rs similarity index 66% rename from src/ifs/ipc/x_data_device.rs rename to src/ifs/data_transfer/x_data_device.rs index 4eeadc91..7ca8a024 100644 --- a/src/ifs/ipc/x_data_device.rs +++ b/src/ifs/data_transfer/x_data_device.rs @@ -2,8 +2,8 @@ use { crate::{ client::{Client, ClientError}, ifs::{ - ipc::{ - DeviceData, IpcLocation, IpcVtable, OfferData, Role, x_data_offer::XDataOffer, + data_transfer::{ + DeviceData, TransferLocation, TransferVtable, OfferData, Role, x_data_offer::XDataOffer, x_data_source::XDataSource, }, wl_seat::WlSeatGlobal, @@ -11,14 +11,14 @@ use { state::State, xwayland::XWaylandEvent, }, - XWaylandEvent::IpcSetOffer, + XWaylandEvent::DataTransferSetOffer, std::rc::Rc, }; -linear_ids!(XIpcDeviceIds, XIpcDeviceId, u64); +linear_ids!(XTransferDeviceIds, XTransferDeviceId, u64); -pub struct XIpcDevice { - pub id: XIpcDeviceId, +pub struct XTransferDevice { + pub id: XTransferDeviceId, pub clipboard: DeviceData, pub primary_selection: DeviceData, pub seat: Rc, @@ -27,45 +27,45 @@ pub struct XIpcDevice { } #[derive(Default)] -pub struct XClipboardIpc; +pub struct XClipboardTransfer; #[derive(Default)] -pub struct XPrimarySelectionIpc; +pub struct XPrimarySelectionTransfer; -pub trait XIpc { - const LOCATION: IpcLocation; +pub trait XTransfer { + const LOCATION: TransferLocation; fn x_unset(seat: &Rc); - fn x_device_data(dd: &XIpcDevice) -> &DeviceData; + fn x_device_data(dd: &XTransferDevice) -> &DeviceData; } -impl XIpc for XClipboardIpc { - const LOCATION: IpcLocation = IpcLocation::Clipboard; +impl XTransfer for XClipboardTransfer { + const LOCATION: TransferLocation = TransferLocation::Clipboard; fn x_unset(seat: &Rc) { seat.unset_selection(); } - fn x_device_data(dd: &XIpcDevice) -> &DeviceData { + fn x_device_data(dd: &XTransferDevice) -> &DeviceData { &dd.clipboard } } -impl XIpc for XPrimarySelectionIpc { - const LOCATION: IpcLocation = IpcLocation::PrimarySelection; +impl XTransfer for XPrimarySelectionTransfer { + const LOCATION: TransferLocation = TransferLocation::PrimarySelection; fn x_unset(seat: &Rc) { seat.unset_primary_selection(); } - fn x_device_data(dd: &XIpcDevice) -> &DeviceData { + fn x_device_data(dd: &XTransferDevice) -> &DeviceData { &dd.primary_selection } } -impl IpcVtable for T { - type Device = XIpcDevice; +impl TransferVtable for T { + type Device = XTransferDevice; type Source = XDataSource; type Offer = XDataOffer; @@ -97,7 +97,7 @@ impl IpcVtable for T { dd.state .xwayland .queue - .push(XWaylandEvent::IpcSetSelection { + .push(XWaylandEvent::DataTransferSetSelection { seat: dd.seat.id(), location: T::LOCATION, offer: offer.cloned(), @@ -105,7 +105,7 @@ impl IpcVtable for T { } fn send_offer(dd: &Self::Device, offer: &Rc) { - dd.state.xwayland.queue.push(IpcSetOffer { + dd.state.xwayland.queue.push(DataTransferSetOffer { location: T::LOCATION, seat: dd.seat.id(), offer: offer.clone(), diff --git a/src/ifs/ipc/x_data_offer.rs b/src/ifs/data_transfer/x_data_offer.rs similarity index 58% rename from src/ifs/ipc/x_data_offer.rs rename to src/ifs/data_transfer/x_data_offer.rs index d6449714..a8a43cd8 100644 --- a/src/ifs/ipc/x_data_offer.rs +++ b/src/ifs/data_transfer/x_data_offer.rs @@ -2,29 +2,29 @@ use { crate::{ client::ClientId, ifs::{ - ipc::{ - DataOffer, DataOfferId, DynDataOffer, IpcLocation, OfferData, cancel_offer, - x_data_device::{XClipboardIpc, XIpcDevice, XPrimarySelectionIpc}, + data_transfer::{ + DataOffer, DataOfferId, DynDataOffer, TransferLocation, OfferData, cancel_offer, + x_data_device::{XClipboardTransfer, XTransferDevice, XPrimarySelectionTransfer}, }, wl_seat::WlSeatGlobal, }, leaks::Tracker, xwayland::XWaylandEvent, }, - XWaylandEvent::IpcAddOfferMimeType, + XWaylandEvent::DataTransferAddOfferMimeType, std::rc::Rc, }; pub struct XDataOffer { pub offer_id: DataOfferId, - pub device: Rc, - pub data: OfferData, + pub device: Rc, + pub data: OfferData, pub tracker: Tracker, - pub location: IpcLocation, + pub location: TransferLocation, } impl DataOffer for XDataOffer { - type Device = XIpcDevice; + type Device = XTransferDevice; fn offer_data(&self) -> &OfferData { &self.data @@ -41,7 +41,7 @@ impl DynDataOffer for XDataOffer { } fn send_offer(&self, mime_type: &str) { - self.device.state.xwayland.queue.push(IpcAddOfferMimeType { + self.device.state.xwayland.queue.push(DataTransferAddOfferMimeType { location: self.location, seat: self.device.seat.id(), offer: self.offer_id, @@ -51,8 +51,8 @@ impl DynDataOffer for XDataOffer { fn cancel(&self) { match self.location { - IpcLocation::Clipboard => cancel_offer::(self), - IpcLocation::PrimarySelection => cancel_offer::(self), + TransferLocation::Clipboard => cancel_offer::(self), + TransferLocation::PrimarySelection => cancel_offer::(self), } } diff --git a/src/ifs/ipc/x_data_source.rs b/src/ifs/data_transfer/x_data_source.rs similarity index 65% rename from src/ifs/ipc/x_data_source.rs rename to src/ifs/data_transfer/x_data_source.rs index 346d0462..7361a1bf 100644 --- a/src/ifs/ipc/x_data_source.rs +++ b/src/ifs/data_transfer/x_data_source.rs @@ -1,14 +1,14 @@ use { crate::{ ifs::{ - ipc::{ - DataSource, DynDataSource, IpcLocation, SourceData, cancel_offers, detach_seat, - x_data_device::XIpcDevice, + data_transfer::{ + DataSource, DynDataSource, TransferLocation, SourceData, cancel_offers, detach_seat, + x_data_device::XTransferDevice, }, wl_seat::WlSeatGlobal, }, state::State, - xwayland::XWaylandEvent::{IpcCancelSource, IpcSendSource, IpcSetSelection}, + xwayland::XWaylandEvent::{DataTransferCancelSource, DataTransferSendSource, DataTransferSetSelection}, }, std::rc::Rc, uapi::OwnedFd, @@ -16,14 +16,14 @@ use { pub struct XDataSource { pub state: Rc, - pub device: Rc, + pub device: Rc, pub data: SourceData, - pub location: IpcLocation, + pub location: TransferLocation, } impl DataSource for XDataSource { fn send_cancelled(&self, seat: &Rc) { - self.state.xwayland.queue.push(IpcCancelSource { + self.state.xwayland.queue.push(DataTransferCancelSource { location: self.location, seat: seat.id(), source: self.data.id, @@ -37,7 +37,7 @@ impl DynDataSource for XDataSource { } fn send_send(&self, mime_type: &str, fd: Rc) { - self.state.xwayland.queue.push(IpcSendSource { + self.state.xwayland.queue.push(DataTransferSendSource { location: self.location, seat: self.device.seat.id(), source: self.data.id, @@ -46,9 +46,9 @@ impl DynDataSource for XDataSource { }); } - fn offer_to_x(self: Rc, _dd: &Rc) { + fn offer_to_x(self: Rc, _dd: &Rc) { self.cancel_unprivileged_offers(); - self.state.xwayland.queue.push(IpcSetSelection { + self.state.xwayland.queue.push(DataTransferSetSelection { location: self.location, seat: self.device.seat.id(), offer: None, diff --git a/src/ifs/ipc/zwp_primary_selection_device_manager_v1.rs b/src/ifs/data_transfer/zwp_primary_selection_device_manager_v1.rs similarity index 99% rename from src/ifs/ipc/zwp_primary_selection_device_manager_v1.rs rename to src/ifs/data_transfer/zwp_primary_selection_device_manager_v1.rs index 20714596..98414949 100644 --- a/src/ifs/ipc/zwp_primary_selection_device_manager_v1.rs +++ b/src/ifs/data_transfer/zwp_primary_selection_device_manager_v1.rs @@ -2,7 +2,7 @@ use { crate::{ client::{Client, ClientError}, globals::{Global, GlobalName}, - ifs::ipc::{ + ifs::data_transfer::{ zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1, zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, }, diff --git a/src/ifs/ipc/zwp_primary_selection_device_v1.rs b/src/ifs/data_transfer/zwp_primary_selection_device_v1.rs similarity index 92% rename from src/ifs/ipc/zwp_primary_selection_device_v1.rs rename to src/ifs/data_transfer/zwp_primary_selection_device_v1.rs index d163b530..1c045c21 100644 --- a/src/ifs/ipc/zwp_primary_selection_device_v1.rs +++ b/src/ifs/data_transfer/zwp_primary_selection_device_v1.rs @@ -2,8 +2,8 @@ use { crate::{ client::{Client, ClientError, ClientId}, ifs::{ - ipc::{ - DeviceData, IpcVtable, IterableIpcVtable, OfferData, Role, break_device_loops, + data_transfer::{ + DeviceData, TransferVtable, IterableTransferVtable, OfferData, Role, break_device_loops, destroy_data_device, zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1, zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, }, @@ -89,16 +89,16 @@ impl ZwpPrimarySelectionDeviceV1RequestHandler for ZwpPrimarySelectionDeviceV1 { } fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { - destroy_data_device::(self); + destroy_data_device::(self); self.seat.remove_primary_selection_device(self); self.client.remove_obj(self)?; Ok(()) } } -pub struct PrimarySelectionIpc; +pub struct PrimarySelectionTransfer; -impl IterableIpcVtable for PrimarySelectionIpc { +impl IterableTransferVtable for PrimarySelectionTransfer { fn for_each_device(seat: &WlSeatGlobal, client: ClientId, f: C) where C: FnMut(&Rc), @@ -107,7 +107,7 @@ impl IterableIpcVtable for PrimarySelectionIpc { } } -impl IpcVtable for PrimarySelectionIpc { +impl TransferVtable for PrimarySelectionTransfer { type Device = ZwpPrimarySelectionDeviceV1; type Source = ZwpPrimarySelectionSourceV1; type Offer = ZwpPrimarySelectionOfferV1; @@ -162,7 +162,7 @@ object_base! { impl Object for ZwpPrimarySelectionDeviceV1 { fn break_loops(&self) { - break_device_loops::(self); + break_device_loops::(self); self.seat.remove_primary_selection_device(self); } } diff --git a/src/ifs/ipc/zwp_primary_selection_offer_v1.rs b/src/ifs/data_transfer/zwp_primary_selection_offer_v1.rs similarity index 86% rename from src/ifs/ipc/zwp_primary_selection_offer_v1.rs rename to src/ifs/data_transfer/zwp_primary_selection_offer_v1.rs index 071b19f3..d4f22603 100644 --- a/src/ifs/ipc/zwp_primary_selection_offer_v1.rs +++ b/src/ifs/data_transfer/zwp_primary_selection_offer_v1.rs @@ -2,11 +2,11 @@ use { crate::{ client::{Client, ClientError, ClientId}, ifs::{ - ipc::{ + data_transfer::{ DataOffer, DataOfferId, DynDataOffer, OfferData, break_offer_loops, cancel_offer, destroy_data_offer, receive_data_offer, zwp_primary_selection_device_v1::{ - PrimarySelectionIpc, ZwpPrimarySelectionDeviceV1, + PrimarySelectionTransfer, ZwpPrimarySelectionDeviceV1, }, }, wl_seat::WlSeatGlobal, @@ -51,7 +51,7 @@ impl DynDataOffer for ZwpPrimarySelectionOfferV1 { } fn cancel(&self) { - cancel_offer::(self); + cancel_offer::(self); } fn get_seat(&self) -> Rc { @@ -72,12 +72,12 @@ impl ZwpPrimarySelectionOfferV1RequestHandler for ZwpPrimarySelectionOfferV1 { type Error = ZwpPrimarySelectionOfferV1Error; fn receive(&self, req: Receive, _slf: &Rc) -> Result<(), Self::Error> { - receive_data_offer::(self, req.mime_type, req.fd); + receive_data_offer::(self, req.mime_type, req.fd); Ok(()) } fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { - destroy_data_offer::(self); + destroy_data_offer::(self); self.client.remove_obj(self)?; Ok(()) } @@ -90,7 +90,7 @@ object_base! { impl Object for ZwpPrimarySelectionOfferV1 { fn break_loops(&self) { - break_offer_loops::(self); + break_offer_loops::(self); } } diff --git a/src/ifs/ipc/zwp_primary_selection_source_v1.rs b/src/ifs/data_transfer/zwp_primary_selection_source_v1.rs similarity index 86% rename from src/ifs/ipc/zwp_primary_selection_source_v1.rs rename to src/ifs/data_transfer/zwp_primary_selection_source_v1.rs index 26d10321..ddea8f1d 100644 --- a/src/ifs/ipc/zwp_primary_selection_source_v1.rs +++ b/src/ifs/data_transfer/zwp_primary_selection_source_v1.rs @@ -2,12 +2,12 @@ use { crate::{ client::{Client, ClientError}, ifs::{ - ipc::{ + data_transfer::{ DataSource, DynDataSource, SourceData, add_data_source_mime_type, break_source_loops, cancel_offers, destroy_data_source, detach_seat, offer_source_to_x, - x_data_device::{XIpcDevice, XPrimarySelectionIpc}, - zwp_primary_selection_device_v1::PrimarySelectionIpc, + x_data_device::{XTransferDevice, XPrimarySelectionTransfer}, + zwp_primary_selection_device_v1::PrimarySelectionTransfer, }, wl_seat::WlSeatGlobal, }, @@ -42,8 +42,8 @@ impl DynDataSource for ZwpPrimarySelectionSourceV1 { ZwpPrimarySelectionSourceV1::send_send(self, mime_type, fd) } - fn offer_to_x(self: Rc, dd: &Rc) { - offer_source_to_x::(self, dd); + fn offer_to_x(self: Rc, dd: &Rc) { + offer_source_to_x::(self, dd); } fn detach_seat(&self, seat: &Rc) { @@ -82,12 +82,12 @@ impl ZwpPrimarySelectionSourceV1RequestHandler for ZwpPrimarySelectionSourceV1 { type Error = ZwpPrimarySelectionSourceV1Error; fn offer(&self, req: Offer, _slf: &Rc) -> Result<(), Self::Error> { - add_data_source_mime_type::(self, req.mime_type); + add_data_source_mime_type::(self, req.mime_type); Ok(()) } fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { - destroy_data_source::(self); + destroy_data_source::(self); self.data.client.remove_obj(self)?; Ok(()) } @@ -100,7 +100,7 @@ object_base! { impl Object for ZwpPrimarySelectionSourceV1 { fn break_loops(&self) { - break_source_loops::(self); + break_source_loops::(self); } } diff --git a/src/ifs/ext_foreign_toplevel_list_v1.rs b/src/ifs/ext_foreign_toplevel_list_v1.rs index c5421a16..91f4ff74 100644 --- a/src/ifs/ext_foreign_toplevel_list_v1.rs +++ b/src/ifs/ext_foreign_toplevel_list_v1.rs @@ -1,6 +1,6 @@ use { crate::{ - client::{CAP_FOREIGN_TOPLEVEL_LIST, Client, ClientCaps, ClientError}, + client::{Client, ClientError}, globals::{Global, GlobalName}, ifs::{ ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, @@ -137,10 +137,6 @@ impl Global for ExtForeignToplevelListV1Global { fn version(&self) -> u32 { 1 } - - fn required_caps(&self) -> ClientCaps { - CAP_FOREIGN_TOPLEVEL_LIST - } } simple_add_global!(ExtForeignToplevelListV1Global); diff --git a/src/ifs/ext_idle_notification_v1.rs b/src/ifs/ext_idle_notification_v1.rs index f0e74085..5773a980 100644 --- a/src/ifs/ext_idle_notification_v1.rs +++ b/src/ifs/ext_idle_notification_v1.rs @@ -1,6 +1,7 @@ +use jay_async_engine::SpawnedFuture; use { crate::{ - async_engine::SpawnedFuture, + client::{Client, ClientError}, ifs::wl_seat::WlSeatGlobal, leaks::Tracker, diff --git a/src/ifs/ext_idle_notifier_v1.rs b/src/ifs/ext_idle_notifier_v1.rs index e239a4f8..6db7b95b 100644 --- a/src/ifs/ext_idle_notifier_v1.rs +++ b/src/ifs/ext_idle_notifier_v1.rs @@ -1,6 +1,6 @@ use { crate::{ - client::{CAP_IDLE_NOTIFIER, Client, ClientCaps, ClientError}, + client::{Client, ClientError}, globals::{Global, GlobalName}, ifs::ext_idle_notification_v1::ExtIdleNotificationV1, leaks::Tracker, @@ -143,10 +143,6 @@ impl Global for ExtIdleNotifierV1Global { fn version(&self) -> u32 { 2 } - - fn required_caps(&self) -> ClientCaps { - CAP_IDLE_NOTIFIER - } } simple_add_global!(ExtIdleNotifierV1Global); diff --git a/src/ifs/ext_image_copy/ext_image_copy_capture_frame_v1.rs b/src/ifs/ext_image_copy/ext_image_copy_capture_frame_v1.rs index 48fa9038..c90d539c 100644 --- a/src/ifs/ext_image_copy/ext_image_copy_capture_frame_v1.rs +++ b/src/ifs/ext_image_copy/ext_image_copy_capture_frame_v1.rs @@ -1,3 +1,4 @@ +use jay_geometry::Region; use { crate::{ client::{Client, ClientError}, @@ -13,7 +14,7 @@ use { }, leaks::Tracker, object::Object, - rect::Region, + tree::{self, Node, OutputNode}, utils::{cell_ext::CellExt, errorfmt::ErrorFmt}, wire::{ExtImageCopyCaptureFrameV1Id, ext_image_copy_capture_frame_v1::*}, diff --git a/src/ifs/ext_image_copy/ext_image_copy_capture_manager_v1.rs b/src/ifs/ext_image_copy/ext_image_copy_capture_manager_v1.rs index 77ad9b55..9ddb2bf6 100644 --- a/src/ifs/ext_image_copy/ext_image_copy_capture_manager_v1.rs +++ b/src/ifs/ext_image_copy/ext_image_copy_capture_manager_v1.rs @@ -1,6 +1,6 @@ use { crate::{ - client::{CAP_SCREENCOPY_MANAGER, Client, ClientCaps, ClientError}, + client::{Client, ClientError}, globals::{Global, GlobalName}, ifs::{ ext_image_capture_source_v1::ImageCaptureSource, @@ -145,10 +145,6 @@ impl Global for ExtImageCopyCaptureManagerV1Global { fn version(&self) -> u32 { 1 } - - fn required_caps(&self) -> ClientCaps { - CAP_SCREENCOPY_MANAGER - } } simple_add_global!(ExtImageCopyCaptureManagerV1Global); diff --git a/src/ifs/ext_image_copy/ext_image_copy_capture_session_v1.rs b/src/ifs/ext_image_copy/ext_image_copy_capture_session_v1.rs index 286e9850..5cec57f4 100644 --- a/src/ifs/ext_image_copy/ext_image_copy_capture_session_v1.rs +++ b/src/ifs/ext_image_copy/ext_image_copy_capture_session_v1.rs @@ -1,3 +1,4 @@ +use jay_time::Time; use { crate::{ client::{Client, ClientError}, @@ -16,7 +17,7 @@ use { }, leaks::Tracker, object::{Object, Version}, - time::Time, + tree::{LatchListener, OutputNode, PresentationListener}, utils::{cell_ext::CellExt, clonecell::CloneCell, event_listener::EventListener}, video::Modifier, diff --git a/src/ifs/ext_session_lock_manager_v1.rs b/src/ifs/ext_session_lock_manager_v1.rs index eee71c17..d7ff8853 100644 --- a/src/ifs/ext_session_lock_manager_v1.rs +++ b/src/ifs/ext_session_lock_manager_v1.rs @@ -1,6 +1,6 @@ use { crate::{ - client::{CAP_SESSION_LOCK_MANAGER, Client, ClientCaps, ClientError}, + client::{Client, ClientError}, globals::{Global, GlobalName}, ifs::ext_session_lock_v1::ExtSessionLockV1, leaks::Tracker, @@ -94,10 +94,6 @@ impl Global for ExtSessionLockManagerV1Global { fn version(&self) -> u32 { 1 } - - fn required_caps(&self) -> ClientCaps { - CAP_SESSION_LOCK_MANAGER - } } simple_add_global!(ExtSessionLockManagerV1Global); diff --git a/src/ifs/head_management.rs b/src/ifs/head_management.rs index 5df25618..fd3ca458 100644 --- a/src/ifs/head_management.rs +++ b/src/ifs/head_management.rs @@ -1,3 +1,4 @@ +use jay_units::scale::Scale; use { crate::{ backend::{ @@ -14,7 +15,6 @@ use { }, wl_output::{BlendSpace, PersistentOutputState}, }, - scale::Scale, state::{OutputData, State}, tree::{OutputNode, TearingMode, Transform, VrrMode}, utils::{copyhashmap::CopyHashMap, hash_map_ext::HashMapExt, rc_eq::RcEq}, diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_compositor_space_scaler_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_compositor_space_scaler_v1.rs index 099c04ad..1a537836 100644 --- a/src/ifs/head_management/jay_head_ext/jay_head_ext_compositor_space_scaler_v1.rs +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_compositor_space_scaler_v1.rs @@ -1,8 +1,8 @@ +use jay_units::scale::Scale; use { crate::{ compositor::{MAX_SCALE, MIN_SCALE}, ifs::head_management::{HeadOp, HeadState}, - scale::Scale, wire::{ jay_head_ext_compositor_space_scaler_v1::{ JayHeadExtCompositorSpaceScalerV1RequestHandler, Range, SetScale, diff --git a/src/ifs/head_management/jay_head_manager_v1.rs b/src/ifs/head_management/jay_head_manager_v1.rs index 5a3e8c3e..33aa9282 100644 --- a/src/ifs/head_management/jay_head_manager_v1.rs +++ b/src/ifs/head_management/jay_head_manager_v1.rs @@ -1,6 +1,6 @@ use { crate::{ - client::{CAP_HEAD_MANAGER, Client, ClientCaps, ClientError}, + client::{Client, ClientError}, globals::{Global, GlobalName}, ifs::head_management::{ HeadMgrCommon, head_management_macros::send_available_extensions, @@ -65,10 +65,6 @@ impl Global for JayHeadManagerV1Global { fn version(&self) -> u32 { 1 } - - fn required_caps(&self) -> ClientCaps { - CAP_HEAD_MANAGER - } } pub(super) struct JayHeadManagerV1 { diff --git a/src/ifs/jay_acceptor_request.rs b/src/ifs/jay_acceptor_request.rs deleted file mode 100644 index b9a73184..00000000 --- a/src/ifs/jay_acceptor_request.rs +++ /dev/null @@ -1,60 +0,0 @@ -use { - crate::{ - client::{Client, ClientError}, - leaks::Tracker, - object::{Object, Version}, - utils::errorfmt::ErrorFmt, - wire::{JayAcceptorRequestId, jay_acceptor_request::*}, - }, - std::{error::Error, rc::Rc}, - thiserror::Error, -}; - -pub struct JayAcceptorRequest { - pub id: JayAcceptorRequestId, - pub client: Rc, - pub tracker: Tracker, - pub version: Version, -} - -impl JayAcceptorRequest { - pub fn send_done(&self, name: &str) { - self.client.event(Done { - self_id: self.id, - name, - }); - } - - pub fn send_failed(&self, err: impl Error) { - let msg = &ErrorFmt(err).to_string(); - self.client.event(Failed { - self_id: self.id, - msg, - }); - } -} - -impl JayAcceptorRequestRequestHandler for JayAcceptorRequest { - type Error = JayAcceptorRequestError; - - fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { - self.client.remove_obj(self)?; - Ok(()) - } -} - -object_base! { - self = JayAcceptorRequest; - version = self.version; -} - -impl Object for JayAcceptorRequest {} - -simple_add_obj!(JayAcceptorRequest); - -#[derive(Debug, Error)] -pub enum JayAcceptorRequestError { - #[error(transparent)] - ClientError(Box), -} -efrom!(JayAcceptorRequestError, ClientError); diff --git a/src/ifs/jay_client_query.rs b/src/ifs/jay_client_query.rs index bba06f2b..99807433 100644 --- a/src/ifs/jay_client_query.rs +++ b/src/ifs/jay_client_query.rs @@ -9,7 +9,7 @@ use { jay_client_query::{ AddAll, AddId, Comm, Destroy, Done, End, Exe, Execute, IsXwayland, JayClientQueryRequestHandler, Pid, SandboxAppId, SandboxEngine, SandboxInstanceId, - Sandboxed, Start, Tag, Uid, + Sandboxed, Start, Uid, }, }, }, @@ -26,8 +26,6 @@ pub struct JayClientQuery { all: Cell, } -const TAG_SINCE: Version = Version(25); - impl JayClientQuery { pub fn new(client: &Rc, id: JayClientQueryId, version: Version) -> Self { Self { @@ -73,38 +71,30 @@ impl JayClientQueryRequestHandler for JayClientQuery { exe: &client.pid_info.exe, }); } - if client.acceptor.sandboxed { + if client.metadata.sandboxed { self.client.event(Sandboxed { self_id: self.id }); } if client.is_xwayland { self.client.event(IsXwayland { self_id: self.id }); } - if let Some(engine) = &client.acceptor.sandbox_engine { + if let Some(engine) = &client.metadata.sandbox_engine { self.client.event(SandboxEngine { self_id: self.id, engine, }); } - if let Some(app_id) = &client.acceptor.app_id { + if let Some(app_id) = &client.metadata.app_id { self.client.event(SandboxAppId { self_id: self.id, app_id, }); } - if let Some(instance_id) = &client.acceptor.instance_id { + if let Some(instance_id) = &client.metadata.instance_id { self.client.event(SandboxInstanceId { self_id: self.id, instance_id, }); } - if self.version >= TAG_SINCE - && let Some(tag) = &client.acceptor.tag - { - self.client.event(Tag { - self_id: self.id, - tag, - }); - } self.client.event(End { self_id: self.id }); }; if self.all.get() { diff --git a/src/ifs/jay_compositor.rs b/src/ifs/jay_compositor.rs index 4373125e..2daba0a9 100644 --- a/src/ifs/jay_compositor.rs +++ b/src/ifs/jay_compositor.rs @@ -1,10 +1,10 @@ +use jay_logger::LogLevel; use { crate::{ - client::{CAP_JAY_COMPOSITOR, Client, ClientCaps, ClientError, ClientId}, - compositor::LogLevel, + backend::transaction::BackendConnectorTransactionError, + client::{Client, ClientError, ClientId}, globals::{Global, GlobalName}, ifs::{ - jay_acceptor_request::JayAcceptorRequest, jay_client_query::JayClientQuery, jay_color_management::JayColorManagement, jay_ei_session_builder::JayEiSessionBuilder, @@ -27,6 +27,7 @@ use { wl_surface::jay_sync_file_surface::JaySyncFileSurface, }, leaks::Tracker, + object::{Object, Version}, screenshoter::take_screenshot, tree::ToplevelIdentifier, @@ -78,11 +79,7 @@ global_base!(JayCompositorGlobal, JayCompositor, JayCompositorError); impl Global for JayCompositorGlobal { fn version(&self) -> u32 { - 30 - } - - fn required_caps(&self) -> ClientCaps { - CAP_JAY_COMPOSITOR + 31 } } @@ -336,13 +333,6 @@ impl JayCompositorRequestHandler for JayCompositor { Ok(()) } - fn create_screencast(&self, req: CreateScreencast, _slf: &Rc) -> Result<(), Self::Error> { - let sc = Rc::new_cyclic(|slf| JayScreencast::new(req.id, &self.client, slf, self.version)); - track!(self.client, sc); - self.client.add_client_obj(&sc)?; - Ok(()) - } - fn get_randr(&self, req: GetRandr, _slf: &Rc) -> Result<(), Self::Error> { let sc = Rc::new(JayRandr::new(req.id, &self.client, self.version)); track!(self.client, sc); @@ -357,6 +347,13 @@ impl JayCompositorRequestHandler for JayCompositor { Ok(()) } + fn create_screencast(&self, req: CreateScreencast, _slf: &Rc) -> Result<(), Self::Error> { + let sc = Rc::new_cyclic(|slf| JayScreencast::new(req.id, &self.client, slf, self.version)); + track!(self.client, sc); + self.client.add_client_obj(&sc)?; + Ok(()) + } + fn select_toplevel(&self, req: SelectToplevel, _slf: &Rc) -> Result<(), Self::Error> { let obj = JaySelectToplevel::new(&self.client, req.id, self.version); track!(self.client, obj); @@ -502,27 +499,6 @@ impl JayCompositorRequestHandler for JayCompositor { Ok(()) } - fn get_tagged_acceptor( - &self, - req: GetTaggedAcceptor<'_>, - _slf: &Rc, - ) -> Result<(), Self::Error> { - let obj = Rc::new(JayAcceptorRequest { - id: req.id, - client: self.client.clone(), - tracker: Default::default(), - version: self.version, - }); - track!(self.client, obj); - self.client.add_client_obj(&obj)?; - let state = &self.client.state; - match state.tagged_acceptors.get(state, req.tag) { - Ok(d) => obj.send_done(&d), - Err(e) => obj.send_failed(e), - } - Ok(()) - } - fn get_sync_file_surface( &self, req: GetSyncFileSurface, @@ -542,6 +518,14 @@ impl JayCompositorRequestHandler for JayCompositor { }); Ok(()) } + + fn set_dpms(&self, req: SetDpms, _slf: &Rc) -> Result<(), Self::Error> { + self.client + .state + .set_dpms_active(req.active != 0) + .map_err(JayCompositorError::SetDpms)?; + Ok(()) + } } object_base! { @@ -559,5 +543,7 @@ pub enum JayCompositorError { ClientError(Box), #[error("Unknown log level {0}")] UnknownLogLevel(u32), + #[error("Could not set DPMS state")] + SetDpms(#[source] BackendConnectorTransactionError), } efrom!(JayCompositorError, ClientError); diff --git a/src/ifs/jay_damage_tracking.rs b/src/ifs/jay_damage_tracking.rs index 703f6a1e..1679fccc 100644 --- a/src/ifs/jay_damage_tracking.rs +++ b/src/ifs/jay_damage_tracking.rs @@ -1,12 +1,13 @@ +use jay_theme::Color; use { crate::{ - client::{CAP_JAY_COMPOSITOR, Client, ClientCaps, ClientError}, + client::{Client, ClientError}, cmm::cmm_eotf::Eotf, gfx_api::AlphaMode, globals::{Global, GlobalName}, leaks::Tracker, object::{Object, Version}, - theme::Color, + wire::{ JayCompositorId, jay_damage_tracking::{ @@ -56,10 +57,6 @@ impl Global for JayDamageTrackingGlobal { fn version(&self) -> u32 { 1 } - - fn required_caps(&self) -> ClientCaps { - CAP_JAY_COMPOSITOR - } } simple_add_global!(JayDamageTrackingGlobal); diff --git a/src/ifs/jay_input.rs b/src/ifs/jay_input.rs index 26a3bcec..63eb04d4 100644 --- a/src/ifs/jay_input.rs +++ b/src/ifs/jay_input.rs @@ -1,12 +1,12 @@ +use jay_keyboard::{KbvmError, KbvmMap}; use { crate::{ backend::{ InputDeviceAccelProfile, InputDeviceCapability, InputDeviceClickMethod, InputDeviceId, }, client::{Client, ClientError}, - clientmem::{ClientMem, ClientMemError}, + clientmem::{ClientMem, ClientMemError, client_mem_client}, ifs::wl_seat::WlSeatGlobal, - kbvm::{KbvmError, KbvmMap}, leaks::Tracker, libinput::consts::{ AccelProfile, ConfigClickMethod, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE, @@ -93,7 +93,7 @@ impl JayInput { let mut caps = ArrayVec::<_, { InputDeviceCapability::LENGTH }>::new(); for cap in InputDeviceCapability::variants() { if data.data.device.has_capability(cap) { - caps.push(cap.to_libinput().raw()); + caps.push(crate::libinput::device_capability(cap).raw()); } } let dev = &data.data.device; @@ -204,7 +204,7 @@ impl JayInput { keymap, len, true, - Some(&self.client), + Some(client_mem_client(&self.client)), None, )?) .offset(0, len); diff --git a/src/ifs/jay_randr.rs b/src/ifs/jay_randr.rs index 6b79475d..f0d7dff6 100644 --- a/src/ifs/jay_randr.rs +++ b/src/ifs/jay_randr.rs @@ -1,3 +1,4 @@ +use jay_units::scale::Scale; use { crate::{ backend::{self, BackendColorSpace, BackendEotfs}, @@ -8,7 +9,6 @@ use { ifs::wl_output, leaks::Tracker, object::{Object, Version}, - scale::Scale, state::{ConnectorData, DrmDevData, OutputData, State}, tree::{OutputNode, TearingMode, Transform, VrrMode}, utils::errorfmt::ErrorFmt, @@ -474,7 +474,7 @@ impl JayRandrRequestHandler for JayRandr { let Some(c) = self.get_output_node(req.output) else { return Ok(()); }; - c.schedule.set_cursor_hz(&self.state, req.hz); + c.schedule.set_cursor_hz(req.hz); Ok(()) } diff --git a/src/ifs/jay_render_ctx.rs b/src/ifs/jay_render_ctx.rs index 74b058e4..8256bc8d 100644 --- a/src/ifs/jay_render_ctx.rs +++ b/src/ifs/jay_render_ctx.rs @@ -58,8 +58,8 @@ impl JayRenderCtx { } let allocator = ctx.allocator(); match allocator.drm() { - Some(drm) => match drm.dup_render() { - Ok(d) => fd = Some(d.fd().clone()), + Some(drm) => match drm.dup_render_fd() { + Ok(d) => fd = Some(d), Err(e) => { log::error!("Could not dup drm fd: {}", ErrorFmt(e)); } diff --git a/src/ifs/jay_screencast.rs b/src/ifs/jay_screencast.rs index 9ec24eb1..07ac9e71 100644 --- a/src/ifs/jay_screencast.rs +++ b/src/ifs/jay_screencast.rs @@ -1,3 +1,4 @@ +use jay_units::scale::Scale; use { crate::{ allocator::{AllocatorError, BO_USE_LINEAR, BO_USE_RENDERING, BufferObject}, @@ -10,7 +11,6 @@ use { ifs::{jay_output::JayOutput, jay_toplevel::JayToplevel, wl_buffer::WlBufferStorage}, leaks::Tracker, object::{Object, Version}, - scale::Scale, state::State, tree::{ LatchListener, OutputNode, ToplevelNode, Transform, WorkspaceNode, WorkspaceNodeId, diff --git a/src/ifs/jay_seat_events.rs b/src/ifs/jay_seat_events.rs index 144d0b1e..dc03cc06 100644 --- a/src/ifs/jay_seat_events.rs +++ b/src/ifs/jay_seat_events.rs @@ -1,8 +1,8 @@ +use jay_units::fixed::Fixed; use { crate::{ backend::{ButtonState, InputDeviceId, KeyState, ScrollAxis}, client::Client, - fixed::Fixed, ifs::wl_seat::{ SeatId, tablet::{ @@ -234,7 +234,7 @@ impl JaySeatEvents { seat: SeatId, input_device: InputDeviceId, time_usec: u64, - event: jay_config::input::SwitchEvent, + event: crate::backend::SwitchEvent, ) { self.client.event(SwitchEvent { self_id: self.id, diff --git a/src/ifs/jay_tree_query.rs b/src/ifs/jay_tree_query.rs index 45c90fa7..784c16e2 100644 --- a/src/ifs/jay_tree_query.rs +++ b/src/ifs/jay_tree_query.rs @@ -1,3 +1,4 @@ +use jay_geometry::Rect; use { crate::{ client::{Client, ClientError}, @@ -13,7 +14,7 @@ use { }, leaks::Tracker, object::{Object, Version}, - rect::Rect, + tree::{ self, ContainerNode, DisplayNode, FloatNode, Node, NodeVisitor, OutputNode, PlaceholderNode, ToplevelData, ToplevelIdentifier, ToplevelNodeBase, ToplevelType, diff --git a/src/ifs/wl_buffer.rs b/src/ifs/wl_buffer.rs index 1fff5db3..cf29694b 100644 --- a/src/ifs/wl_buffer.rs +++ b/src/ifs/wl_buffer.rs @@ -1,3 +1,4 @@ +use jay_geometry::{Rect, Region}; use { crate::{ client::{Client, ClientError}, @@ -7,7 +8,7 @@ use { ifs::wl_surface::WlSurface, leaks::Tracker, object::{Object, Version}, - rect::{Rect, Region}, + utils::{errorfmt::ErrorFmt, event_listener::EventListener, page_size::page_size}, video::{ LINEAR_MODIFIER, diff --git a/src/ifs/wl_output.rs b/src/ifs/wl_output.rs index 1687824d..6655d876 100644 --- a/src/ifs/wl_output.rs +++ b/src/ifs/wl_output.rs @@ -1,5 +1,6 @@ mod removed_output; +use jay_geometry::Rect; use { crate::{ backend::{self, BackendColorSpace, BackendEotfs, BackendLuminance}, @@ -19,7 +20,7 @@ use { }, leaks::Tracker, object::{Object, Version}, - rect::Rect, + state::{ConnectorData, State}, tree::{OutputNode, TearingMode, Transform, VrrMode, calculate_logical_size}, utils::{ @@ -33,12 +34,13 @@ use { std::{ cell::{Cell, RefCell}, collections::hash_map::Entry, - hash::{Hash, Hasher}, rc::Rc, }, thiserror::Error, }; +pub use jay_output_types::OutputId; + const SP_UNKNOWN: i32 = 0; #[expect(dead_code)] const SP_NONE: i32 = 1; @@ -51,13 +53,21 @@ const SP_VERTICAL_RGB: i32 = 4; #[expect(dead_code)] const SP_VERTICAL_BGR: i32 = 5; +#[allow(dead_code)] pub const TF_NORMAL: i32 = 0; +#[allow(dead_code)] pub const TF_90: i32 = 1; +#[allow(dead_code)] pub const TF_180: i32 = 2; +#[allow(dead_code)] pub const TF_270: i32 = 3; +#[allow(dead_code)] pub const TF_FLIPPED: i32 = 4; +#[allow(dead_code)] pub const TF_FLIPPED_90: i32 = 5; +#[allow(dead_code)] pub const TF_FLIPPED_180: i32 = 6; +#[allow(dead_code)] pub const TF_FLIPPED_270: i32 = 7; const MODE_CURRENT: u32 = 1; @@ -139,7 +149,7 @@ impl BlendSpace { pub struct PersistentOutputState { pub transform: Cell, - pub scale: Cell, + pub scale: Cell, pub pos: Cell<(i32, i32)>, pub vrr_mode: Cell, pub vrr_cursor_hz: Cell>, @@ -165,70 +175,6 @@ impl Default for PersistentOutputState { } } -#[derive(Eq, Debug)] -pub struct OutputId { - pub _connector: Option, - pub manufacturer: String, - pub model: String, - pub serial_number: String, - pub hash: OutputIdHash, -} - -hash_type!(OutputIdHash); - -impl PartialEq for OutputId { - fn eq(&self, other: &Self) -> bool { - self.hash == other.hash - } -} - -impl Hash for OutputId { - fn hash(&self, state: &mut H) { - self.hash.hash(state); - } -} - -impl OutputId { - pub fn new( - connector: impl Into, - manufacturer: impl Into, - model: impl Into, - serial_number: impl Into, - ) -> Rc { - let connector = connector.into(); - let manufacturer = manufacturer.into(); - let model = model.into(); - let serial_number = serial_number.into(); - Self::new_(connector, manufacturer, model, serial_number) - } - - fn new_( - connector: String, - manufacturer: String, - model: String, - serial_number: String, - ) -> Rc { - let connector = serial_number.is_empty().then_some(connector); - let mut hasher = blake3::Hasher::new(); - hasher.update(&[connector.is_some() as u8]); - let mut hash = |s: &str| { - hasher.update(&(s.len() as u64).to_le_bytes()); - hasher.update(s.as_bytes()); - }; - connector.as_deref().map(&mut hash); - hash(&manufacturer); - hash(&model); - hash(&serial_number); - Rc::new(Self { - _connector: connector, - manufacturer, - model, - serial_number, - hash: OutputIdHash(*hasher.finalize().as_bytes()), - }) - } -} - impl WlOutputGlobal { pub fn clear(&self) { self.opt.clear(); diff --git a/src/ifs/wl_region.rs b/src/ifs/wl_region.rs index d65c1761..27f91c5f 100644 --- a/src/ifs/wl_region.rs +++ b/src/ifs/wl_region.rs @@ -1,9 +1,10 @@ +use jay_geometry::{Rect, Region, RegionBuilder}; use { crate::{ client::{Client, ClientError}, leaks::Tracker, object::{Object, Version}, - rect::{Rect, Region, RegionBuilder}, + wire::{WlRegionId, wl_region::*}, }, std::{cell::RefCell, rc::Rc}, diff --git a/src/ifs/wl_registry.rs b/src/ifs/wl_registry.rs index f0dfda67..b9de9c26 100644 --- a/src/ifs/wl_registry.rs +++ b/src/ifs/wl_registry.rs @@ -61,11 +61,7 @@ impl WlRegistryRequestHandler for WlRegistry { fn bind(&self, bind: Bind, _slf: &Rc) -> Result<(), Self::Error> { let name = GlobalName::from_raw(bind.name); let globals = &self.client.state.globals; - let global = globals.get( - name, - self.client.effective_caps.get(), - self.client.is_xwayland, - )?; + let global = globals.get(name, self.client.is_xwayland)?; if global.interface().name() != bind.interface { return Err(WlRegistryError::InvalidInterface(InterfaceError { name: global.name(), diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 5fba889c..ee692e4f 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -1,12 +1,18 @@ +mod device_handler; mod event_handling; pub mod ext_transient_seat_manager_v1; pub mod ext_transient_seat_v1; +mod focus; mod gesture_owner; mod kb_owner; mod pointer_owner; +mod position_hint; +mod seat_object; +mod selection; pub mod tablet; pub mod text_input; mod touch_owner; +mod window_management; pub mod wl_keyboard; pub mod wl_pointer; pub mod wl_touch; @@ -21,31 +27,33 @@ pub mod zwp_relative_pointer_v1; pub mod zwp_virtual_keyboard_manager_v1; pub mod zwp_virtual_keyboard_v1; +use jay_geometry::Rect; +use jay_async_engine::SpawnedFuture; +use jay_keyboard::{ + DynKeyboardState, KbvmMap, KbvmMapId, KbvmState, KeyboardState, KeyboardStateId, KeymapFd, + LedsListener, +}; +use jay_units::fixed::Fixed; use { crate::{ - async_engine::SpawnedFuture, + backend::{ ButtonState, InputDeviceAccelProfile, InputDeviceClickMethod, Leds, TransformMatrix, }, client::{Client, ClientError, ClientId}, cursor_user::{CursorUser, CursorUserGroup, CursorUserOwner}, ei::ei_ifs::ei_seat::EiSeat, - fixed::Fixed, globals::{Global, GlobalName}, ifs::{ - ext_idle_notification_v1::ExtIdleNotificationV1, - ipc::{ - self, DynDataSource, IpcError, IpcLocation, + data_transfer::{ + self, DynDataSource, TransferError, data_control::{DataControlDeviceId, DynDataControlDevice}, - offer_source_to_regular_client, - wl_data_device::{ClipboardIpc, WlDataDevice}, + wl_data_device::WlDataDevice, wl_data_source::WlDataSource, - x_data_device::{XClipboardIpc, XIpcDevice, XIpcDeviceId, XPrimarySelectionIpc}, - zwp_primary_selection_device_v1::{ - PrimarySelectionIpc, ZwpPrimarySelectionDeviceV1, - }, - zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, + x_data_device::{XTransferDevice, XTransferDeviceId}, + zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1, }, + ext_idle_notification_v1::ExtIdleNotificationV1, wl_output::WlOutputGlobal, wl_seat::{ event_handling::FocusHistoryData, @@ -72,21 +80,17 @@ use { dnd_icon::DndIcon, tray::{DynTrayItem, TrayItemId}, xdg_surface::{xdg_popup::XdgPopup, xdg_toplevel::ResizeEdges}, - zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, }, xdg_toplevel_drag_v1::XdgToplevelDragV1, }, - kbvm::{KbvmMap, KbvmMapId, KbvmState, PhysicalKeyboardState}, - keyboard::{DynKeyboardState, KeyboardState, KeyboardStateId, KeymapFd, LedsListener}, + kbvm::PhysicalKeyboardState, leaks::Tracker, object::{Object, Version}, - rect::Rect, + state::{DeviceHandlerData, State}, tree::{ - ChangeGroupAction, ContainerNode, ContainerSplit, Direction, FoundNode, Node, NodeId, - NodeLayer, NodeLayerLink, NodeLocation, OutputNode, StackedNode, ToplevelNode, - WorkspaceNode, generic_node_visitor, toplevel_create_split, toplevel_parent_container, - toplevel_set_floating, toplevel_set_workspace, + FoundNode, Node, NodeId, NodeLocation, OutputNode, ToplevelNode, WorkspaceNode, + generic_node_visitor, toplevel_set_workspace, }, utils::{ asyncevent::AsyncEvent, @@ -94,9 +98,9 @@ use { clonecell::CloneCell, copyhashmap::CopyHashMap, event_listener::{EventListener, EventSource}, - linkedlist::{LinkedList, LinkedNode, NodeRef}, + linkedlist::{LinkedList, LinkedNode}, numcell::NumCell, - rc_eq::{rc_eq, rc_weak_eq}, + rc_eq::rc_eq, smallmap::SmallMap, static_text::StaticText, }, @@ -107,7 +111,6 @@ use { }, wire_ei::EiSeatId, }, - CursorPositionType::Warp, ahash::AHashMap, jay_config::{ input::FallbackOutputMode as ConfigFallbackOutputMode, @@ -115,13 +118,12 @@ use { }, kbvm::Keycode, linearize::Linearize, - run_on_drop::on_drop, smallvec::SmallVec, std::{ cell::{Cell, RefCell}, collections::hash_map::Entry, mem, - ops::{Deref, DerefMut}, + ops::DerefMut, rc::{Rc, Weak}, }, thiserror::Error, @@ -129,6 +131,11 @@ use { pub use { event_handling::NodeSeatState, pointer_owner::{ToplevelSelector, WorkspaceSelector}, + position_hint::{ + CursorPositionType, PositionHintRequest, handle_position_hint_requests, + handle_warp_mouse_to_focus, + }, + seat_object::WlSeatError, }; pub const POINTER: u32 = 1; @@ -159,7 +166,7 @@ pub struct DroppedDnd { impl Drop for DroppedDnd { fn drop(&mut self) { if let Some(src) = self.dnd.src.take() { - ipc::detach_seat(&*src, &self.dnd.seat); + data_transfer::detach_seat(&*src, &self.dnd.seat); } } } @@ -191,7 +198,7 @@ pub struct WlSeatGlobal { found_tree: RefCell>, keyboard_node: CloneCell>, bindings: RefCell>>>, - x_data_devices: SmallMap, 1>, + x_data_devices: SmallMap, 1>, data_devices: RefCell>>>, primary_selection_devices: RefCell< AHashMap< @@ -474,15 +481,15 @@ impl WlSeatGlobal { .insert(device.id, device.clone()); } - pub fn set_x_data_device(&self, device: &Rc) { + pub fn set_x_data_device(&self, device: &Rc) { self.x_data_devices.insert(device.id, device.clone()); } - pub fn unset_x_data_device(&self, id: XIpcDeviceId) { + pub fn unset_x_data_device(&self, id: XTransferDeviceId) { self.x_data_devices.remove(&id); } - pub fn for_each_x_data_device(&self, mut f: impl FnMut(&Rc)) { + pub fn for_each_x_data_device(&self, mut f: impl FnMut(&Rc)) { for (_, dev) in &self.x_data_devices { f(&dev); } @@ -706,104 +713,6 @@ impl WlSeatGlobal { self.kb_owner.ungrab(self); } - pub fn kb_parent_container(&self) -> Option> { - if let Some(tl) = self.keyboard_node.get().node_toplevel() { - return toplevel_parent_container(&*tl); - } - None - } - - pub fn get_mono(&self) -> Option { - self.kb_parent_container().map(|c| c.mono_child.is_some()) - } - - pub fn get_split(&self) -> Option { - self.kb_parent_container().map(|c| c.split.get()) - } - - pub fn set_mono(&self, mono: bool) { - if let Some(tl) = self.keyboard_node.get().node_toplevel() - && let Some(parent) = tl.tl_data().parent.get() - && let Some(container) = parent.node_into_container() - { - let node = if mono { Some(tl.deref()) } else { None }; - container.set_mono(node); - } - } - - pub fn set_split(&self, axis: ContainerSplit) { - if let Some(c) = self.kb_parent_container() { - c.set_split(axis); - } - } - - pub fn create_split(&self, axis: ContainerSplit) { - let tl = match self.keyboard_node.get().node_toplevel() { - Some(tl) => tl, - _ => return, - }; - toplevel_create_split(&self.state, tl, axis); - } - - pub fn toggle_tab(&self) { - if let Some(c) = self.kb_parent_container() { - c.change_group(ChangeGroupAction::ToggleTab); - } - } - - pub fn make_group(&self, axis: ContainerSplit, ephemeral: bool) { - if let Some(c) = self.kb_parent_container() { - c.make_group(axis, ephemeral); - } - } - - pub fn change_group_opposite(&self) { - if let Some(c) = self.kb_parent_container() { - c.change_group(ChangeGroupAction::Opposite); - } - } - - pub fn equalize(&self, recursive: bool) { - if let Some(c) = self.kb_parent_container() { - if recursive { - c.equalize_recursive(); - } else { - c.equalize(); - } - } - } - - pub fn move_tab(&self, right: bool) { - if let Some(c) = self.kb_parent_container() { - c.move_tab(right); - } - } - - pub fn focus_parent(self: &Rc) { - if let Some(tl) = self.keyboard_node.get().node_toplevel() - && let Some(parent) = tl.tl_data().parent.get() - && let Some(tl) = parent.node_toplevel() - { - self.focus_node(tl); - self.maybe_schedule_warp_mouse_to_focus(); - } - } - - pub fn get_floating(self: &Rc) -> Option { - match self.keyboard_node.get().node_toplevel() { - Some(tl) => Some(tl.tl_data().parent_is_float.get()), - _ => None, - } - } - - pub fn set_floating(self: &Rc, floating: bool) { - let tl = match self.keyboard_node.get().node_toplevel() { - Some(tl) => tl, - _ => return, - }; - toplevel_set_floating(&self.state, tl, floating); - } - pub fn get_rate(&self) -> (i32, i32) { self.repeat_rate.get() } @@ -827,516 +736,6 @@ impl WlSeatGlobal { } } - pub fn close(self: &Rc) { - let kb_node = self.keyboard_node.get(); - if let Some(tl) = kb_node.node_toplevel() { - tl.tl_close(); - } - } - - pub fn move_focus(self: &Rc, direction: Direction) { - let tl = match self.keyboard_node.get().node_toplevel() { - Some(tl) => tl, - _ => { - if let Some(ws) = self.keyboard_node.get().node_into_workspace() - && let Some(target) = self - .state - .find_output_in_direction(&ws.output.get(), direction) - { - target.take_keyboard_navigation_focus(self, direction); - self.maybe_schedule_warp_mouse_to_focus(); - } - return; - } - }; - if direction == Direction::Down && tl.node_is_container() { - tl.node_do_focus(self, direction); - } else { - let data = tl.tl_data(); - if data.is_fullscreen.get() - && let Some(output) = data.output_opt() - && let Some(target) = self.state.find_output_in_direction(&output, direction) - { - target.take_keyboard_navigation_focus(self, direction); - } else if let Some(p) = data.parent.get() - && let Some(c) = p.node_into_container() - { - c.move_focus_from_child(self, tl.deref(), direction); - } else if let Some(float) = data.float.get() { - let ws = float.workspace.get(); - let floats: Vec<_> = ws - .stacked - .iter() - .filter_map(|node| (*node).clone().node_into_float()) - .filter(|f| f.child.get().is_some()) - .collect(); - if let Some(pos) = floats.iter().position(|f| f.id == float.id) { - let target = match direction { - Direction::Left | Direction::Down => { - if pos == 0 { - floats.last() - } else { - floats.get(pos - 1) - } - } - _ => { - if pos + 1 >= floats.len() { - floats.first() - } else { - floats.get(pos + 1) - } - } - }; - if let Some(f) = target - && f.id != float.id - { - f.clone().node_do_focus(self, Direction::Unspecified); - } - } - } - } - self.maybe_schedule_warp_mouse_to_focus(); - } - - pub fn maybe_schedule_warp_mouse_to_focus(self: &Rc) { - if self.mouse_follows_focus() { - self.warp_mouse_to_focus_skip_target_check.set(true); - self.schedule_warp_mouse_to_focus(); - } - } - - pub fn schedule_warp_mouse_to_focus(self: &Rc) { - if !self.warp_mouse_to_focus_scheduled.replace(true) { - self.state.pending_warp_mouse_to_focus.push(self.clone()); - } - } - - pub fn move_focused(self: &Rc, direction: Direction) { - let kb_node = self.keyboard_node.get(); - let Some(tl) = kb_node.node_toplevel() else { - if let Some(ws) = self.keyboard_node.get().node_into_workspace() - && let Some(target) = self - .state - .find_output_in_direction(&ws.output.get(), direction) - { - self.state.move_ws_to_output(&ws, &target); - } - return; - }; - let data = tl.tl_data(); - if data.is_fullscreen.get() - && let Some(output) = data.output_opt() - && let Some(target) = self.state.find_output_in_direction(&output, direction) - { - let ws = target.ensure_workspace(); - toplevel_set_workspace(&self.state, tl, &ws); - self.maybe_schedule_warp_mouse_to_focus(); - } else if let Some(parent) = data.parent.get() - && let Some(c) = parent.node_into_container() - { - c.move_child(tl, direction); - self.maybe_schedule_warp_mouse_to_focus(); - } - } - - pub fn get_last_focus_on_workspace(&self, ws: &WorkspaceNode) -> Option> { - let mut node = self.focus_history.last()?; - loop { - if let Some(node) = node.node.upgrade() - && let Some(NodeLocation::Workspace(_, new)) = node.node_location() - && new == ws.id - { - return Some(node); - } - node = node.prev()?; - } - } - - fn get_focus_history( - &self, - next: impl Fn(&NodeRef) -> Option>, - first: impl FnOnce(&LinkedList) -> Option>, - ) -> Option<(Rc, bool)> { - let original = self.keyboard_node.get(); - let mut output = None; - let mut workspace = None; - if let Some(old) = original.node_location() { - match old { - NodeLocation::Workspace(o, w) => { - workspace = Some(w); - output = Some(o); - } - NodeLocation::Output(o) => { - output = Some(o); - } - } - } - if (output.is_none() || workspace.is_none()) - && let Some(old) = self.last_focus_location.get() - { - match old { - NodeLocation::Workspace(o, w) => { - workspace = workspace.or(Some(w)); - output = output.or(Some(o)); - } - NodeLocation::Output(o) => { - output = output.or(Some(o)); - } - } - } - if workspace.is_none() - && let Some(output) = original.node_output() - && let Some(ws) = output.workspace.get() - { - workspace = Some(ws.id); - } - let matches = |node: &FocusHistoryData| { - let visible = node.visible.get(); - if self.focus_history_visible_only.get() && !visible { - return None; - } - let node = node.node.upgrade()?; - if self.focus_history_same_workspace.get() { - let new = node.node_location()?; - let o = match new { - NodeLocation::Workspace(o, w) => { - if workspace != Some(w) { - return None; - } - o - } - NodeLocation::Output(o) => o, - }; - if output != Some(o) { - return None; - } - } - Some((node, visible)) - }; - let node = original.node_seat_state().get_focus_history(self); - if let Some(mut node) = node { - loop { - node = match next(&node) { - Some(n) => n, - _ => break, - }; - if let Some(matches) = matches(&node) { - return Some(matches); - } - } - } - let mut node = first(&self.focus_history)?; - loop { - if rc_weak_eq(&original, &node.node) { - return None; - } - if let Some(matches) = matches(&node) { - return Some(matches); - } - node = next(&node)?; - } - } - - fn focus_history( - self: &Rc, - next: impl Fn(&NodeRef) -> Option>, - first: impl FnOnce(&LinkedList) -> Option>, - ) { - let Some((node, visible)) = self.get_focus_history(next, first) else { - return; - }; - self.focus_history_rotate.fetch_add(1); - let _reset = on_drop(|| { - self.focus_history_rotate.fetch_sub(1); - }); - if !visible { - node.clone().node_make_visible(); - if !node.node_visible() { - return; - } - } - self.focus_node(node); - self.maybe_schedule_warp_mouse_to_focus(); - } - - pub fn focus_prev(self: &Rc) { - self.focus_history(|s| s.prev(), |l| l.last()); - } - - pub fn focus_next(self: &Rc) { - self.focus_history(|s| s.next(), |l| l.first()); - } - - pub fn focus_history_set_visible(&self, visible: bool) { - self.focus_history_visible_only.set(visible); - } - - pub fn focus_history_set_same_workspace(&self, same_workspace: bool) { - self.focus_history_same_workspace.set(same_workspace); - } - - fn focus_layer_rel( - self: &Rc, - next_layer: impl Fn(NodeLayer) -> NodeLayer, - layer_node_next: impl Fn( - &NodeRef>, - ) -> Option>>, - stacked_node_next: impl Fn( - &NodeRef>, - ) -> Option>>, - layer_list_iter: impl Fn(&LinkedList>) -> LI, - stacked_list_iter: impl Fn(&LinkedList>) -> SI, - ) where - LI: Iterator>>, - SI: Iterator>>, - { - fn node_viable(n: &(impl Node + ?Sized)) -> bool { - n.node_visible() && n.node_accepts_focus() - } - - let current = self.keyboard_node.get(); - let Some(output) = current.node_output() else { - return; - }; - let current_layer = current.node_layer(); - match ¤t_layer { - NodeLayerLink::Layer0(l) - | NodeLayerLink::Layer1(l) - | NodeLayerLink::Layer2(l) - | NodeLayerLink::Layer3(l) => { - if let Some(n) = layer_node_next(l) - && node_viable(&**n) - { - n.deref() - .clone() - .node_do_focus(self, Direction::Unspecified); - self.maybe_schedule_warp_mouse_to_focus(); - return; - } - } - NodeLayerLink::Stacked(l) | NodeLayerLink::StackedAboveLayers(l) => { - if let Some(n) = stacked_node_next(l) - && node_viable(&**n) - && n.node_output().map(|o| o.id) == Some(output.id) - { - n.deref() - .clone() - .node_do_focus(self, Direction::Unspecified); - self.maybe_schedule_warp_mouse_to_focus(); - return; - } - } - NodeLayerLink::Display => {} - NodeLayerLink::Output => {} - NodeLayerLink::Workspace => {} - NodeLayerLink::Tiled => {} - NodeLayerLink::Fullscreen => {} - NodeLayerLink::Lock => {} - NodeLayerLink::InputMethod => {} - } - let handle_layer_shell = |l: &LinkedList>| { - for n in layer_list_iter(l) { - if node_viable(&**n) { - return Some(n.deref().clone() as Rc); - } - } - None - }; - let handle_stacked = |l: &LinkedList>| { - for n in stacked_list_iter(l) { - if node_viable(&**n) && n.node_output().map(|o| o.id) == Some(output.id) { - return Some(n.deref().clone() as Rc); - } - } - None - }; - let ws = output.workspace.get(); - let first = next_layer(current_layer.layer()); - let mut layer = first; - loop { - let node = match layer { - NodeLayer::Display => None, - NodeLayer::Layer0 => handle_layer_shell(&output.layers[0]), - NodeLayer::Layer1 => handle_layer_shell(&output.layers[1]), - NodeLayer::Output => None, - NodeLayer::Workspace => { - if let Some(ws) = &ws - && ws.container_visible() - { - self.focus_node(ws.clone()); - self.maybe_schedule_warp_mouse_to_focus(); - return; - } - None - } - NodeLayer::Tiled => ws - .as_ref() - .and_then(|w| w.container.get()) - .map(|n| n as Rc), - NodeLayer::Fullscreen => ws - .as_ref() - .and_then(|w| w.fullscreen.get()) - .map(|n| n as Rc), - NodeLayer::Stacked => handle_stacked(&self.state.root.stacked), - NodeLayer::Layer2 => handle_layer_shell(&output.layers[2]), - NodeLayer::Layer3 => handle_layer_shell(&output.layers[3]), - NodeLayer::StackedAboveLayers => { - handle_stacked(&self.state.root.stacked_above_layers) - } - NodeLayer::Lock => None, - NodeLayer::InputMethod => None, - }; - if let Some(n) = node { - if node_viable(&*n) { - n.node_do_focus(self, Direction::Unspecified); - self.maybe_schedule_warp_mouse_to_focus(); - return; - } - } - layer = next_layer(layer); - if layer == first { - return; - } - } - } - - pub fn focus_layer_below(self: &Rc) { - self.focus_layer_rel( - |l| l.prev(), - |n| n.prev(), - |n| n.prev(), - |l| l.rev_iter(), - |l| l.rev_iter(), - ); - } - - pub fn focus_layer_above(self: &Rc) { - self.focus_layer_rel( - |l| l.next(), - |n| n.next(), - |n| n.next(), - |l| l.iter(), - |l| l.iter(), - ); - } - - pub fn toggle_focus_float_tiled(self: &Rc) { - let current = self.keyboard_node.get(); - match current.node_layer().layer() { - NodeLayer::Tiled | NodeLayer::Fullscreen => self.focus_floats(), - _ => self.focus_tiles(), - } - self.maybe_schedule_warp_mouse_to_focus(); - } - - pub fn focus_floats(self: &Rc) { - let current = self.keyboard_node.get(); - if current.node_layer().layer() == NodeLayer::Stacked { - return; - } - let Some(output) = current.node_output() else { - return; - }; - let Some(ws) = output.workspace.get() else { - return; - }; - if let Some(child) = ws - .stacked - .rev_iter() - .filter_map(|node| (*node).clone().node_into_float()) - .find_map(|float| float.child.get()) - { - child.node_do_focus(self, Direction::Unspecified); - } - } - - pub fn focus_tiles(self: &Rc) { - let current = self.keyboard_node.get(); - if matches!( - current.node_layer().layer(), - NodeLayer::Tiled | NodeLayer::Fullscreen, - ) { - return; - } - let Some(output) = current.node_output() else { - return; - }; - let Some(ws) = output.workspace.get() else { - return; - }; - let node = match ws.fullscreen.get() { - Some(fs) => fs as Rc, - _ => match ws.container.get() { - Some(c) => c, - _ => return, - }, - }; - if node.node_visible() && node.node_accepts_focus() { - node.node_do_focus(self, Direction::Unspecified); - self.maybe_schedule_warp_mouse_to_focus(); - } - } - - fn set_selection_( - self: &Rc, - field: &CloneCell>>, - src: Option>, - location: IpcLocation, - ) -> Result<(), WlSeatError> - where - T: ipc::IterableIpcVtable, - X: ipc::IpcVtable, - S: DynDataSource, - { - if let (Some(new), Some(old)) = (&src, &field.get()) - && new.source_data().id == old.source_data().id - { - return Ok(()); - } - if let Some(new) = &src { - ipc::attach_seat(&**new, self, ipc::Role::Selection)?; - } - let src_dyn = src.clone().map(|s| s as Rc); - if let Some(old) = field.set(src_dyn) { - old.detach_seat(self); - } - if let Some(client) = self.keyboard_node.get().node_client() { - self.offer_selection_to_client::(src.clone().map(|v| v as Rc<_>), &client); - // client.flush(); - } - let dyn_source = src.map(|s| s as Rc); - for dd in self.data_control_devices.lock().values() { - dd.clone().handle_new_source(location, dyn_source.clone()); - } - Ok(()) - } - - fn offer_selection_to_client( - &self, - selection: Option>, - client: &Rc, - ) where - T: ipc::IterableIpcVtable, - X: ipc::IpcVtable, - { - if let Some(src) = &selection { - src.cancel_unprivileged_offers(); - } - if client.is_xwayland { - self.for_each_x_data_device(|dd| match &selection { - Some(src) => src.clone().offer_to_x(&dd), - _ => X::send_selection(&dd, None), - }); - } else { - match selection { - Some(src) => offer_source_to_regular_client::(src, client), - _ => T::for_each_device(self, client.id, |device| { - T::send_selection(device, None); - }), - } - } - } - pub fn start_drag( self: &Rc, origin: &Rc, @@ -1388,88 +787,6 @@ impl WlSeatGlobal { self.pointer_owner.cancel_dnd(self); } - pub fn unset_selection(self: &Rc) { - let _ = self.set_wl_data_source_selection(None, None); - } - - pub fn set_wl_data_source_selection( - self: &Rc, - selection: Option>, - serial: Option, - ) -> Result<(), WlSeatError> { - if let Some(serial) = serial { - self.selection_serial.set(serial); - } - if let Some(selection) = &selection - && selection.toplevel_drag.is_some() - { - return Err(WlSeatError::OfferHasDrag); - } - self.set_selection(selection) - } - - pub fn set_selection( - self: &Rc, - selection: Option>, - ) -> Result<(), WlSeatError> { - self.set_selection_::( - &self.selection, - selection, - IpcLocation::Clipboard, - ) - } - - pub fn get_selection(&self) -> Option> { - self.selection.get() - } - - pub fn may_modify_selection(&self, client: &Rc, serial: u64) -> bool { - if serial < self.selection_serial.get() { - return false; - } - self.keyboard_node.get().node_client_id() == Some(client.id) - } - - pub fn may_modify_primary_selection(&self, client: &Rc, serial: Option) -> bool { - if let Some(serial) = serial - && serial < self.primary_selection_serial.get() - { - return false; - } - self.keyboard_node.get().node_client_id() == Some(client.id) - || self.pointer_node().and_then(|n| n.node_client_id()) == Some(client.id) - } - - pub fn unset_primary_selection(self: &Rc) { - let _ = self.set_zwp_primary_selection(None, None); - } - - pub fn set_zwp_primary_selection( - self: &Rc, - selection: Option>, - serial: Option, - ) -> Result<(), WlSeatError> { - if let Some(serial) = serial { - self.primary_selection_serial.set(serial); - } - self.set_primary_selection(selection) - } - - pub fn set_primary_selection( - self: &Rc, - selection: Option>, - ) -> Result<(), WlSeatError> { - self.set_selection_::( - &self.primary_selection, - selection, - IpcLocation::PrimarySelection, - ) - } - - pub fn get_primary_selection(&self) -> Option> { - self.primary_selection.get() - } - pub fn dnd_icon(&self) -> Option> { self.pointer_owner.dnd_icon() } @@ -1779,148 +1096,6 @@ pub struct WlSeat { tracker: Tracker, } -const READ_ONLY_KEYMAP_SINCE: Version = Version(7); - -impl WlSeat { - fn send_capabilities(self: &Rc) { - self.client.event(Capabilities { - self_id: self.id, - capabilities: self.global.capabilities.get(), - }) - } - - fn send_name(self: &Rc, name: &str) { - self.client.event(Name { - self_id: self.id, - name, - }) - } - - pub fn keymap_fd(&self, state: &KeyboardState) -> Result { - let fd = match self.client.is_xwayland { - true => &state.map.xwayland_map, - _ => &state.map.map, - }; - if self.version >= READ_ONLY_KEYMAP_SINCE { - return Ok(fd.clone()); - } - Ok(fd.create_unprotected_fd()?) - } -} - -impl WlSeatRequestHandler for WlSeat { - type Error = WlSeatError; - - fn get_pointer(&self, req: GetPointer, slf: &Rc) -> Result<(), Self::Error> { - let p = Rc::new(WlPointer::new(req.id, slf)); - track!(self.client, p); - self.client.add_client_obj(&p)?; - self.pointers.set(req.id, p.clone()); - let surface = self - .global - .pointer_node() - .and_then(|n| n.node_into_surface()); - if let Some(surface) = surface - && surface.client.id == self.client.id - { - let (x, y) = self.global.pointer_cursor.position(); - let (x_int, y_int) = surface - .buffer_abs_pos - .get() - .translate(x.round_down(), y.round_down()); - p.send_enter( - self.client.next_serial(), - surface.id, - x.apply_fract(x_int), - y.apply_fract(y_int), - ); - } - Ok(()) - } - - fn get_keyboard(&self, req: GetKeyboard, slf: &Rc) -> Result<(), Self::Error> { - let p = Rc::new(WlKeyboard::new(req.id, slf)); - track!(self.client, p); - self.client.add_client_obj(&p)?; - self.keyboards.set(req.id, p.clone()); - if let Some(surface) = self.global.keyboard_node.get().node_into_surface() - && surface.client.id == self.client.id - { - p.enter( - self.client.next_serial(), - surface.id, - &self.global.seat_kb_state.get().borrow().kb_state, - ); - } - if self.version >= REPEAT_INFO_SINCE { - let (rate, delay) = self.global.repeat_rate.get(); - p.send_repeat_info(rate, delay); - } - Ok(()) - } - - fn get_touch(&self, req: GetTouch, slf: &Rc) -> Result<(), Self::Error> { - let p = Rc::new(WlTouch::new(req.id, slf)); - track!(self.client, p); - self.client.add_client_obj(&p)?; - self.touches.set(req.id, p); - Ok(()) - } - - fn release(&self, _req: Release, _slf: &Rc) -> Result<(), Self::Error> { - { - let mut bindings = self.global.bindings.borrow_mut(); - if let Entry::Occupied(mut hm) = bindings.entry(self.client.id) { - hm.get_mut().remove(&self.id); - if hm.get().is_empty() { - hm.remove(); - } - } - } - self.client.remove_obj(self)?; - Ok(()) - } -} - -object_base! { - self = WlSeat; - version = self.version; -} - -impl Object for WlSeat { - fn break_loops(&self) { - { - let mut bindings = self.global.bindings.borrow_mut(); - if let Entry::Occupied(mut hm) = bindings.entry(self.client.id) { - hm.get_mut().remove(&self.id); - if hm.get().is_empty() { - hm.remove(); - } - } - } - self.pointers.clear(); - self.relative_pointers.clear(); - self.keyboards.clear(); - self.touches.clear(); - } -} - -dedicated_add_obj!(WlSeat, WlSeatId, seats); - -#[derive(Debug, Error)] -pub enum WlSeatError { - #[error(transparent)] - ClientError(Box), - #[error(transparent)] - IpcError(#[from] IpcError), - #[error(transparent)] - WlKeyboardError(Box), - #[error("Data source has a toplevel attached")] - OfferHasDrag, -} -efrom!(WlSeatError, ClientError); -efrom!(WlSeatError, WlKeyboardError); - pub fn collect_kb_foci2(node: Rc, seats: &mut SmallVec<[Rc; 3]>) { node.node_visit(&mut generic_node_visitor(|node| { node.node_seat_state().for_each_kb_focus(|s| seats.push(s)); @@ -1932,207 +1107,3 @@ pub fn collect_kb_foci(node: Rc) -> SmallVec<[Rc; 3]> { collect_kb_foci2(node, &mut res); res } - -impl DeviceHandlerData { - pub fn set_seat(&self, _state: &State, seat: Option>) { - if let Some(new) = &seat { - if let Some(old) = self.seat.get() - && old.id() == new.id() - { - return; - } - } else { - if self.seat.is_none() { - return; - } - } - self.destroy_physical_keyboard_state(); - let old = self.seat.set(seat.clone()); - if let Some(old) = old { - if let Some(info) = &self.tablet_init { - old.tablet_remove_tablet(info.id); - } - if let Some(info) = &self.tablet_pad_init { - old.tablet_remove_tablet_pad(info.id); - } - if self.is_touch { - old.num_touch_devices.fetch_sub(1); - old.update_capabilities(); - } - } - if let Some(seat) = &seat { - if let Some(info) = &self.tablet_init { - seat.tablet_add_tablet(self.device.id(), info); - } - if let Some(info) = &self.tablet_pad_init { - seat.tablet_add_tablet_pad(self.device.id(), info); - } - if self.is_touch { - seat.num_touch_devices.fetch_add(1); - seat.update_capabilities(); - } - } - self.attach_event_listeners(); - } - - fn destroy_physical_keyboard_state(&self) { - self.mods_listener.detach(); - if let Some(seat) = self.seat.get() { - seat.destroy_physical_keyboard(self.keyboard_id); - }; - } - - fn attach_event_listeners(&self) { - if self.is_kb - && let Some(seat) = self.seat.get() - { - seat.attach_modifiers_listener( - self.keyboard_id, - &self.mods_listener, - self.keymap.get().as_ref(), - ); - }; - } - - pub fn set_keymap(&self, _state: &State, keymap: Option>) { - self.destroy_physical_keyboard_state(); - self.keymap.set(keymap); - self.attach_event_listeners(); - } - - pub fn set_output(&self, _state: &State, output: Option<&WlOutputGlobal>) { - match output { - None => { - log::info!("Removing output mapping of {}", self.device.name()); - self.output.take(); - } - Some(o) => { - log::info!("Mapping {} to {}", self.device.name(), o.connector.name); - self.output.set(Some(o.opt.clone())); - } - } - } - - pub fn get_rect(&self, state: &State) -> Rect { - if let Some(output) = self.output.get() - && let Some(output) = output.get() - { - return output.pos.get(); - } - state.root.extents.get() - } - - pub fn set_accel_profile(&self, _state: &State, v: InputDeviceAccelProfile) { - self.device.set_accel_profile(v); - } - - pub fn set_accel_speed(&self, _state: &State, v: f64) { - self.device.set_accel_speed(v); - } - - pub fn set_tap_enabled(&self, _state: &State, v: bool) { - self.device.set_tap_enabled(v); - } - - pub fn set_drag_enabled(&self, _state: &State, v: bool) { - self.device.set_drag_enabled(v); - } - - pub fn set_drag_lock_enabled(&self, _state: &State, v: bool) { - self.device.set_drag_lock_enabled(v); - } - - pub fn set_left_handed(&self, _state: &State, v: bool) { - self.device.set_left_handed(v); - } - - pub fn set_natural_scrolling_enabled(&self, _state: &State, v: bool) { - self.device.set_natural_scrolling_enabled(v); - } - - pub fn set_px_per_scroll_wheel(&self, _state: &State, v: f64) { - self.px_per_scroll_wheel.set(v); - } - - pub fn set_transform_matrix(&self, _state: &State, v: TransformMatrix) { - self.device.set_transform_matrix(v); - } - - pub fn set_calibration_matrix(&self, _state: &State, v: [[f32; 3]; 2]) { - self.device.set_calibration_matrix(v); - } - - pub fn set_click_method(&self, _state: &State, v: InputDeviceClickMethod) { - self.device.set_click_method(v); - } - - pub fn set_middle_button_emulation_enabled(&self, _state: &State, v: bool) { - self.device.set_middle_button_emulation_enabled(v); - } -} - -impl LedsListener for DeviceHandlerData { - fn leds(&self, leds: Leds) { - self.device.set_enabled_leds(leds); - } -} - -impl LedsListener for WlSeatGlobal { - fn leds(&self, leds: Leds) { - self.dispatch_seat_leds_listeners(leds) - } -} - -pub struct PositionHintRequest { - seat: Rc, - client_id: ClientId, - old_pos: (Fixed, Fixed), - new_pos: (Fixed, Fixed), -} - -pub async fn handle_position_hint_requests(state: Rc) { - loop { - let req = state.position_hint_requests.pop().await; - let (x, y) = (req.new_pos.0.round_down(), req.new_pos.1.round_down()); - if state.node_at(x, y).node.node_client_id() != Some(req.client_id) { - continue; - } - let current_pos = req.seat.pointer_cursor.position(); - let (x, y) = ( - req.new_pos.0 + (current_pos.0 - req.old_pos.0), - req.new_pos.1 + (current_pos.1 - req.old_pos.1), - ); - req.seat.motion_event_abs(state.now_usec(), x, y, Warp); - } -} - -pub async fn handle_warp_mouse_to_focus(state: Rc) { - loop { - state.pending_warp_mouse_to_focus.non_empty().await; - state.eng.yield_now().await; - while let Some(seat) = state.pending_warp_mouse_to_focus.try_pop() { - seat.warp_mouse_to_focus_scheduled.set(false); - let skip_target_check = seat.warp_mouse_to_focus_skip_target_check.take(); - let Some(tl) = seat.keyboard_node.get().node_toplevel() else { - continue; - }; - let (x, y) = tl.node_absolute_position().center(); - if !skip_target_check { - let Some(target) = state.node_at(x, y).node.node_toplevel() else { - continue; - }; - if target.node_id() != tl.node_id() { - continue; - } - } - let (x, y) = (Fixed::from_int(x), Fixed::from_int(y)); - seat.motion_event_abs(state.now_usec(), x, y, Warp); - } - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum CursorPositionType { - Motion, - Warp, -} diff --git a/src/ifs/wl_seat/device_handler.rs b/src/ifs/wl_seat/device_handler.rs new file mode 100644 index 00000000..7bb01f5d --- /dev/null +++ b/src/ifs/wl_seat/device_handler.rs @@ -0,0 +1,151 @@ +use super::*; + +impl DeviceHandlerData { + pub fn set_seat(&self, _state: &State, seat: Option>) { + if let Some(new) = &seat { + if let Some(old) = self.seat.get() + && old.id() == new.id() + { + return; + } + } else { + if self.seat.is_none() { + return; + } + } + self.destroy_physical_keyboard_state(); + let old = self.seat.set(seat.clone()); + if let Some(old) = old { + if let Some(info) = &self.tablet_init { + old.tablet_remove_tablet(info.id); + } + if let Some(info) = &self.tablet_pad_init { + old.tablet_remove_tablet_pad(info.id); + } + if self.is_touch { + old.num_touch_devices.fetch_sub(1); + old.update_capabilities(); + } + } + if let Some(seat) = &seat { + if let Some(info) = &self.tablet_init { + seat.tablet_add_tablet(self.device.id(), info); + } + if let Some(info) = &self.tablet_pad_init { + seat.tablet_add_tablet_pad(self.device.id(), info); + } + if self.is_touch { + seat.num_touch_devices.fetch_add(1); + seat.update_capabilities(); + } + } + self.attach_event_listeners(); + } + + fn destroy_physical_keyboard_state(&self) { + self.mods_listener.detach(); + if let Some(seat) = self.seat.get() { + seat.destroy_physical_keyboard(self.keyboard_id); + }; + } + + fn attach_event_listeners(&self) { + if self.is_kb + && let Some(seat) = self.seat.get() + { + seat.attach_modifiers_listener( + self.keyboard_id, + &self.mods_listener, + self.keymap.get().as_ref(), + ); + }; + } + + pub fn set_keymap(&self, _state: &State, keymap: Option>) { + self.destroy_physical_keyboard_state(); + self.keymap.set(keymap); + self.attach_event_listeners(); + } + + pub fn set_output(&self, _state: &State, output: Option<&WlOutputGlobal>) { + match output { + None => { + log::info!("Removing output mapping of {}", self.device.name()); + self.output.take(); + } + Some(o) => { + log::info!("Mapping {} to {}", self.device.name(), o.connector.name); + self.output.set(Some(o.opt.clone())); + } + } + } + + pub fn get_rect(&self, state: &State) -> Rect { + if let Some(output) = self.output.get() + && let Some(output) = output.get() + { + return output.pos.get(); + } + state.root.extents.get() + } + + pub fn set_accel_profile(&self, _state: &State, v: InputDeviceAccelProfile) { + self.device.set_accel_profile(v); + } + + pub fn set_accel_speed(&self, _state: &State, v: f64) { + self.device.set_accel_speed(v); + } + + pub fn set_tap_enabled(&self, _state: &State, v: bool) { + self.device.set_tap_enabled(v); + } + + pub fn set_drag_enabled(&self, _state: &State, v: bool) { + self.device.set_drag_enabled(v); + } + + pub fn set_drag_lock_enabled(&self, _state: &State, v: bool) { + self.device.set_drag_lock_enabled(v); + } + + pub fn set_left_handed(&self, _state: &State, v: bool) { + self.device.set_left_handed(v); + } + + pub fn set_natural_scrolling_enabled(&self, _state: &State, v: bool) { + self.device.set_natural_scrolling_enabled(v); + } + + pub fn set_px_per_scroll_wheel(&self, _state: &State, v: f64) { + self.px_per_scroll_wheel.set(v); + } + + pub fn set_transform_matrix(&self, _state: &State, v: TransformMatrix) { + self.device.set_transform_matrix(v); + } + + pub fn set_calibration_matrix(&self, _state: &State, v: [[f32; 3]; 2]) { + self.device.set_calibration_matrix(v); + } + + pub fn set_click_method(&self, _state: &State, v: InputDeviceClickMethod) { + self.device.set_click_method(v); + } + + pub fn set_middle_button_emulation_enabled(&self, _state: &State, v: bool) { + self.device.set_middle_button_emulation_enabled(v); + } +} + +impl LedsListener for DeviceHandlerData { + fn leds(&self, leds: Leds) { + self.device.set_enabled_leds(leds); + } +} + +impl LedsListener for WlSeatGlobal { + fn leds(&self, leds: Leds) { + self.dispatch_seat_leds_listeners(leds) + } +} diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index 877151c4..8dd2edd5 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -1,20 +1,22 @@ +use jay_geometry::Rect; +use jay_keyboard::{KbvmState, KeyboardState}; +use jay_units::fixed::Fixed; use { crate::{ backend::{ AXIS_120, AxisSource, ButtonState, ConnectorId, InputDeviceId, InputEvent, KeyState, - ScrollAxis, + ScrollAxis, SwitchEvent, }, client::ClientId, config::InvokedShortcut, ei::ei_ifs::ei_seat::EiSeat, - fixed::Fixed, ifs::{ - ipc::{ + data_transfer::{ offer_source_to_regular_client, - wl_data_device::{ClipboardIpc, WlDataDevice}, - x_data_device::{XClipboardIpc, XPrimarySelectionIpc}, + wl_data_device::{ClipboardTransfer, WlDataDevice}, + x_data_device::{XClipboardTransfer, XPrimarySelectionTransfer}, zwp_primary_selection_device_v1::{ - PrimarySelectionIpc, ZwpPrimarySelectionDeviceV1, + PrimarySelectionTransfer, ZwpPrimarySelectionDeviceV1, }, }, wl_seat::{ @@ -35,10 +37,8 @@ use { }, wl_surface::{WlSurface, xdg_surface::xdg_popup::XdgPopup}, }, - kbvm::KbvmState, - keyboard::KeyboardState, object::Version, - rect::Rect, + state::DeviceHandlerData, tree::{Direction, Node, ToplevelNode}, utils::{ @@ -52,12 +52,9 @@ use { }, CursorPositionType::Motion, isnt::std_1::primitive::IsntSliceExt, - jay_config::{ - input::SwitchEvent, - keyboard::{ - mods::{CAPS, Modifiers, NUM, RELEASE}, - syms::KeySym, - }, + jay_config::keyboard::{ + mods::{CAPS, Modifiers, NUM, RELEASE}, + syms::KeySym, }, kbvm::{Keycode, ModifierMask, evdev, state_machine::Event}, linearize::LinearizeExt, @@ -70,6 +67,15 @@ use { }, }; +fn config_switch_event(event: SwitchEvent) -> jay_config::input::SwitchEvent { + match event { + SwitchEvent::LidOpened => jay_config::input::SwitchEvent::LidOpened, + SwitchEvent::LidClosed => jay_config::input::SwitchEvent::LidClosed, + SwitchEvent::ConvertedToLaptop => jay_config::input::SwitchEvent::ConvertedToLaptop, + SwitchEvent::ConvertedToTablet => jay_config::input::SwitchEvent::ConvertedToTablet, + } +} + #[derive(Default)] pub struct NodeSeatState { pointer_foci: SmallMap, 1>, @@ -816,7 +822,7 @@ impl WlSeatGlobal { t.send_switch_event(self.id, dev, time_usec, event); }); if let Some(config) = self.state.config.get() { - config.switch_event(self.id, dev, event); + config.switch_event(self.id, dev, config_switch_event(event)); } } @@ -1605,11 +1611,11 @@ impl WlSeatGlobal { }); if self.keyboard_node.get().node_client_id() != Some(surface.client.id) { - self.offer_selection_to_client::( + self.offer_selection_to_client::( self.selection.get(), &surface.client, ); - self.offer_selection_to_client::( + self.offer_selection_to_client::( self.primary_selection.get(), &surface.client, ); @@ -1733,7 +1739,7 @@ impl WlSeatGlobal { ) { if let Some(src) = &dnd.src { if !surface.client.is_xwayland { - offer_source_to_regular_client::(src.clone(), &surface.client); + offer_source_to_regular_client::(src.clone(), &surface.client); } src.for_each_data_offer(|offer| { offer.send_enter(surface.id, x, y, serial); diff --git a/src/ifs/wl_seat/ext_transient_seat_manager_v1.rs b/src/ifs/wl_seat/ext_transient_seat_manager_v1.rs index 8bb016a1..b5086be2 100644 --- a/src/ifs/wl_seat/ext_transient_seat_manager_v1.rs +++ b/src/ifs/wl_seat/ext_transient_seat_manager_v1.rs @@ -1,6 +1,6 @@ use { crate::{ - client::{CAP_SEAT_MANAGER, Client, ClientCaps, ClientError}, + client::{Client, ClientError}, globals::{Global, GlobalName}, ifs::wl_seat::ext_transient_seat_v1::ExtTransientSeatV1, leaks::Tracker, @@ -55,10 +55,6 @@ impl Global for ExtTransientSeatManagerV1Global { fn version(&self) -> u32 { 1 } - - fn required_caps(&self) -> ClientCaps { - CAP_SEAT_MANAGER - } } simple_add_global!(ExtTransientSeatManagerV1Global); diff --git a/src/ifs/wl_seat/focus.rs b/src/ifs/wl_seat/focus.rs new file mode 100644 index 00000000..edcb550e --- /dev/null +++ b/src/ifs/wl_seat/focus.rs @@ -0,0 +1,471 @@ +use { + super::{WlSeatGlobal, event_handling::FocusHistoryData}, + crate::{ + ifs::wl_surface::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, + tree::{ + Direction, Node, NodeLayer, NodeLayerLink, NodeLocation, StackedNode, WorkspaceNode, + toplevel_set_workspace, + }, + utils::{ + linkedlist::{LinkedList, NodeRef}, + rc_eq::rc_weak_eq, + }, + }, + run_on_drop::on_drop, + std::{ops::Deref, rc::Rc}, +}; + +impl WlSeatGlobal { + pub fn close(self: &Rc) { + let kb_node = self.keyboard_node.get(); + if let Some(tl) = kb_node.node_toplevel() { + tl.tl_close(); + } + } + + pub fn move_focus(self: &Rc, direction: Direction) { + let tl = match self.keyboard_node.get().node_toplevel() { + Some(tl) => tl, + _ => { + if let Some(ws) = self.keyboard_node.get().node_into_workspace() + && let Some(target) = self + .state + .find_output_in_direction(&ws.output.get(), direction) + { + target.take_keyboard_navigation_focus(self, direction); + self.maybe_schedule_warp_mouse_to_focus(); + } + return; + } + }; + if direction == Direction::Down && tl.node_is_container() { + tl.node_do_focus(self, direction); + } else { + let data = tl.tl_data(); + if data.is_fullscreen.get() + && let Some(output) = data.output_opt() + && let Some(target) = self.state.find_output_in_direction(&output, direction) + { + target.take_keyboard_navigation_focus(self, direction); + } else if let Some(p) = data.parent.get() + && let Some(c) = p.node_into_container() + { + c.move_focus_from_child(self, tl.deref(), direction); + } else if let Some(float) = data.float.get() { + let ws = float.workspace.get(); + let floats: Vec<_> = ws + .stacked + .iter() + .filter_map(|node| (*node).clone().node_into_float()) + .filter(|f| f.child.get().is_some()) + .collect(); + if let Some(pos) = floats.iter().position(|f| f.id == float.id) { + let target = match direction { + Direction::Left | Direction::Down => { + if pos == 0 { + floats.last() + } else { + floats.get(pos - 1) + } + } + _ => { + if pos + 1 >= floats.len() { + floats.first() + } else { + floats.get(pos + 1) + } + } + }; + if let Some(f) = target + && f.id != float.id + { + f.clone().node_do_focus(self, Direction::Unspecified); + } + } + } + } + self.maybe_schedule_warp_mouse_to_focus(); + } + + pub fn maybe_schedule_warp_mouse_to_focus(self: &Rc) { + if self.mouse_follows_focus() { + self.warp_mouse_to_focus_skip_target_check.set(true); + self.schedule_warp_mouse_to_focus(); + } + } + + pub fn schedule_warp_mouse_to_focus(self: &Rc) { + if !self.warp_mouse_to_focus_scheduled.replace(true) { + self.state.pending_warp_mouse_to_focus.push(self.clone()); + } + } + + pub fn move_focused(self: &Rc, direction: Direction) { + let kb_node = self.keyboard_node.get(); + let Some(tl) = kb_node.node_toplevel() else { + if let Some(ws) = self.keyboard_node.get().node_into_workspace() + && let Some(target) = self + .state + .find_output_in_direction(&ws.output.get(), direction) + { + self.state.move_ws_to_output(&ws, &target); + } + return; + }; + let data = tl.tl_data(); + if data.is_fullscreen.get() + && let Some(output) = data.output_opt() + && let Some(target) = self.state.find_output_in_direction(&output, direction) + { + let ws = target.ensure_workspace(); + toplevel_set_workspace(&self.state, tl, &ws); + self.maybe_schedule_warp_mouse_to_focus(); + } else if let Some(parent) = data.parent.get() + && let Some(c) = parent.node_into_container() + { + c.move_child(tl, direction); + self.maybe_schedule_warp_mouse_to_focus(); + } else if let Some(float) = data.float.get() { + float.move_by_direction(direction); + self.maybe_schedule_warp_mouse_to_focus(); + } + } + + pub fn get_last_focus_on_workspace(&self, ws: &WorkspaceNode) -> Option> { + let mut node = self.focus_history.last()?; + loop { + if let Some(node) = node.node.upgrade() + && let Some(NodeLocation::Workspace(_, new)) = node.node_location() + && new == ws.id + { + return Some(node); + } + node = node.prev()?; + } + } + + fn get_focus_history( + &self, + next: impl Fn(&NodeRef) -> Option>, + first: impl FnOnce(&LinkedList) -> Option>, + ) -> Option<(Rc, bool)> { + let original = self.keyboard_node.get(); + let mut output = None; + let mut workspace = None; + if let Some(old) = original.node_location() { + match old { + NodeLocation::Workspace(o, w) => { + workspace = Some(w); + output = Some(o); + } + NodeLocation::Output(o) => { + output = Some(o); + } + } + } + if (output.is_none() || workspace.is_none()) + && let Some(old) = self.last_focus_location.get() + { + match old { + NodeLocation::Workspace(o, w) => { + workspace = workspace.or(Some(w)); + output = output.or(Some(o)); + } + NodeLocation::Output(o) => { + output = output.or(Some(o)); + } + } + } + if workspace.is_none() + && let Some(output) = original.node_output() + && let Some(ws) = output.workspace.get() + { + workspace = Some(ws.id); + } + let matches = |node: &FocusHistoryData| { + let visible = node.visible.get(); + if self.focus_history_visible_only.get() && !visible { + return None; + } + let node = node.node.upgrade()?; + if self.focus_history_same_workspace.get() { + let new = node.node_location()?; + let o = match new { + NodeLocation::Workspace(o, w) => { + if workspace != Some(w) { + return None; + } + o + } + NodeLocation::Output(o) => o, + }; + if output != Some(o) { + return None; + } + } + Some((node, visible)) + }; + let node = original.node_seat_state().get_focus_history(self); + if let Some(mut node) = node { + loop { + node = match next(&node) { + Some(n) => n, + _ => break, + }; + if let Some(matches) = matches(&node) { + return Some(matches); + } + } + } + let mut node = first(&self.focus_history)?; + loop { + if rc_weak_eq(&original, &node.node) { + return None; + } + if let Some(matches) = matches(&node) { + return Some(matches); + } + node = next(&node)?; + } + } + + fn focus_history( + self: &Rc, + next: impl Fn(&NodeRef) -> Option>, + first: impl FnOnce(&LinkedList) -> Option>, + ) { + let Some((node, visible)) = self.get_focus_history(next, first) else { + return; + }; + self.focus_history_rotate.fetch_add(1); + let _reset = on_drop(|| { + self.focus_history_rotate.fetch_sub(1); + }); + if !visible { + node.clone().node_make_visible(); + if !node.node_visible() { + return; + } + } + self.focus_node(node); + self.maybe_schedule_warp_mouse_to_focus(); + } + + pub fn focus_prev(self: &Rc) { + self.focus_history(|s| s.prev(), |l| l.last()); + } + + pub fn focus_next(self: &Rc) { + self.focus_history(|s| s.next(), |l| l.first()); + } + + pub fn focus_history_set_visible(&self, visible: bool) { + self.focus_history_visible_only.set(visible); + } + + pub fn focus_history_set_same_workspace(&self, same_workspace: bool) { + self.focus_history_same_workspace.set(same_workspace); + } + + fn focus_layer_rel( + self: &Rc, + next_layer: impl Fn(NodeLayer) -> NodeLayer, + layer_node_next: impl Fn( + &NodeRef>, + ) -> Option>>, + stacked_node_next: impl Fn( + &NodeRef>, + ) -> Option>>, + layer_list_iter: impl Fn(&LinkedList>) -> LI, + stacked_list_iter: impl Fn(&LinkedList>) -> SI, + ) where + LI: Iterator>>, + SI: Iterator>>, + { + fn node_viable(n: &(impl Node + ?Sized)) -> bool { + n.node_visible() && n.node_accepts_focus() + } + + let current = self.keyboard_node.get(); + let Some(output) = current.node_output() else { + return; + }; + let current_layer = current.node_layer(); + match ¤t_layer { + NodeLayerLink::Layer0(l) + | NodeLayerLink::Layer1(l) + | NodeLayerLink::Layer2(l) + | NodeLayerLink::Layer3(l) => { + if let Some(n) = layer_node_next(l) + && node_viable(&**n) + { + n.deref() + .clone() + .node_do_focus(self, Direction::Unspecified); + self.maybe_schedule_warp_mouse_to_focus(); + return; + } + } + NodeLayerLink::Stacked(l) | NodeLayerLink::StackedAboveLayers(l) => { + if let Some(n) = stacked_node_next(l) + && node_viable(&**n) + && n.node_output().map(|o| o.id) == Some(output.id) + { + n.deref() + .clone() + .node_do_focus(self, Direction::Unspecified); + self.maybe_schedule_warp_mouse_to_focus(); + return; + } + } + NodeLayerLink::Display => {} + NodeLayerLink::Output => {} + NodeLayerLink::Workspace => {} + NodeLayerLink::Tiled => {} + NodeLayerLink::Fullscreen => {} + NodeLayerLink::Lock => {} + NodeLayerLink::InputMethod => {} + } + let handle_layer_shell = |l: &LinkedList>| { + for n in layer_list_iter(l) { + if node_viable(&**n) { + return Some(n.deref().clone() as Rc); + } + } + None + }; + let handle_stacked = |l: &LinkedList>| { + for n in stacked_list_iter(l) { + if node_viable(&**n) && n.node_output().map(|o| o.id) == Some(output.id) { + return Some(n.deref().clone() as Rc); + } + } + None + }; + let ws = output.workspace.get(); + let first = next_layer(current_layer.layer()); + let mut layer = first; + loop { + let node = match layer { + NodeLayer::Display => None, + NodeLayer::Layer0 => handle_layer_shell(&output.layers[0]), + NodeLayer::Layer1 => handle_layer_shell(&output.layers[1]), + NodeLayer::Output => None, + NodeLayer::Workspace => { + if let Some(ws) = &ws + && ws.container_visible() + { + self.focus_node(ws.clone()); + self.maybe_schedule_warp_mouse_to_focus(); + return; + } + None + } + NodeLayer::Tiled => ws + .as_ref() + .and_then(|w| w.container.get()) + .map(|n| n as Rc), + NodeLayer::Fullscreen => ws + .as_ref() + .and_then(|w| w.fullscreen.get()) + .map(|n| n as Rc), + NodeLayer::Stacked => handle_stacked(&self.state.root.stacked), + NodeLayer::Layer2 => handle_layer_shell(&output.layers[2]), + NodeLayer::Layer3 => handle_layer_shell(&output.layers[3]), + NodeLayer::StackedAboveLayers => { + handle_stacked(&self.state.root.stacked_above_layers) + } + NodeLayer::Lock => None, + NodeLayer::InputMethod => None, + }; + if let Some(n) = node { + if node_viable(&*n) { + n.node_do_focus(self, Direction::Unspecified); + self.maybe_schedule_warp_mouse_to_focus(); + return; + } + } + layer = next_layer(layer); + if layer == first { + return; + } + } + } + + pub fn focus_layer_below(self: &Rc) { + self.focus_layer_rel( + |l| l.prev(), + |n| n.prev(), + |n| n.prev(), + |l| l.rev_iter(), + |l| l.rev_iter(), + ); + } + + pub fn focus_layer_above(self: &Rc) { + self.focus_layer_rel( + |l| l.next(), + |n| n.next(), + |n| n.next(), + |l| l.iter(), + |l| l.iter(), + ); + } + + pub fn toggle_focus_float_tiled(self: &Rc) { + let current = self.keyboard_node.get(); + match current.node_layer().layer() { + NodeLayer::Tiled | NodeLayer::Fullscreen => self.focus_floats(), + _ => self.focus_tiles(), + } + self.maybe_schedule_warp_mouse_to_focus(); + } + + pub fn focus_floats(self: &Rc) { + let current = self.keyboard_node.get(); + if current.node_layer().layer() == NodeLayer::Stacked { + return; + } + let Some(output) = current.node_output() else { + return; + }; + let Some(ws) = output.workspace.get() else { + return; + }; + if let Some(child) = ws + .stacked + .rev_iter() + .filter_map(|node| (*node).clone().node_into_float()) + .find_map(|float| float.child.get()) + { + child.node_do_focus(self, Direction::Unspecified); + } + } + + pub fn focus_tiles(self: &Rc) { + let current = self.keyboard_node.get(); + if matches!( + current.node_layer().layer(), + NodeLayer::Tiled | NodeLayer::Fullscreen, + ) { + return; + } + let Some(output) = current.node_output() else { + return; + }; + let Some(ws) = output.workspace.get() else { + return; + }; + let node = match ws.fullscreen.get() { + Some(fs) => fs as Rc, + _ => match ws.container.get() { + Some(c) => c, + _ => return, + }, + }; + if node.node_visible() && node.node_accepts_focus() { + node.node_do_focus(self, Direction::Unspecified); + self.maybe_schedule_warp_mouse_to_focus(); + } + } +} diff --git a/src/ifs/wl_seat/gesture_owner.rs b/src/ifs/wl_seat/gesture_owner.rs index 2340f0bb..fb66a28c 100644 --- a/src/ifs/wl_seat/gesture_owner.rs +++ b/src/ifs/wl_seat/gesture_owner.rs @@ -1,5 +1,6 @@ +use jay_units::fixed::Fixed; use { - crate::{fixed::Fixed, ifs::wl_seat::WlSeatGlobal, tree::Node, utils::clonecell::CloneCell}, + crate::{ifs::wl_seat::WlSeatGlobal, tree::Node, utils::clonecell::CloneCell}, std::rc::Rc, }; diff --git a/src/ifs/wl_seat/pointer_owner.rs b/src/ifs/wl_seat/pointer_owner.rs index d1133c46..6acadc8a 100644 --- a/src/ifs/wl_seat/pointer_owner.rs +++ b/src/ifs/wl_seat/pointer_owner.rs @@ -1,11 +1,13 @@ +use jay_time::Time; +use jay_geometry::Rect; +use jay_units::fixed::Fixed; use { crate::{ backend::{AXIS_120, AxisSource, ButtonState, ScrollAxis}, cursor::KnownCursor, - fixed::Fixed, ifs::{ - ipc, - ipc::wl_data_source::WlDataSource, + data_transfer, + data_transfer::wl_data_source::WlDataSource, wl_seat::{ BTN_LEFT, BTN_RIGHT, CHANGE_CURSOR_MOVED, CHANGE_TREE, Dnd, DroppedDnd, NodeSeatState, WlSeatError, WlSeatGlobal, wl_pointer::PendingScroll, @@ -17,8 +19,8 @@ use { }, xdg_toplevel_drag_v1::XdgToplevelDragV1, }, - rect::Rect, - time::Time, + + tree::{ ContainerNode, ContainerSplit, ContainingNode, FindTreeUsecase, FoundNode, Node, PlaceholderNode, TddType, ToplevelNode, WorkspaceDragDestination, WorkspaceNode, @@ -606,7 +608,7 @@ impl PointerOwner for DndPointerOwner { target.node_on_dnd_leave(&self.dnd); target.node_seat_state().remove_dnd_target(seat); if !should_drop && let Some(src) = &self.dnd.src { - ipc::detach_seat(&**src, seat); + data_transfer::detach_seat(&**src, seat); } if let Some(icon) = self.icon.get() { icon.disable(); @@ -651,7 +653,7 @@ impl PointerOwner for DndPointerOwner { target.node_on_dnd_leave(&self.dnd); target.node_seat_state().remove_dnd_target(seat); if let Some(src) = &self.dnd.src { - ipc::detach_seat(&**src, seat); + data_transfer::detach_seat(&**src, seat); } if let Some(icon) = self.icon.get() { icon.disable(); @@ -887,7 +889,7 @@ impl SimplePointerOwnerUsecase for DefaultPointerUsecase { icon.enable(); } if let Some(new) = &src { - ipc::attach_seat(&**new, seat, ipc::Role::Dnd)?; + data_transfer::attach_seat(&**new, seat, data_transfer::Role::Dnd)?; if let Some(drag) = new.toplevel_drag.get() { drag.start_drag(); } diff --git a/src/ifs/wl_seat/position_hint.rs b/src/ifs/wl_seat/position_hint.rs new file mode 100644 index 00000000..17b74454 --- /dev/null +++ b/src/ifs/wl_seat/position_hint.rs @@ -0,0 +1,55 @@ +use {super::*, CursorPositionType::Warp}; + +pub struct PositionHintRequest { + pub(super) seat: Rc, + pub(super) client_id: ClientId, + pub(super) old_pos: (Fixed, Fixed), + pub(super) new_pos: (Fixed, Fixed), +} + +pub async fn handle_position_hint_requests(state: Rc) { + loop { + let req = state.position_hint_requests.pop().await; + let (x, y) = (req.new_pos.0.round_down(), req.new_pos.1.round_down()); + if state.node_at(x, y).node.node_client_id() != Some(req.client_id) { + continue; + } + let current_pos = req.seat.pointer_cursor.position(); + let (x, y) = ( + req.new_pos.0 + (current_pos.0 - req.old_pos.0), + req.new_pos.1 + (current_pos.1 - req.old_pos.1), + ); + req.seat.motion_event_abs(state.now_usec(), x, y, Warp); + } +} + +pub async fn handle_warp_mouse_to_focus(state: Rc) { + loop { + state.pending_warp_mouse_to_focus.non_empty().await; + state.eng.yield_now().await; + while let Some(seat) = state.pending_warp_mouse_to_focus.try_pop() { + seat.warp_mouse_to_focus_scheduled.set(false); + let skip_target_check = seat.warp_mouse_to_focus_skip_target_check.take(); + let Some(tl) = seat.keyboard_node.get().node_toplevel() else { + continue; + }; + let (x, y) = tl.node_absolute_position().center(); + if !skip_target_check { + let Some(target) = state.node_at(x, y).node.node_toplevel() else { + continue; + }; + if target.node_id() != tl.node_id() { + continue; + } + } + let (x, y) = (Fixed::from_int(x), Fixed::from_int(y)); + seat.motion_event_abs(state.now_usec(), x, y, Warp); + } + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum CursorPositionType { + Motion, + Warp, +} diff --git a/src/ifs/wl_seat/seat_object.rs b/src/ifs/wl_seat/seat_object.rs new file mode 100644 index 00000000..4c39bc53 --- /dev/null +++ b/src/ifs/wl_seat/seat_object.rs @@ -0,0 +1,143 @@ +use super::*; + +const READ_ONLY_KEYMAP_SINCE: Version = Version(7); + +impl WlSeat { + pub(super) fn send_capabilities(self: &Rc) { + self.client.event(Capabilities { + self_id: self.id, + capabilities: self.global.capabilities.get(), + }) + } + + pub(super) fn send_name(self: &Rc, name: &str) { + self.client.event(Name { + self_id: self.id, + name, + }) + } + + pub fn keymap_fd(&self, state: &KeyboardState) -> Result { + let fd = match self.client.is_xwayland { + true => &state.map.xwayland_map, + _ => &state.map.map, + }; + if self.version >= READ_ONLY_KEYMAP_SINCE { + return Ok(fd.clone()); + } + Ok(fd.create_unprotected_fd()?) + } +} + +impl WlSeatRequestHandler for WlSeat { + type Error = WlSeatError; + + fn get_pointer(&self, req: GetPointer, slf: &Rc) -> Result<(), Self::Error> { + let p = Rc::new(WlPointer::new(req.id, slf)); + track!(self.client, p); + self.client.add_client_obj(&p)?; + self.pointers.set(req.id, p.clone()); + let surface = self + .global + .pointer_node() + .and_then(|n| n.node_into_surface()); + if let Some(surface) = surface + && surface.client.id == self.client.id + { + let (x, y) = self.global.pointer_cursor.position(); + let (x_int, y_int) = surface + .buffer_abs_pos + .get() + .translate(x.round_down(), y.round_down()); + p.send_enter( + self.client.next_serial(), + surface.id, + x.apply_fract(x_int), + y.apply_fract(y_int), + ); + } + Ok(()) + } + + fn get_keyboard(&self, req: GetKeyboard, slf: &Rc) -> Result<(), Self::Error> { + let p = Rc::new(WlKeyboard::new(req.id, slf)); + track!(self.client, p); + self.client.add_client_obj(&p)?; + self.keyboards.set(req.id, p.clone()); + if let Some(surface) = self.global.keyboard_node.get().node_into_surface() + && surface.client.id == self.client.id + { + p.enter( + self.client.next_serial(), + surface.id, + &self.global.seat_kb_state.get().borrow().kb_state, + ); + } + if self.version >= REPEAT_INFO_SINCE { + let (rate, delay) = self.global.repeat_rate.get(); + p.send_repeat_info(rate, delay); + } + Ok(()) + } + + fn get_touch(&self, req: GetTouch, slf: &Rc) -> Result<(), Self::Error> { + let p = Rc::new(WlTouch::new(req.id, slf)); + track!(self.client, p); + self.client.add_client_obj(&p)?; + self.touches.set(req.id, p); + Ok(()) + } + + fn release(&self, _req: Release, _slf: &Rc) -> Result<(), Self::Error> { + { + let mut bindings = self.global.bindings.borrow_mut(); + if let Entry::Occupied(mut hm) = bindings.entry(self.client.id) { + hm.get_mut().remove(&self.id); + if hm.get().is_empty() { + hm.remove(); + } + } + } + self.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + self = WlSeat; + version = self.version; +} + +impl Object for WlSeat { + fn break_loops(&self) { + { + let mut bindings = self.global.bindings.borrow_mut(); + if let Entry::Occupied(mut hm) = bindings.entry(self.client.id) { + hm.get_mut().remove(&self.id); + if hm.get().is_empty() { + hm.remove(); + } + } + } + self.pointers.clear(); + self.relative_pointers.clear(); + self.keyboards.clear(); + self.touches.clear(); + } +} + +dedicated_add_obj!(WlSeat, WlSeatId, seats); + +#[derive(Debug, Error)] +pub enum WlSeatError { + #[error(transparent)] + ClientError(Box), + #[error(transparent)] + TransferError(#[from] TransferError), + #[error(transparent)] + WlKeyboardError(Box), + #[error("Data source has a toplevel attached")] + OfferHasDrag, +} +efrom!(WlSeatError, ClientError); +efrom!(WlSeatError, WlKeyboardError); diff --git a/src/ifs/wl_seat/selection.rs b/src/ifs/wl_seat/selection.rs new file mode 100644 index 00000000..7509507e --- /dev/null +++ b/src/ifs/wl_seat/selection.rs @@ -0,0 +1,160 @@ +use { + super::{WlSeatError, WlSeatGlobal}, + crate::{ + client::Client, + ifs::data_transfer::{ + self, DynDataSource, TransferLocation, offer_source_to_regular_client, + wl_data_device::ClipboardTransfer, + wl_data_source::WlDataSource, + x_data_device::{XClipboardTransfer, XPrimarySelectionTransfer, XTransferDevice}, + zwp_primary_selection_device_v1::PrimarySelectionTransfer, + zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, + }, + utils::clonecell::CloneCell, + }, + std::rc::Rc, +}; + +impl WlSeatGlobal { + fn set_selection_( + self: &Rc, + field: &CloneCell>>, + src: Option>, + location: TransferLocation, + ) -> Result<(), WlSeatError> + where + T: data_transfer::IterableTransferVtable, + X: data_transfer::TransferVtable, + S: DynDataSource, + { + if let (Some(new), Some(old)) = (&src, &field.get()) + && new.source_data().id == old.source_data().id + { + return Ok(()); + } + if let Some(new) = &src { + data_transfer::attach_seat(&**new, self, data_transfer::Role::Selection)?; + } + let src_dyn = src.clone().map(|s| s as Rc); + if let Some(old) = field.set(src_dyn) { + old.detach_seat(self); + } + if let Some(client) = self.keyboard_node.get().node_client() { + self.offer_selection_to_client::(src.clone().map(|v| v as Rc<_>), &client); + // client.flush(); + } + let dyn_source = src.map(|s| s as Rc); + for dd in self.data_control_devices.lock().values() { + dd.clone().handle_new_source(location, dyn_source.clone()); + } + Ok(()) + } + + pub(super) fn offer_selection_to_client( + &self, + selection: Option>, + client: &Rc, + ) where + T: data_transfer::IterableTransferVtable, + X: data_transfer::TransferVtable, + { + if let Some(src) = &selection { + src.cancel_unprivileged_offers(); + } + if client.is_xwayland { + self.for_each_x_data_device(|dd| match &selection { + Some(src) => src.clone().offer_to_x(&dd), + _ => X::send_selection(&dd, None), + }); + } else { + match selection { + Some(src) => offer_source_to_regular_client::(src, client), + _ => T::for_each_device(self, client.id, |device| { + T::send_selection(device, None); + }), + } + } + } + + pub fn unset_selection(self: &Rc) { + let _ = self.set_wl_data_source_selection(None, None); + } + + pub fn set_wl_data_source_selection( + self: &Rc, + selection: Option>, + serial: Option, + ) -> Result<(), WlSeatError> { + if let Some(serial) = serial { + self.selection_serial.set(serial); + } + if let Some(selection) = &selection + && selection.toplevel_drag.is_some() + { + return Err(WlSeatError::OfferHasDrag); + } + self.set_selection(selection) + } + + pub fn set_selection( + self: &Rc, + selection: Option>, + ) -> Result<(), WlSeatError> { + self.set_selection_::( + &self.selection, + selection, + TransferLocation::Clipboard, + ) + } + + pub fn get_selection(&self) -> Option> { + self.selection.get() + } + + pub fn may_modify_selection(&self, client: &Rc, serial: u64) -> bool { + if serial < self.selection_serial.get() { + return false; + } + self.keyboard_node.get().node_client_id() == Some(client.id) + } + + pub fn may_modify_primary_selection(&self, client: &Rc, serial: Option) -> bool { + if let Some(serial) = serial + && serial < self.primary_selection_serial.get() + { + return false; + } + self.keyboard_node.get().node_client_id() == Some(client.id) + || self.pointer_node().and_then(|n| n.node_client_id()) == Some(client.id) + } + + pub fn unset_primary_selection(self: &Rc) { + let _ = self.set_zwp_primary_selection(None, None); + } + + pub fn set_zwp_primary_selection( + self: &Rc, + selection: Option>, + serial: Option, + ) -> Result<(), WlSeatError> { + if let Some(serial) = serial { + self.primary_selection_serial.set(serial); + } + self.set_primary_selection(selection) + } + + pub fn set_primary_selection( + self: &Rc, + selection: Option>, + ) -> Result<(), WlSeatError> { + self.set_selection_::( + &self.primary_selection, + selection, + TransferLocation::PrimarySelection, + ) + } + + pub fn get_primary_selection(&self) -> Option> { + self.primary_selection.get() + } +} diff --git a/src/ifs/wl_seat/tablet.rs b/src/ifs/wl_seat/tablet.rs index 3148f040..b68a47db 100644 --- a/src/ifs/wl_seat/tablet.rs +++ b/src/ifs/wl_seat/tablet.rs @@ -1,6 +1,6 @@ use { crate::{ - backend::{InputDeviceGroupId, InputDeviceId}, + backend::InputDeviceId, cursor_user::CursorUser, ifs::{ wl_seat::{ @@ -30,6 +30,15 @@ use { }, }; +#[allow(unused_imports)] +pub use jay_input_types::{ + InputDeviceGroupId, PadButtonState, TabletId, TabletIds, TabletInit, TabletPadGroupInit, + TabletPadId, TabletPadIds, TabletPadInit, TabletRingEventSource, TabletStripEventSource, + TabletTool2dChange, TabletToolCapability, TabletToolChanges, TabletToolId, TabletToolIds, + TabletToolInit, TabletToolPositionChange, TabletToolType, TabletToolWheelChange, + ToolButtonState, +}; + mod pad; mod pad_owner; mod tablet_bindings; @@ -53,63 +62,6 @@ pub struct TabletSeatData { pads: CopyHashMap>, } -#[derive(Debug, Clone)] -pub struct TabletInit { - pub id: TabletId, - pub group: InputDeviceGroupId, - pub name: String, - pub pid: u32, - pub vid: u32, - pub bustype: Option, - pub path: String, -} - -#[derive(Debug, Clone)] -pub struct TabletToolInit { - pub tablet_id: TabletId, - pub id: TabletToolId, - pub type_: TabletToolType, - pub hardware_serial: u64, - pub hardware_id_wacom: u64, - pub capabilities: Vec, -} - -#[derive(Debug, Clone)] -pub struct TabletPadInit { - pub id: TabletPadId, - pub group: InputDeviceGroupId, - pub path: String, - pub buttons: u32, - pub strips: u32, - pub rings: u32, - pub dials: u32, - pub groups: Vec, -} - -#[derive(Debug, Clone)] -pub struct TabletPadGroupInit { - pub buttons: Vec, - pub rings: Vec, - pub strips: Vec, - pub dials: Vec, - pub modes: u32, - pub mode: u32, -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum PadButtonState { - Released, - Pressed, -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum ToolButtonState { - Released, - Pressed, -} - -linear_ids!(TabletIds, TabletId); - pub struct Tablet { _id: TabletId, dev: InputDeviceId, @@ -126,31 +78,6 @@ pub struct Tablet { seat: Rc, } -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum TabletToolType { - Pen, - Eraser, - Brush, - Pencil, - Airbrush, - #[expect(dead_code)] - Finger, - Mouse, - Lens, -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum TabletToolCapability { - Tilt, - Pressure, - Distance, - Rotation, - Slider, - Wheel, -} - -linear_ids!(TabletToolIds, TabletToolId, usize); - #[derive(Default)] pub struct TabletToolOpt { tool: CloneCell>>, @@ -178,8 +105,6 @@ pub struct TabletTool { slider: Cell, } -linear_ids!(TabletPadIds, TabletPadId); - pub struct TabletPad { pub id: TabletPadId, dev: InputDeviceId, @@ -219,46 +144,6 @@ pub struct TabletPadDial { bindings: TabletBindings, } -#[derive(Copy, Clone, Debug)] -pub enum TabletRingEventSource { - Finger, -} - -#[derive(Copy, Clone, Debug)] -pub enum TabletStripEventSource { - Finger, -} - -#[derive(Debug, Default)] -pub struct TabletToolChanges { - pub down: Option, - pub pos: Option>, - pub pressure: Option, - pub distance: Option, - pub tilt: Option>, - pub rotation: Option, - pub slider: Option, - pub wheel: Option, -} - -#[derive(Copy, Clone, Debug)] -pub struct TabletTool2dChange { - pub x: T, - pub y: T, -} - -#[derive(Copy, Clone, Debug)] -pub struct TabletToolPositionChange { - pub x: f64, - pub dx: f64, -} - -#[derive(Copy, Clone, Debug)] -pub struct TabletToolWheelChange { - pub degrees: f64, - pub clicks: i32, -} - impl WlSeatGlobal { fn tablet_add_seat(&self, seat: &Rc) { self.tablet.seats.add(&seat.client, seat); diff --git a/src/ifs/wl_seat/tablet/pad.rs b/src/ifs/wl_seat/tablet/pad.rs index 34c1cc91..96952bce 100644 --- a/src/ifs/wl_seat/tablet/pad.rs +++ b/src/ifs/wl_seat/tablet/pad.rs @@ -1,7 +1,8 @@ +use jay_time::usec_to_msec; +use jay_units::fixed::Fixed; use { crate::{ backend::InputDeviceId, - fixed::Fixed, ifs::{ wl_seat::{ WlSeatGlobal, @@ -14,7 +15,7 @@ use { }, wl_surface::WlSurface, }, - time::usec_to_msec, + utils::{clonecell::CloneCell, hash_map_ext::HashMapExt}, }, std::{cell::Cell, rc::Rc}, diff --git a/src/ifs/wl_seat/tablet/tool.rs b/src/ifs/wl_seat/tablet/tool.rs index bd6c598d..d955eb3b 100644 --- a/src/ifs/wl_seat/tablet/tool.rs +++ b/src/ifs/wl_seat/tablet/tool.rs @@ -1,7 +1,9 @@ +use jay_time::usec_to_msec; +use jay_geometry::Rect; +use jay_units::fixed::Fixed; use { crate::{ cursor::KnownCursor, - fixed::Fixed, ifs::{ wl_seat::{ WlSeatGlobal, @@ -13,8 +15,8 @@ use { }, wl_surface::WlSurface, }, - rect::Rect, - time::usec_to_msec, + + utils::{clonecell::CloneCell, hash_map_ext::HashMapExt}, }, std::{cell::Cell, rc::Rc}, diff --git a/src/ifs/wl_seat/tablet/tool_owner.rs b/src/ifs/wl_seat/tablet/tool_owner.rs index 351e7e4f..213e9810 100644 --- a/src/ifs/wl_seat/tablet/tool_owner.rs +++ b/src/ifs/wl_seat/tablet/tool_owner.rs @@ -1,6 +1,6 @@ +use jay_units::fixed::Fixed; use { crate::{ - fixed::Fixed, ifs::wl_seat::tablet::{TabletTool, TabletToolChanges, ToolButtonState}, tree::{FindTreeUsecase, FoundNode, Node}, utils::{clonecell::CloneCell, smallmap::SmallMap}, diff --git a/src/ifs/wl_seat/tablet/zwp_tablet_pad_ring_v2.rs b/src/ifs/wl_seat/tablet/zwp_tablet_pad_ring_v2.rs index 4cf2f4ad..cafc0747 100644 --- a/src/ifs/wl_seat/tablet/zwp_tablet_pad_ring_v2.rs +++ b/src/ifs/wl_seat/tablet/zwp_tablet_pad_ring_v2.rs @@ -1,7 +1,7 @@ +use jay_units::fixed::Fixed; use { crate::{ client::{Client, ClientError}, - fixed::Fixed, ifs::wl_seat::tablet::{ TabletPadRing, TabletRingEventSource, zwp_tablet_seat_v2::ZwpTabletSeatV2, }, diff --git a/src/ifs/wl_seat/tablet/zwp_tablet_tool_v2.rs b/src/ifs/wl_seat/tablet/zwp_tablet_tool_v2.rs index a39a4905..220cccdd 100644 --- a/src/ifs/wl_seat/tablet/zwp_tablet_tool_v2.rs +++ b/src/ifs/wl_seat/tablet/zwp_tablet_tool_v2.rs @@ -1,8 +1,8 @@ +use jay_units::fixed::Fixed; use { crate::{ client::{Client, ClientError}, cursor::Cursor, - fixed::Fixed, ifs::{ wl_seat::tablet::{ TabletToolCapability, TabletToolOpt, TabletToolType, ToolButtonState, diff --git a/src/ifs/wl_seat/text_input.rs b/src/ifs/wl_seat/text_input.rs index 51cea1d3..e2886dc4 100644 --- a/src/ifs/wl_seat/text_input.rs +++ b/src/ifs/wl_seat/text_input.rs @@ -1,3 +1,4 @@ +use jay_keyboard::KeyboardState; use { crate::{ backend::KeyState, @@ -8,7 +9,6 @@ use { }, wl_surface::{WlSurface, zwp_input_popup_surface_v2::ZwpInputPopupSurfaceV2}, }, - keyboard::KeyboardState, utils::smallmap::SmallMap, wire::ZwpInputPopupSurfaceV2Id, }, diff --git a/src/ifs/wl_seat/text_input/simple_im.rs b/src/ifs/wl_seat/text_input/simple_im.rs index 917e3312..6f9c3deb 100644 --- a/src/ifs/wl_seat/text_input/simple_im.rs +++ b/src/ifs/wl_seat/text_input/simple_im.rs @@ -1,3 +1,4 @@ +use jay_keyboard::KeyboardState; use { crate::{ backend::KeyState, @@ -10,7 +11,6 @@ use { }, wl_surface::zwp_input_popup_surface_v2::ZwpInputPopupSurfaceV2, }, - keyboard::KeyboardState, utils::{clonecell::CloneCell, smallmap::SmallMap}, wire::ZwpInputPopupSurfaceV2Id, }, diff --git a/src/ifs/wl_seat/text_input/zwp_input_method_keyboard_grab_v2.rs b/src/ifs/wl_seat/text_input/zwp_input_method_keyboard_grab_v2.rs index 718f63cc..c4253169 100644 --- a/src/ifs/wl_seat/text_input/zwp_input_method_keyboard_grab_v2.rs +++ b/src/ifs/wl_seat/text_input/zwp_input_method_keyboard_grab_v2.rs @@ -1,3 +1,4 @@ +use jay_keyboard::{KeyboardState, KeyboardStateId}; use { crate::{ backend::KeyState, @@ -6,7 +7,6 @@ use { text_input::{InputMethodKeyboardGrab, zwp_input_method_v2::ZwpInputMethodV2}, wl_keyboard, }, - keyboard::{KeyboardState, KeyboardStateId}, leaks::Tracker, object::{Object, Version}, utils::errorfmt::ErrorFmt, diff --git a/src/ifs/wl_seat/text_input/zwp_input_method_manager_v2.rs b/src/ifs/wl_seat/text_input/zwp_input_method_manager_v2.rs index 9c3d75e4..25a31af1 100644 --- a/src/ifs/wl_seat/text_input/zwp_input_method_manager_v2.rs +++ b/src/ifs/wl_seat/text_input/zwp_input_method_manager_v2.rs @@ -1,6 +1,6 @@ use { crate::{ - client::{CAP_INPUT_METHOD, Client, ClientCaps, ClientError}, + client::{Client, ClientError}, globals::{Global, GlobalName}, ifs::wl_seat::text_input::zwp_input_method_v2::ZwpInputMethodV2, leaks::Tracker, @@ -55,10 +55,6 @@ impl Global for ZwpInputMethodManagerV2Global { fn version(&self) -> u32 { 1 } - - fn required_caps(&self) -> ClientCaps { - CAP_INPUT_METHOD - } } simple_add_global!(ZwpInputMethodManagerV2Global); diff --git a/src/ifs/wl_seat/text_input/zwp_input_method_v2.rs b/src/ifs/wl_seat/text_input/zwp_input_method_v2.rs index 35c4467a..8ce4b416 100644 --- a/src/ifs/wl_seat/text_input/zwp_input_method_v2.rs +++ b/src/ifs/wl_seat/text_input/zwp_input_method_v2.rs @@ -1,3 +1,4 @@ +use jay_keyboard::KeyboardStateId; use { crate::{ client::{Client, ClientError}, @@ -13,7 +14,6 @@ use { ZwpInputPopupSurfaceV2, ZwpInputPopupSurfaceV2Error, }, }, - keyboard::KeyboardStateId, leaks::Tracker, object::{Object, Version}, utils::{clonecell::CloneCell, numcell::NumCell, smallmap::SmallMap}, diff --git a/src/ifs/wl_seat/text_input/zwp_text_input_v3.rs b/src/ifs/wl_seat/text_input/zwp_text_input_v3.rs index f831ac33..54310e8c 100644 --- a/src/ifs/wl_seat/text_input/zwp_text_input_v3.rs +++ b/src/ifs/wl_seat/text_input/zwp_text_input_v3.rs @@ -1,3 +1,4 @@ +use jay_geometry::Rect; use { crate::{ client::{Client, ClientError}, @@ -13,7 +14,7 @@ use { }, leaks::Tracker, object::{Object, Version}, - rect::Rect, + utils::{clonecell::CloneCell, numcell::NumCell}, wire::{ZwpTextInputV3Id, zwp_text_input_v3::*}, }, diff --git a/src/ifs/wl_seat/touch_owner.rs b/src/ifs/wl_seat/touch_owner.rs index 11756d2f..d461dc28 100644 --- a/src/ifs/wl_seat/touch_owner.rs +++ b/src/ifs/wl_seat/touch_owner.rs @@ -1,6 +1,6 @@ +use jay_units::fixed::Fixed; use { crate::{ - fixed::Fixed, ifs::wl_seat::WlSeatGlobal, tree::Node, utils::{clonecell::CloneCell, smallmap::SmallMap}, diff --git a/src/ifs/wl_seat/window_management.rs b/src/ifs/wl_seat/window_management.rs new file mode 100644 index 00000000..0163a15a --- /dev/null +++ b/src/ifs/wl_seat/window_management.rs @@ -0,0 +1,108 @@ +use { + super::WlSeatGlobal, + crate::tree::{ + ChangeGroupAction, ContainerNode, ContainerSplit, toplevel_create_split, + toplevel_parent_container, toplevel_set_floating, + }, + std::{ops::Deref, rc::Rc}, +}; + +impl WlSeatGlobal { + pub fn kb_parent_container(&self) -> Option> { + if let Some(tl) = self.keyboard_node.get().node_toplevel() { + return toplevel_parent_container(&*tl); + } + None + } + + pub fn get_mono(&self) -> Option { + self.kb_parent_container().map(|c| c.mono_child.is_some()) + } + + pub fn get_split(&self) -> Option { + self.kb_parent_container().map(|c| c.split.get()) + } + + pub fn set_mono(&self, mono: bool) { + if let Some(tl) = self.keyboard_node.get().node_toplevel() + && let Some(parent) = tl.tl_data().parent.get() + && let Some(container) = parent.node_into_container() + { + let node = if mono { Some(tl.deref()) } else { None }; + container.set_mono(node); + } + } + + pub fn set_split(&self, axis: ContainerSplit) { + if let Some(c) = self.kb_parent_container() { + c.set_split(axis); + } + } + + pub fn create_split(&self, axis: ContainerSplit) { + let tl = match self.keyboard_node.get().node_toplevel() { + Some(tl) => tl, + _ => return, + }; + toplevel_create_split(&self.state, tl, axis); + } + + pub fn toggle_tab(&self) { + if let Some(c) = self.kb_parent_container() { + c.change_group(ChangeGroupAction::ToggleTab); + } + } + + pub fn make_group(&self, axis: ContainerSplit, ephemeral: bool) { + if let Some(c) = self.kb_parent_container() { + c.make_group(axis, ephemeral); + } + } + + pub fn change_group_opposite(&self) { + if let Some(c) = self.kb_parent_container() { + c.change_group(ChangeGroupAction::Opposite); + } + } + + pub fn equalize(&self, recursive: bool) { + if let Some(c) = self.kb_parent_container() { + if recursive { + c.equalize_recursive(); + } else { + c.equalize(); + } + } + } + + pub fn move_tab(&self, right: bool) { + if let Some(c) = self.kb_parent_container() { + c.move_tab(right); + } + } + + pub fn focus_parent(self: &Rc) { + if let Some(tl) = self.keyboard_node.get().node_toplevel() + && let Some(parent) = tl.tl_data().parent.get() + && let Some(tl) = parent.node_toplevel() + { + self.focus_node(tl); + self.maybe_schedule_warp_mouse_to_focus(); + } + } + + pub fn get_floating(self: &Rc) -> Option { + match self.keyboard_node.get().node_toplevel() { + Some(tl) => Some(tl.tl_data().parent_is_float.get()), + _ => None, + } + } + + pub fn set_floating(self: &Rc, floating: bool) { + let tl = match self.keyboard_node.get().node_toplevel() { + Some(tl) => tl, + _ => return, + }; + toplevel_set_floating(&self.state, tl, floating); + } +} diff --git a/src/ifs/wl_seat/wl_keyboard.rs b/src/ifs/wl_seat/wl_keyboard.rs index e598f30b..af036da3 100644 --- a/src/ifs/wl_seat/wl_keyboard.rs +++ b/src/ifs/wl_seat/wl_keyboard.rs @@ -1,9 +1,9 @@ +use jay_keyboard::{KeyboardError, KeyboardState, KeyboardStateId}; use { crate::{ backend::KeyState, client::{Client, ClientError}, ifs::wl_seat::WlSeat, - keyboard::{KeyboardError, KeyboardState, KeyboardStateId}, leaks::Tracker, object::{Object, Version}, utils::{errorfmt::ErrorFmt, vecset::VecSet}, diff --git a/src/ifs/wl_seat/wl_pointer.rs b/src/ifs/wl_seat/wl_pointer.rs index dbeed04e..1366e4c8 100644 --- a/src/ifs/wl_seat/wl_pointer.rs +++ b/src/ifs/wl_seat/wl_pointer.rs @@ -1,8 +1,8 @@ +use jay_units::fixed::Fixed; use { crate::{ client::ClientError, cursor::Cursor, - fixed::Fixed, ifs::{wl_seat::WlSeat, wl_surface::WlSurfaceError}, leaks::Tracker, object::{Object, Version}, diff --git a/src/ifs/wl_seat/wl_touch.rs b/src/ifs/wl_seat/wl_touch.rs index e423571f..5585eb0c 100644 --- a/src/ifs/wl_seat/wl_touch.rs +++ b/src/ifs/wl_seat/wl_touch.rs @@ -1,7 +1,7 @@ +use jay_units::fixed::Fixed; use { crate::{ client::ClientError, - fixed::Fixed, ifs::wl_seat::WlSeat, leaks::Tracker, object::{Object, Version}, diff --git a/src/ifs/wl_seat/zwp_pointer_constraints_v1.rs b/src/ifs/wl_seat/zwp_pointer_constraints_v1.rs index 3d2ed4fc..4370fcc7 100644 --- a/src/ifs/wl_seat/zwp_pointer_constraints_v1.rs +++ b/src/ifs/wl_seat/zwp_pointer_constraints_v1.rs @@ -1,7 +1,8 @@ +use jay_geometry::{Rect, Region}; +use jay_units::fixed::Fixed; use { crate::{ client::{Client, ClientError}, - fixed::Fixed, globals::{Global, GlobalName}, ifs::{ wl_seat::{ @@ -12,7 +13,7 @@ use { }, leaks::Tracker, object::{Object, Version}, - rect::{Rect, Region}, + utils::clonecell::CloneCell, wire::{ WlPointerId, WlRegionId, WlSurfaceId, ZwpPointerConstraintsV1Id, diff --git a/src/ifs/wl_seat/zwp_pointer_gesture_pinch_v1.rs b/src/ifs/wl_seat/zwp_pointer_gesture_pinch_v1.rs index b758ea48..acf90ca8 100644 --- a/src/ifs/wl_seat/zwp_pointer_gesture_pinch_v1.rs +++ b/src/ifs/wl_seat/zwp_pointer_gesture_pinch_v1.rs @@ -1,7 +1,7 @@ +use jay_units::fixed::Fixed; use { crate::{ client::{Client, ClientError}, - fixed::Fixed, ifs::{wl_seat::WlSeatGlobal, wl_surface::WlSurface}, leaks::Tracker, object::{Object, Version}, diff --git a/src/ifs/wl_seat/zwp_pointer_gesture_swipe_v1.rs b/src/ifs/wl_seat/zwp_pointer_gesture_swipe_v1.rs index e8a0f5dc..2f15b6a1 100644 --- a/src/ifs/wl_seat/zwp_pointer_gesture_swipe_v1.rs +++ b/src/ifs/wl_seat/zwp_pointer_gesture_swipe_v1.rs @@ -1,7 +1,7 @@ +use jay_units::fixed::Fixed; use { crate::{ client::{Client, ClientError}, - fixed::Fixed, ifs::{wl_seat::WlSeatGlobal, wl_surface::WlSurface}, leaks::Tracker, object::{Object, Version}, diff --git a/src/ifs/wl_seat/zwp_relative_pointer_v1.rs b/src/ifs/wl_seat/zwp_relative_pointer_v1.rs index 228291e0..8a20e5eb 100644 --- a/src/ifs/wl_seat/zwp_relative_pointer_v1.rs +++ b/src/ifs/wl_seat/zwp_relative_pointer_v1.rs @@ -1,7 +1,7 @@ +use jay_units::fixed::Fixed; use { crate::{ client::{Client, ClientError}, - fixed::Fixed, ifs::wl_seat::WlSeat, leaks::Tracker, object::{Object, Version}, diff --git a/src/ifs/wl_seat/zwp_virtual_keyboard_manager_v1.rs b/src/ifs/wl_seat/zwp_virtual_keyboard_manager_v1.rs index 7ed8604f..6c13b4ea 100644 --- a/src/ifs/wl_seat/zwp_virtual_keyboard_manager_v1.rs +++ b/src/ifs/wl_seat/zwp_virtual_keyboard_manager_v1.rs @@ -1,9 +1,9 @@ +use jay_keyboard::KeyboardState; use { crate::{ - client::{CAP_VIRTUAL_KEYBOARD_MANAGER, Client, ClientCaps, ClientError}, + client::{Client, ClientError}, globals::{Global, GlobalName}, ifs::wl_seat::zwp_virtual_keyboard_v1::ZwpVirtualKeyboardV1, - keyboard::KeyboardState, leaks::Tracker, object::{Object, Version}, wire::{ZwpVirtualKeyboardManagerV1Id, zwp_virtual_keyboard_manager_v1::*}, @@ -56,10 +56,6 @@ impl Global for ZwpVirtualKeyboardManagerV1Global { fn version(&self) -> u32 { 1 } - - fn required_caps(&self) -> ClientCaps { - CAP_VIRTUAL_KEYBOARD_MANAGER - } } simple_add_global!(ZwpVirtualKeyboardManagerV1Global); diff --git a/src/ifs/wl_seat/zwp_virtual_keyboard_v1.rs b/src/ifs/wl_seat/zwp_virtual_keyboard_v1.rs index ceaec57b..25317284 100644 --- a/src/ifs/wl_seat/zwp_virtual_keyboard_v1.rs +++ b/src/ifs/wl_seat/zwp_virtual_keyboard_v1.rs @@ -1,8 +1,9 @@ +use jay_keyboard::{KbvmError, KeyboardState}; use { crate::{ backend::KeyState, client::{Client, ClientError}, - clientmem::{ClientMem, ClientMemError}, + clientmem::{ClientMem, ClientMemError, client_mem_client}, ifs::{ wl_seat::{ WlSeatGlobal, @@ -10,8 +11,6 @@ use { }, wl_surface::WlSurface, }, - kbvm::KbvmError, - keyboard::KeyboardState, leaks::Tracker, object::{Object, Version}, wire::{ZwpVirtualKeyboardV1Id, zwp_virtual_keyboard_v1::*}, @@ -59,7 +58,8 @@ impl ZwpVirtualKeyboardV1RequestHandler for ZwpVirtualKeyboardV1 { return Err(ZwpVirtualKeyboardV1Error::OversizedKeymap); } let size = req.size as usize - 1; - let client_mem = ClientMem::new_private(&req.fd, size, true, Some(&self.client), None) + let client_mem = + ClientMem::new_private(&req.fd, size, true, Some(client_mem_client(&self.client)), None) .map(Rc::new) .map_err(ZwpVirtualKeyboardV1Error::MapKeymap)?; let mut map = vec![]; diff --git a/src/ifs/wl_shm_pool.rs b/src/ifs/wl_shm_pool.rs index 6f037dbe..3cb22823 100644 --- a/src/ifs/wl_shm_pool.rs +++ b/src/ifs/wl_shm_pool.rs @@ -1,7 +1,7 @@ use { crate::{ client::{Client, ClientError}, - clientmem::{ClientMem, ClientMemError}, + clientmem::{ClientMem, ClientMemError, client_mem_client}, format::{formats, map_wayland_format_id}, ifs::wl_buffer::{WlBuffer, WlBufferError}, leaks::Tracker, @@ -40,7 +40,7 @@ impl WlShmPool { &fd, len, false, - Some(client), + Some(client_mem_client(client)), Some(&client.state.cpu_worker), false, )?)), @@ -97,7 +97,7 @@ impl WlShmPoolRequestHandler for WlShmPool { &self.fd, len, false, - Some(&self.client), + Some(client_mem_client(&self.client)), Some(&self.client.state.cpu_worker), false, )?)); diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 547b7e2a..ca375c39 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -22,6 +22,18 @@ pub mod zwlr_layer_surface_v1; pub mod zwp_idle_inhibitor_v1; pub mod zwp_input_popup_surface_v2; +mod node; +mod presentation; +mod request; + +pub use presentation::SyncobjRelease; + +use jay_io_uring::IoUringError; +use jay_geometry::{DamageQueue, Rect, Region}; +use jay_keyboard::KeyboardState; +use jay_units::fixed::Fixed; +use presentation::{FrameRequest, SurfaceRelease}; + use { crate::{ backend::{ButtonState, KeyState}, @@ -30,7 +42,6 @@ use { cursor_user::{CursorUser, CursorUserId}, damage::DamageMatrix, drm_feedback::DrmFeedback, - fixed::Fixed, gfx_api::{ AlphaMode, AsyncShmGfxTexture, BufferResv, BufferResvUser, FdSync, GfxError, GfxStagingBuffer, ReleaseSync, SampleRect, SyncFile, @@ -73,11 +84,10 @@ use { wp_presentation_feedback::PresentationFeedback, zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, }, - io_uring::IoUringError, - keyboard::KeyboardState, + leaks::Tracker, object::{Object, Version}, - rect::{DamageQueue, Rect, Region}, + renderer::Renderer, state::State, tree::{ @@ -164,9 +174,6 @@ pub struct SurfaceSendPreferredScaleVisitor; impl SurfaceSendPreferredScaleVisitor { fn schedule_realloc(&self, tl: &impl ToplevelNode) { let data = tl.tl_data(); - for sc in data.jay_screencasts.lock().values() { - sc.schedule_realloc_or_reconfigure(); - } for sc in data.ext_copy_sessions.lock().values() { sc.buffer_size_changed(); } @@ -1011,168 +1018,6 @@ impl WlSurface { const MAX_DAMAGE: usize = 32; -impl WlSurfaceRequestHandler for WlSurface { - type Error = WlSurfaceError; - - fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { - self.commit_timeline.clear(ClearReason::Destroy); - self.unset_dnd_icons(); - self.unset_cursors(); - self.ext.get().on_surface_destroy()?; - self.destroy_node(); - { - let mut children = self.children.borrow_mut(); - if let Some(children) = &mut *children { - for ss in children.subsurfaces.values() { - ss.surface.unset_ext(); - } - } - *children = None; - } - self.buffer.set(None); - self.reset_shm_textures(); - if let Some(xwayland_serial) = self.xwayland_serial.get() { - self.client - .surfaces_by_xwayland_serial - .remove(&xwayland_serial); - } - self.frame_requests.borrow_mut().clear(); - self.toplevel.set(None); - self.client.remove_obj(self)?; - self.idle_inhibitors.clear(); - self.constraints.take(); - self.destroyed.set(true); - Ok(()) - } - - fn attach(&self, req: Attach, _slf: &Rc) -> Result<(), Self::Error> { - let pending = &mut *self.pending.borrow_mut(); - if self.version >= OFFSET_SINCE { - if req.x != 0 || req.y != 0 { - return Err(WlSurfaceError::OffsetInAttach); - } - } else { - pending.offset = (req.x, req.y); - } - let buf = if req.buffer.is_some() { - Some(self.client.lookup(req.buffer)?) - } else { - None - }; - pending.buffer = Some(buf.map(|buf| AttachedBuffer { - send_release: false, - buf, - })); - Ok(()) - } - - fn damage(&self, req: Damage, _slf: &Rc) -> Result<(), Self::Error> { - self.do_damage(req.x, req.y, req.width, req.height, |p| { - &mut p.surface_damage - }) - } - - fn frame(&self, req: Frame, _slf: &Rc) -> Result<(), Self::Error> { - let cb = Rc::new(WlCallback::new(req.callback, &self.client)); - track!(self.client, cb); - self.client.add_client_obj(&cb)?; - self.pending - .borrow_mut() - .frame_request - .push(FrameRequest { now: 0, cb }); - Ok(()) - } - - fn set_opaque_region( - &self, - region: SetOpaqueRegion, - _slf: &Rc, - ) -> Result<(), Self::Error> { - let region = if region.region.is_some() { - Some(self.client.lookup(region.region)?.region()) - } else { - None - }; - self.pending.borrow_mut().opaque_region = Some(region); - Ok(()) - } - - fn set_input_region(&self, req: SetInputRegion, _slf: &Rc) -> Result<(), Self::Error> { - let region = if req.region.is_some() { - Some(self.client.lookup(req.region)?.region()) - } else { - None - }; - self.pending.borrow_mut().input_region = Some(region); - Ok(()) - } - - fn commit(&self, _req: Commit, slf: &Rc) -> Result<(), Self::Error> { - let ext = self.ext.get(); - let pending = &mut *self.pending.borrow_mut(); - if let Some(Some(buffer)) = &mut pending.buffer - && pending.release_point.is_none() - && pending.sync_file_release.is_none() - { - buffer.send_release = true; - } - if let Some(release) = &mut pending.release_point { - release.committed = true; - } - self.verify_syncobj_sync(pending)?; - if pending.surface_release.is_not_empty() && not_matches!(pending.buffer, Some(Some(_))) { - return Err(WlSurfaceError::SurfaceReleaseWithoutAttach); - } - if pending.sync_file_release.is_some() && not_matches!(pending.buffer, Some(Some(_))) { - return Err(WlSurfaceError::SyncFileReleaseWithoutAttach); - } - if ext.commit_requested(pending) == CommitAction::ContinueCommit { - self.commit_timeline.commit(slf, pending)?; - } - Ok(()) - } - - fn set_buffer_transform( - &self, - req: SetBufferTransform, - _slf: &Rc, - ) -> Result<(), Self::Error> { - let Some(tf) = Transform::from_wl(req.transform) else { - return Err(WlSurfaceError::UnknownBufferTransform(req.transform)); - }; - self.pending.borrow_mut().transform = Some(tf); - Ok(()) - } - - fn set_buffer_scale(&self, req: SetBufferScale, _slf: &Rc) -> Result<(), Self::Error> { - if req.scale < 1 { - return Err(WlSurfaceError::NonPositiveBufferScale); - } - self.pending.borrow_mut().scale = Some(req.scale); - Ok(()) - } - - fn damage_buffer(&self, req: DamageBuffer, _slf: &Rc) -> Result<(), Self::Error> { - self.do_damage(req.x, req.y, req.width, req.height, |p| { - &mut p.buffer_damage - }) - } - - fn offset(&self, req: Offset, _slf: &Rc) -> Result<(), Self::Error> { - self.pending.borrow_mut().offset = (req.x, req.y); - Ok(()) - } - - fn get_release(&self, req: GetRelease, _slf: &Rc) -> Result<(), Self::Error> { - let cb = Rc::new(WlCallback::new(req.callback, &self.client)); - track!(self.client, cb); - self.client.add_client_obj(&cb)?; - let release = SurfaceRelease { cb }; - self.pending.borrow_mut().surface_release.push(release); - Ok(()) - } -} - impl WlSurface { fn apply_state(self: &Rc, pending: &mut PendingState) -> Result<(), WlSurfaceError> { for (_, pending) in &mut pending.subsurfaces { @@ -1520,25 +1365,25 @@ impl WlSurface { let bounds = self.toplevel.get().and_then(|tl| tl.tl_render_bounds()); let pos = self.buffer_abs_pos.get(); let apply_damage = |pos: Rect| { - if pending.damage_full { - let mut damage = pos; + let clip_damage = |mut damage: Rect| { + damage = damage.intersect(pos); if let Some(bounds) = bounds { damage = damage.intersect(bounds); } - self.client.state.damage(damage); + damage + }; + if pending.damage_full { + self.client.state.damage(clip_damage(pos)); } else { let matrix = self.damage_matrix.get(); if let Some(buffer) = self.buffer.get() { for damage in &pending.buffer_damage { - let mut damage = matrix.apply( + let damage = matrix.apply( pos.x1(), pos.y1(), damage.intersect(buffer.buffer.buf.rect), ); - if let Some(bounds) = bounds { - damage = damage.intersect(bounds); - } - self.client.state.damage(damage); + self.client.state.damage(clip_damage(damage)); } } for damage in &pending.surface_damage { @@ -1550,8 +1395,7 @@ impl WlSurface { let y2 = (damage.y2() + scale - 1) / scale; damage = Rect::new_saturating(x1, y1, x2, y2); } - damage = damage.intersect(bounds.unwrap_or(pos)); - self.client.state.damage(damage); + self.client.state.damage(clip_damage(damage)); } } }; @@ -1829,340 +1673,6 @@ impl Object for WlSurface { dedicated_add_obj!(WlSurface, WlSurfaceId, surfaces); tree_id!(SurfaceNodeId); -impl Node for WlSurface { - fn node_id(&self) -> NodeId { - self.node_id.into() - } - - fn node_seat_state(&self) -> &NodeSeatState { - &self.seat_state - } - - fn node_visit(self: Rc, visitor: &mut dyn NodeVisitor) { - visitor.visit_surface(&self); - } - - fn node_visit_children(&self, visitor: &mut dyn NodeVisitor) { - let children = self.children.borrow_mut(); - if let Some(c) = children.deref() { - for child in c.subsurfaces.values() { - visitor.visit_surface(&child.surface); - } - } - } - - fn node_visible(&self) -> bool { - self.visible.get() - } - - fn node_absolute_position(&self) -> Rect { - self.buffer_abs_pos.get() - } - - fn node_output(&self) -> Option> { - Some(self.output.get()) - } - - fn node_location(&self) -> Option { - Some(self.location.get()) - } - - fn node_layer(&self) -> NodeLayerLink { - self.ext.get().node_layer() - } - - fn node_active_changed(&self, active: bool) { - if let Some(tl) = self.toplevel.get() { - tl.tl_surface_active_changed(active); - } - } - - fn node_render(&self, renderer: &mut Renderer, x: i32, y: i32, bounds: Option<&Rect>) { - renderer.render_surface(self, x, y, bounds); - } - - fn node_client(&self) -> Option> { - Some(self.client.clone()) - } - - fn node_toplevel(self: Rc) -> Option> { - self.toplevel.get() - } - - fn node_tray_item(&self) -> Option { - self.ext.get().tray_item() - } - - fn node_make_visible(self: Rc) { - if let Some(tl) = self.toplevel.get() { - tl.node_make_visible(); - } - } - - fn node_on_key( - &self, - seat: &WlSeatGlobal, - time_usec: u64, - key: u32, - state: KeyState, - kb_state: &KeyboardState, - ) { - seat.key_surface(self, time_usec, key, state, kb_state); - } - - fn node_on_mods(&self, seat: &WlSeatGlobal, kb_state: &KeyboardState) { - seat.mods_surface(self, kb_state); - } - - fn node_on_touch_down( - self: Rc, - seat: &Rc, - time_usec: u64, - id: i32, - x: Fixed, - y: Fixed, - ) { - seat.touch_down_surface(&self, time_usec, id, x, y) - } - - fn node_on_touch_up(self: Rc, seat: &Rc, time_usec: u64, id: i32) { - seat.touch_up_surface(&self, time_usec, id) - } - - fn node_on_touch_motion( - self: Rc, - seat: &WlSeatGlobal, - time_usec: u64, - id: i32, - x: Fixed, - y: Fixed, - ) { - seat.touch_motion_surface(&self, time_usec, id, x, y) - } - - fn node_on_touch_frame(&self, seat: &WlSeatGlobal) { - seat.touch_frame_surface(&self) - } - - fn node_on_touch_cancel(&self, seat: &WlSeatGlobal) { - seat.touch_cancel_surface(&self) - } - - fn node_on_button( - self: Rc, - seat: &Rc, - time_usec: u64, - button: u32, - state: ButtonState, - serial: u64, - ) { - seat.button_surface(&self, time_usec, button, state, serial); - } - - fn node_on_axis_event(self: Rc, seat: &Rc, event: &PendingScroll) { - seat.scroll_surface(&self, event); - } - - fn node_on_focus(self: Rc, seat: &WlSeatGlobal) { - if let Some(xsurface) = self.ext.get().into_xsurface() - && let Some(window) = xsurface.xwindow.get() - { - self.client - .state - .xwayland - .queue - .push(XWaylandEvent::Activate(window.data.clone())); - } - seat.focus_surface(&self); - } - - fn node_on_unfocus(&self, seat: &WlSeatGlobal) { - seat.unfocus_surface(self); - } - - fn node_on_leave(&self, seat: &WlSeatGlobal) { - seat.leave_surface(self); - } - - fn node_on_pointer_enter(self: Rc, seat: &Rc, x: Fixed, y: Fixed) { - seat.enter_surface(&self, x, y) - } - - fn node_on_pointer_motion(self: Rc, seat: &Rc, x: Fixed, y: Fixed) { - seat.motion_surface(&self, x, y) - } - - fn node_on_pointer_relative_motion( - &self, - seat: &Rc, - time_usec: u64, - dx: Fixed, - dy: Fixed, - dx_unaccelerated: Fixed, - dy_unaccelerated: Fixed, - ) { - seat.relative_motion_surface(self, time_usec, dx, dy, dx_unaccelerated, dy_unaccelerated); - } - - fn node_on_dnd_drop(&self, dnd: &Dnd) { - dnd.seat.dnd_surface_drop(self, dnd); - } - - fn node_on_dnd_leave(&self, dnd: &Dnd) { - dnd.seat.dnd_surface_leave(self, dnd); - } - - fn node_on_dnd_enter(&self, dnd: &Dnd, x: Fixed, y: Fixed, serial: u64) { - dnd.seat.dnd_surface_enter(self, dnd, x, y, serial); - } - - fn node_on_dnd_motion(&self, dnd: &Dnd, time_usec: u64, x: Fixed, y: Fixed) { - dnd.seat.dnd_surface_motion(self, dnd, time_usec, x, y); - } - - fn node_on_swipe_begin(&self, seat: &Rc, time_usec: u64, finger_count: u32) { - seat.swipe_begin_surface(self, time_usec, finger_count) - } - - fn node_on_swipe_update(&self, seat: &Rc, time_usec: u64, dx: Fixed, dy: Fixed) { - seat.swipe_update_surface(self, time_usec, dx, dy) - } - - fn node_on_swipe_end(&self, seat: &Rc, time_usec: u64, cancelled: bool) { - seat.swipe_end_surface(self, time_usec, cancelled) - } - - fn node_on_pinch_begin(&self, seat: &Rc, time_usec: u64, finger_count: u32) { - seat.pinch_begin_surface(self, time_usec, finger_count) - } - - fn node_on_pinch_update( - &self, - seat: &Rc, - time_usec: u64, - dx: Fixed, - dy: Fixed, - scale: Fixed, - rotation: Fixed, - ) { - seat.pinch_update_surface(self, time_usec, dx, dy, scale, rotation) - } - - fn node_on_pinch_end(&self, seat: &Rc, time_usec: u64, cancelled: bool) { - seat.pinch_end_surface(self, time_usec, cancelled) - } - - fn node_on_hold_begin(&self, seat: &Rc, time_usec: u64, finger_count: u32) { - seat.hold_begin_surface(self, time_usec, finger_count) - } - - fn node_on_hold_end(&self, seat: &Rc, time_usec: u64, cancelled: bool) { - seat.hold_end_surface(self, time_usec, cancelled) - } - - fn node_on_tablet_pad_enter(&self, pad: &Rc) { - pad.surface_enter(self); - } - - fn node_on_tablet_pad_leave(&self, pad: &Rc) { - pad.surface_leave(self); - } - - fn node_on_tablet_pad_button( - &self, - pad: &Rc, - time_usec: u64, - button: u32, - state: PadButtonState, - ) { - pad.surface_button(self, time_usec, button, state); - } - - fn node_on_tablet_pad_mode_switch( - &self, - pad: &Rc, - group: &Rc, - time_usec: u64, - mode: u32, - ) { - pad.surface_mode_switch(self, group, time_usec, mode); - } - - fn node_on_tablet_pad_ring( - &self, - pad: &Rc, - ring: &Rc, - source: Option, - angle: Option, - time_usec: u64, - ) { - pad.surface_ring(self, ring, source, angle, time_usec); - } - - fn node_on_tablet_pad_strip( - &self, - pad: &Rc, - strip: &Rc, - source: Option, - position: Option, - time_usec: u64, - ) { - pad.surface_strip(self, strip, source, position, time_usec); - } - - fn node_on_tablet_pad_dial( - &self, - pad: &Rc, - dial: &Rc, - value120: i32, - time_usec: u64, - ) { - pad.surface_dial(self, dial, value120, time_usec); - } - - fn node_on_tablet_tool_leave(&self, tool: &Rc, time_usec: u64) { - tool.surface_leave(self, time_usec); - } - - fn node_on_tablet_tool_enter( - self: Rc, - tool: &Rc, - time_usec: u64, - x: Fixed, - y: Fixed, - ) { - tool.surface_enter(&self, time_usec, x, y); - } - - fn node_on_tablet_tool_button( - &self, - tool: &Rc, - time_usec: u64, - button: u32, - state: ToolButtonState, - ) { - tool.surface_button(self, time_usec, button, state); - } - - fn node_on_tablet_tool_apply_changes( - self: Rc, - tool: &Rc, - time_usec: u64, - changes: Option<&TabletToolChanges>, - x: Fixed, - y: Fixed, - ) { - tool.surface_apply_changes(&self, time_usec, changes, x, y); - } - - fn node_into_surface(self: Rc) -> Option> { - Some(self.clone()) - } - - fn node_is_xwayland_surface(&self) -> bool { - self.client.is_xwayland - } -} #[derive(Debug, Error)] pub enum WlSurfaceError { @@ -2215,144 +1725,3 @@ efrom!(WlSurfaceError, ClientError); efrom!(WlSurfaceError, XdgSurfaceError); efrom!(WlSurfaceError, ZwlrLayerSurfaceV1Error); efrom!(WlSurfaceError, CommitTimelineError); - -impl VblankListener for WlSurface { - fn after_vblank(self: Rc) { - if self.visible.get() { - let now = self.client.state.now_msec() as u32; - for mut fr in self.frame_requests.borrow_mut().drain(..) { - fr.now = now; - drop(fr); - } - } - if self.clear_fifo_on_vblank.take() { - self.commit_timeline.clear_fifo_barrier(); - } - self.vblank_listener.detach(); - } -} - -impl BeforeLatchListener for WlSurface { - fn before_latch(self: Rc, present: u64) -> BeforeLatchResult { - self.commit_timeline.before_latch(&self, present) - } -} - -impl LatchListener for WlSurface { - fn after_latch(self: Rc, _on: &OutputNode, tearing: bool) { - if self.visible.get() { - if self.latched_commit_version.get() < self.commit_version.get() { - let latched = &mut *self.latched_presentation_feedback.borrow_mut(); - latched.clear(); - latched.append(&mut self.presentation_feedback.borrow_mut()); - if latched.is_not_empty() { - self.presentation_listener - .attach(&self.output.get().presentation_event); - } - self.latched_commit_version.set(self.commit_version.get()); - } - } - if tearing && self.visible.get() { - if self.commit_timeline.has_fifo_barrier() { - self.vblank_listener.attach(&self.output.get().vblank_event); - self.clear_fifo_on_vblank.set(true); - } - } else { - self.commit_timeline.clear_fifo_barrier(); - } - self.latch_listener.detach(); - } -} - -impl PresentationListener for WlSurface { - fn presented( - self: Rc, - output: &OutputNode, - tv_sec: u64, - tv_nsec: u32, - refresh: u32, - seq: u64, - flags: u32, - vrr: bool, - ) { - let bindings = output.global.bindings.borrow(); - let bindings = bindings.get(&self.client.id); - for pf in self.latched_presentation_feedback.borrow_mut().drain(..) { - pf.presented(bindings, tv_sec, tv_nsec, refresh, seq, flags, vrr); - } - self.presentation_listener.detach(); - } -} - -pub struct FrameRequest { - now: u32, - cb: Rc, -} - -impl Drop for FrameRequest { - fn drop(&mut self) { - self.cb.send_done(self.now); - let _ = self.cb.client.remove_obj(&*self.cb); - } -} - -pub struct SyncobjRelease { - state: Rc, - committed: bool, - syncobj: Option>, - point: SyncobjPoint, -} - -impl SyncobjRelease { - fn signal(&mut self, syncs: Option<&SmallVec<[(BufferResvUser, FdSync); 1]>>) { - if !self.committed { - return; - } - let Some(syncobj) = self.syncobj.take() else { - return; - }; - let Some(ctx) = self.state.render_ctx.get() else { - log::error!("Cannot signal release point because there is no render context"); - return; - }; - let Some(ctx) = ctx.syncobj_ctx() else { - log::error!("Cannot signal release point because there is no syncobj context"); - return; - }; - if let Some(syncs) = syncs - && syncs.is_not_empty() - { - let res = ctx.import_sync_files( - &syncobj, - self.point, - syncs.iter().flat_map(|f| f.1.get_sync_file()), - ); - match res { - Ok(_) => return, - Err(e) => { - log::error!("Could not import sync files into syncobj: {}", ErrorFmt(e)); - } - } - } - if let Err(e) = ctx.signal(&syncobj, self.point) { - log::error!("Could not signal release point: {}", ErrorFmt(e)); - } - } -} - -impl Drop for SyncobjRelease { - fn drop(&mut self) { - self.signal(None); - } -} - -pub struct SurfaceRelease { - cb: Rc, -} - -impl Drop for SurfaceRelease { - fn drop(&mut self) { - self.cb.send_done(0); - let _ = self.cb.client.remove_obj(&*self.cb); - } -} diff --git a/src/ifs/wl_surface/commit_timeline.rs b/src/ifs/wl_surface/commit_timeline.rs index 80ac2b4f..0bb0091d 100644 --- a/src/ifs/wl_surface/commit_timeline.rs +++ b/src/ifs/wl_surface/commit_timeline.rs @@ -1,15 +1,15 @@ +use jay_async_engine::{AsyncEngine, SpawnedFuture}; +use jay_io_uring::{ + IoUring, IoUringError, PendingPoll, PendingTimeout, PollCallback, TimeoutCallback, +}; use { crate::{ - async_engine::{AsyncEngine, SpawnedFuture}, client::Client, gfx_api::{AsyncShmGfxTextureCallback, GfxError, PendingShmTransfer, STAGING_UPLOAD}, ifs::{ wl_buffer::WlBufferStorage, wl_surface::{PendingState, WlSurface, WlSurfaceError}, }, - io_uring::{ - IoUring, IoUringError, PendingPoll, PendingTimeout, PollCallback, TimeoutCallback, - }, tree::BeforeLatchResult, utils::{ clonecell::CloneCell, @@ -628,6 +628,11 @@ fn schedule_async_upload( { back_tex_opt = None; } + if let Some(back_tex) = &back_tex_opt + && Rc::strong_count(back_tex) > 1 + { + back_tex_opt = None; + } let damage_full = || { back.damage.clear(); back.damage.damage(slice::from_ref(&buf.rect)); diff --git a/src/ifs/wl_surface/cursor.rs b/src/ifs/wl_surface/cursor.rs index fabcea95..7800f31b 100644 --- a/src/ifs/wl_surface/cursor.rs +++ b/src/ifs/wl_surface/cursor.rs @@ -1,13 +1,14 @@ +use jay_geometry::Rect; +use jay_units::fixed::Fixed; +use jay_units::scale::Scale; use { crate::{ cursor::Cursor, cursor_user::CursorUser, - fixed::Fixed, ifs::wl_surface::WlSurface, leaks::Tracker, - rect::Rect, + renderer::Renderer, - scale::Scale, tree::{Node, NodeLocation, NodeVisitorBase, OutputNode}, }, std::{cell::Cell, rc::Rc}, diff --git a/src/ifs/wl_surface/dnd_icon.rs b/src/ifs/wl_surface/dnd_icon.rs index ef1bc180..876a86c4 100644 --- a/src/ifs/wl_surface/dnd_icon.rs +++ b/src/ifs/wl_surface/dnd_icon.rs @@ -1,7 +1,8 @@ +use jay_geometry::Rect; use { crate::{ ifs::{wl_seat::WlSeatGlobal, wl_surface::WlSurface}, - rect::Rect, + renderer::Renderer, }, std::rc::Rc, diff --git a/src/ifs/wl_surface/ext_session_lock_surface_v1.rs b/src/ifs/wl_surface/ext_session_lock_surface_v1.rs index f0931675..40723cdf 100644 --- a/src/ifs/wl_surface/ext_session_lock_surface_v1.rs +++ b/src/ifs/wl_surface/ext_session_lock_surface_v1.rs @@ -1,7 +1,8 @@ +use jay_geometry::Rect; +use jay_units::fixed::Fixed; use { crate::{ client::{Client, ClientError}, - fixed::Fixed, ifs::{ wl_output::OutputGlobalOpt, wl_seat::{NodeSeatState, WlSeatGlobal}, @@ -9,7 +10,7 @@ use { }, leaks::Tracker, object::{Object, Version}, - rect::Rect, + tree::{ FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeLayerLink, NodeLocation, NodeVisitor, OutputNode, diff --git a/src/ifs/wl_surface/node.rs b/src/ifs/wl_surface/node.rs new file mode 100644 index 00000000..d2650d7e --- /dev/null +++ b/src/ifs/wl_surface/node.rs @@ -0,0 +1,336 @@ +use super::*; + +impl Node for WlSurface { + fn node_id(&self) -> NodeId { + self.node_id.into() + } + + fn node_seat_state(&self) -> &NodeSeatState { + &self.seat_state + } + + fn node_visit(self: Rc, visitor: &mut dyn NodeVisitor) { + visitor.visit_surface(&self); + } + + fn node_visit_children(&self, visitor: &mut dyn NodeVisitor) { + let children = self.children.borrow_mut(); + if let Some(c) = children.deref() { + for child in c.subsurfaces.values() { + visitor.visit_surface(&child.surface); + } + } + } + + fn node_visible(&self) -> bool { + self.visible.get() + } + + fn node_absolute_position(&self) -> Rect { + self.buffer_abs_pos.get() + } + + fn node_output(&self) -> Option> { + Some(self.output.get()) + } + + fn node_location(&self) -> Option { + Some(self.location.get()) + } + + fn node_layer(&self) -> NodeLayerLink { + self.ext.get().node_layer() + } + + fn node_active_changed(&self, active: bool) { + if let Some(tl) = self.toplevel.get() { + tl.tl_surface_active_changed(active); + } + } + + fn node_render(&self, renderer: &mut Renderer, x: i32, y: i32, bounds: Option<&Rect>) { + renderer.render_surface(self, x, y, bounds); + } + + fn node_client(&self) -> Option> { + Some(self.client.clone()) + } + + fn node_toplevel(self: Rc) -> Option> { + self.toplevel.get() + } + + fn node_tray_item(&self) -> Option { + self.ext.get().tray_item() + } + + fn node_make_visible(self: Rc) { + if let Some(tl) = self.toplevel.get() { + tl.node_make_visible(); + } + } + + fn node_on_key( + &self, + seat: &WlSeatGlobal, + time_usec: u64, + key: u32, + state: KeyState, + kb_state: &KeyboardState, + ) { + seat.key_surface(self, time_usec, key, state, kb_state); + } + + fn node_on_mods(&self, seat: &WlSeatGlobal, kb_state: &KeyboardState) { + seat.mods_surface(self, kb_state); + } + + fn node_on_touch_down( + self: Rc, + seat: &Rc, + time_usec: u64, + id: i32, + x: Fixed, + y: Fixed, + ) { + seat.touch_down_surface(&self, time_usec, id, x, y) + } + + fn node_on_touch_up(self: Rc, seat: &Rc, time_usec: u64, id: i32) { + seat.touch_up_surface(&self, time_usec, id) + } + + fn node_on_touch_motion( + self: Rc, + seat: &WlSeatGlobal, + time_usec: u64, + id: i32, + x: Fixed, + y: Fixed, + ) { + seat.touch_motion_surface(&self, time_usec, id, x, y) + } + + fn node_on_touch_frame(&self, seat: &WlSeatGlobal) { + seat.touch_frame_surface(&self) + } + + fn node_on_touch_cancel(&self, seat: &WlSeatGlobal) { + seat.touch_cancel_surface(&self) + } + + fn node_on_button( + self: Rc, + seat: &Rc, + time_usec: u64, + button: u32, + state: ButtonState, + serial: u64, + ) { + seat.button_surface(&self, time_usec, button, state, serial); + } + + fn node_on_axis_event(self: Rc, seat: &Rc, event: &PendingScroll) { + seat.scroll_surface(&self, event); + } + + fn node_on_focus(self: Rc, seat: &WlSeatGlobal) { + if let Some(xsurface) = self.ext.get().into_xsurface() + && let Some(window) = xsurface.xwindow.get() + { + self.client + .state + .xwayland + .queue + .push(XWaylandEvent::Activate(window.data.clone())); + } + seat.focus_surface(&self); + } + + fn node_on_unfocus(&self, seat: &WlSeatGlobal) { + seat.unfocus_surface(self); + } + + fn node_on_leave(&self, seat: &WlSeatGlobal) { + seat.leave_surface(self); + } + + fn node_on_pointer_enter(self: Rc, seat: &Rc, x: Fixed, y: Fixed) { + seat.enter_surface(&self, x, y) + } + + fn node_on_pointer_motion(self: Rc, seat: &Rc, x: Fixed, y: Fixed) { + seat.motion_surface(&self, x, y) + } + + fn node_on_pointer_relative_motion( + &self, + seat: &Rc, + time_usec: u64, + dx: Fixed, + dy: Fixed, + dx_unaccelerated: Fixed, + dy_unaccelerated: Fixed, + ) { + seat.relative_motion_surface(self, time_usec, dx, dy, dx_unaccelerated, dy_unaccelerated); + } + + fn node_on_dnd_drop(&self, dnd: &Dnd) { + dnd.seat.dnd_surface_drop(self, dnd); + } + + fn node_on_dnd_leave(&self, dnd: &Dnd) { + dnd.seat.dnd_surface_leave(self, dnd); + } + + fn node_on_dnd_enter(&self, dnd: &Dnd, x: Fixed, y: Fixed, serial: u64) { + dnd.seat.dnd_surface_enter(self, dnd, x, y, serial); + } + + fn node_on_dnd_motion(&self, dnd: &Dnd, time_usec: u64, x: Fixed, y: Fixed) { + dnd.seat.dnd_surface_motion(self, dnd, time_usec, x, y); + } + + fn node_on_swipe_begin(&self, seat: &Rc, time_usec: u64, finger_count: u32) { + seat.swipe_begin_surface(self, time_usec, finger_count) + } + + fn node_on_swipe_update(&self, seat: &Rc, time_usec: u64, dx: Fixed, dy: Fixed) { + seat.swipe_update_surface(self, time_usec, dx, dy) + } + + fn node_on_swipe_end(&self, seat: &Rc, time_usec: u64, cancelled: bool) { + seat.swipe_end_surface(self, time_usec, cancelled) + } + + fn node_on_pinch_begin(&self, seat: &Rc, time_usec: u64, finger_count: u32) { + seat.pinch_begin_surface(self, time_usec, finger_count) + } + + fn node_on_pinch_update( + &self, + seat: &Rc, + time_usec: u64, + dx: Fixed, + dy: Fixed, + scale: Fixed, + rotation: Fixed, + ) { + seat.pinch_update_surface(self, time_usec, dx, dy, scale, rotation) + } + + fn node_on_pinch_end(&self, seat: &Rc, time_usec: u64, cancelled: bool) { + seat.pinch_end_surface(self, time_usec, cancelled) + } + + fn node_on_hold_begin(&self, seat: &Rc, time_usec: u64, finger_count: u32) { + seat.hold_begin_surface(self, time_usec, finger_count) + } + + fn node_on_hold_end(&self, seat: &Rc, time_usec: u64, cancelled: bool) { + seat.hold_end_surface(self, time_usec, cancelled) + } + + fn node_on_tablet_pad_enter(&self, pad: &Rc) { + pad.surface_enter(self); + } + + fn node_on_tablet_pad_leave(&self, pad: &Rc) { + pad.surface_leave(self); + } + + fn node_on_tablet_pad_button( + &self, + pad: &Rc, + time_usec: u64, + button: u32, + state: PadButtonState, + ) { + pad.surface_button(self, time_usec, button, state); + } + + fn node_on_tablet_pad_mode_switch( + &self, + pad: &Rc, + group: &Rc, + time_usec: u64, + mode: u32, + ) { + pad.surface_mode_switch(self, group, time_usec, mode); + } + + fn node_on_tablet_pad_ring( + &self, + pad: &Rc, + ring: &Rc, + source: Option, + angle: Option, + time_usec: u64, + ) { + pad.surface_ring(self, ring, source, angle, time_usec); + } + + fn node_on_tablet_pad_strip( + &self, + pad: &Rc, + strip: &Rc, + source: Option, + position: Option, + time_usec: u64, + ) { + pad.surface_strip(self, strip, source, position, time_usec); + } + + fn node_on_tablet_pad_dial( + &self, + pad: &Rc, + dial: &Rc, + value120: i32, + time_usec: u64, + ) { + pad.surface_dial(self, dial, value120, time_usec); + } + + fn node_on_tablet_tool_leave(&self, tool: &Rc, time_usec: u64) { + tool.surface_leave(self, time_usec); + } + + fn node_on_tablet_tool_enter( + self: Rc, + tool: &Rc, + time_usec: u64, + x: Fixed, + y: Fixed, + ) { + tool.surface_enter(&self, time_usec, x, y); + } + + fn node_on_tablet_tool_button( + &self, + tool: &Rc, + time_usec: u64, + button: u32, + state: ToolButtonState, + ) { + tool.surface_button(self, time_usec, button, state); + } + + fn node_on_tablet_tool_apply_changes( + self: Rc, + tool: &Rc, + time_usec: u64, + changes: Option<&TabletToolChanges>, + x: Fixed, + y: Fixed, + ) { + tool.surface_apply_changes(&self, time_usec, changes, x, y); + } + + fn node_into_surface(self: Rc) -> Option> { + Some(self.clone()) + } + + fn node_is_xwayland_surface(&self) -> bool { + self.client.is_xwayland + } +} diff --git a/src/ifs/wl_surface/presentation.rs b/src/ifs/wl_surface/presentation.rs new file mode 100644 index 00000000..363356df --- /dev/null +++ b/src/ifs/wl_surface/presentation.rs @@ -0,0 +1,142 @@ +use super::*; + +impl VblankListener for WlSurface { + fn after_vblank(self: Rc) { + if self.visible.get() { + let now = self.client.state.now_msec() as u32; + for mut fr in self.frame_requests.borrow_mut().drain(..) { + fr.now = now; + drop(fr); + } + } + if self.clear_fifo_on_vblank.take() { + self.commit_timeline.clear_fifo_barrier(); + } + self.vblank_listener.detach(); + } +} + +impl BeforeLatchListener for WlSurface { + fn before_latch(self: Rc, present: u64) -> BeforeLatchResult { + self.commit_timeline.before_latch(&self, present) + } +} + +impl LatchListener for WlSurface { + fn after_latch(self: Rc, _on: &OutputNode, tearing: bool) { + if self.visible.get() { + if self.latched_commit_version.get() < self.commit_version.get() { + let latched = &mut *self.latched_presentation_feedback.borrow_mut(); + latched.clear(); + latched.append(&mut self.presentation_feedback.borrow_mut()); + if latched.is_not_empty() { + self.presentation_listener + .attach(&self.output.get().presentation_event); + } + self.latched_commit_version.set(self.commit_version.get()); + } + } + if tearing && self.visible.get() { + if self.commit_timeline.has_fifo_barrier() { + self.vblank_listener.attach(&self.output.get().vblank_event); + self.clear_fifo_on_vblank.set(true); + } + } else { + self.commit_timeline.clear_fifo_barrier(); + } + self.latch_listener.detach(); + } +} + +impl PresentationListener for WlSurface { + fn presented( + self: Rc, + output: &OutputNode, + tv_sec: u64, + tv_nsec: u32, + refresh: u32, + seq: u64, + flags: u32, + vrr: bool, + ) { + let bindings = output.global.bindings.borrow(); + let bindings = bindings.get(&self.client.id); + for pf in self.latched_presentation_feedback.borrow_mut().drain(..) { + pf.presented(bindings, tv_sec, tv_nsec, refresh, seq, flags, vrr); + } + self.presentation_listener.detach(); + } +} + +pub(super) struct FrameRequest { + pub(super) now: u32, + pub(super) cb: Rc, +} + +impl Drop for FrameRequest { + fn drop(&mut self) { + self.cb.send_done(self.now); + let _ = self.cb.client.remove_obj(&*self.cb); + } +} + +pub struct SyncobjRelease { + pub(super) state: Rc, + pub(super) committed: bool, + pub(super) syncobj: Option>, + pub(super) point: SyncobjPoint, +} + +impl SyncobjRelease { + pub(super) fn signal(&mut self, syncs: Option<&SmallVec<[(BufferResvUser, FdSync); 1]>>) { + if !self.committed { + return; + } + let Some(syncobj) = self.syncobj.take() else { + return; + }; + let Some(ctx) = self.state.render_ctx.get() else { + log::error!("Cannot signal release point because there is no render context"); + return; + }; + let Some(ctx) = ctx.syncobj_ctx() else { + log::error!("Cannot signal release point because there is no syncobj context"); + return; + }; + if let Some(syncs) = syncs + && syncs.is_not_empty() + { + let res = ctx.import_sync_files( + &syncobj, + self.point, + syncs.iter().flat_map(|f| f.1.get_sync_file()), + ); + match res { + Ok(_) => return, + Err(e) => { + log::error!("Could not import sync files into syncobj: {}", ErrorFmt(e)); + } + } + } + if let Err(e) = ctx.signal(&syncobj, self.point) { + log::error!("Could not signal release point: {}", ErrorFmt(e)); + } + } +} + +impl Drop for SyncobjRelease { + fn drop(&mut self) { + self.signal(None); + } +} + +pub(super) struct SurfaceRelease { + pub(super) cb: Rc, +} + +impl Drop for SurfaceRelease { + fn drop(&mut self) { + self.cb.send_done(0); + let _ = self.cb.client.remove_obj(&*self.cb); + } +} diff --git a/src/ifs/wl_surface/request.rs b/src/ifs/wl_surface/request.rs new file mode 100644 index 00000000..a6466147 --- /dev/null +++ b/src/ifs/wl_surface/request.rs @@ -0,0 +1,163 @@ +use super::*; + +impl WlSurfaceRequestHandler for WlSurface { + type Error = WlSurfaceError; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.commit_timeline.clear(ClearReason::Destroy); + self.unset_dnd_icons(); + self.unset_cursors(); + self.ext.get().on_surface_destroy()?; + self.destroy_node(); + { + let mut children = self.children.borrow_mut(); + if let Some(children) = &mut *children { + for ss in children.subsurfaces.values() { + ss.surface.unset_ext(); + } + } + *children = None; + } + self.buffer.set(None); + self.reset_shm_textures(); + if let Some(xwayland_serial) = self.xwayland_serial.get() { + self.client + .surfaces_by_xwayland_serial + .remove(&xwayland_serial); + } + self.frame_requests.borrow_mut().clear(); + self.toplevel.set(None); + self.client.remove_obj(self)?; + self.idle_inhibitors.clear(); + self.constraints.take(); + self.destroyed.set(true); + Ok(()) + } + + fn attach(&self, req: Attach, _slf: &Rc) -> Result<(), Self::Error> { + let pending = &mut *self.pending.borrow_mut(); + if self.version >= OFFSET_SINCE { + if req.x != 0 || req.y != 0 { + return Err(WlSurfaceError::OffsetInAttach); + } + } else { + pending.offset = (req.x, req.y); + } + let buf = if req.buffer.is_some() { + Some(self.client.lookup(req.buffer)?) + } else { + None + }; + pending.buffer = Some(buf.map(|buf| AttachedBuffer { + send_release: false, + buf, + })); + Ok(()) + } + + fn damage(&self, req: Damage, _slf: &Rc) -> Result<(), Self::Error> { + self.do_damage(req.x, req.y, req.width, req.height, |p| { + &mut p.surface_damage + }) + } + + fn frame(&self, req: Frame, _slf: &Rc) -> Result<(), Self::Error> { + let cb = Rc::new(WlCallback::new(req.callback, &self.client)); + track!(self.client, cb); + self.client.add_client_obj(&cb)?; + self.pending + .borrow_mut() + .frame_request + .push(FrameRequest { now: 0, cb }); + Ok(()) + } + + fn set_opaque_region( + &self, + region: SetOpaqueRegion, + _slf: &Rc, + ) -> Result<(), Self::Error> { + let region = if region.region.is_some() { + Some(self.client.lookup(region.region)?.region()) + } else { + None + }; + self.pending.borrow_mut().opaque_region = Some(region); + Ok(()) + } + + fn set_input_region(&self, req: SetInputRegion, _slf: &Rc) -> Result<(), Self::Error> { + let region = if req.region.is_some() { + Some(self.client.lookup(req.region)?.region()) + } else { + None + }; + self.pending.borrow_mut().input_region = Some(region); + Ok(()) + } + + fn commit(&self, _req: Commit, slf: &Rc) -> Result<(), Self::Error> { + let ext = self.ext.get(); + let pending = &mut *self.pending.borrow_mut(); + if let Some(Some(buffer)) = &mut pending.buffer + && pending.release_point.is_none() + && pending.sync_file_release.is_none() + { + buffer.send_release = true; + } + if let Some(release) = &mut pending.release_point { + release.committed = true; + } + self.verify_syncobj_sync(pending)?; + if pending.surface_release.is_not_empty() && not_matches!(pending.buffer, Some(Some(_))) { + return Err(WlSurfaceError::SurfaceReleaseWithoutAttach); + } + if pending.sync_file_release.is_some() && not_matches!(pending.buffer, Some(Some(_))) { + return Err(WlSurfaceError::SyncFileReleaseWithoutAttach); + } + if ext.commit_requested(pending) == CommitAction::ContinueCommit { + self.commit_timeline.commit(slf, pending)?; + } + Ok(()) + } + + fn set_buffer_transform( + &self, + req: SetBufferTransform, + _slf: &Rc, + ) -> Result<(), Self::Error> { + let Some(tf) = Transform::from_wl(req.transform) else { + return Err(WlSurfaceError::UnknownBufferTransform(req.transform)); + }; + self.pending.borrow_mut().transform = Some(tf); + Ok(()) + } + + fn set_buffer_scale(&self, req: SetBufferScale, _slf: &Rc) -> Result<(), Self::Error> { + if req.scale < 1 { + return Err(WlSurfaceError::NonPositiveBufferScale); + } + self.pending.borrow_mut().scale = Some(req.scale); + Ok(()) + } + + fn damage_buffer(&self, req: DamageBuffer, _slf: &Rc) -> Result<(), Self::Error> { + self.do_damage(req.x, req.y, req.width, req.height, |p| { + &mut p.buffer_damage + }) + } + + fn offset(&self, req: Offset, _slf: &Rc) -> Result<(), Self::Error> { + self.pending.borrow_mut().offset = (req.x, req.y); + Ok(()) + } + + fn get_release(&self, req: GetRelease, _slf: &Rc) -> Result<(), Self::Error> { + let cb = Rc::new(WlCallback::new(req.callback, &self.client)); + track!(self.client, cb); + self.client.add_client_obj(&cb)?; + let release = SurfaceRelease { cb }; + self.pending.borrow_mut().surface_release.push(release); + Ok(()) + } +} diff --git a/src/ifs/wl_surface/tray.rs b/src/ifs/wl_surface/tray.rs index 9ec856c4..47d88671 100644 --- a/src/ifs/wl_surface/tray.rs +++ b/src/ifs/wl_surface/tray.rs @@ -1,3 +1,4 @@ +use jay_geometry::Rect; use { crate::{ client::{Client, ClientError, ClientId}, @@ -9,7 +10,7 @@ use { xdg_surface::xdg_popup::{XdgPopup, XdgPopupParent}, }, }, - rect::Rect, + tree::{ FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeLayerLink, NodeLocation, NodeVisitor, OutputNode, StackedNode, diff --git a/src/ifs/wl_surface/tray/jay_tray_item_v1.rs b/src/ifs/wl_surface/tray/jay_tray_item_v1.rs index 88b5731f..704ec056 100644 --- a/src/ifs/wl_surface/tray/jay_tray_item_v1.rs +++ b/src/ifs/wl_surface/tray/jay_tray_item_v1.rs @@ -1,3 +1,4 @@ +use jay_theme::BarPosition; use { crate::{ ifs::{ @@ -15,7 +16,7 @@ use { }, leaks::Tracker, object::{Object, Version}, - theme::BarPosition, + tree::NodeVisitor, utils::copyhashmap::CopyHashMap, wire::{JayTrayItemV1Id, XdgPopupId, jay_tray_item_v1::*}, diff --git a/src/ifs/wl_surface/wp_color_management_surface_v1.rs b/src/ifs/wl_surface/wp_color_management_surface_v1.rs index 9783aa87..36f4de9b 100644 --- a/src/ifs/wl_surface/wp_color_management_surface_v1.rs +++ b/src/ifs/wl_surface/wp_color_management_surface_v1.rs @@ -1,7 +1,7 @@ use { crate::{ client::{Client, ClientError}, - cmm::cmm_render_intent::RenderIntent, + cmm::cmm_render_intent, ifs::wl_surface::WlSurface, leaks::Tracker, object::{Object, Version}, @@ -52,7 +52,7 @@ impl WpColorManagementSurfaceV1RequestHandler for WpColorManagementSurfaceV1 { req: SetImageDescription, _slf: &Rc, ) -> Result<(), Self::Error> { - let Some(intent) = RenderIntent::from_wayland(req.render_intent, self.version) else { + let Some(intent) = cmm_render_intent::from_wayland(req.render_intent, self.version) else { return Err(WpColorManagementSurfaceV1Error::UnsupportedRenderIntent( req.render_intent, )); diff --git a/src/ifs/wl_surface/wp_fractional_scale_v1.rs b/src/ifs/wl_surface/wp_fractional_scale_v1.rs index 47fadbe5..b9aba204 100644 --- a/src/ifs/wl_surface/wp_fractional_scale_v1.rs +++ b/src/ifs/wl_surface/wp_fractional_scale_v1.rs @@ -1,10 +1,10 @@ +use jay_units::scale::Scale; use { crate::{ client::{Client, ClientError}, ifs::wl_surface::WlSurface, leaks::Tracker, object::{Object, Version}, - scale::Scale, utils::cell_ext::CellExt, wire::{WpFractionalScaleV1Id, wp_fractional_scale_v1::*}, }, diff --git a/src/ifs/wl_surface/x_surface.rs b/src/ifs/wl_surface/x_surface.rs index 1c3e295c..4f6db63c 100644 --- a/src/ifs/wl_surface/x_surface.rs +++ b/src/ifs/wl_surface/x_surface.rs @@ -1,7 +1,7 @@ use { crate::{ ifs::wl_surface::{ - SurfaceExt, WlSurface, WlSurfaceError, + PendingState, SurfaceExt, WlSurface, WlSurfaceError, x_surface::{xwayland_surface_v1::XwaylandSurfaceV1, xwindow::Xwindow}, }, leaks::Tracker, @@ -30,6 +30,22 @@ impl SurfaceExt for XSurface { win.node_layer() } + fn before_apply_commit( + self: Rc, + pending: &mut PendingState, + ) -> Result<(), WlSurfaceError> { + if pending + .buffer + .as_ref() + .is_some_and(|buffer| buffer.is_none()) + && self.surface.buffer.is_some() + && let Some(xwindow) = self.xwindow.get() + { + xwindow.queue_spawn_out(); + } + Ok(()) + } + fn after_apply_commit(self: Rc) { if let Some(xwindow) = self.xwindow.get() { xwindow.map_status_changed(); @@ -45,6 +61,7 @@ impl SurfaceExt for XSurface { } self.surface.unset_ext(); if let Some(xwindow) = self.xwindow.take() { + xwindow.queue_spawn_out(); xwindow.tl_destroy(); xwindow.data.window.set(None); xwindow.data.surface_id.set(None); diff --git a/src/ifs/wl_surface/x_surface/xwindow.rs b/src/ifs/wl_surface/x_surface/xwindow.rs index f1c68730..2bba3fd6 100644 --- a/src/ifs/wl_surface/x_surface/xwindow.rs +++ b/src/ifs/wl_surface/x_surface/xwindow.rs @@ -1,13 +1,15 @@ +use jay_geometry::Rect; +use jay_units::fixed::Fixed; use { crate::{ + animation::RetainedToplevel, client::Client, cursor::KnownCursor, - fixed::Fixed, ifs::{ wl_seat::{NodeSeatState, WlSeatGlobal, tablet::TabletTool}, wl_surface::{WlSurface, WlSurfaceError, x_surface::XSurface}, }, - rect::Rect, + renderer::Renderer, state::State, tree::{ @@ -252,6 +254,11 @@ impl Xwindow { self.x.surface.buffer.is_some() && self.data.info.mapped.get() } + pub fn queue_spawn_out(&self) { + self.toplevel_data + .queue_spawn_out(self, self.tl_animation_snapshot()); + } + fn map_change(&self) -> Change { match (self.may_be_mapped(), self.is_mapped()) { (true, false) => Change::Map, @@ -274,6 +281,7 @@ impl Xwindow { match map_change { Change::None => return, Change::Unmap => { + self.queue_spawn_out(); self.data .info .pending_extents @@ -514,6 +522,10 @@ impl ToplevelNodeBase for Xwindow { Some(self.x.surface.clone()) } + fn tl_animation_snapshot(&self) -> Option> { + RetainedToplevel::capture_surface(&self.x.surface, (0, 0)) + } + fn tl_admits_children(&self) -> bool { false } diff --git a/src/ifs/wl_surface/xdg_surface.rs b/src/ifs/wl_surface/xdg_surface.rs index 9b5130d7..946bce7b 100644 --- a/src/ifs/wl_surface/xdg_surface.rs +++ b/src/ifs/wl_surface/xdg_surface.rs @@ -1,6 +1,7 @@ pub mod xdg_popup; pub mod xdg_toplevel; +use jay_geometry::Rect; use { crate::{ client::ClientError, @@ -17,7 +18,7 @@ use { }, leaks::Tracker, object::Object, - rect::Rect, + state::State, tree::{ FindTreeResult, FoundNode, Node, NodeLayerLink, NodeLocation, OutputNode, StackedNode, @@ -226,6 +227,10 @@ pub trait XdgSurfaceExt: Debug { // nothing } + fn prepare_unmap(&self) { + // nothing + } + fn extents_changed(&self) { // nothing } @@ -664,6 +669,15 @@ impl SurfaceExt for XdgSurface { if let Some(serial) = pending.serial.take() { self.applied_serial.set(serial); } + if pending + .buffer + .as_ref() + .is_some_and(|buffer| buffer.is_none()) + && self.surface.buffer.is_some() + && let Some(ext) = self.ext.get() + { + ext.prepare_unmap(); + } Ok(()) } diff --git a/src/ifs/wl_surface/xdg_surface/xdg_popup.rs b/src/ifs/wl_surface/xdg_surface/xdg_popup.rs index 14b131fe..afb39591 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_popup.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_popup.rs @@ -1,10 +1,11 @@ pub mod jay_popup_ext_v1; +use jay_geometry::Rect; +use jay_units::fixed::Fixed; use { crate::{ client::{Client, ClientError}, cursor::KnownCursor, - fixed::Fixed, ifs::{ wl_seat::{NodeSeatState, SeatId, WlSeatGlobal, tablet::TabletTool}, wl_surface::{ @@ -20,7 +21,7 @@ use { }, leaks::Tracker, object::Object, - rect::Rect, + renderer::Renderer, tree::{ Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeLayerLink, diff --git a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs index 768a8367..fa71f0dd 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs @@ -1,12 +1,15 @@ pub mod xdg_dialog_v1; +use jay_geometry::Rect; +use jay_bugs::Bugs; +use jay_units::fixed::Fixed; use { crate::{ - bugs, - bugs::Bugs, + animation::RetainedToplevel, + + client::{Client, ClientError}, cursor::KnownCursor, - fixed::Fixed, ifs::{ ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1, wl_seat::{NodeSeatState, WlSeatGlobal, tablet::TabletTool}, @@ -21,7 +24,7 @@ use { }, leaks::Tracker, object::{Object, Version}, - rect::Rect, + renderer::Renderer, state::State, tree::{ @@ -167,7 +170,7 @@ impl XdgToplevel { children: RefCell::new(Default::default()), states: NumCell::new(states), decoration: Cell::new(Decoration::Server), - bugs: Cell::new(&bugs::NONE), + bugs: Cell::new(&jay_bugs::NONE), min_width: Cell::new(None), min_height: Cell::new(None), max_width: Cell::new(None), @@ -259,6 +262,7 @@ impl XdgToplevelRequestHandler for XdgToplevel { type Error = XdgToplevelError; fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.queue_spawn_out(); self.tl_destroy(); self.xdg.unset_ext(); { @@ -302,7 +306,7 @@ impl XdgToplevelRequestHandler for XdgToplevel { fn set_app_id(&self, req: SetAppId, _slf: &Rc) -> Result<(), Self::Error> { self.toplevel_data.set_app_id(req.app_id); - self.bugs.set(bugs::get(req.app_id)); + self.bugs.set(jay_bugs::get(req.app_id)); Ok(()) } @@ -398,6 +402,11 @@ impl XdgToplevelRequestHandler for XdgToplevel { } impl XdgToplevel { + fn queue_spawn_out(&self) { + self.toplevel_data + .queue_spawn_out(self, self.tl_animation_snapshot()); + } + fn map( self: &Rc, parent: Option<&XdgToplevel>, @@ -779,6 +788,11 @@ impl ToplevelNodeBase for XdgToplevel { Some(self.xdg.surface.clone()) } + fn tl_animation_snapshot(&self) -> Option> { + let geo = self.xdg.geometry(); + RetainedToplevel::capture_surface(&self.xdg.surface, (-geo.x1(), -geo.y1())) + } + fn tl_restack_popups(&self) { self.xdg.restack_popups(); } @@ -818,6 +832,10 @@ impl XdgSurfaceExt for XdgToplevel { self.after_commit(None); } + fn prepare_unmap(&self) { + self.queue_spawn_out(); + } + fn extents_changed(&self) { self.toplevel_data.pos.set(self.xdg.extents.get()); self.tl_extents_changed(); diff --git a/src/ifs/wl_surface/zwlr_layer_surface_v1.rs b/src/ifs/wl_surface/zwlr_layer_surface_v1.rs index ad8e40e8..0316f23a 100644 --- a/src/ifs/wl_surface/zwlr_layer_surface_v1.rs +++ b/src/ifs/wl_surface/zwlr_layer_surface_v1.rs @@ -1,3 +1,4 @@ +use jay_geometry::Rect; use { crate::{ client::{Client, ClientError}, @@ -12,7 +13,7 @@ use { }, leaks::Tracker, object::Object, - rect::Rect, + renderer::Renderer, tree::{ Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeLayerLink, diff --git a/src/ifs/wl_surface/zwp_input_popup_surface_v2.rs b/src/ifs/wl_surface/zwp_input_popup_surface_v2.rs index 3550b94d..690d6fab 100644 --- a/src/ifs/wl_surface/zwp_input_popup_surface_v2.rs +++ b/src/ifs/wl_surface/zwp_input_popup_surface_v2.rs @@ -1,3 +1,4 @@ +use jay_geometry::Rect; use { crate::{ client::{Client, ClientError}, @@ -7,7 +8,7 @@ use { }, leaks::Tracker, object::{Object, Version}, - rect::Rect, + state::State, tree::NodeLayerLink, wire::{WlSurfaceId, ZwpInputPopupSurfaceV2Id, zwp_input_popup_surface_v2::*}, diff --git a/src/ifs/wlr_output_manager/zwlr_output_configuration_head.rs b/src/ifs/wlr_output_manager/zwlr_output_configuration_head.rs index 8034861b..5d5457bc 100644 --- a/src/ifs/wlr_output_manager/zwlr_output_configuration_head.rs +++ b/src/ifs/wlr_output_manager/zwlr_output_configuration_head.rs @@ -1,14 +1,14 @@ +use jay_units::fixed::Fixed; +use jay_units::scale::Scale; use { crate::{ backend::Mode, client::{Client, ClientError}, - fixed::Fixed, ifs::wlr_output_manager::zwlr_output_head_v1::{ ADAPTIVE_SYNC_STATE_DISABLED, ADAPTIVE_SYNC_STATE_ENABLED, WlrOutputHeadId, }, leaks::Tracker, object::{Object, Version}, - scale::Scale, tree::{Transform, VrrMode}, wire::{ZwlrOutputConfigurationHeadV1Id, zwlr_output_configuration_head_v1::*}, }, diff --git a/src/ifs/wlr_output_manager/zwlr_output_head_v1.rs b/src/ifs/wlr_output_manager/zwlr_output_head_v1.rs index 5e51a558..ffc254d2 100644 --- a/src/ifs/wlr_output_manager/zwlr_output_head_v1.rs +++ b/src/ifs/wlr_output_manager/zwlr_output_head_v1.rs @@ -1,19 +1,19 @@ +use jay_units::fixed::Fixed; +use jay_units::scale::Scale as UnitScale; use { crate::{ backend::{self, ConnectorId}, client::{Client, ClientError}, - fixed::Fixed, ifs::wlr_output_manager::{ zwlr_output_manager_v1::{WlrOutputManagerId, ZwlrOutputManagerV1}, zwlr_output_mode_v1::ZwlrOutputModeV1, }, leaks::Tracker, object::{Object, Version}, - scale, state::OutputData, tree::{self, VrrMode}, utils::copyhashmap::CopyHashMap, - wire::{ZwlrOutputHeadV1Id, zwlr_output_head_v1::*}, + wire::{zwlr_output_head_v1::*, ZwlrOutputHeadV1Id}, }, std::rc::Rc, thiserror::Error, @@ -116,7 +116,7 @@ impl ZwlrOutputHeadV1 { }); } - pub fn send_scale(&self, scale: scale::Scale) { + pub fn send_scale(&self, scale: UnitScale) { let scale = Fixed::from_f64(scale.to_f64()); self.client.event(Scale { self_id: self.id, @@ -208,7 +208,7 @@ impl ZwlrOutputHeadV1 { self.manager.schedule_done(); } - pub fn handle_new_scale(&self, scale: scale::Scale) { + pub fn handle_new_scale(&self, scale: UnitScale) { self.send_scale(scale); self.manager.schedule_done(); } diff --git a/src/ifs/wlr_output_manager/zwlr_output_manager_v1.rs b/src/ifs/wlr_output_manager/zwlr_output_manager_v1.rs index a4c116a6..ef8ef48a 100644 --- a/src/ifs/wlr_output_manager/zwlr_output_manager_v1.rs +++ b/src/ifs/wlr_output_manager/zwlr_output_manager_v1.rs @@ -1,7 +1,7 @@ use { crate::{ backend::Mode, - client::{CAP_HEAD_MANAGER, Client, ClientCaps, ClientError}, + client::{Client, ClientError}, globals::{Global, GlobalName}, ifs::wlr_output_manager::{ zwlr_output_configuration_v1::ZwlrOutputConfigurationV1, @@ -276,10 +276,6 @@ impl Global for ZwlrOutputManagerV1Global { fn version(&self) -> u32 { 4 } - - fn required_caps(&self) -> ClientCaps { - CAP_HEAD_MANAGER - } } simple_add_global!(ZwlrOutputManagerV1Global); diff --git a/src/ifs/workspace_manager/ext_workspace_manager_v1.rs b/src/ifs/workspace_manager/ext_workspace_manager_v1.rs index dfa36189..12850ec9 100644 --- a/src/ifs/workspace_manager/ext_workspace_manager_v1.rs +++ b/src/ifs/workspace_manager/ext_workspace_manager_v1.rs @@ -1,6 +1,6 @@ use { crate::{ - client::{CAP_WORKSPACE, Client, ClientCaps, ClientError}, + client::{Client, ClientError}, globals::{Global, GlobalName}, ifs::{ wl_output::OutputGlobalOpt, @@ -249,10 +249,6 @@ impl Global for ExtWorkspaceManagerV1Global { fn version(&self) -> u32 { 1 } - - fn required_caps(&self) -> ClientCaps { - CAP_WORKSPACE - } } simple_add_global!(ExtWorkspaceManagerV1Global); diff --git a/src/ifs/wp_drm_lease_device_v1.rs b/src/ifs/wp_drm_lease_device_v1.rs index f438fda3..a9eb8260 100644 --- a/src/ifs/wp_drm_lease_device_v1.rs +++ b/src/ifs/wp_drm_lease_device_v1.rs @@ -1,7 +1,7 @@ use { crate::{ backend::DrmDeviceId, - client::{CAP_DRM_LEASE, Client, ClientCaps, ClientError}, + client::{Client, ClientError}, globals::{Global, GlobalName}, ifs::{ wp_drm_lease_connector_v1::WpDrmLeaseConnectorV1, @@ -84,10 +84,6 @@ impl Global for WpDrmLeaseDeviceV1Global { fn version(&self) -> u32 { 1 } - - fn required_caps(&self) -> ClientCaps { - CAP_DRM_LEASE - } } pub struct WpDrmLeaseDeviceV1 { diff --git a/src/ifs/wp_drm_lease_device_v1/removed_device.rs b/src/ifs/wp_drm_lease_device_v1/removed_device.rs index 20835874..cffe5765 100644 --- a/src/ifs/wp_drm_lease_device_v1/removed_device.rs +++ b/src/ifs/wp_drm_lease_device_v1/removed_device.rs @@ -1,7 +1,7 @@ use { crate::{ backend::DrmDeviceId, - client::{CAP_DRM_LEASE, Client, ClientCaps, ClientError}, + client::{Client, ClientError}, globals::{Global, GlobalName, RemovableWaylandGlobal}, ifs::wp_drm_lease_device_v1::{WpDrmLeaseDeviceV1, WpDrmLeaseDeviceV1Global}, object::Version, @@ -53,10 +53,6 @@ impl Global for RemovedWpDrmLeaseDeviceV1Global { fn version(&self) -> u32 { 1 } - - fn required_caps(&self) -> ClientCaps { - CAP_DRM_LEASE - } } impl RemovableWaylandGlobal for WpDrmLeaseDeviceV1Global { diff --git a/src/ifs/wp_security_context_manager_v1.rs b/src/ifs/wp_security_context_manager_v1.rs deleted file mode 100644 index 8a6c668c..00000000 --- a/src/ifs/wp_security_context_manager_v1.rs +++ /dev/null @@ -1,103 +0,0 @@ -use { - crate::{ - client::{Client, ClientError}, - globals::{Global, GlobalName}, - ifs::wp_security_context_v1::WpSecurityContextV1, - leaks::Tracker, - object::{Object, Version}, - wire::{WpSecurityContextManagerV1Id, wp_security_context_manager_v1::*}, - }, - std::rc::Rc, - thiserror::Error, -}; - -pub struct WpSecurityContextManagerV1Global { - pub name: GlobalName, -} - -impl WpSecurityContextManagerV1Global { - pub fn new(name: GlobalName) -> Self { - Self { name } - } - - fn bind_( - self: Rc, - id: WpSecurityContextManagerV1Id, - client: &Rc, - version: Version, - ) -> Result<(), WpSecurityContextManagerV1Error> { - let obj = Rc::new(WpSecurityContextManagerV1 { - id, - client: client.clone(), - tracker: Default::default(), - version, - }); - track!(client, obj); - client.add_client_obj(&obj)?; - Ok(()) - } -} - -global_base!( - WpSecurityContextManagerV1Global, - WpSecurityContextManagerV1, - WpSecurityContextManagerV1Error -); - -impl Global for WpSecurityContextManagerV1Global { - fn version(&self) -> u32 { - 1 - } -} - -simple_add_global!(WpSecurityContextManagerV1Global); - -pub struct WpSecurityContextManagerV1 { - pub id: WpSecurityContextManagerV1Id, - pub client: Rc, - pub tracker: Tracker, - pub version: Version, -} - -impl WpSecurityContextManagerV1RequestHandler for WpSecurityContextManagerV1 { - type Error = WpSecurityContextManagerV1Error; - - fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { - self.client.remove_obj(self)?; - Ok(()) - } - - fn create_listener(&self, req: CreateListener, _slf: &Rc) -> Result<(), Self::Error> { - let obj = Rc::new(WpSecurityContextV1 { - id: req.id, - client: self.client.clone(), - tracker: Default::default(), - version: self.version, - listen_fd: req.listen_fd, - close_fd: req.close_fd, - sandbox_engine: Default::default(), - app_id: Default::default(), - instance_id: Default::default(), - committed: Default::default(), - }); - track!(self.client, obj); - self.client.add_client_obj(&obj)?; - Ok(()) - } -} - -object_base! { - self = WpSecurityContextManagerV1; - version = self.version; -} - -impl Object for WpSecurityContextManagerV1 {} - -simple_add_obj!(WpSecurityContextManagerV1); - -#[derive(Debug, Error)] -pub enum WpSecurityContextManagerV1Error { - #[error(transparent)] - ClientError(Box), -} -efrom!(WpSecurityContextManagerV1Error, ClientError); diff --git a/src/ifs/wp_security_context_v1.rs b/src/ifs/wp_security_context_v1.rs deleted file mode 100644 index 26e6525f..00000000 --- a/src/ifs/wp_security_context_v1.rs +++ /dev/null @@ -1,118 +0,0 @@ -use { - crate::{ - client::{Client, ClientError}, - leaks::Tracker, - object::{Object, Version}, - wire::{WpSecurityContextV1Id, wp_security_context_v1::*}, - }, - std::{ - cell::{Cell, RefCell}, - rc::Rc, - }, - thiserror::Error, - uapi::OwnedFd, -}; - -pub struct WpSecurityContextV1 { - pub id: WpSecurityContextV1Id, - pub client: Rc, - pub tracker: Tracker, - pub version: Version, - pub listen_fd: Rc, - pub close_fd: Rc, - pub sandbox_engine: RefCell>, - pub app_id: RefCell>, - pub instance_id: RefCell>, - pub committed: Cell, -} - -impl WpSecurityContextV1 { - fn check_committed(&self) -> Result<(), WpSecurityContextV1Error> { - if self.committed.get() { - return Err(WpSecurityContextV1Error::Committed); - } - Ok(()) - } -} - -impl WpSecurityContextV1RequestHandler for WpSecurityContextV1 { - type Error = WpSecurityContextV1Error; - - fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { - self.client.remove_obj(self)?; - Ok(()) - } - - fn set_sandbox_engine( - &self, - req: SetSandboxEngine<'_>, - _slf: &Rc, - ) -> Result<(), Self::Error> { - self.check_committed()?; - let val = &mut *self.sandbox_engine.borrow_mut(); - if val.is_some() { - return Err(WpSecurityContextV1Error::EnginSet); - } - *val = Some(req.name.to_string()); - Ok(()) - } - - fn set_app_id(&self, req: SetAppId<'_>, _slf: &Rc) -> Result<(), Self::Error> { - self.check_committed()?; - let val = &mut *self.app_id.borrow_mut(); - if val.is_some() { - return Err(WpSecurityContextV1Error::AppSet); - } - *val = Some(req.app_id.to_string()); - Ok(()) - } - - fn set_instance_id(&self, req: SetInstanceId<'_>, _slf: &Rc) -> Result<(), Self::Error> { - self.check_committed()?; - let val = &mut *self.instance_id.borrow_mut(); - if val.is_some() { - return Err(WpSecurityContextV1Error::InstanceSet); - } - *val = Some(req.instance_id.to_string()); - Ok(()) - } - - fn commit(&self, _req: Commit, _slf: &Rc) -> Result<(), Self::Error> { - self.check_committed()?; - self.committed.set(true); - self.client.state.security_context_acceptors.spawn( - &self.client.state, - self.sandbox_engine.take(), - self.app_id.take(), - self.instance_id.take(), - &self.listen_fd, - &self.close_fd, - self.client.bounding_caps_for_children.get(), - ); - Ok(()) - } -} - -object_base! { - self = WpSecurityContextV1; - version = self.version; -} - -impl Object for WpSecurityContextV1 {} - -simple_add_obj!(WpSecurityContextV1); - -#[derive(Debug, Error)] -pub enum WpSecurityContextV1Error { - #[error(transparent)] - ClientError(Box), - #[error("The sandbox engine has already been set")] - EnginSet, - #[error("The app id has already been set")] - AppSet, - #[error("The instance id has already been set")] - InstanceSet, - #[error("The context has already been committed")] - Committed, -} -efrom!(WpSecurityContextV1Error, ClientError); diff --git a/src/ifs/xdg_positioner.rs b/src/ifs/xdg_positioner.rs index 3b0d02cf..0254e012 100644 --- a/src/ifs/xdg_positioner.rs +++ b/src/ifs/xdg_positioner.rs @@ -1,10 +1,11 @@ +use jay_geometry::Rect; use { crate::{ client::{Client, ClientError}, ifs::xdg_wm_base::XdgWmBase, leaks::Tracker, object::Object, - rect::Rect, + wire::{XdgPositionerId, xdg_positioner::*}, }, std::{cell::RefCell, rc::Rc}, diff --git a/src/ifs/xdg_toplevel_drag_v1.rs b/src/ifs/xdg_toplevel_drag_v1.rs index 3234be47..c31e21e6 100644 --- a/src/ifs/xdg_toplevel_drag_v1.rs +++ b/src/ifs/xdg_toplevel_drag_v1.rs @@ -1,13 +1,14 @@ +use jay_geometry::Rect; use { crate::{ client::{Client, ClientError}, ifs::{ - ipc::wl_data_source::WlDataSource, wl_seat::WlSeatGlobal, + data_transfer::wl_data_source::WlDataSource, wl_seat::WlSeatGlobal, wl_surface::xdg_surface::xdg_toplevel::XdgToplevel, }, leaks::Tracker, object::{Object, Version}, - rect::Rect, + renderer::Renderer, tree::{Node, ToplevelNode}, utils::clonecell::CloneCell, diff --git a/src/ifs/xx_foreign_toplevel_geometry_tracker_v1.rs b/src/ifs/xx_foreign_toplevel_geometry_tracker_v1.rs index d3977902..6a2d8e16 100644 --- a/src/ifs/xx_foreign_toplevel_geometry_tracker_v1.rs +++ b/src/ifs/xx_foreign_toplevel_geometry_tracker_v1.rs @@ -1,9 +1,10 @@ +use jay_geometry::Rect; use { crate::{ client::{Client, ClientError}, leaks::Tracker, object::{Object, Version}, - rect::Rect, + state::State, tree::ToplevelOpt, wire::{XxForeignToplevelGeometryTrackerV1Id, xx_foreign_toplevel_geometry_tracker_v1::*}, diff --git a/src/ifs/xx_foreign_toplevel_geometry_tracking_manager_v1.rs b/src/ifs/xx_foreign_toplevel_geometry_tracking_manager_v1.rs index c1a23f51..f8ccf931 100644 --- a/src/ifs/xx_foreign_toplevel_geometry_tracking_manager_v1.rs +++ b/src/ifs/xx_foreign_toplevel_geometry_tracking_manager_v1.rs @@ -1,6 +1,6 @@ use { crate::{ - client::{CAP_FOREIGN_TOPLEVEL_GEOMETRY_TRACKING, Client, ClientCaps, ClientError}, + client::{Client, ClientError}, globals::{Global, GlobalName}, ifs::{ ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, @@ -100,10 +100,6 @@ impl Global for XxForeignToplevelGeometryTrackingManagerV1Global { fn version(&self) -> u32 { 1 } - - fn required_caps(&self) -> ClientCaps { - CAP_FOREIGN_TOPLEVEL_GEOMETRY_TRACKING - } } simple_add_global!(XxForeignToplevelGeometryTrackingManagerV1Global); diff --git a/src/ifs/zwlr_foreign_toplevel_manager_v1.rs b/src/ifs/zwlr_foreign_toplevel_manager_v1.rs index f82feb9d..47856ddd 100644 --- a/src/ifs/zwlr_foreign_toplevel_manager_v1.rs +++ b/src/ifs/zwlr_foreign_toplevel_manager_v1.rs @@ -1,6 +1,6 @@ use { crate::{ - client::{CAP_FOREIGN_TOPLEVEL_MANAGER, Client, ClientCaps, ClientError}, + client::{Client, ClientError}, globals::{Global, GlobalName}, ifs::{ wl_surface::{x_surface::xwindow::Xwindow, xdg_surface::xdg_toplevel::XdgToplevel}, @@ -129,10 +129,6 @@ impl Global for ZwlrForeignToplevelManagerV1Global { fn version(&self) -> u32 { 3 } - - fn required_caps(&self) -> ClientCaps { - CAP_FOREIGN_TOPLEVEL_MANAGER - } } simple_add_global!(ZwlrForeignToplevelManagerV1Global); diff --git a/src/ifs/zwlr_gamma_control_manager_v1.rs b/src/ifs/zwlr_gamma_control_manager_v1.rs index f2102ee9..e75426de 100644 --- a/src/ifs/zwlr_gamma_control_manager_v1.rs +++ b/src/ifs/zwlr_gamma_control_manager_v1.rs @@ -1,6 +1,6 @@ use { crate::{ - client::{CAP_GAMMA_CONTROL_MANAGER, Client, ClientCaps, ClientError}, + client::{Client, ClientError}, globals::{Global, GlobalName}, ifs::zwlr_gamma_control_v1::*, leaks::Tracker, @@ -50,10 +50,6 @@ impl Global for ZwlrGammaControlManagerV1Global { fn version(&self) -> u32 { 1 } - - fn required_caps(&self) -> ClientCaps { - CAP_GAMMA_CONTROL_MANAGER - } } pub struct ZwlrGammaControlManagerV1 { diff --git a/src/ifs/zwlr_gamma_control_v1.rs b/src/ifs/zwlr_gamma_control_v1.rs index 8467c6d1..1da5457f 100644 --- a/src/ifs/zwlr_gamma_control_v1.rs +++ b/src/ifs/zwlr_gamma_control_v1.rs @@ -2,7 +2,7 @@ use { crate::{ backend::{BackendGammaLut, BackendGammaLutElement}, client::{Client, ClientError, ClientId}, - clientmem::{ClientMem, ClientMemError}, + clientmem::{ClientMem, ClientMemError, client_mem_client}, ifs::{ wl_output::OutputGlobalOpt, zwlr_gamma_control_manager_v1::ZwlrGammaControlManagerV1, }, @@ -119,7 +119,7 @@ impl ZwlrGammaControlV1RequestHandler for ZwlrGammaControlV1 { &req.fd, data_size, true, - Some(&self.client), + Some(client_mem_client(&self.client)), None, )?) .offset(0, data_size) diff --git a/src/ifs/zwlr_layer_shell_v1.rs b/src/ifs/zwlr_layer_shell_v1.rs index 6394fcbe..8301a35f 100644 --- a/src/ifs/zwlr_layer_shell_v1.rs +++ b/src/ifs/zwlr_layer_shell_v1.rs @@ -1,6 +1,6 @@ use { crate::{ - client::{CAP_LAYER_SHELL, Client, ClientCaps, ClientError}, + client::{Client, ClientError}, globals::{Global, GlobalName}, ifs::{ wl_output::OutputGlobalOpt, @@ -112,10 +112,6 @@ impl Global for ZwlrLayerShellV1Global { fn version(&self) -> u32 { 5 } - - fn required_caps(&self) -> ClientCaps { - CAP_LAYER_SHELL - } } simple_add_global!(ZwlrLayerShellV1Global); diff --git a/src/ifs/zwlr_screencopy_frame_v1.rs b/src/ifs/zwlr_screencopy_frame_v1.rs index d66035e9..fd1f4b23 100644 --- a/src/ifs/zwlr_screencopy_frame_v1.rs +++ b/src/ifs/zwlr_screencopy_frame_v1.rs @@ -1,3 +1,4 @@ +use jay_geometry::Rect; use { crate::{ client::{Client, ClientError}, @@ -9,7 +10,7 @@ use { }, leaks::Tracker, object::{Object, Version}, - rect::Rect, + utils::errorfmt::ErrorFmt, wire::{WlBufferId, ZwlrScreencopyFrameV1Id, zwlr_screencopy_frame_v1::*}, }, @@ -123,14 +124,14 @@ impl ZwlrScreencopyFrameV1 { self.with_damage.set(with_damage); node.screencopies .set((self.client.id, self.id), self.clone()); - node.screencast_changed(); + node.captures_changed(); Ok(()) } fn detach(&self) { if let Some(node) = self.output.node() { node.screencopies.remove(&(self.client.id, self.id)); - node.screencast_changed(); + node.captures_changed(); } self.pending.take(); } diff --git a/src/ifs/zwlr_screencopy_manager_v1.rs b/src/ifs/zwlr_screencopy_manager_v1.rs index 2ff6c8a4..77dbed24 100644 --- a/src/ifs/zwlr_screencopy_manager_v1.rs +++ b/src/ifs/zwlr_screencopy_manager_v1.rs @@ -1,11 +1,12 @@ +use jay_geometry::Rect; use { crate::{ - client::{CAP_SCREENCOPY_MANAGER, Client, ClientCaps, ClientError}, + client::{Client, ClientError}, globals::{Global, GlobalName}, ifs::zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1, leaks::Tracker, object::{Object, Version}, - rect::Rect, + wire::{ WlOutputId, ZwlrScreencopyFrameV1Id, ZwlrScreencopyManagerV1Id, zwlr_screencopy_manager_v1::*, @@ -54,10 +55,6 @@ impl Global for ZwlrScreencopyManagerV1Global { fn version(&self) -> u32 { 3 } - - fn required_caps(&self) -> ClientCaps { - CAP_SCREENCOPY_MANAGER - } } pub struct ZwlrScreencopyManagerV1 { diff --git a/src/ifs/zwlr_virtual_pointer_manager_v1.rs b/src/ifs/zwlr_virtual_pointer_manager_v1.rs index d36fbd17..9746dd0e 100644 --- a/src/ifs/zwlr_virtual_pointer_manager_v1.rs +++ b/src/ifs/zwlr_virtual_pointer_manager_v1.rs @@ -1,6 +1,6 @@ use { crate::{ - client::{CAP_VIRTUAL_POINTER_MANAGER, Client, ClientCaps, ClientError}, + client::{Client, ClientError}, globals::{Global, GlobalName}, ifs::zwlr_virtual_pointer_v1::ZwlrVirtualPointerV1, leaks::Tracker, @@ -53,10 +53,6 @@ impl Global for ZwlrVirtualPointerManagerV1Global { fn version(&self) -> u32 { 2 } - - fn required_caps(&self) -> ClientCaps { - CAP_VIRTUAL_POINTER_MANAGER - } } pub struct ZwlrVirtualPointerManagerV1 { diff --git a/src/ifs/zwlr_virtual_pointer_v1.rs b/src/ifs/zwlr_virtual_pointer_v1.rs index 4c63ae8e..b5c84f03 100644 --- a/src/ifs/zwlr_virtual_pointer_v1.rs +++ b/src/ifs/zwlr_virtual_pointer_v1.rs @@ -1,8 +1,8 @@ +use jay_units::fixed::Fixed; use { crate::{ backend::{AxisSource as BackendAxisSource, ButtonState, ScrollAxis}, client::{Client, ClientError}, - fixed::Fixed, ifs::{ wl_output::OutputGlobalOpt, wl_seat::{ diff --git a/src/ifs/zwp_linux_buffer_params_v1.rs b/src/ifs/zwp_linux_buffer_params_v1.rs index eac91ff6..9f017aa0 100644 --- a/src/ifs/zwp_linux_buffer_params_v1.rs +++ b/src/ifs/zwp_linux_buffer_params_v1.rs @@ -1,7 +1,7 @@ use { crate::{ client::ClientError, - clientmem::{ClientMem, ClientMemError}, + clientmem::{ClientMem, ClientMemError, client_mem_client}, gfx_api::GfxError, ifs::{ wl_buffer::{WlBuffer, WlBufferError}, @@ -119,7 +119,7 @@ impl ZwpLinuxBufferParamsV1 { &p.fd, size, true, - Some(&self.parent.client), + Some(client_mem_client(&self.parent.client)), Some(&self.parent.client.state.cpu_worker), true, ) diff --git a/src/it.rs b/src/it.rs index 1ce659ee..c3b4d8e9 100644 --- a/src/it.rs +++ b/src/it.rs @@ -1,6 +1,8 @@ +use std::time::SystemTime; +use jay_async_engine::Phase; use { crate::{ - async_engine::Phase, + it::{ test_backend::TestBackend, test_config::{TestConfig, with_test_config}, @@ -17,7 +19,7 @@ use { parking_lot::Mutex, std::{ any::Any, cell::Cell, collections::VecDeque, future::pending, pin::Pin, rc::Rc, sync::Arc, - time::SystemTime, + }, uapi::c, }; @@ -126,7 +128,7 @@ fn run_test(it_run: &ItRun, test: &'static dyn TestCase, cfg: Rc) { let mut addr: c::sockaddr_un = uapi::pod_zeroed(); addr.sun_family = c::AF_UNIX as _; let acceptor = state.acceptor.get().unwrap(); - let path = acceptor.secure_path(); + let path = acceptor.socket_path(); let sun_path = uapi::as_bytes_mut(&mut addr.sun_path[..]); sun_path[..path.len()].copy_from_slice(path.as_bytes()); sun_path[path.len()] = 0; diff --git a/src/it/test_backend.rs b/src/it/test_backend.rs index 7c177b7b..93711111 100644 --- a/src/it/test_backend.rs +++ b/src/it/test_backend.rs @@ -1,12 +1,15 @@ +use jay_udmabuf::Udmabuf; +use jay_async_engine::SpawnedFuture; +use jay_units::fixed::Fixed; use { crate::{ allocator::{Allocator, AllocatorError}, - async_engine::SpawnedFuture, + backend::{ AxisSource, Backend, BackendConnectorState, BackendEvent, ButtonState, Connector, ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, InputDevice, InputDeviceAccelProfile, InputDeviceCapability, InputDeviceClickMethod, InputDeviceId, - InputEvent, KeyState, Mode, MonitorInfo, ScrollAxis, TransformMatrix, + InputEvent, KeyState, Mode, MonitorInfo, OutputId, ScrollAxis, TransformMatrix, transaction::{ BackendAppliedConnectorTransaction, BackendConnectorTransaction, BackendConnectorTransactionError, BackendConnectorTransactionType, @@ -16,16 +19,14 @@ use { cmm::cmm_primaries::Primaries, compositor::TestFuture, drm_feedback::DrmFeedback, - fixed::Fixed, format::XRGB8888, gfx_api::GfxError, gfx_apis::create_vulkan_allocator, - ifs::wl_output::OutputId, it::{ test_error::TestResult, test_gfx_api::TestGfxCtx, test_utils::test_expected_event::TEEH, }, state::State, - udmabuf::Udmabuf, + utils::{ clonecell::CloneCell, copyhashmap::CopyHashMap, diff --git a/src/it/test_client.rs b/src/it/test_client.rs index 5252bda4..88b2c218 100644 --- a/src/it/test_client.rs +++ b/src/it/test_client.rs @@ -1,3 +1,4 @@ +use jay_theme::Color; use { crate::{ cli::{ScreenshotFormat, screenshot::buf_to_bytes}, @@ -20,7 +21,7 @@ use { test_utils::{test_surface_ext::TestSurfaceExt, test_window::TestWindow}, testrun::TestRun, }, - theme::Color, + }, std::{cell::Cell, rc::Rc}, }; diff --git a/src/it/test_config.rs b/src/it/test_config.rs index 56ee5272..a81ba905 100644 --- a/src/it/test_config.rs +++ b/src/it/test_config.rs @@ -6,12 +6,11 @@ use { tree::OutputNode, utils::{copyhashmap::CopyHashMap, stack::Stack}, }, - bincode::Options, isnt::std_1::primitive::IsntConstPtrExt, jay_config::{ - _private::{ - ConfigEntry, VERSION, bincode_ops, - ipc::{ClientMessage, Response, ServerMessage}, + protocol::{ + ClientMessage, ConfigEntry, InitMessage, Response, ServerHandler, ServerMessage, Unref, + VERSION, }, Axis, Direction, input::{InputDevice, Seat}, @@ -53,12 +52,11 @@ where res } -unsafe extern "C" fn init( +unsafe fn init( srv_data: *const u8, - srv_unref: unsafe extern "C" fn(data: *const u8), - srv_handler: unsafe extern "C" fn(data: *const u8, msg: *const u8, size: usize), - _msg: *const u8, - _size: usize, + srv_unref: Unref, + srv_handler: ServerHandler, + _msg: InitMessage, ) -> *const u8 { let tc = CONFIG.get(); assert!(tc.is_not_null()); @@ -76,23 +74,15 @@ unsafe extern "C" fn init( } } -unsafe extern "C" fn unref(data: *const u8) { +unsafe fn unref(data: *const u8) { unsafe { Rc::decrement_strong_count(data.cast::()); } } -unsafe extern "C" fn handle_msg(data: *const u8, msg: *const u8, size: usize) { +unsafe fn handle_msg(data: *const u8, msg: &ServerMessage) { let tc = unsafe { &*data.cast::() }; - let msg = unsafe { std::slice::from_raw_parts(msg, size) }; - let res = bincode_ops().deserialize::(msg); - let msg = match res { - Ok(msg) => msg, - Err(e) => { - log::error!("could not deserialize message: {}", e); - return; - } - }; + let msg = msg.clone(); match msg { ServerMessage::Configure { .. } => {} ServerMessage::Response { response } => { @@ -138,8 +128,8 @@ unsafe extern "C" fn handle_msg(data: *const u8, msg: *const u8, size: usize) { #[derive(Copy, Clone)] struct ServerData { srv_data: *const u8, - srv_unref: unsafe extern "C" fn(data: *const u8), - srv_handler: unsafe extern "C" fn(data: *const u8, msg: *const u8, size: usize), + srv_unref: Unref, + srv_handler: ServerHandler, } pub struct TestConfig { @@ -170,10 +160,8 @@ impl TestConfig { Some(srv) => srv, _ => bail!("srv not set"), }; - let mut buf = vec![]; - bincode_ops().serialize_into(&mut buf, msg).unwrap(); unsafe { - (srv.srv_handler)(srv.srv_data, buf.as_ptr(), buf.len()); + (srv.srv_handler)(srv.srv_data, msg); } Ok(()) } @@ -284,6 +272,27 @@ impl TestConfig { }) } + pub fn send_to_scratchpad(&self, seat: SeatId, name: &str) -> TestResult { + self.send(ClientMessage::SeatSendToScratchpad { + seat: Seat(seat.raw() as _), + name, + }) + } + + pub fn toggle_scratchpad(&self, seat: SeatId, name: &str) -> TestResult { + self.send(ClientMessage::SeatToggleScratchpad { + seat: Seat(seat.raw() as _), + name, + }) + } + + pub fn cycle_scratchpad(&self, seat: SeatId, name: &str) -> TestResult { + self.send(ClientMessage::SeatCycleScratchpad { + seat: Seat(seat.raw() as _), + name, + }) + } + fn clear(&self) { unsafe { if let Some(srv) = self.srv.take() { @@ -331,6 +340,10 @@ impl TestConfig { pub fn set_show_titles(&self, show: bool) -> TestResult { self.send(ClientMessage::SetShowTitles { show }) } + + pub fn set_autotile(&self, enabled: bool) -> TestResult { + self.send(ClientMessage::SetAutotile { enabled }) + } } impl Drop for TestConfig { diff --git a/src/it/test_gfx_api.rs b/src/it/test_gfx_api.rs index 532eedd6..05cef23e 100644 --- a/src/it/test_gfx_api.rs +++ b/src/it/test_gfx_api.rs @@ -1,8 +1,11 @@ +use jay_theme::Color; +use jay_geometry::{Rect, Region}; +use jay_cpu_worker::CpuWorker; use { crate::{ allocator::{Allocator, AllocatorError, BufferObject, BufferUsage}, cmm::cmm_description::{ColorDescription, LinearColorDescription}, - cpu_worker::CpuWorker, + format::{ARGB8888, Format, XRGB8888}, gfx_api::{ AcquireSync, AsyncShmGfxTexture, AsyncShmGfxTextureCallback, CopyTexture, FdSync, @@ -11,8 +14,8 @@ use { GfxTexture, GfxWriteModifier, PendingShmTransfer, ReleaseSync, ResetStatus, ShmGfxTexture, ShmMemory, }, - rect::{Rect, Region}, - theme::Color, + + video::{LINEAR_MODIFIER, dmabuf::DmaBuf, drm::syncobj::SyncobjCtx}, }, ahash::AHashMap, diff --git a/src/it/test_ifs/test_pointer_warp.rs b/src/it/test_ifs/test_pointer_warp.rs index 35b9f3e9..e274577c 100644 --- a/src/it/test_ifs/test_pointer_warp.rs +++ b/src/it/test_ifs/test_pointer_warp.rs @@ -1,6 +1,6 @@ +use jay_units::fixed::Fixed; use { crate::{ - fixed::Fixed, it::{ test_error::TestResult, test_ifs::{test_pointer::TestPointer, test_surface::TestSurface}, diff --git a/src/it/test_ifs/test_region.rs b/src/it/test_ifs/test_region.rs index c2a0aec2..13ead662 100644 --- a/src/it/test_ifs/test_region.rs +++ b/src/it/test_ifs/test_region.rs @@ -1,8 +1,9 @@ +use jay_geometry::{Rect, RegionBuilder}; use { crate::{ ifs::wl_region::WlRegion, it::{test_error::TestError, test_object::TestObject, test_transport::TestTransport}, - rect::{Rect, RegionBuilder}, + wire::{WlRegionId, wl_region::*}, }, std::{ diff --git a/src/it/test_ifs/test_shm_buffer.rs b/src/it/test_ifs/test_shm_buffer.rs index 7a2c7fee..1eb1549a 100644 --- a/src/it/test_ifs/test_shm_buffer.rs +++ b/src/it/test_ifs/test_shm_buffer.rs @@ -1,7 +1,8 @@ +use jay_theme::Color; use { crate::{ it::{test_ifs::test_buffer::TestBuffer, test_mem::TestMem}, - theme::Color, + utils::windows::WindowsExt, }, std::{ diff --git a/src/it/test_ifs/test_single_pixel_buffer_manager.rs b/src/it/test_ifs/test_single_pixel_buffer_manager.rs index 33afb24d..783da3f1 100644 --- a/src/it/test_ifs/test_single_pixel_buffer_manager.rs +++ b/src/it/test_ifs/test_single_pixel_buffer_manager.rs @@ -1,3 +1,4 @@ +use jay_theme::Color; use { crate::{ cmm::cmm_eotf::Eotf, @@ -5,7 +6,7 @@ use { test_error::TestResult, test_ifs::test_buffer::TestBuffer, test_object::TestObject, test_transport::TestTransport, }, - theme::Color, + wire::{WpSinglePixelBufferManagerV1Id, wp_single_pixel_buffer_manager_v1::*}, }, std::{cell::Cell, rc::Rc}, diff --git a/src/it/test_ifs/test_viewport.rs b/src/it/test_ifs/test_viewport.rs index b25105c8..2555671d 100644 --- a/src/it/test_ifs/test_viewport.rs +++ b/src/it/test_ifs/test_viewport.rs @@ -1,6 +1,6 @@ +use jay_units::fixed::Fixed; use { crate::{ - fixed::Fixed, it::{test_error::TestError, test_object::TestObject, test_transport::TestTransport}, wire::{WpViewportId, wp_viewport::*}, }, @@ -29,6 +29,17 @@ impl TestViewport { Ok(()) } + pub fn unset_source(&self) -> Result<(), TestError> { + self.tran.send(SetSource { + self_id: self.id, + x: Fixed::from_int(-1), + y: Fixed::from_int(-1), + width: Fixed::from_int(-1), + height: Fixed::from_int(-1), + })?; + Ok(()) + } + pub fn set_destination(&self, width: i32, height: i32) -> Result<(), TestError> { self.tran.send(SetDestination { self_id: self.id, @@ -37,6 +48,15 @@ impl TestViewport { })?; Ok(()) } + + pub fn unset_destination(&self) -> Result<(), TestError> { + self.tran.send(SetDestination { + self_id: self.id, + width: -1, + height: -1, + })?; + Ok(()) + } } impl Drop for TestViewport { diff --git a/src/it/test_logger.rs b/src/it/test_logger.rs index af287bdb..709986f1 100644 --- a/src/it/test_logger.rs +++ b/src/it/test_logger.rs @@ -1,3 +1,4 @@ +use std::time::SystemTime; use { crate::utils::clonecell::CloneCell, log::{Level, LevelFilter, Log, Metadata, Record}, @@ -6,7 +7,7 @@ use { io::Write, rc::Rc, sync::atomic::{AtomicUsize, Ordering}, - time::SystemTime, + }, uapi::{Fd, OwnedFd}, }; diff --git a/src/it/test_transport.rs b/src/it/test_transport.rs index c83ffad0..7c716c05 100644 --- a/src/it/test_transport.rs +++ b/src/it/test_transport.rs @@ -1,6 +1,7 @@ +use jay_async_engine::SpawnedFuture; use { crate::{ - async_engine::SpawnedFuture, + client::{Client, ClientId, EventFormatter}, it::{ test_error::{StdError, TestError}, diff --git a/src/it/test_utils/test_surface_ext.rs b/src/it/test_utils/test_surface_ext.rs index 85b1d660..7a6e34cd 100644 --- a/src/it/test_utils/test_surface_ext.rs +++ b/src/it/test_utils/test_surface_ext.rs @@ -1,3 +1,4 @@ +use jay_theme::Color; use { crate::{ it::{ @@ -7,7 +8,7 @@ use { test_surface::TestSurface, test_viewport::TestViewport, }, }, - theme::Color, + }, std::{cell::Cell, ops::Deref, rc::Rc}, }; diff --git a/src/it/testrun.rs b/src/it/testrun.rs index 0045a7bf..c6a8da83 100644 --- a/src/it/testrun.rs +++ b/src/it/testrun.rs @@ -1,7 +1,7 @@ +use jay_units::fixed::Fixed; use { crate::{ client::{ClientId, RequestParser}, - fixed::Fixed, ifs::wl_seat::WlSeatGlobal, it::{ test_backend::{TestBackend, TestBackendKb, TestBackendMouse, TestConnector}, diff --git a/src/it/tests.rs b/src/it/tests.rs index dc28888c..35b6be97 100644 --- a/src/it/tests.rs +++ b/src/it/tests.rs @@ -85,6 +85,8 @@ mod t0051_pointer_warp; mod t0052_bar; mod t0053_theme; mod t0054_subsurface_already_attached; +mod t0055_autotiling; +mod t0055_scratchpad; pub trait TestCase: Sync { fn name(&self) -> &'static str; @@ -158,5 +160,7 @@ pub fn tests() -> Vec<&'static dyn TestCase> { t0052_bar, t0053_theme, t0054_subsurface_already_attached, + t0055_autotiling, + t0055_scratchpad, } } diff --git a/src/it/tests/t0002_window.rs b/src/it/tests/t0002_window.rs index 84571c57..28ee359f 100644 --- a/src/it/tests/t0002_window.rs +++ b/src/it/tests/t0002_window.rs @@ -1,7 +1,6 @@ use { crate::{ it::{test_error::TestError, testrun::TestRun}, - rect::Rect, tree::Node, }, std::rc::Rc, @@ -11,29 +10,19 @@ testcase!(); /// Create and map a single surface async fn test(run: Rc) -> Result<(), TestError> { - run.backend.install_default()?; + let ds = run.create_default_setup().await?; let client = run.create_client().await?; let window = client.create_window().await?; window.map().await?; - tassert_eq!(window.tl.core.width.get(), 800); - tassert_eq!( - window.tl.core.height.get(), - 600 - 2 * run.state.theme.title_plus_underline_height() - ); + let workspace_rect = ds.output.workspace_rect.get(); - tassert_eq!( - window.tl.server.node_absolute_position(), - Rect::new_sized( - 0, - 2 * run.state.theme.title_plus_underline_height(), - window.tl.core.width.get(), - window.tl.core.height.get(), - ) - .unwrap() - ); + tassert_eq!(window.tl.core.width.get(), workspace_rect.width()); + tassert_eq!(window.tl.core.height.get(), workspace_rect.height()); + + tassert_eq!(window.tl.server.node_absolute_position(), workspace_rect); Ok(()) } diff --git a/src/it/tests/t0003_multi_window.rs b/src/it/tests/t0003_multi_window.rs index 3fbf599c..a4bf7b3c 100644 --- a/src/it/tests/t0003_multi_window.rs +++ b/src/it/tests/t0003_multi_window.rs @@ -1,7 +1,8 @@ +use jay_geometry::Rect; use { crate::{ it::{test_error::TestError, testrun::TestRun}, - rect::Rect, + tree::Node, }, std::rc::Rc, @@ -11,7 +12,7 @@ testcase!(); /// Create and map two surfaces async fn test(run: Rc) -> Result<(), TestError> { - run.backend.install_default()?; + let ds = run.create_default_setup().await?; let client = run.create_client().await?; @@ -21,17 +22,30 @@ async fn test(run: Rc) -> Result<(), TestError> { let window2 = client.create_window().await?; window2.map().await?; - let otop = 2 * run.state.theme.title_plus_underline_height(); + let workspace_rect = ds.output.workspace_rect.get(); let bw = run.state.theme.sizes.border_width.get(); + let child_width = (workspace_rect.width() - bw) / 2; tassert_eq!( window.tl.server.node_absolute_position(), - Rect::new_sized(0, otop, (800 - bw) / 2, 600 - otop).unwrap() + Rect::new_sized( + workspace_rect.x1(), + workspace_rect.y1(), + child_width, + workspace_rect.height(), + ) + .unwrap() ); tassert_eq!( window2.tl.server.node_absolute_position(), - Rect::new_sized((800 - bw) / 2 + bw, otop, (800 - bw) / 2, 600 - otop).unwrap() + Rect::new_sized( + workspace_rect.x1() + child_width + bw, + workspace_rect.y1(), + child_width, + workspace_rect.height(), + ) + .unwrap() ); Ok(()) diff --git a/src/it/tests/t0006_region.rs b/src/it/tests/t0006_region.rs index 9aaa57ea..12d77b03 100644 --- a/src/it/tests/t0006_region.rs +++ b/src/it/tests/t0006_region.rs @@ -1,7 +1,8 @@ +use jay_geometry::Rect; use { crate::{ it::{test_error::TestError, testrun::TestRun}, - rect::Rect, + }, std::rc::Rc, }; diff --git a/src/it/tests/t0007_subsurface.rs b/src/it/tests/t0007_subsurface.rs index ea75622f..28ecdd9e 100644 --- a/src/it/tests/t0007_subsurface.rs +++ b/src/it/tests/t0007_subsurface.rs @@ -1,7 +1,8 @@ +use jay_theme::Color; use { crate::{ it::{test_error::TestError, testrun::TestRun}, - theme::Color, + }, std::rc::Rc, }; diff --git a/src/it/tests/t0007_subsurface/screenshot_1.qoi b/src/it/tests/t0007_subsurface/screenshot_1.qoi index 230c0408..b5954651 100644 Binary files a/src/it/tests/t0007_subsurface/screenshot_1.qoi and b/src/it/tests/t0007_subsurface/screenshot_1.qoi differ diff --git a/src/it/tests/t0007_subsurface/screenshot_2.qoi b/src/it/tests/t0007_subsurface/screenshot_2.qoi index 722271f6..718d5c29 100644 Binary files a/src/it/tests/t0007_subsurface/screenshot_2.qoi and b/src/it/tests/t0007_subsurface/screenshot_2.qoi differ diff --git a/src/it/tests/t0012_subsurface_focus.rs b/src/it/tests/t0012_subsurface_focus.rs index 7fc2ccab..df2865ef 100644 --- a/src/it/tests/t0012_subsurface_focus.rs +++ b/src/it/tests/t0012_subsurface_focus.rs @@ -1,3 +1,4 @@ +use jay_theme::Color; use { crate::{ ifs::wl_seat::BTN_LEFT, @@ -5,7 +6,7 @@ use { test_error::{TestErrorExt, TestResult}, testrun::TestRun, }, - theme::Color, + }, std::rc::Rc, }; diff --git a/src/it/tests/t0014_container_scroll_focus.rs b/src/it/tests/t0014_container_scroll_focus.rs index 0186cbaf..dccd1096 100644 --- a/src/it/tests/t0014_container_scroll_focus.rs +++ b/src/it/tests/t0014_container_scroll_focus.rs @@ -48,13 +48,18 @@ async fn test(run: Rc) -> TestResult { let mono_container = w_mono2.tl.container_parent()?; let container_pos = mono_container.tl_data().pos.get(); - let w_mono1_title = mono_container.render_data.borrow_mut().title_rects[0] - .move_(container_pos.x1(), container_pos.y1()); - ds.mouse.abs( - &ds.connector, - w_mono1_title.x1() as _, - w_mono1_title.y1() as _, - ); + let (tab_x, tab_y) = { + let tab_bar = mono_container.tab_bar.borrow(); + let Some(tab_bar) = tab_bar.as_ref() else { + bail!("no tab bar"); + }; + let w_mono1_title = &tab_bar.entries[0]; + ( + container_pos.x1() + w_mono1_title.x.get() + w_mono1_title.width.get() / 2, + container_pos.y1() + tab_bar.height / 2, + ) + }; + ds.mouse.abs(&ds.connector, tab_x as _, tab_y as _); client.sync().await; tassert!(enters.next().is_err()); diff --git a/src/it/tests/t0015_scroll_partial.rs b/src/it/tests/t0015_scroll_partial.rs index c6cf49b7..f5cb6e3c 100644 --- a/src/it/tests/t0015_scroll_partial.rs +++ b/src/it/tests/t0015_scroll_partial.rs @@ -26,12 +26,18 @@ async fn test(run: Rc) -> TestResult { let container = w_mono2.tl.container_parent()?; let pos = container.tl_data().pos.get(); - let w_mono1_title = container.render_data.borrow_mut().title_rects[0].move_(pos.x1(), pos.y1()); - ds.mouse.abs( - &ds.connector, - w_mono1_title.x1() as f64, - w_mono1_title.y1() as f64, - ); + let (tab_x, tab_y) = { + let tab_bar = container.tab_bar.borrow(); + let Some(tab_bar) = tab_bar.as_ref() else { + bail!("no tab bar"); + }; + let w_mono1_title = &tab_bar.entries[0]; + ( + pos.x1() + w_mono1_title.x.get() + w_mono1_title.width.get() / 2, + pos.y1() + tab_bar.height / 2, + ) + }; + ds.mouse.abs(&ds.connector, tab_x as f64, tab_y as f64); client.sync().await; let enters = dss.kb.enter.expect()?; diff --git a/src/it/tests/t0020_surface_offset.rs b/src/it/tests/t0020_surface_offset.rs index b7cc9d4a..6d86a19c 100644 --- a/src/it/tests/t0020_surface_offset.rs +++ b/src/it/tests/t0020_surface_offset.rs @@ -1,3 +1,4 @@ +use jay_theme::Color; use { crate::{ it::{ @@ -9,7 +10,7 @@ use { }, testrun::TestRun, }, - theme::Color, + }, std::rc::Rc, }; diff --git a/src/it/tests/t0020_surface_offset/screenshot_1.qoi b/src/it/tests/t0020_surface_offset/screenshot_1.qoi index eef5f37a..4c826f86 100644 Binary files a/src/it/tests/t0020_surface_offset/screenshot_1.qoi and b/src/it/tests/t0020_surface_offset/screenshot_1.qoi differ diff --git a/src/it/tests/t0020_surface_offset/screenshot_2.qoi b/src/it/tests/t0020_surface_offset/screenshot_2.qoi index 7e8cf143..0fb763e2 100644 Binary files a/src/it/tests/t0020_surface_offset/screenshot_2.qoi and b/src/it/tests/t0020_surface_offset/screenshot_2.qoi differ diff --git a/src/it/tests/t0022_toplevel_suspended.rs b/src/it/tests/t0022_toplevel_suspended.rs index 1fdacb1a..524856e3 100644 --- a/src/it/tests/t0022_toplevel_suspended.rs +++ b/src/it/tests/t0022_toplevel_suspended.rs @@ -2,7 +2,7 @@ use { crate::{ ifs::wl_surface::xdg_surface::xdg_toplevel::STATE_SUSPENDED, it::{ - test_error::TestResult, + test_error::{TestErrorExt, TestResult}, test_utils::{ test_ouput_node_ext::TestOutputNodeExt, test_toplevel_node_ext::TestToplevelNodeExt, }, @@ -10,7 +10,7 @@ use { }, }, isnt::std_1::collections::IsntHashSetExt, - std::rc::Rc, + std::{rc::Rc, time::Duration}, }; testcase!(); @@ -19,6 +19,7 @@ async fn test(run: Rc) -> TestResult { let ds = run.create_default_setup().await?; let client = run.create_client().await?; + let default_seat = client.get_default_seat().await?; let win1 = client.create_window().await?; win1.set_color(255, 0, 0, 255); @@ -44,5 +45,23 @@ async fn test(run: Rc) -> TestResult { client.sync().await; tassert!(win2.tl.core.states.borrow().not_contains(&STATE_SUSPENDED)); + let leaves = default_seat.kb.leave.expect()?; + let enters = default_seat.kb.enter.expect()?; + + run.cfg.set_idle(Duration::from_micros(100))?; + run.cfg.set_idle_grace_period(Duration::from_secs(0))?; + run.state.wheel.timeout(3).await?; + + client.sync().await; + tassert!(win2.tl.core.states.borrow().contains(&STATE_SUSPENDED)); + let leave = leaves.next().with_context(|| "no leave on suspend")?; + tassert_eq!(leave.surface, win2.surface.id); + + ds.mouse.rel(1.0, 1.0); + client.sync().await; + tassert!(win2.tl.core.states.borrow().not_contains(&STATE_SUSPENDED)); + let enter = enters.next().with_context(|| "no enter on restore")?; + tassert_eq!(enter.surface, win2.surface.id); + Ok(()) } diff --git a/src/it/tests/t0023_xdg_activation/screenshot_1.qoi b/src/it/tests/t0023_xdg_activation/screenshot_1.qoi index 1fa8d204..960da20a 100644 Binary files a/src/it/tests/t0023_xdg_activation/screenshot_1.qoi and b/src/it/tests/t0023_xdg_activation/screenshot_1.qoi differ diff --git a/src/it/tests/t0026_output_transform/screenshot_1.qoi b/src/it/tests/t0026_output_transform/screenshot_1.qoi index 2206fc85..f11111bb 100644 Binary files a/src/it/tests/t0026_output_transform/screenshot_1.qoi and b/src/it/tests/t0026_output_transform/screenshot_1.qoi differ diff --git a/src/it/tests/t0028_top_level_restacking/screenshot_1.qoi b/src/it/tests/t0028_top_level_restacking/screenshot_1.qoi index f7bf53bf..9f5fca3c 100644 Binary files a/src/it/tests/t0028_top_level_restacking/screenshot_1.qoi and b/src/it/tests/t0028_top_level_restacking/screenshot_1.qoi differ diff --git a/src/it/tests/t0028_top_level_restacking/screenshot_2.qoi b/src/it/tests/t0028_top_level_restacking/screenshot_2.qoi index b454acd3..aaf1b108 100644 Binary files a/src/it/tests/t0028_top_level_restacking/screenshot_2.qoi and b/src/it/tests/t0028_top_level_restacking/screenshot_2.qoi differ diff --git a/src/it/tests/t0029_double_click_float/screenshot_1.qoi b/src/it/tests/t0029_double_click_float/screenshot_1.qoi index dd974ccf..e08dc525 100644 Binary files a/src/it/tests/t0029_double_click_float/screenshot_1.qoi and b/src/it/tests/t0029_double_click_float/screenshot_1.qoi differ diff --git a/src/it/tests/t0029_double_click_float/screenshot_2.qoi b/src/it/tests/t0029_double_click_float/screenshot_2.qoi index f49edd4d..e08dc525 100644 Binary files a/src/it/tests/t0029_double_click_float/screenshot_2.qoi and b/src/it/tests/t0029_double_click_float/screenshot_2.qoi differ diff --git a/src/it/tests/t0031_syncobj.rs b/src/it/tests/t0031_syncobj.rs index a4bac2a9..f716d478 100644 --- a/src/it/tests/t0031_syncobj.rs +++ b/src/it/tests/t0031_syncobj.rs @@ -1,7 +1,8 @@ +use jay_theme::Color; use { crate::{ it::{test_error::TestResult, testrun::TestRun}, - theme::Color, + utils::errorfmt::ErrorFmt, video::drm::{DrmError, syncobj::SyncobjPoint, wait_for_syncobj::SyncobjWaiter}, }, diff --git a/src/it/tests/t0033_float_size_memoization.rs b/src/it/tests/t0033_float_size_memoization.rs index 0c8c0b89..96056775 100644 --- a/src/it/tests/t0033_float_size_memoization.rs +++ b/src/it/tests/t0033_float_size_memoization.rs @@ -1,7 +1,8 @@ +use jay_geometry::Rect; use { crate::{ it::{test_error::TestResult, testrun::TestRun}, - rect::Rect, + }, std::rc::Rc, }; diff --git a/src/it/tests/t0034_workspace_restoration.rs b/src/it/tests/t0034_workspace_restoration.rs index 4b01cac9..91cdc750 100644 --- a/src/it/tests/t0034_workspace_restoration.rs +++ b/src/it/tests/t0034_workspace_restoration.rs @@ -2,10 +2,10 @@ use { crate::{ backend::{ BackendConnectorState, BackendEvent, ConnectorEvent, ConnectorKernelId, MonitorInfo, + OutputId, }, cmm::cmm_primaries::Primaries, format::XRGB8888, - ifs::wl_output::OutputId, it::{test_backend::TestConnector, test_error::TestResult, testrun::TestRun}, utils::numcell::NumCell, video::drm::ConnectorType, diff --git a/src/it/tests/t0037_toplevel_drag/screenshot_2.qoi b/src/it/tests/t0037_toplevel_drag/screenshot_2.qoi index b9826001..36c68e4e 100644 Binary files a/src/it/tests/t0037_toplevel_drag/screenshot_2.qoi and b/src/it/tests/t0037_toplevel_drag/screenshot_2.qoi differ diff --git a/src/it/tests/t0038_subsurface_parent_state.rs b/src/it/tests/t0038_subsurface_parent_state.rs index b43910eb..bff8dc89 100644 --- a/src/it/tests/t0038_subsurface_parent_state.rs +++ b/src/it/tests/t0038_subsurface_parent_state.rs @@ -1,7 +1,8 @@ +use jay_theme::Color; use { crate::{ it::{test_error::TestResult, testrun::TestRun}, - theme::Color, + }, std::rc::Rc, }; diff --git a/src/it/tests/t0038_subsurface_parent_state/screenshot_1.qoi b/src/it/tests/t0038_subsurface_parent_state/screenshot_1.qoi index 988bc767..e6f6db74 100644 Binary files a/src/it/tests/t0038_subsurface_parent_state/screenshot_1.qoi and b/src/it/tests/t0038_subsurface_parent_state/screenshot_1.qoi differ diff --git a/src/it/tests/t0038_subsurface_parent_state/screenshot_2.qoi b/src/it/tests/t0038_subsurface_parent_state/screenshot_2.qoi index a7509404..9abc8de3 100644 Binary files a/src/it/tests/t0038_subsurface_parent_state/screenshot_2.qoi and b/src/it/tests/t0038_subsurface_parent_state/screenshot_2.qoi differ diff --git a/src/it/tests/t0039_alpha_modifier.rs b/src/it/tests/t0039_alpha_modifier.rs index 0a90bc60..6f8e3a02 100644 --- a/src/it/tests/t0039_alpha_modifier.rs +++ b/src/it/tests/t0039_alpha_modifier.rs @@ -1,7 +1,8 @@ +use jay_theme::Color; use { crate::{ it::{test_error::TestResult, testrun::TestRun}, - theme::Color, + }, std::rc::Rc, }; diff --git a/src/it/tests/t0039_alpha_modifier/screenshot_1.qoi b/src/it/tests/t0039_alpha_modifier/screenshot_1.qoi index 8fe5d0b2..80a29c84 100644 Binary files a/src/it/tests/t0039_alpha_modifier/screenshot_1.qoi and b/src/it/tests/t0039_alpha_modifier/screenshot_1.qoi differ diff --git a/src/it/tests/t0039_alpha_modifier/screenshot_2.qoi b/src/it/tests/t0039_alpha_modifier/screenshot_2.qoi index 9874e2f5..735af290 100644 Binary files a/src/it/tests/t0039_alpha_modifier/screenshot_2.qoi and b/src/it/tests/t0039_alpha_modifier/screenshot_2.qoi differ diff --git a/src/it/tests/t0040_virtual_keyboard.rs b/src/it/tests/t0040_virtual_keyboard.rs index bcb0db4b..4fb67262 100644 --- a/src/it/tests/t0040_virtual_keyboard.rs +++ b/src/it/tests/t0040_virtual_keyboard.rs @@ -1,9 +1,9 @@ +use jay_keyboard::KbvmContext; use { crate::{ backend::KeyState, clientmem::ClientMem, it::{test_error::TestResult, testrun::TestRun}, - kbvm::KbvmContext, }, bstr::ByteSlice, std::rc::Rc, diff --git a/src/it/tests/t0041_input_method/screenshot_1.qoi b/src/it/tests/t0041_input_method/screenshot_1.qoi index d25fcf64..cd07ecd4 100644 Binary files a/src/it/tests/t0041_input_method/screenshot_1.qoi and b/src/it/tests/t0041_input_method/screenshot_1.qoi differ diff --git a/src/it/tests/t0041_input_method/screenshot_2.qoi b/src/it/tests/t0041_input_method/screenshot_2.qoi index 7f93231a..d76ea9a0 100644 Binary files a/src/it/tests/t0041_input_method/screenshot_2.qoi and b/src/it/tests/t0041_input_method/screenshot_2.qoi differ diff --git a/src/it/tests/t0041_input_method/screenshot_3.qoi b/src/it/tests/t0041_input_method/screenshot_3.qoi index d25fcf64..cd07ecd4 100644 Binary files a/src/it/tests/t0041_input_method/screenshot_3.qoi and b/src/it/tests/t0041_input_method/screenshot_3.qoi differ diff --git a/src/it/tests/t0042_toplevel_select/screenshot_1.qoi b/src/it/tests/t0042_toplevel_select/screenshot_1.qoi index 6423ef6d..6d57d140 100644 Binary files a/src/it/tests/t0042_toplevel_select/screenshot_1.qoi and b/src/it/tests/t0042_toplevel_select/screenshot_1.qoi differ diff --git a/src/it/tests/t0042_toplevel_select/screenshot_2.qoi b/src/it/tests/t0042_toplevel_select/screenshot_2.qoi index 823fd750..478b3c43 100644 Binary files a/src/it/tests/t0042_toplevel_select/screenshot_2.qoi and b/src/it/tests/t0042_toplevel_select/screenshot_2.qoi differ diff --git a/src/it/tests/t0042_toplevel_select/screenshot_3.qoi b/src/it/tests/t0042_toplevel_select/screenshot_3.qoi index 823fd750..478b3c43 100644 Binary files a/src/it/tests/t0042_toplevel_select/screenshot_3.qoi and b/src/it/tests/t0042_toplevel_select/screenshot_3.qoi differ diff --git a/src/it/tests/t0042_toplevel_select/screenshot_4.qoi b/src/it/tests/t0042_toplevel_select/screenshot_4.qoi index 714222f1..07dd87fb 100644 Binary files a/src/it/tests/t0042_toplevel_select/screenshot_4.qoi and b/src/it/tests/t0042_toplevel_select/screenshot_4.qoi differ diff --git a/src/it/tests/t0046_buffer_release.rs b/src/it/tests/t0046_buffer_release.rs index 908bc0d6..344df52a 100644 --- a/src/it/tests/t0046_buffer_release.rs +++ b/src/it/tests/t0046_buffer_release.rs @@ -1,7 +1,8 @@ +use jay_theme::Color; use { crate::{ it::{test_error::TestResult, testrun::TestRun}, - theme::Color, + wire::WlBufferId, }, std::rc::Rc, diff --git a/src/it/tests/t0047_surface_damage.rs b/src/it/tests/t0047_surface_damage.rs index d9760bc8..f577f907 100644 --- a/src/it/tests/t0047_surface_damage.rs +++ b/src/it/tests/t0047_surface_damage.rs @@ -1,7 +1,8 @@ +use jay_geometry::Rect; use { crate::{ it::{test_error::TestResult, testrun::TestRun}, - rect::Rect, + }, std::rc::Rc, }; @@ -23,7 +24,7 @@ async fn test(run: Rc) -> TestResult { let surface = client.comp.create_surface().await?; let buffer = client .spbm - .create_buffer(crate::theme::Color::from_srgb(255, 0, 0))?; + .create_buffer(jay_theme::Color::from_srgb(255, 0, 0))?; surface.attach(buffer.id)?; surface.commit()?; // Initial commit to attach buffer client.sync().await; @@ -308,9 +309,8 @@ async fn test(run: Rc) -> TestResult { let output_damage = connector_data.damage.borrow(); tassert!(!output_damage.is_empty()); - // Buffer damage is transformed by the damage matrix which includes the surface position - // The buffer damage (0,0,1,1) should be transformed to surface coordinates - let expected_buffer_damage = buffer_damage.move_(surface_pos.x1(), surface_pos.y1()); + // The test window maps its 1x1 buffer through a viewport to the full window size. + let expected_buffer_damage = surface_pos; // Find the exact output damage that matches our expected buffer damage let mut found_exact_buffer_damage = false; @@ -331,10 +331,12 @@ async fn test(run: Rc) -> TestResult { // Test 7: Check output damage from existing window's viewport (which already has scaling) connector_data.damage.borrow_mut().clear(); - // The existing window was created with create_surface_ext() which automatically creates a viewport - // Let's verify that the viewport's existing scaling affects buffer damage correctly - // First, let's modify the viewport scaling that already exists on the window - window.surface.viewport.set_destination(150, 100)?; // Change scaling to 150x100 + // The existing window was created with create_surface_ext() which automatically creates a viewport. + // Commit the viewport size change separately; that commit intentionally damages the old/new extents. + window.surface.viewport.set_destination(150, 100)?; + window.surface.commit()?; + client.sync().await; + connector_data.damage.borrow_mut().clear(); // Add buffer damage to test viewport scaling coordinate transformation window.surface.damage_buffer(0, 0, 1, 1)?; // Damage entire 1x1 buffer @@ -346,8 +348,8 @@ async fn test(run: Rc) -> TestResult { let output_damage = connector_data.damage.borrow(); tassert!(!output_damage.is_empty()); - // With viewporter scaling, the 1x1 buffer damage should scale to 150x100 - // and be moved by surface position (0, 36) to get output coordinates (0, 36, 150, 136) + // With viewporter scaling, the 1x1 buffer damage should scale to the viewport destination. + let surface_pos = window.surface.server.buffer_abs_pos.get(); let expected_scaled_damage = Rect::new_sized(0, 0, 150, 100).unwrap(); let expected_output_damage = expected_scaled_damage.move_(surface_pos.x1(), surface_pos.y1()); @@ -402,8 +404,9 @@ async fn test(run: Rc) -> TestResult { rotation_window.map().await?; client.sync().await; - // Disable viewporter by setting destination to 0x0 to rely purely on buffer dimensions - rotation_window.surface.viewport.set_destination(0, 0)?; // Disable viewporter + // Disable viewporter to rely purely on buffer dimensions. + rotation_window.surface.viewport.unset_source()?; + rotation_window.surface.viewport.unset_destination()?; // Use a rectangular buffer (4x2) so rotation has a visible geometric effect // Attach AFTER mapping to avoid being overwritten by map()'s single-pixel buffer diff --git a/src/it/tests/t0048_frame_callback.rs b/src/it/tests/t0048_frame_callback.rs index fd11ce90..081cd69f 100644 --- a/src/it/tests/t0048_frame_callback.rs +++ b/src/it/tests/t0048_frame_callback.rs @@ -58,7 +58,7 @@ async fn test(run: Rc) -> TestResult { let invisible_surface = client.comp.create_surface().await?; let buffer = client .spbm - .create_buffer(crate::theme::Color::from_srgb(255, 0, 0))?; + .create_buffer(jay_theme::Color::from_srgb(255, 0, 0))?; invisible_surface.attach(buffer.id)?; let callback_invisible = invisible_surface.frame()?; diff --git a/src/it/tests/t0049_surface_damage_backend.rs b/src/it/tests/t0049_surface_damage_backend.rs index d25d3a13..6bc33b3a 100644 --- a/src/it/tests/t0049_surface_damage_backend.rs +++ b/src/it/tests/t0049_surface_damage_backend.rs @@ -21,7 +21,7 @@ async fn test(run: Rc) -> TestResult { let window = client.create_window().await?; let buffer = client .spbm - .create_buffer(crate::theme::Color::from_srgb(0, 255, 0))?; + .create_buffer(jay_theme::Color::from_srgb(0, 255, 0))?; window.surface.attach(buffer.id)?; window.map().await?; client.sync().await; @@ -59,7 +59,7 @@ async fn test(run: Rc) -> TestResult { let invisible_surface = client.comp.create_surface().await?; let invisible_buffer = client .spbm - .create_buffer(crate::theme::Color::from_srgb(255, 255, 0))?; + .create_buffer(jay_theme::Color::from_srgb(255, 255, 0))?; invisible_surface.attach(invisible_buffer.id)?; invisible_surface.commit()?; // Initial commit to attach buffer client.sync().await; diff --git a/src/it/tests/t0050_fifo.rs b/src/it/tests/t0050_fifo.rs index dd011138..183d9777 100644 --- a/src/it/tests/t0050_fifo.rs +++ b/src/it/tests/t0050_fifo.rs @@ -1,10 +1,11 @@ +use jay_theme::Color; use { crate::{ it::{ test_error::{TestError, TestResult}, testrun::TestRun, }, - theme::Color, + }, std::rc::Rc, }; diff --git a/src/it/tests/t0051_pointer_warp.rs b/src/it/tests/t0051_pointer_warp.rs index 2741c7df..36adb7ed 100644 --- a/src/it/tests/t0051_pointer_warp.rs +++ b/src/it/tests/t0051_pointer_warp.rs @@ -1,6 +1,6 @@ +use jay_units::fixed::Fixed; use { crate::{ - fixed::Fixed, it::{test_error::TestResult, testrun::TestRun}, tree::Node, }, diff --git a/src/it/tests/t0054_subsurface_already_attached.rs b/src/it/tests/t0054_subsurface_already_attached.rs index a60cde94..ad58c75f 100644 --- a/src/it/tests/t0054_subsurface_already_attached.rs +++ b/src/it/tests/t0054_subsurface_already_attached.rs @@ -1,7 +1,8 @@ +use jay_theme::Color; use { crate::{ it::{test_error::TestError, testrun::TestRun}, - theme::Color, + tree::Node, }, std::rc::Rc, diff --git a/src/it/tests/t0055_autotiling.rs b/src/it/tests/t0055_autotiling.rs new file mode 100644 index 00000000..4b3611c4 --- /dev/null +++ b/src/it/tests/t0055_autotiling.rs @@ -0,0 +1,58 @@ +use { + crate::{ + it::{test_error::TestResult, testrun::TestRun}, + tree::{ContainerSplit, Node, ToplevelNodeBase}, + }, + std::rc::Rc, +}; + +testcase!(); + +async fn test(run: Rc) -> TestResult { + run.backend.install_default()?; + run.cfg.set_autotile(true)?; + + let client = run.create_client().await?; + + let win1 = client.create_window().await?; + win1.map().await?; + let root = win1.tl.container_parent()?; + tassert_eq!(root.split.get(), ContainerSplit::Horizontal); + + let win2 = client.create_window().await?; + win2.map().await?; + client.sync().await; + + tassert_eq!(root.split.get(), ContainerSplit::Horizontal); + tassert_eq!(win1.tl.container_parent()?.node_id(), root.node_id()); + tassert_eq!(win2.tl.container_parent()?.node_id(), root.node_id()); + + let win3 = client.create_window().await?; + win3.map().await?; + client.sync().await; + + let v_group = win3.tl.container_parent()?; + tassert_eq!(root.split.get(), ContainerSplit::Horizontal); + tassert_eq!(v_group.split.get(), ContainerSplit::Vertical); + tassert_eq!(win2.tl.container_parent()?.node_id(), v_group.node_id()); + + let win4 = client.create_window().await?; + win4.map().await?; + client.sync().await; + + let h_group = win4.tl.container_parent()?; + tassert_eq!(h_group.split.get(), ContainerSplit::Horizontal); + tassert_eq!(win3.tl.container_parent()?.node_id(), h_group.node_id()); + let h_parent = match h_group + .tl_data() + .parent + .get() + .and_then(|p| p.node_into_container()) + { + Some(parent) => parent, + None => bail!("autotile group does not have a container parent"), + }; + tassert_eq!(h_parent.node_id(), v_group.node_id()); + + Ok(()) +} diff --git a/src/it/tests/t0055_scratchpad.rs b/src/it/tests/t0055_scratchpad.rs new file mode 100644 index 00000000..5abf2440 --- /dev/null +++ b/src/it/tests/t0055_scratchpad.rs @@ -0,0 +1,107 @@ +use { + crate::{ + it::{test_error::TestResult, testrun::TestRun}, + tree::{Node, ToplevelNodeBase}, + }, + std::rc::Rc, +}; + +testcase!(); + +async fn test(run: Rc) -> TestResult { + let ds = run.create_default_setup().await?; + + let client = run.create_client().await?; + let win1 = client.create_window().await?; + win1.map2().await?; + let win2 = client.create_window().await?; + win2.map2().await?; + + run.cfg.send_to_scratchpad(ds.seat.id(), "term")?; + client.sync().await; + tassert!(win1.tl.server.node_visible()); + tassert!(!win2.tl.server.node_visible()); + + run.cfg.show_workspace(ds.seat.id(), "2")?; + run.cfg.toggle_scratchpad(ds.seat.id(), "term")?; + client.sync().await; + tassert!(win2.tl.server.node_visible()); + tassert_eq!(ds.output.workspace.get().unwrap().name.as_str(), "2"); + + run.cfg.toggle_scratchpad(ds.seat.id(), "term")?; + client.sync().await; + tassert!(!win2.tl.server.node_visible()); + + run.cfg.toggle_scratchpad(ds.seat.id(), "term")?; + client.sync().await; + tassert!(win2.tl.server.node_visible()); + tassert_eq!(ds.output.workspace.get().unwrap().name.as_str(), "2"); + + run.cfg.show_workspace(ds.seat.id(), "3")?; + client.sync().await; + tassert!(!win2.tl.server.node_visible()); + + run.cfg.toggle_scratchpad(ds.seat.id(), "term")?; + client.sync().await; + tassert!(win2.tl.server.node_visible()); + tassert_eq!(ds.output.workspace.get().unwrap().name.as_str(), "3"); + // Scratchpad windows are always shown floating. + tassert!(win2.tl.server.tl_data().parent_is_float.get()); + + // Park win2 again, then build a multi-window scratchpad and cycle it. + run.cfg.toggle_scratchpad(ds.seat.id(), "term")?; + client.sync().await; + tassert!(!win2.tl.server.node_visible()); + + // Build a three-window scratchpad. Each window is focused right after it is + // mapped, so sending the focused window parks them in a known order. + let cyc1 = client.create_window().await?; + cyc1.map2().await?; + run.cfg.send_to_scratchpad(ds.seat.id(), "cyc")?; + let cyc2 = client.create_window().await?; + cyc2.map2().await?; + run.cfg.send_to_scratchpad(ds.seat.id(), "cyc")?; + let cyc3 = client.create_window().await?; + cyc3.map2().await?; + run.cfg.send_to_scratchpad(ds.seat.id(), "cyc")?; + client.sync().await; + tassert!(!cyc1.tl.server.node_visible()); + tassert!(!cyc2.tl.server.node_visible()); + tassert!(!cyc3.tl.server.node_visible()); + + // Nothing shown: cycle brings up the first window (insertion order: cyc1). + run.cfg.cycle_scratchpad(ds.seat.id(), "cyc")?; + client.sync().await; + tassert!(cyc1.tl.server.node_visible()); + tassert!(!cyc2.tl.server.node_visible()); + tassert!(!cyc3.tl.server.node_visible()); + // Scratchpad windows are always shown floating. + tassert!(cyc1.tl.server.tl_data().parent_is_float.get()); + + // Cycle advances one at a time. + run.cfg.cycle_scratchpad(ds.seat.id(), "cyc")?; + client.sync().await; + tassert!(!cyc1.tl.server.node_visible()); + tassert!(cyc2.tl.server.node_visible()); + tassert!(!cyc3.tl.server.node_visible()); + + run.cfg.cycle_scratchpad(ds.seat.id(), "cyc")?; + client.sync().await; + tassert!(!cyc1.tl.server.node_visible()); + tassert!(!cyc2.tl.server.node_visible()); + tassert!(cyc3.tl.server.node_visible()); + + // On the final window, the next cycle hides everything. + run.cfg.cycle_scratchpad(ds.seat.id(), "cyc")?; + client.sync().await; + tassert!(!cyc1.tl.server.node_visible()); + tassert!(!cyc2.tl.server.node_visible()); + tassert!(!cyc3.tl.server.node_visible()); + + // And it wraps back to the first window. + run.cfg.cycle_scratchpad(ds.seat.id(), "cyc")?; + client.sync().await; + tassert!(cyc1.tl.server.node_visible()); + + Ok(()) +} diff --git a/src/kbvm.rs b/src/kbvm.rs index 5d8b2921..e5e4cfb1 100644 --- a/src/kbvm.rs +++ b/src/kbvm.rs @@ -1,77 +1,21 @@ +use jay_keyboard::KbvmState; + use { crate::{ backend::KeyState, ifs::wl_seat::WlSeatGlobal, - keyboard::{DynKeyboardState, KeyboardState, KeyboardStateId, KeymapFd}, - utils::{ - oserror::{OsError, OsErrorExt}, - syncqueue::SyncQueue, - vecset::VecSet, - }, + utils::{syncqueue::SyncQueue, vecset::VecSet}, }, kbvm::{ Keycode, - lookup::LookupTable, - state_machine::{self, Direction, Event, StateMachine}, - xkb::{ - self, Keymap, - diagnostic::{Diagnostic, WriteToLog}, - keymap::{Indicator, IndicatorMatcher}, - rmlvo::Group, - }, + state_machine::{Direction, Event}, }, std::{ - cell::{Cell, Ref, RefCell}, - io::Write, + cell::{Cell, RefCell}, rc::Rc, }, - thiserror::Error, - uapi::c, }; -#[derive(Debug, Error)] -pub enum KbvmError { - #[error("could not parse the keymap")] - CouldNotParseKeymap(#[source] Diagnostic), - #[error("Could not create a keymap memfd")] - KeymapMemfd(#[source] OsError), -} - -pub struct KbvmContext { - pub ctx: xkb::Context, -} - -impl Default for KbvmContext { - fn default() -> Self { - let mut ctx = xkb::Context::builder(); - ctx.enable_environment(true); - Self { ctx: ctx.build() } - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub struct KbvmMapId([u8; 32]); - -pub struct KbvmMap { - pub id: KbvmMapId, - pub state_machine: StateMachine, - pub lookup_table: LookupTable, - pub map: KeymapFd, - pub xwayland_map: KeymapFd, - pub has_indicators: bool, - pub num_lock: Option, - pub caps_lock: Option, - pub scroll_lock: Option, - pub compose: Option, - pub kana: Option, -} - -pub struct KbvmState { - pub map: Rc, - pub state: state_machine::State, - pub kb_state: KeyboardState, -} - pub struct PhysicalKeyboardState { state: Rc>, inner: RefCell, @@ -85,156 +29,6 @@ struct PkInner { event_stash: Vec, } -impl DynKeyboardState for RefCell { - fn borrow(&self) -> Ref<'_, KeyboardState> { - Ref::map(self.borrow(), |v| &v.kb_state) - } -} - -impl KbvmContext { - pub fn parse_keymap(&self, keymap: &[u8]) -> Result, KbvmError> { - let map = self - .ctx - .keymap_from_bytes(WriteToLog, None, keymap) - .map_err(KbvmError::CouldNotParseKeymap)?; - let id = KbvmMapId(*blake3::hash(keymap).as_bytes()); - self.create_keymap(id, map) - } - - pub fn keymap_from_rmlvo( - &self, - rules: Option<&str>, - model: Option<&str>, - layout: Option<&str>, - variant: Option<&str>, - options: Option<&str>, - ) -> Result, KbvmError> { - let mut groups = None::>; - if layout.is_some() || variant.is_some() { - groups = Some( - Group::from_layouts_and_variants( - layout.unwrap_or_default(), - variant.unwrap_or_default(), - ) - .collect(), - ); - } - let mut options_vec = None::>; - if let Some(options) = options { - options_vec = Some(options.split(",").collect()); - } - self.keymap_from_names(rules, model, groups.as_deref(), options_vec.as_deref()) - } - - pub fn keymap_from_names( - &self, - rules: Option<&str>, - model: Option<&str>, - groups: Option<&[Group<'_>]>, - options: Option<&[&str]>, - ) -> Result, KbvmError> { - let map = self - .ctx - .keymap_from_names(WriteToLog, rules, model, groups, options); - let id = KbvmMapId(*blake3::hash(map.format().to_string().as_bytes()).as_bytes()); - self.create_keymap(id, map) - } - - fn create_keymap(&self, id: KbvmMapId, map: Keymap) -> Result, KbvmError> { - let mut has_indicators = false; - let mut num_lock = None; - let mut caps_lock = None; - let mut scroll_lock = None; - let mut compose = None; - let mut kana = None; - for indicator in map.indicators() { - match indicator.name() { - Indicator::NUM_LOCK => num_lock = Some(indicator.matcher()), - Indicator::CAPS_LOCK => caps_lock = Some(indicator.matcher()), - Indicator::SCROLL_LOCK => scroll_lock = Some(indicator.matcher()), - Indicator::COMPOSE => compose = Some(indicator.matcher()), - Indicator::KANA => kana = Some(indicator.matcher()), - _ => continue, - } - has_indicators = true; - } - let builder = map.to_builder(); - let (_, xwayland_map) = create_keymap_memfd(&map, true).map_err(KbvmError::KeymapMemfd)?; - let (_, map) = create_keymap_memfd(&map, false).map_err(KbvmError::KeymapMemfd)?; - Ok(Rc::new(KbvmMap { - id, - state_machine: builder.build_state_machine(), - map, - xwayland_map, - lookup_table: builder.build_lookup_table(), - has_indicators, - num_lock, - caps_lock, - scroll_lock, - compose, - kana, - })) - } -} - -fn create_keymap_memfd(map: &Keymap, xwayland: bool) -> Result<(String, KeymapFd), OsError> { - let mut format = map.format(); - if xwayland { - format = format.lookup_only(true).rename_long_keys(true); - } - let str = format!("{}\n", format); - let mut memfd = - uapi::memfd_create("keymap", c::MFD_CLOEXEC | c::MFD_ALLOW_SEALING).to_os_error()?; - memfd.write_all(str.as_bytes())?; - memfd.write_all(&[0])?; - uapi::lseek(memfd.raw(), 0, c::SEEK_SET).to_os_error()?; - uapi::fcntl_add_seals( - memfd.raw(), - c::F_SEAL_SEAL | c::F_SEAL_GROW | c::F_SEAL_SHRINK | c::F_SEAL_WRITE, - ) - .to_os_error()?; - let fd = KeymapFd { - map: Rc::new(memfd), - len: str.len() + 1, - }; - Ok((str, fd)) -} - -impl KbvmMap { - pub fn state(self: &Rc, id: KeyboardStateId) -> KbvmState { - KbvmState { - map: self.clone(), - state: self.state_machine.create_state(), - kb_state: KeyboardState { - id, - map: self.clone(), - pressed_keys: Default::default(), - mods: Default::default(), - leds: Default::default(), - leds_changed: Default::default(), - }, - } - } -} - -impl KbvmState { - pub fn apply_events(&mut self, events: &SyncQueue) { - let state = &mut self.kb_state; - while let Some(event) = events.pop() { - state.apply_event(event); - match event { - Event::KeyDown(kc) => { - state.pressed_keys.insert(kc.to_evdev()); - } - Event::KeyUp(kc) => { - state.pressed_keys.remove(&kc.to_evdev()); - } - _ => {} - } - } - } -} - impl PhysicalKeyboardState { pub fn new(state: &Rc>) -> Self { Self { diff --git a/src/keyboard.rs b/src/keyboard.rs deleted file mode 100644 index 04d53d7e..00000000 --- a/src/keyboard.rs +++ /dev/null @@ -1,116 +0,0 @@ -use { - crate::{ - backend::{LED_CAPS_LOCK, LED_COMPOSE, LED_KANA, LED_NUM_LOCK, LED_SCROLL_LOCK, Leds}, - kbvm::KbvmMap, - utils::{ - event_listener::EventSource, - oserror::{OsError, OsErrorExt, OsErrorExt2}, - vecset::VecSet, - }, - }, - kbvm::{Components, state_machine::Event}, - std::{ - cell::{Ref, RefCell}, - rc::Rc, - }, - thiserror::Error, - uapi::{OwnedFd, c}, -}; - -#[derive(Debug, Error)] -pub enum KeyboardError { - #[error("Could not create a keymap memfd")] - KeymapMemfd(#[source] OsError), - #[error("Could not copy the keymap")] - KeymapCopy(#[source] OsError), -} - -linear_ids!(KeyboardStateIds, KeyboardStateId, u64); - -pub struct KeyboardState { - pub id: KeyboardStateId, - pub map: Rc, - pub pressed_keys: VecSet, - pub mods: Components, - pub leds: Leds, - pub leds_changed: EventSource, -} - -pub trait LedsListener { - fn leds(&self, leds: Leds); -} - -pub trait DynKeyboardState { - fn borrow(&self) -> Ref<'_, KeyboardState>; -} - -impl DynKeyboardState for RefCell { - fn borrow(&self) -> Ref<'_, KeyboardState> { - self.borrow() - } -} - -impl KeyboardState { - pub fn apply_event(&mut self, event: Event) -> bool { - let changed = self.mods.apply_event(event); - if changed && self.map.has_indicators { - self.update_leds(); - } - changed - } - - pub fn update_leds(&mut self) { - if !self.map.has_indicators { - return; - } - let mut new = Leds::none(); - macro_rules! map_led { - ($field:ident, $led:ident) => { - if let Some(m) = &self.map.$field - && m.matches(&self.mods) - { - new |= $led; - } - }; - } - map_led!(num_lock, LED_NUM_LOCK); - map_led!(caps_lock, LED_CAPS_LOCK); - map_led!(scroll_lock, LED_SCROLL_LOCK); - map_led!(compose, LED_COMPOSE); - map_led!(kana, LED_KANA); - if new != self.leds { - self.leds = new; - for listener in self.leds_changed.iter() { - listener.leds(new); - } - } - } -} - -#[derive(Clone)] -pub struct KeymapFd { - pub map: Rc, - pub len: usize, -} - -impl KeymapFd { - pub fn create_unprotected_fd(&self) -> Result { - let fd = uapi::memfd_create("shared-keymap", c::MFD_CLOEXEC) - .map_os_err(KeyboardError::KeymapMemfd)?; - let target = self.len as c::off_t; - let mut pos = 0; - while pos < target { - let rem = target - pos; - let res = uapi::sendfile(fd.raw(), self.map.raw(), Some(&mut pos), rem as usize) - .to_os_error(); - match res { - Ok(_) | Err(OsError(c::EINTR)) => {} - Err(e) => return Err(KeyboardError::KeymapCopy(e)), - } - } - Ok(Self { - map: Rc::new(fd), - len: self.len, - }) - } -} diff --git a/src/libinput.rs b/src/libinput.rs index ecb3ed13..3b1f7a5b 100644 --- a/src/libinput.rs +++ b/src/libinput.rs @@ -1,190 +1,17 @@ -#![allow(non_camel_case_types)] +pub use jay_libinput::*; -pub mod consts; -pub mod device; -pub mod event; -mod sys; +use crate::backend::InputDeviceCapability; -use { - crate::{ - libinput::{ - consts::{ - LIBINPUT_LOG_PRIORITY_DEBUG, LIBINPUT_LOG_PRIORITY_ERROR, - LIBINPUT_LOG_PRIORITY_INFO, LogPriority, - }, - device::RegisteredDevice, - event::LibInputEvent, - sys::{ - libinput, libinput_device_ref, libinput_dispatch, libinput_get_event, - libinput_get_fd, libinput_interface, libinput_log_priority, - libinput_log_set_handler, libinput_log_set_priority, libinput_path_add_device, - libinput_path_create_context, libinput_unref, - }, - }, - udev::UdevError, - utils::{errorfmt::ErrorFmt, oserror::OsError, ptr_ext::PtrExt}, - }, - bstr::ByteSlice, - isnt::std_1::primitive::IsntConstPtrExt, - std::{ffi::CStr, rc::Rc}, - thiserror::Error, - uapi::{IntoUstr, OwnedFd, c}, -}; +pub fn device_capability(cap: InputDeviceCapability) -> consts::DeviceCapability { + use consts::*; -static INTERFACE: libinput_interface = libinput_interface { - open_restricted, - close_restricted, -}; - -unsafe extern "C" fn open_restricted( - path: *const c::c_char, - _flags: c::c_int, - user_data: *mut c::c_void, -) -> c::c_int { - unsafe { - let ud = (user_data as *const UserData).deref(); - match ud.adapter.open(CStr::from_ptr(path)) { - Ok(f) => f.unwrap(), - Err(e) => { - log::error!("Could not open device for libinput: {}", ErrorFmt(e)); - -1 - } - } + match cap { + InputDeviceCapability::Keyboard => LIBINPUT_DEVICE_CAP_KEYBOARD, + InputDeviceCapability::Pointer => LIBINPUT_DEVICE_CAP_POINTER, + InputDeviceCapability::Touch => LIBINPUT_DEVICE_CAP_TOUCH, + InputDeviceCapability::TabletTool => LIBINPUT_DEVICE_CAP_TABLET_TOOL, + InputDeviceCapability::TabletPad => LIBINPUT_DEVICE_CAP_TABLET_PAD, + InputDeviceCapability::Gesture => LIBINPUT_DEVICE_CAP_GESTURE, + InputDeviceCapability::Switch => LIBINPUT_DEVICE_CAP_SWITCH, } } - -unsafe extern "C" fn close_restricted(fd: c::c_int, _user_data: *mut c::c_void) { - drop(OwnedFd::new(fd)); -} - -struct UserData { - adapter: Rc, -} - -pub trait LibInputAdapter { - fn open(&self, path: &CStr) -> Result; -} - -#[derive(Debug, Error)] -pub enum LibInputError { - #[error("Could not create a libinput instance")] - New, - #[error("Could not open a libinput device")] - Open, - #[error("Could not dispatch libinput events")] - Dispatch(#[source] OsError), - #[error("The requested device is not available")] - DeviceUnavailable, - #[error("Dupfd failed")] - DupFd(#[source] OsError), - #[error("The udev subsystem produced an error")] - Udev(#[from] UdevError), - #[error("Stat failed")] - Stat(#[source] OsError), -} - -pub struct LibInput { - _data: Box, - li: *mut libinput, -} - -unsafe extern "C" { - fn jay_libinput_log_handler_bridge(); -} - -impl LibInput { - pub fn new(adapter: Rc) -> Result { - let mut ud = Box::new(UserData { adapter }); - let li = unsafe { - libinput_path_create_context(&INTERFACE, &mut *ud as *mut _ as *mut c::c_void) - }; - if li.is_null() { - return Err(LibInputError::New); - } - unsafe { - libinput_log_set_handler(li, jay_libinput_log_handler_bridge); - let priority = if log::log_enabled!(log::Level::Debug) { - LIBINPUT_LOG_PRIORITY_DEBUG - } else if log::log_enabled!(log::Level::Info) { - LIBINPUT_LOG_PRIORITY_INFO - } else { - LIBINPUT_LOG_PRIORITY_ERROR - }; - libinput_log_set_priority(li, priority.raw() as _); - } - Ok(Self { _data: ud, li }) - } - - pub fn fd(&self) -> c::c_int { - unsafe { libinput_get_fd(self.li) } - } - - pub fn open<'a>( - self: &Rc, - path: impl IntoUstr<'a>, - ) -> Result { - let path = path.into_ustr(); - let res = unsafe { libinput_path_add_device(self.li, path.as_ptr()) }; - if res.is_null() { - Err(LibInputError::Open) - } else { - unsafe { - libinput_device_ref(res); - } - Ok(RegisteredDevice { - _li: self.clone(), - dev: res, - }) - } - } - - pub fn dispatch(&self) -> Result<(), LibInputError> { - let res = unsafe { libinput_dispatch(self.li) }; - if res < 0 { - Err(LibInputError::Dispatch(OsError(-res))) - } else { - Ok(()) - } - } - - pub fn event(&self) -> Option> { - let res = unsafe { libinput_get_event(self.li) }; - if res.is_null() { - None - } else { - Some(LibInputEvent { - event: res, - _phantom: Default::default(), - }) - } - } -} - -impl Drop for LibInput { - fn drop(&mut self) { - unsafe { - libinput_unref(self.li); - } - } -} - -#[unsafe(no_mangle)] -unsafe extern "C" fn jay_libinput_log_handler( - _libinput: *mut libinput, - priority: libinput_log_priority, - line: *const c::c_char, -) { - assert!(line.is_not_null()); - let str = unsafe { CStr::from_ptr(line) }; - let priority = match LogPriority(priority as _) { - LIBINPUT_LOG_PRIORITY_DEBUG => log::Level::Debug, - LIBINPUT_LOG_PRIORITY_INFO => log::Level::Info, - LIBINPUT_LOG_PRIORITY_ERROR => log::Level::Error, - _ => log::Level::Error, - }; - log::log!( - priority, - "libinput: {}", - str.to_bytes().trim_ascii().as_bstr() - ); -} diff --git a/src/macros.rs b/src/macros.rs index df16289b..e125433e 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -109,7 +109,7 @@ macro_rules! id { #[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)] pub struct $name(u32); - #[expect(dead_code)] + #[allow(dead_code)] impl $name { pub const NONE: Self = $name(0); @@ -150,40 +150,6 @@ macro_rules! id { }; } -macro_rules! shared_ids { - ($id:ident) => { - shared_ids!($id, u32); - }; - ($id:ident, $ty:ty) => { - #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] - pub struct $id($ty); - - impl $id { - #[expect(dead_code)] - pub fn raw(&self) -> $ty { - self.0 - } - - #[expect(dead_code)] - pub fn from_raw(id: $ty) -> Self { - Self(id) - } - } - - impl From<$ty> for $id { - fn from(id: $ty) -> Self { - Self(id) - } - } - - impl std::fmt::Display for $id { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Display::fmt(&self.0, f) - } - } - }; -} - macro_rules! linear_ids { ($(#[$attr1:meta])* $ids:ident, $id:ident $(,)?) => { linear_ids!($(#[$attr1])* $ids, $id, u32); @@ -232,23 +198,38 @@ macro_rules! linear_ids { }; } -macro_rules! cenum { - ($name:ident, $uc:ident; $($name2:ident = $val:expr,)*) => { - #[derive(Copy, Clone, Debug, Eq, PartialEq)] - pub struct $name(pub i32); +macro_rules! shared_ids { + ($id:ident) => { + shared_ids!($id, u32); + }; + ($id:ident, $ty:ty) => { + #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] + pub struct $id($ty); - impl $name { - pub fn raw(self) -> i32 { + impl $id { + #[expect(dead_code)] + pub fn raw(&self) -> $ty { self.0 } + + #[expect(dead_code)] + pub fn from_raw(id: $ty) -> Self { + Self(id) + } } - pub const $uc: &[i32] = &[$($val,)*]; + impl From<$ty> for $id { + fn from(id: $ty) -> Self { + Self(id) + } + } - $( - pub const $name2: $name = $name($val); - )* - } + impl std::fmt::Display for $id { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } + } + }; } #[expect(unused_macros)] @@ -370,6 +351,7 @@ macro_rules! dedicated_add_global { }; } +#[expect(unused_macros)] macro_rules! assert_size_eq { ($t:ty, $u:ty) => {{ struct AssertEqSize(std::marker::PhantomData, std::marker::PhantomData); @@ -401,6 +383,7 @@ macro_rules! assert_size_le { }}; } +#[expect(unused_macros)] macro_rules! assert_align_eq { ($t:ty, $u:ty) => {{ struct AssertEqAlign(std::marker::PhantomData, std::marker::PhantomData); @@ -632,92 +615,12 @@ macro_rules! bitflags { }; } -macro_rules! pw_opcodes { - ($name:ident; $($var:ident = $val:expr,)*) => { - #[derive(Copy, Clone, Debug)] - pub enum $name { - $( - $var, - )* - } - - #[allow(clippy::allow_attributes, dead_code)] - impl $name { - pub fn from_id(id: u8) -> Option { - let v = match id { - $($val => Self::$var,)* - _ => return None, - }; - Some(v) - } - - pub fn name(self) -> &'static str { - match self { - $(Self::$var => stringify!($var),)* - } - } - } - - impl crate::pipewire::pw_object::PwOpcode for $name { - fn id(&self) -> u8 { - match self { - $(Self::$var => $val,)* - } - } - } - } -} - -macro_rules! pw_object_base { - ($name:ident, $if:expr, $events:ident; $($event:ident => $method:ident,)*) => { - impl crate::pipewire::pw_object::PwObjectBase for $name { - fn data(&self) -> &crate::pipewire::pw_object::PwObjectData { - &self.data - } - - fn interface(&self) -> &str { - $if - } - - fn handle_msg(self: std::rc::Rc, opcode: u8, parser: crate::pipewire::pw_parser::PwParser<'_>) -> Result<(), crate::pipewire::pw_object::PwObjectError> { - match $events::from_id(opcode) { - None => Err(crate::pipewire::pw_object::PwObjectError { - interface: $if, - source: crate::pipewire::pw_object::PwObjectErrorType::UnknownEvent(opcode), - }), - Some(m) => { - let (res, method) = match m { - $( - $events::$event => (self.$method(parser), stringify!($event)), - )* - }; - match res { - Ok(_) => Ok(()), - Err(source) => Err(crate::pipewire::pw_object::PwObjectError { - interface: $if, - source: crate::pipewire::pw_object::PwObjectErrorType::EventError { - method, - source: Box::new(source), - }, - }) - } - }, - } - } - - fn event_name(&self, opcode: u8) -> Option<&'static str> { - $events::from_id(opcode).map(|o| o.name()) - } - } - } -} - macro_rules! ei_id { ($name:ident) => { #[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)] pub struct $name(u64); - #[expect(dead_code)] + #[allow(dead_code)] impl $name { pub const NONE: Self = $name(0); @@ -795,6 +698,90 @@ macro_rules! ei_object_base { }; } +macro_rules! pw_opcodes { + ($name:ident; $($var:ident = $val:expr,)*) => { + #[derive(Copy, Clone, Debug)] + pub enum $name { + $( + $var, + )* + } + + #[allow(clippy::allow_attributes, dead_code)] + impl $name { + pub fn from_id(id: u8) -> Option { + let v = match id { + $($val => Self::$var,)* + _ => return None, + }; + Some(v) + } + + pub fn name(self) -> &'static str { + match self { + $(Self::$var => stringify!($var),)* + } + } + } + + impl crate::pipewire::pw_object::PwOpcode for $name { + fn id(&self) -> u8 { + match self { + $(Self::$var => $val,)* + } + } + } + } +} + +macro_rules! pw_object_base { + ($name:ident, $if:expr, $events:ident; $($event:ident => $method:ident,)*) => { + impl crate::pipewire::pw_object::PwObjectBase for $name { + fn data(&self) -> &crate::pipewire::pw_object::PwObjectData { + &self.data + } + + fn interface(&self) -> &str { + $if + } + + fn handle_msg( + self: std::rc::Rc, + opcode: u8, + parser: crate::pipewire::pw_parser::PwParser<'_>, + ) -> Result<(), crate::pipewire::pw_object::PwObjectError> { + match $events::from_id(opcode) { + None => Err(crate::pipewire::pw_object::PwObjectError { + interface: $if, + source: crate::pipewire::pw_object::PwObjectErrorType::UnknownEvent(opcode), + }), + Some(m) => { + let (res, method) = match m { + $( + $events::$event => (self.$method(parser), stringify!($event)), + )* + }; + match res { + Ok(_) => Ok(()), + Err(source) => Err(crate::pipewire::pw_object::PwObjectError { + interface: $if, + source: crate::pipewire::pw_object::PwObjectErrorType::EventError { + method, + source: Box::new(source), + }, + }), + } + } + } + } + + fn event_name(&self, opcode: u8) -> Option<&'static str> { + $events::from_id(opcode).map(|o| o.name()) + } + } + } +} + macro_rules! logical_to_client_wire_scale { ($client:expr, $($field:expr),+ $(,)?) => { #[expect(clippy::allow_attributes)] @@ -829,12 +816,6 @@ macro_rules! not_matches { }; } -macro_rules! jay_allow_realtime_config_so { - () => { - "JAY_ALLOW_REALTIME_CONFIG_SO" - }; -} - #[allow(clippy::allow_attributes, unused_macros)] macro_rules! dbg { ($val:expr) => { @@ -933,35 +914,3 @@ macro_rules! opaque { } }; } - -macro_rules! hash_type { - ($name:ident) => { - #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] - pub struct $name(pub [u8; 32]); - - impl $name { - #[allow(clippy::allow_attributes, dead_code)] - pub fn hash(t: impl AsRef<[u8]>) -> Self { - Self(*blake3::hash(t.as_ref()).as_bytes()) - } - } - - impl serde::Serialize for $name { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - self.0.serialize(serializer) - } - } - - impl<'de> serde::Deserialize<'de> for $name { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - <[u8; 32]>::deserialize(deserializer).map(Self) - } - } - }; -} diff --git a/src/main.rs b/src/main.rs index 5a566f9b..fe938d39 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,13 +45,13 @@ mod macros; #[macro_use] mod leaks; #[macro_use] -mod tracy; +extern crate jay_tracy; + mod acceptor; mod allocator; -mod async_engine; +mod animation; mod backend; mod backends; -mod bugs; mod cli; mod client; mod clientmem; @@ -59,17 +59,13 @@ mod cmm; mod compositor; mod config; mod copy_device; -mod cpu_worker; mod criteria; mod cursor; mod cursor_user; mod damage; mod dbus; mod drm_feedback; -mod edid; mod ei; -mod eventfd_cache; -mod fixed; mod forker; mod format; mod gfx_api; @@ -77,43 +73,30 @@ mod gfx_apis; mod globals; mod icons; mod ifs; -mod io_uring; #[cfg(feature = "it")] mod it; mod kbvm; -mod keyboard; mod libinput; -mod logger; mod logind; mod object; mod output_schedule; -mod pango; mod pipewire; mod portal; -mod pr_caps; -mod rect; mod renderer; -mod scale; mod screenshoter; -mod security_context_acceptor; -mod sighand; mod state; mod tagged_acceptor; mod tasks; mod text; -mod theme; -mod time; mod tools; mod tree; mod udev; -mod udmabuf; mod user_session; mod utils; mod version; mod video; mod virtual_output; mod vulkan_core; -mod wheel; mod wire; mod wire_dbus; mod wire_ei; diff --git a/src/object.rs b/src/object.rs index 24005b66..3d85dea2 100644 --- a/src/object.rs +++ b/src/object.rs @@ -7,35 +7,13 @@ use { std::{ any::Any, cmp::Ordering, - fmt::{Display, Formatter}, rc::Rc, }, }; +pub use jay_wire_types::ObjectId; pub const WL_DISPLAY_ID: WlDisplayId = WlDisplayId::from_raw(1); -#[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)] -pub struct ObjectId(u32); - -impl ObjectId { - #[expect(dead_code)] - pub const NONE: Self = ObjectId(0); - - pub fn from_raw(raw: u32) -> Self { - Self(raw) - } - - pub fn raw(self) -> u32 { - self.0 - } -} - -impl Display for ObjectId { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - Display::fmt(&self.0, f) - } -} - pub trait ObjectBase: Any { fn id(&self) -> ObjectId; fn version(&self) -> Version; diff --git a/src/output_schedule.rs b/src/output_schedule.rs index 098c1e91..f3b4ba88 100644 --- a/src/output_schedule.rs +++ b/src/output_schedule.rs @@ -1,215 +1,35 @@ +pub use jay_output_schedule::*; + use { crate::{ - async_engine::AsyncEngine, - backend::HardwareCursor, ifs::wl_output::PersistentOutputState, - io_uring::{IoUring, IoUringError}, state::{ConnectorData, State}, - utils::{ - asyncevent::AsyncEvent, cell_ext::CellExt, clonecell::CloneCell, errorfmt::ErrorFmt, - numcell::NumCell, - }, }, - futures_util::{FutureExt, select}, - num_traits::ToPrimitive, - std::{cell::Cell, rc::Rc}, + std::rc::Rc, }; -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -enum Change { - /// The backend has applied the latest changes. - None, - /// There are changes that the backend is not yet aware of. - Scheduled, - /// The backend is aware that there are changes and will apply them as part of the - /// next latch event. - AwaitingLatch, -} - -pub struct OutputSchedule { - changed: AsyncEvent, - run: Cell, - - connector: Rc, - hardware_cursor: CloneCell>>, - - persistent: Rc, - - last_present_nsec: Cell, - cursor_delta_nsec: Cell>, - - ring: Rc, - eng: Rc, - - vrr_enabled: Cell, - - hardware_cursor_change: Cell, - software_cursor_change: Cell, - - iteration: NumCell, -} - -impl OutputSchedule { - pub fn new( - state: &State, - connector: &Rc, - persistent: &Rc, - ) -> Self { - let slf = Self { - changed: Default::default(), - run: Default::default(), - connector: connector.clone(), - ring: state.ring.clone(), - eng: state.eng.clone(), - vrr_enabled: Default::default(), - hardware_cursor_change: Cell::new(Change::None), - software_cursor_change: Cell::new(Change::None), - hardware_cursor: Default::default(), - persistent: persistent.clone(), - last_present_nsec: Default::default(), - cursor_delta_nsec: Default::default(), - iteration: Default::default(), - }; - if let Some(hz) = persistent.vrr_cursor_hz.get() { - slf.set_cursor_hz(state, hz); - } - slf +impl OutputSchedulePersistent for PersistentOutputState { + fn vrr_cursor_hz(&self) -> Option { + self.vrr_cursor_hz.get() } - pub async fn drive(self: Rc) { - loop { - self.run_once().await; - while !self.run.take() { - self.changed.triggered().await; - } - } - } - - fn trigger(&self) { - let trigger = self.vrr_enabled.get() - && self.cursor_delta_nsec.is_some() - && (self.software_cursor_change.get() == Change::Scheduled - || self.hardware_cursor_change.get() == Change::Scheduled); - if trigger { - self.run.set(true); - self.changed.trigger(); - } - } - - pub fn latched(&self) { - self.last_present_nsec.set(self.eng.now().nsec()); - if self.software_cursor_change.get() == Change::AwaitingLatch { - self.software_cursor_change.set(Change::None); - } - if self.hardware_cursor_change.get() == Change::AwaitingLatch { - self.hardware_cursor_change.set(Change::None); - } - self.iteration.fetch_add(1); - self.trigger(); - } - - pub fn vrr_enabled(&self) -> bool { - self.vrr_enabled.get() - } - - pub fn set_vrr_enabled(&self, enabled: bool) { - self.vrr_enabled.set(enabled); - self.trigger(); - } - - pub fn set_cursor_hz(&self, _state: &State, hz: f64) { - let (hz, delta) = match map_cursor_hz(hz) { - None => { - log::warn!("Ignoring cursor frequency {hz}"); - return; - } - Some(v) => v, - }; - self.persistent.vrr_cursor_hz.set(hz); - self.connector.head_managers.handle_cursor_hz_change(hz); - self.cursor_delta_nsec.set(delta); - self.trigger(); - } - - pub fn set_hardware_cursor(&self, hc: &Option>) { - self.hardware_cursor.set(hc.clone()); - } - - pub fn defer_cursor_updates(&self) -> bool { - self.vrr_enabled.get() && self.cursor_delta_nsec.is_some() - } - - pub fn hardware_cursor_changed(&self) { - if self.hardware_cursor_change.get() == Change::None { - self.hardware_cursor_change.set(Change::Scheduled); - self.trigger(); - } - } - - pub fn software_cursor_changed(&self) { - if self.software_cursor_change.get() == Change::None { - self.software_cursor_change.set(Change::Scheduled); - self.trigger(); - } - } - - async fn run_once(&self) { - loop { - if self.hardware_cursor_change.get() != Change::Scheduled - && self.software_cursor_change.get() != Change::Scheduled - { - return; - } - if !self.vrr_enabled.get() { - return; - } - let Some(duration) = self.cursor_delta_nsec.get() else { - return; - }; - let iteration = self.iteration.get(); - let next_present = self.last_present_nsec.get().saturating_add(duration); - let res: Result<(), IoUringError> = select! { - _ = self.changed.triggered().fuse() => continue, - v = self.ring.timeout(next_present).fuse() => v, - }; - if let Err(e) = res { - log::error!("Could not wait for timer to expire: {}", ErrorFmt(e)); - return; - } - if iteration == self.iteration.get() { - break; - } - } - self.commit_cursor(); - } - - pub fn commit_cursor(&self) { - if self.hardware_cursor_change.get() == Change::Scheduled { - if let Some(hc) = self.hardware_cursor.get() { - hc.damage(); - } - self.hardware_cursor_change.set(Change::AwaitingLatch); - } - if self.software_cursor_change.get() == Change::Scheduled { - self.connector.damage(); - self.software_cursor_change.set(Change::AwaitingLatch); - } + fn set_vrr_cursor_hz(&self, hz: Option) { + self.vrr_cursor_hz.set(hz); } } -pub fn map_cursor_hz(hz: f64) -> Option<(Option, Option)> { - if hz <= 0.0 { - return Some((Some(0.0), Some(u64::MAX))); - } - let delta = (1_000_000_000.0 / hz).to_u64(); - if delta.is_none() { - if hz > 0.0 { - return Some((None, None)); - } - return None; - } - if delta == Some(0) { - return Some((None, None)); - } - Some((Some(hz), delta)) +pub fn create_output_schedule( + state: &State, + connector: &Rc, + persistent: &Rc, +) -> OutputSchedule { + let damage_connector = connector.clone(); + let cursor_hz_connector = connector.clone(); + OutputSchedule::new( + state.ring.clone(), + state.eng.clone(), + persistent.clone(), + Rc::new(move || damage_connector.damage()), + Rc::new(move |hz| cursor_hz_connector.head_managers.handle_cursor_hz_change(hz)), + ) } diff --git a/src/pipewire/pw_con.rs b/src/pipewire/pw_con.rs index b994acd3..afdfd8e6 100644 --- a/src/pipewire/pw_con.rs +++ b/src/pipewire/pw_con.rs @@ -1,7 +1,9 @@ +use jay_io_uring::{IoUring, IoUringError}; +use jay_async_engine::{AsyncEngine, SpawnedFuture}; use { crate::{ - async_engine::{AsyncEngine, SpawnedFuture}, - io_uring::{IoUring, IoUringError}, + + pipewire::{ pw_formatter::{PwFormatter, format}, pw_ifs::{ diff --git a/src/pipewire/pw_ifs/pw_client_node.rs b/src/pipewire/pw_ifs/pw_client_node.rs index b445aac5..d896a88b 100644 --- a/src/pipewire/pw_ifs/pw_client_node.rs +++ b/src/pipewire/pw_ifs/pw_client_node.rs @@ -1,9 +1,10 @@ #![allow(non_upper_case_globals)] +use jay_async_engine::SpawnedFuture; use { crate::{ - async_engine::SpawnedFuture, - format::{Format, pw_formats}, + + format::{Format, pipewire_format, pw_formats}, pipewire::{ pw_con::PwCon, pw_mem::{PwMemError, PwMemMap, PwMemSlice, PwMemTyped}, @@ -354,8 +355,8 @@ impl PwClientNode { } f.write_property(SPA_FORMAT_VIDEO_format.0, PwPropFlag::none(), |f| { f.write_choice(PW_CHOICE_Enum, 0, |f| { - f.write_id(format.format.pipewire.0); - f.write_id(format.format.pipewire.0); + f.write_id(pipewire_format(format.format).0); + f.write_id(pipewire_format(format.format).0); }); }); f.write_property( diff --git a/src/portal.rs b/src/portal.rs index f8847498..b9bf65e9 100644 --- a/src/portal.rs +++ b/src/portal.rs @@ -6,20 +6,24 @@ mod ptl_session; mod ptl_text; mod ptr_gui; +use jay_wheel::Wheel; +use jay_logger::{LogLevel, Logger}; +use jay_io_uring::IoUring; +use jay_eventfd_cache::EventfdCache; +use jay_async_engine::AsyncEngine; use { crate::{ - async_engine::AsyncEngine, + cli::GlobalArgs, cmm::cmm_manager::ColorManager, - compositor::LogLevel, dbus::{ BUS_DEST, BUS_PATH, DBUS_NAME_FLAG_DO_NOT_QUEUE, DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER, Dbus, DbusSocket, }, - eventfd_cache::EventfdCache, + forker::ForkerError, - io_uring::IoUring, - logger::Logger, + + pipewire::pw_con::{PwCon, PwConHolder, PwConOwner}, portal::{ ptl_display::{PortalDisplay, PortalDisplayId, watch_displays}, @@ -42,7 +46,7 @@ use { }, version::VERSION, video::dmabuf::DmaBufIds, - wheel::Wheel, + wire_dbus::org, }, std::{ @@ -250,6 +254,7 @@ async fn run_async( eventfd_cache, eng, wheel, + displays: Default::default(), dbus, sessions: Default::default(), diff --git a/src/portal/ptl_display.rs b/src/portal/ptl_display.rs index f3c30db4..19be9fd6 100644 --- a/src/portal/ptl_display.rs +++ b/src/portal/ptl_display.rs @@ -286,17 +286,28 @@ impl UsrJayOutputOwner for PortalOutput { impl UsrWlOutputOwner for PortalOutput {} -async fn maybe_add_display(state: &Rc, name: &str) { +fn parse_display_name(name: &str) -> Option { let tail = match name.strip_prefix("wayland-") { Some(t) => t, - _ => return, + _ => return None, }; let head = match tail.strip_suffix(".jay") { Some(h) => h, - _ => return, + _ => tail, }; let num = match u32::from_str(head) { Ok(n) => n, + _ => return None, + }; + if !(1..1000).contains(&num) { + return None; + } + Some(num) +} + +async fn maybe_add_display(state: &Rc, name: &str) { + let num = match parse_display_name(name) { + Some(n) => n, _ => return, }; let path = format!("{}/{}", state.xrd, name); diff --git a/src/portal/ptl_remote_desktop/remote_desktop_gui.rs b/src/portal/ptl_remote_desktop/remote_desktop_gui.rs index f9b52189..f3f51fe9 100644 --- a/src/portal/ptl_remote_desktop/remote_desktop_gui.rs +++ b/src/portal/ptl_remote_desktop/remote_desktop_gui.rs @@ -1,3 +1,4 @@ +use jay_theme::Color; use { crate::{ globals::GlobalName, @@ -10,7 +11,7 @@ use { OverlayWindowOwner, }, }, - theme::Color, + utils::{copyhashmap::CopyHashMap, hash_map_ext::HashMapExt}, }, std::rc::Rc, diff --git a/src/portal/ptl_screencast.rs b/src/portal/ptl_screencast.rs index d3563f0f..91c86b34 100644 --- a/src/portal/ptl_screencast.rs +++ b/src/portal/ptl_screencast.rs @@ -4,7 +4,7 @@ use { crate::{ allocator::{AllocatorError, BO_USE_RENDERING, BufferObject, BufferUsage}, dbus::{DbusObject, DictEntry, PendingReply, prelude::Variant}, - format::{Format, XRGB8888}, + format::{Format, XRGB8888, pipewire_format}, ifs::{jay_compositor::GET_TOPLEVEL_SINCE, jay_screencast::CLIENT_BUFFERS_SINCE}, pipewire::{ pw_con::PwCon, @@ -723,7 +723,7 @@ fn init_supported_formats( if format.write_modifiers.is_empty() { continue; } - if format.format.pipewire == SPA_VIDEO_FORMAT_UNKNOWN { + if pipewire_format(format.format) == SPA_VIDEO_FORMAT_UNKNOWN { continue; } let ptl_format = PwClientNodePortSupportedFormat { diff --git a/src/portal/ptl_screencast/screencast_gui.rs b/src/portal/ptl_screencast/screencast_gui.rs index d792f233..1d7f5835 100644 --- a/src/portal/ptl_screencast/screencast_gui.rs +++ b/src/portal/ptl_screencast/screencast_gui.rs @@ -1,3 +1,4 @@ +use jay_theme::Color; use { crate::{ globals::GlobalName, @@ -13,7 +14,7 @@ use { OverlayWindowOwner, }, }, - theme::Color, + utils::{copyhashmap::CopyHashMap, hash_map_ext::HashMapExt}, wl_usr::usr_ifs::{ usr_jay_select_toplevel::UsrJaySelectToplevelOwner, diff --git a/src/portal/ptl_text.rs b/src/portal/ptl_text.rs index 188112ba..305c3ed1 100644 --- a/src/portal/ptl_text.rs +++ b/src/portal/ptl_text.rs @@ -1,14 +1,14 @@ +use jay_theme::Color; +use jay_geometry::Rect; +use jay_pango::{ + CairoContext, CairoImageSurface, PangoCairoContext, PangoFontDescription, PangoLayout, + consts::{CAIRO_FORMAT_ARGB32, CAIRO_OPERATOR_SOURCE}, +}; use { crate::{ cmm::cmm_eotf::Eotf, format::ARGB8888, gfx_api::{GfxContext, GfxTexture}, - pango::{ - CairoContext, CairoImageSurface, PangoCairoContext, PangoFontDescription, PangoLayout, - consts::{CAIRO_FORMAT_ARGB32, CAIRO_OPERATOR_SOURCE}, - }, - rect::Rect, - theme::Color, }, std::{ops::Neg, rc::Rc, sync::Arc}, }; diff --git a/src/portal/ptr_gui.rs b/src/portal/ptr_gui.rs index 1484a583..06851bd0 100644 --- a/src/portal/ptr_gui.rs +++ b/src/portal/ptr_gui.rs @@ -1,10 +1,13 @@ +use jay_theme::Color; +use jay_async_engine::{Phase, SpawnedFuture}; +use jay_units::fixed::Fixed; +use jay_units::scale::Scale; use { crate::{ allocator::{BO_USE_RENDERING, BufferObject, BufferUsage}, - async_engine::{Phase, SpawnedFuture}, + cmm::{cmm_manager::ColorManager, cmm_render_intent::RenderIntent}, cursor::KnownCursor, - fixed::Fixed, format::ARGB8888, gfx_api::{ AcquireSync, AlphaMode, GfxContext, GfxFramebuffer, GfxTexture, ReleaseSync, @@ -17,8 +20,7 @@ use { ptl_text::{self, TextMeasurement}, }, renderer::renderer_base::RendererBase, - scale::Scale, - theme::Color, + utils::{ asyncevent::AsyncEvent, clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, hash_map_ext::HashMapExt, rc_eq::rc_eq, diff --git a/src/renderer.rs b/src/renderer.rs index e601a0e0..73dfb0c9 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -1,21 +1,27 @@ +use jay_theme::{Color, CornerRadius}; +use jay_geometry::Rect; +use jay_units::scale::Scale; use { crate::{ + animation::{ + RetainedContent, RetainedExitFrame, RetainedExitLayer, RetainedSurface, + RetainedToplevel, + }, cmm::cmm_render_intent::RenderIntent, - gfx_api::{AcquireSync, AlphaMode, GfxApiOpt, ReleaseSync, SampleRect}, + gfx_api::{AcquireSync, AlphaMode, BufferResv, GfxApiOpt, ReleaseSync, SampleRect}, ifs::wl_surface::{ SurfaceBuffer, WlSurface, x_surface::xwindow::Xwindow, xdg_surface::{XdgSurface, xdg_toplevel::XdgToplevel}, zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, }, - rect::Rect, + renderer::renderer_base::RendererBase, - scale::Scale, state::State, - theme::{Color, CornerRadius}, + tree::{ - ContainerNode, DisplayNode, FloatNode, OutputNode, PlaceholderNode, ToplevelData, - ToplevelNodeBase, WorkspaceNode, tab_bar::TabBar, + ContainerNode, DisplayNode, FloatNode, Node, OutputNode, PlaceholderNode, ToplevelData, + ToplevelNode, ToplevelNodeBase, WorkspaceNode, tab_bar::TabBar, }, }, std::{ops::Deref, rc::Rc, slice}, @@ -200,14 +206,22 @@ impl Renderer<'_> { self.render_workspace(&ws, x, y); } } + let now = self.state.now_nsec(); + let exit_frames = self.state.animations.exit_frames(now); + self.render_exit_frames(&exit_frames, RetainedExitLayer::Tiled, &opos); macro_rules! render_stacked { ($stack:expr) => { for stacked in $stack.iter() { if stacked.node_visible() { self.base.sync(); let pos = stacked.node_absolute_position(); - if pos.intersects(&opos) { - let (x, y) = opos.translate(pos.x1(), pos.y1()); + let visual = self.state.animations.visual_rect( + stacked.node_id(), + pos, + self.state.now_nsec(), + ); + if visual.intersects(&opos) { + let (x, y) = opos.translate(visual.x1(), visual.y1()); stacked.node_render(self, x, y, None); } } @@ -215,6 +229,7 @@ impl Renderer<'_> { }; } render_stacked!(self.state.root.stacked); + self.render_exit_frames(&exit_frames, RetainedExitLayer::Floating, &opos); // Flush RoundedFillRect ops from container/float borders so they don't // sort after (and render on top of) layer-shell CopyTexture ops. self.base.sync(); @@ -345,7 +360,7 @@ impl Renderer<'_> { if let Some(tex) = tex_ref.as_ref() && let Some(texture) = tex.texture() { - use crate::theme::TabTitleAlign; + use jay_theme::TabTitleAlign; let (tw, _th) = texture.size(); let tex_width = (tw as f64 / render_scale.to_f64()).round() as i32; let tab_inner = ew - 2 * (text_padding + border_width); @@ -453,6 +468,265 @@ impl Renderer<'_> { .fill_boxes2(&rd.border_rects, &c, srgb, perceptual, x, y); } + fn presentation_child_body( + &self, + container: &ContainerNode, + child: &Rc, + body: Rect, + ) -> Rect { + let abs = body.move_(container.abs_x1.get(), container.abs_y1.get()); + let visual = self + .state + .animations + .visual_rect(child.node_id(), abs, self.state.now_nsec()); + visual.move_(-container.abs_x1.get(), -container.abs_y1.get()) + } + + fn render_child_or_snapshot( + &mut self, + child: &Rc, + x: i32, + y: i32, + bounds: Option<&Rect>, + ) { + if let Some(retained) = self + .state + .animations + .retained_snapshot(child.node_id(), self.state.now_nsec()) + { + self.render_retained_toplevel(&retained, x, y, bounds); + } else { + child.node_render(self, x, y, bounds); + } + } + + fn render_retained_toplevel( + &mut self, + retained: &RetainedToplevel, + x: i32, + y: i32, + bounds: Option<&Rect>, + ) { + let (x, y) = self + .base + .scale_point(x + retained.offset.0, y + retained.offset.1); + self.render_retained_surface_scaled(&retained.surface, x, y, None, bounds); + } + + fn render_exit_frames( + &mut self, + frames: &[RetainedExitFrame], + layer: RetainedExitLayer, + output_rect: &Rect, + ) { + for frame in frames { + if frame.layer != layer || !frame.rect.intersects(output_rect) { + continue; + } + self.render_exit_frame(frame, output_rect); + } + } + + fn render_exit_frame(&mut self, frame: &RetainedExitFrame, output_rect: &Rect) { + let (x, y) = output_rect.translate(frame.rect.x1(), frame.rect.y1()); + let inset = frame.frame_inset; + if inset > 0 { + let color = if frame.active { + self.state.theme.colors.active_border.get() + } else { + self.state.theme.colors.border.get() + }; + self.render_rounded_frame( + Rect::new_sized_saturating(0, 0, frame.rect.width(), frame.rect.height()), + &color, + self.state.theme.corner_radius.get(), + inset, + x, + y, + ); + } + let body = Rect::new_sized_saturating( + x + inset, + y + inset, + frame.rect.width() - 2 * inset, + frame.rect.height() - 2 * inset, + ); + if body.is_empty() { + return; + } + if inset > 0 && !self.state.theme.corner_radius.get().is_zero() { + let inner_cr = self.scale_corner_radius( + self.state + .theme + .corner_radius + .get() + .expanded_by(-(inset as f32)), + ); + self.corner_radius = Some(inner_cr); + } + self.render_window_body_background(body); + let bounds = self.base.scale_rect(body); + self.stretch = if frame.source_body_size != body.size() { + Some(self.base.scale_point(body.width(), body.height())) + } else { + None + }; + self.render_retained_toplevel(&frame.retained, body.x1(), body.y1(), Some(&bounds)); + self.stretch = None; + self.corner_radius = None; + } + + fn render_window_body_background(&mut self, body: Rect) { + if body.is_empty() { + return; + } + let color = self.state.theme.colors.background.get(); + let srgb_srgb = self.state.color_manager.srgb_gamma22(); + let srgb = &srgb_srgb.linear; + let perceptual = RenderIntent::Perceptual; + self.base.sync(); + if let Some(cr) = self.corner_radius + && !cr.is_zero() + { + self.base + .fill_rounded_rect(body, &color, None, srgb, perceptual, cr, 0.0); + } else { + let bounds = self.base.scale_rect(body); + self.base + .fill_scaled_boxes(slice::from_ref(&bounds), &color, None, srgb, perceptual); + } + } + + fn render_retained_surface_scaled( + &mut self, + retained: &RetainedSurface, + x: i32, + y: i32, + pos_rel: Option<(i32, i32)>, + bounds: Option<&Rect>, + ) { + let stretch = self.stretch.take(); + let corner_radius = self.corner_radius.take(); + let mut size = retained.size; + if let Some((x_rel, y_rel)) = pos_rel { + let (x, y) = self.base.scale_point(x_rel, y_rel); + let (w, h) = self.base.scale_point(x_rel + size.0, y_rel + size.1); + size = (w - x, h - y); + } else { + size = self.base.scale_point(size.0, size.1); + } + let mut stretched_source = None; + if let Some(s) = stretch { + if let RetainedContent::Texture { source, .. } = &retained.content { + let mut source = *source; + if size.0 > 0 && size.1 > 0 { + let sx = s.0 as f32 / size.0 as f32; + let sy = s.1 as f32 / size.1 as f32; + source.x2 *= sx; + source.y2 *= sy; + } + stretched_source = Some(source); + } + size = s; + } + for child in &retained.below { + let (x1, y1) = self.base.scale_point(child.offset.0, child.offset.1); + self.render_retained_surface_scaled(child, x + x1, y + y1, Some(child.offset), bounds); + } + self.corner_radius = corner_radius; + self.render_retained_content(retained, stretched_source, x, y, size, bounds); + for child in &retained.above { + let (x1, y1) = self.base.scale_point(child.offset.0, child.offset.1); + self.render_retained_surface_scaled(child, x + x1, y + y1, Some(child.offset), bounds); + } + } + + fn render_retained_content( + &mut self, + retained: &RetainedSurface, + stretched_source: Option, + x: i32, + y: i32, + size: (i32, i32), + bounds: Option<&Rect>, + ) { + let corner_radius = self.corner_radius.take(); + match &retained.content { + RetainedContent::Texture { + texture, + buffer, + source, + alpha, + color_description, + render_intent, + alpha_mode, + opaque, + } => { + let source = stretched_source.unwrap_or(*source); + if let Some(cr) = corner_radius { + self.base.render_rounded_texture( + texture, + *alpha, + x, + y, + Some(source), + Some(size), + self.base.scale, + bounds, + Some(buffer.clone() as Rc), + AcquireSync::Unnecessary, + buffer.release_sync, + color_description, + *render_intent, + *alpha_mode, + cr, + ); + } else { + self.base.render_texture( + texture, + *alpha, + x, + y, + Some(source), + Some(size), + self.base.scale, + bounds, + Some(buffer.clone() as Rc), + AcquireSync::Unnecessary, + buffer.release_sync, + *opaque, + color_description, + *render_intent, + *alpha_mode, + ); + } + } + RetainedContent::Color { + color, + alpha, + color_description, + render_intent, + } => { + if let Some(rect) = Rect::new_sized(x, y, size.0, size.1) { + let rect = match bounds { + None => rect, + Some(bounds) => rect.intersect(*bounds), + }; + if !rect.is_empty() { + self.base.sync(); + self.base.fill_scaled_boxes( + &[rect], + color, + *alpha, + &color_description.linear, + *render_intent, + ); + } + } + } + } + } + pub fn render_container(&mut self, container: &ContainerNode, x: i32, y: i32) { self.render_container_decorations(container, x, y); @@ -465,6 +739,7 @@ impl Renderer<'_> { } } let mb = container.mono_body.get(); + let visual_mb = self.presentation_child_body(container, &child.node, mb); if self.state.theme.sizes.gap.get() != 0 { let bw = self.state.theme.sizes.border_width.get(); let border_color = self.state.theme.colors.border.get(); @@ -476,10 +751,10 @@ impl Renderer<'_> { }; if !child.node.node_is_container() { let frame = Rect::new_sized_saturating( - mb.x1() - bw, - mb.y1() - bw, - mb.width() + 2 * bw, - mb.height() + 2 * bw, + visual_mb.x1() - bw, + visual_mb.y1() - bw, + visual_mb.width() + 2 * bw, + visual_mb.height() + 2 * bw, ); self.render_rounded_frame( frame, @@ -491,14 +766,17 @@ impl Renderer<'_> { ); } } - let body = mb.move_(x, y); - let body = self.base.scale_rect(body); - let content = container.mono_content.get(); - self.stretch = if content.width() != mb.width() || content.height() != mb.height() { - Some(self.base.scale_point(mb.width(), mb.height())) - } else { - None - }; + let body = visual_mb.move_(x, y); + let content = container + .mono_content + .get() + .at_point(visual_mb.x1(), visual_mb.y1()); + self.stretch = + if content.width() != visual_mb.width() || content.height() != visual_mb.height() { + Some(self.base.scale_point(visual_mb.width(), visual_mb.height())) + } else { + None + }; if self.state.theme.sizes.gap.get() != 0 && !child.node.node_is_container() { let cr = self.state.theme.corner_radius.get(); if !cr.is_zero() { @@ -507,9 +785,16 @@ impl Renderer<'_> { self.corner_radius = Some(inner_cr); } } - child - .node - .node_render(self, x + content.x1(), y + content.y1(), Some(&body)); + if !child.node.node_is_container() { + self.render_window_body_background(body); + } + let body = self.base.scale_rect(body); + self.render_child_or_snapshot( + &child.node, + x + content.x1(), + y + content.y1(), + Some(&body), + ); self.stretch = None; self.corner_radius = None; } else { @@ -524,10 +809,13 @@ impl Renderer<'_> { }; let cr = self.state.theme.corner_radius.get(); for child in container.children.iter() { - let body = child.body.get(); - if body.x1() >= container.width.get() || body.y1() >= container.height.get() { + let layout_body = child.body.get(); + if layout_body.x1() >= container.width.get() + || layout_body.y1() >= container.height.get() + { break; } + let body = self.presentation_child_body(container, &child.node, layout_body); if gap != 0 { let c = if child.border_color_is_focused.get() { &focused_border_color @@ -544,7 +832,7 @@ impl Renderer<'_> { self.render_rounded_frame(frame, c, cr, bw, x, y); } } - let content = child.content.get(); + let content = child.content.get().at_point(body.x1(), body.y1()); self.stretch = if content.width() != body.width() || content.height() != body.height() { Some(self.base.scale_point(body.width(), body.height())) @@ -556,10 +844,16 @@ impl Renderer<'_> { self.corner_radius = Some(inner_cr); } let body = body.move_(x, y); + if !child.node.node_is_container() { + self.render_window_body_background(body); + } let body = self.base.scale_rect(body); - child - .node - .node_render(self, x + content.x1(), y + content.y1(), Some(&body)); + self.render_child_or_snapshot( + &child.node, + x + content.x1(), + y + content.y1(), + Some(&body), + ); self.stretch = None; self.corner_radius = None; } @@ -793,6 +1087,10 @@ impl Renderer<'_> { _ => return, }; let pos = floating.position.get(); + let visual = + self.state + .animations + .visual_rect(floating.node_id(), pos, self.state.now_nsec()); let theme = &self.state.theme; let bw = theme.sizes.border_width.get(); let bc = if floating.active.get() { @@ -801,16 +1099,27 @@ impl Renderer<'_> { theme.colors.border.get() }; let cr = theme.corner_radius.get(); - let outer = Rect::new_sized_saturating(0, 0, pos.width(), pos.height()); + let outer = Rect::new_sized_saturating(0, 0, visual.width(), visual.height()); self.render_rounded_frame(outer, &bc, cr, bw, x, y); - let body = - Rect::new_sized_saturating(x + bw, y + bw, pos.width() - 2 * bw, pos.height() - 2 * bw); + let body = Rect::new_sized_saturating( + x + bw, + y + bw, + visual.width() - 2 * bw, + visual.height() - 2 * bw, + ); let scissor_body = self.base.scale_rect(body); + self.stretch = if pos.width() != visual.width() || pos.height() != visual.height() { + Some(self.base.scale_point(body.width(), body.height())) + } else { + None + }; if !cr.is_zero() { let inner_cr = self.scale_corner_radius(cr.expanded_by(-(bw as f32))); self.corner_radius = Some(inner_cr); } - child.node_render(self, body.x1(), body.y1(), Some(&scissor_body)); + self.render_window_body_background(body); + self.render_child_or_snapshot(&child, body.x1(), body.y1(), Some(&scissor_body)); + self.stretch = None; self.corner_radius = None; } diff --git a/src/renderer/renderer_base.rs b/src/renderer/renderer_base.rs index a4f4ad41..a0c66003 100644 --- a/src/renderer/renderer_base.rs +++ b/src/renderer/renderer_base.rs @@ -1,3 +1,6 @@ +use jay_theme::{Color, CornerRadius}; +use jay_geometry::Rect; +use jay_units::scale::Scale; use { crate::{ cmm::{ @@ -8,9 +11,8 @@ use { AcquireSync, AlphaMode, BufferResv, CopyTexture, FillRect, FramebufferRect, GfxApiOpt, GfxTexture, ReleaseSync, RoundedCopyTexture, RoundedFillRect, SampleRect, }, - rect::Rect, - scale::Scale, - theme::{Color, CornerRadius}, + + tree::Transform, }, std::rc::Rc, @@ -62,7 +64,7 @@ impl RendererBase<'_> { let y1 = (rect.1 as f64 * self.scalef).round() as _; let x2 = (rect.2 as f64 * self.scalef).round() as _; let y2 = (rect.3 as f64 * self.scalef).round() as _; - rect = (x1, y1, x2, y2) + rect = (x1, y1, x2, y2); } rect } diff --git a/src/screenshoter.rs b/src/screenshoter.rs index 732aa9f5..b381a94a 100644 --- a/src/screenshoter.rs +++ b/src/screenshoter.rs @@ -1,12 +1,11 @@ +use jay_units::scale::Scale; use { crate::{ allocator::{AllocatorError, BO_USE_RENDERING, BufferObject, BufferUsage}, format::XRGB8888, gfx_api::{AcquireSync, GfxError, ReleaseSync, needs_render_usage}, - scale::Scale, state::State, tree::Transform, - video::drm::DrmError, }, indexmap::IndexMap, std::{ops::Deref, rc::Rc}, @@ -24,8 +23,6 @@ pub enum ScreenshooterError { AllocatorError(#[from] AllocatorError), #[error(transparent)] RenderError(#[from] GfxError), - #[error(transparent)] - DrmError(#[from] DrmError), #[error("Render context does not support XRGB8888")] XRGB8888, #[error("Render context supports no modifiers for XRGB8888 rendering")] @@ -93,7 +90,7 @@ pub fn take_screenshot( state.color_manager.srgb_linear(), )?; let drm = match allocator.drm() { - Some(drm) => Some(drm.dup_render()?.fd().clone()), + Some(drm) => Some(drm.dup_render_fd()?), _ => None, }; Ok(Screenshot { drm, bo }) diff --git a/src/security_context_acceptor.rs b/src/security_context_acceptor.rs deleted file mode 100644 index 0a5f9bb0..00000000 --- a/src/security_context_acceptor.rs +++ /dev/null @@ -1,143 +0,0 @@ -use { - crate::{ - async_engine::SpawnedFuture, - client::ClientCaps, - state::State, - utils::{copyhashmap::CopyHashMap, errorfmt::ErrorFmt, hash_map_ext::HashMapExt}, - }, - std::{ - cell::Cell, - fmt::{Display, Formatter}, - rc::Rc, - }, - uapi::{OwnedFd, c}, -}; - -#[derive(Default)] -pub struct SecurityContextAcceptors { - ids: AcceptorIds, - acceptors: CopyHashMap>, -} - -linear_ids!(AcceptorIds, AcceptorId, u64); - -struct Acceptor { - id: AcceptorId, - state: Rc, - metadata: Rc, - listen_fd: Rc, - close_fd: Rc, - bounding_caps: ClientCaps, - listen_future: Cell>>, - close_future: Cell>>, -} - -#[derive(Default)] -pub struct AcceptorMetadata { - pub secure: bool, - pub sandboxed: bool, - pub sandbox_engine: Option, - pub app_id: Option, - pub instance_id: Option, - pub tag: Option, -} - -impl SecurityContextAcceptors { - pub fn clear(&self) { - for acceptor in self.acceptors.lock().drain_values() { - acceptor.kill(); - } - } - - pub fn spawn( - &self, - state: &Rc, - sandbox_engine: Option, - app_id: Option, - instance_id: Option, - listen_fd: &Rc, - close_fd: &Rc, - bounding_caps: ClientCaps, - ) { - let acceptor = Rc::new(Acceptor { - id: self.ids.next(), - state: state.clone(), - metadata: Rc::new(AcceptorMetadata { - secure: false, - sandboxed: true, - sandbox_engine, - app_id, - instance_id, - tag: None, - }), - listen_fd: listen_fd.clone(), - close_fd: close_fd.clone(), - bounding_caps, - listen_future: Cell::new(None), - close_future: Cell::new(None), - }); - log::info!("Creating security acceptor {acceptor}"); - acceptor.listen_future.set(Some( - state - .eng - .spawn("security accept", acceptor.clone().accept()), - )); - acceptor.close_future.set(Some( - state - .eng - .spawn("security await close", acceptor.clone().close()), - )); - self.acceptors.set(acceptor.id, acceptor); - } -} - -impl Acceptor { - fn kill(&self) { - log::info!("Destroying security acceptor {self}"); - self.listen_future.take(); - self.close_future.take(); - self.state - .security_context_acceptors - .acceptors - .remove(&self.id); - } - - async fn accept(self: Rc) { - let s = &self.state; - loop { - let fd = match s.ring.accept(&self.listen_fd, c::SOCK_CLOEXEC).await { - Ok(fd) => fd, - Err(e) => { - log::error!("Could not accept a client: {}", ErrorFmt(e)); - break; - } - }; - let id = s.clients.id(); - if let Err(e) = s - .clients - .spawn(id, s, fd, self.bounding_caps, true, &self.metadata) - { - log::error!("Could not spawn a client: {}", ErrorFmt(e)); - break; - } - } - self.kill(); - } - - async fn close(self: Rc) { - let _ = self.state.ring.poll(&self.close_fd, 0).await; - self.kill(); - } -} - -impl Display for Acceptor { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}/{}/{}", - self.metadata.sandbox_engine.as_deref().unwrap_or(""), - self.metadata.app_id.as_deref().unwrap_or(""), - self.metadata.instance_id.as_deref().unwrap_or(""), - ) - } -} diff --git a/src/state.rs b/src/state.rs index 4ae761a0..d46a4b16 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,26 +1,51 @@ +mod animations; +mod connectors; +mod idle; +mod render_context; +mod rendering; +mod settings; +mod tree_ops; +mod xwayland; + +pub(crate) use animations::LayoutAnimationCandidate; +pub use connectors::{ConnectorData, DrmDevData, OutputData}; +pub use idle::IdleState; +pub use xwayland::XWaylandState; + +use jay_wheel::Wheel; +use jay_udmabuf::UdmabufHolder; +use jay_time::Time; +use std::time::SystemTime; +use jay_theme::Theme; +use jay_keyboard::{KeyboardStateIds, KbvmContext, KbvmMap, LedsListener}; +use jay_pr_caps::PrCapsThread; +use jay_logger::Logger; +use jay_io_uring::IoUring; +use jay_geometry::Rect; +use jay_eventfd_cache::EventfdCache; +use jay_cpu_worker::CpuWorker; +use jay_async_engine::{AsyncEngine, SpawnedFuture}; +use jay_units::scale::Scale; use { crate::{ acceptor::Acceptor, allocator::BufferObject, - async_engine::{AsyncEngine, SpawnedFuture}, + animation::{AnimationCurve, AnimationState, AnimationStyle}, + backend::{ - Backend, BackendConnectorState, BackendConnectorStateSerials, BackendDrmDevice, - BackendEvent, Connector, ConnectorId, ConnectorIds, DrmDeviceId, DrmDeviceIds, - HardwareCursorUpdate, InputDevice, InputDeviceGroupIds, InputDeviceId, InputDeviceIds, - MonitorInfo, transaction::BackendConnectorTransactionError, + Backend, BackendConnectorStateSerials, BackendEvent, ConnectorId, ConnectorIds, + DrmDeviceId, DrmDeviceIds, HardwareCursorUpdate, InputDevice, InputDeviceGroupIds, + InputDeviceId, InputDeviceIds, TabletIds, TabletInit, TabletPadIds, TabletPadInit, + TabletToolIds, }, backends::dummy::DummyBackend, cli::RunArgs, - client::{Client, ClientCaps, ClientId, Clients, NUM_CACHED_SERIAL_RANGES, SerialRange}, - clientmem::ClientMemOffset, - cmm::{ - cmm_description::ColorDescription, cmm_manager::ColorManager, - cmm_render_intent::RenderIntent, - }, - compositor::{LIBEI_SOCKET, LogLevel}, + client::{Client, ClientId, Clients, NUM_CACHED_SERIAL_RANGES, SerialRange}, + cmm::cmm_manager::ColorManager, + compositor::LIBEI_SOCKET, config::ConfigProxy, copy_device::CopyDeviceRegistry, - cpu_worker::CpuWorker, + criteria::{clm::ClMatcherManager, tlm::TlMatcherManager}, cursor::{Cursor, ServerCursors}, cursor_user::{CursorUserGroup, CursorUserGroupId, CursorUserGroupIds, CursorUserIds}, @@ -31,87 +56,55 @@ use { ei_acceptor::EiAcceptor, ei_client::{EiClient, EiClients}, }, - eventfd_cache::EventfdCache, - fixed::Fixed, + forker::ForkerProxy, - format::Format, - gfx_api::{ - AcquireSync, AlphaMode, BufferResv, FdSync, GfxApi, GfxBlendBuffer, GfxContext, - GfxError, GfxFramebuffer, GfxTexture, PendingShmTransfer, ReleaseSync, - STAGING_DOWNLOAD, SampleRect, - }, - gfx_apis::create_gfx_context, + gfx_api::{GfxApi, GfxContext}, globals::{Globals, GlobalsError, RemovableWaylandGlobal, WaylandGlobal}, icons::Icons, ifs::{ + data_transfer::{DataOfferIds, DataSourceIds, data_control::DataControlDeviceIds}, ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1, - ext_idle_notification_v1::ExtIdleNotificationV1, ext_session_lock_v1::ExtSessionLockV1, head_management::{ - HeadManagers, HeadNames, + HeadNames, jay_head_manager_session_v1::{HeadManagerEvent, JayHeadManagerSessionV1}, }, - ipc::{ - DataOfferIds, DataSourceIds, data_control::DataControlDeviceIds, - x_data_device::XIpcDeviceIds, - }, jay_render_ctx::JayRenderCtx, jay_screencast::JayScreencast, jay_seat_events::JaySeatEvents, jay_workspace_watcher::JayWorkspaceWatcher, wl_buffer::WlBuffer, - wl_output::{BlendSpace, OutputGlobalOpt, OutputId, PersistentOutputState}, + wl_output::{OutputGlobalOpt, OutputId, PersistentOutputState}, wl_seat::{ - PhysicalKeyboardId, PhysicalKeyboardIds, PositionHintRequest, SeatIds, - WlSeatGlobal, - tablet::{TabletIds, TabletInit, TabletPadIds, TabletPadInit, TabletToolIds}, + PhysicalKeyboardId, PhysicalKeyboardIds, PositionHintRequest, SeatIds, WlSeatGlobal, }, wl_surface::{ - NoneSurfaceExt, - tray::TrayItemIds, - wl_subsurface::SubsurfaceIds, - x_surface::xwindow::{Xwindow, XwindowId}, - xdg_surface::XdgSurfaceConfigureEvent, - zwp_idle_inhibitor_v1::{IdleInhibitorId, IdleInhibitorIds, ZwpIdleInhibitorV1}, + NoneSurfaceExt, tray::TrayItemIds, wl_subsurface::SubsurfaceIds, + xdg_surface::XdgSurfaceConfigureEvent, zwp_idle_inhibitor_v1::IdleInhibitorIds, zwp_input_popup_surface_v2::ZwpInputPopupSurfaceV2, }, - wlr_output_manager::{ - WlrOutputManagerState, zwlr_output_head_v1::ZwlrOutputHeadV1, - zwlr_output_manager_v1::WlrOutputManagerId, - }, + wlr_output_manager::WlrOutputManagerState, workspace_manager::WorkspaceManagerState, - wp_drm_lease_connector_v1::WpDrmLeaseConnectorV1, - wp_drm_lease_device_v1::WpDrmLeaseDeviceV1Global, xdg_activation_token_v1::ActivationToken, zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1, - zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1, zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, }, - io_uring::IoUring, - kbvm::{KbvmContext, KbvmMap}, - keyboard::{KeyboardStateIds, LedsListener}, + leaks::Tracker, - logger::Logger, - pr_caps::PrCapsThread, - rect::{Rect, Region}, - renderer::Renderer, - scale::Scale, - security_context_acceptor::SecurityContextAcceptors, + + + tagged_acceptor::TaggedAcceptors, - theme::{BarPosition, Color, Theme, ThemeColor, ThemeSized}, - time::Time, + + tree::{ - ContainerNode, ContainerSplit, Direction, DisplayNode, FindTreeUsecase, FloatNode, - FoundNode, LatchListener, Node, NodeIds, NodeVisitorBase, OutputNode, PlaceholderNode, - TearingMode, TileState, ToplevelData, ToplevelIdentifier, ToplevelNode, - ToplevelNodeBase, Transform, VrrMode, WorkspaceDisplayOrder, WorkspaceNode, - WorkspaceNodeId, - WsMoveConfig, generic_node_visitor, move_ws_to_output, + ContainerNode, DisplayNode, FloatNode, FoundNode, LatchListener, NodeIds, OutputNode, + PlaceholderNode, TearingMode, ToplevelIdentifier, ToplevelNode, VrrMode, + WorkspaceDisplayOrder, WorkspaceNode, WorkspaceNodeId, }, - udmabuf::UdmabufHolder, + utils::{ asyncevent::AsyncEvent, - bindings::Bindings, clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, @@ -125,33 +118,25 @@ use { refcounted::RefCounted, run_toplevel::RunToplevel, }, - video::{ - dmabuf::DmaBufIds, - drm::{Drm, wait_for_syncobj::WaitForSyncobj}, - }, + video::{dmabuf::DmaBufIds, drm::wait_for_syncobj::WaitForSyncobj}, virtual_output::VirtualOutputs, - wheel::Wheel, + wire::{ - ExtForeignToplevelListV1Id, ExtIdleNotificationV1Id, JayHeadManagerSessionV1Id, - JayRenderCtxId, JaySeatEventsId, JayWorkspaceWatcherId, ZwlrForeignToplevelManagerV1Id, - ZwpLinuxDmabufFeedbackV1Id, + ExtForeignToplevelListV1Id, JayHeadManagerSessionV1Id, JayRenderCtxId, JaySeatEventsId, + JayWorkspaceWatcherId, ZwlrForeignToplevelManagerV1Id, ZwpLinuxDmabufFeedbackV1Id, }, - xwayland::{self, XWaylandEvent}, }, ahash::AHashMap, - bstr::ByteSlice, - jay_config::PciId, std::{ cell::{Cell, RefCell}, fmt::{Debug, Formatter}, mem, - ops::{Deref, DerefMut}, + ops::DerefMut, rc::{Rc, Weak}, sync::Arc, - time::{Duration, SystemTime}, + }, - thiserror::Error, - uapi::{OwnedFd, c}, + uapi::c, }; pub struct State { @@ -209,10 +194,10 @@ pub struct State { pub run_args: RunArgs, pub xwayland: XWaylandState, pub acceptor: CloneCell>>, + pub tagged_acceptors: TaggedAcceptors, pub serial: NumCell, pub run_toplevel: Rc, pub config_dir: Option, - pub config_file_id: NumCell, pub tracker: Tracker, pub data_offer_ids: DataOfferIds, pub data_source_ids: DataSourceIds, @@ -242,8 +227,6 @@ pub struct State { pub explicit_sync_supported: Cell, pub keyboard_state_ids: KeyboardStateIds, pub physical_keyboard_ids: PhysicalKeyboardIds, - pub security_context_acceptors: SecurityContextAcceptors, - pub tagged_acceptors: TaggedAcceptors, pub cursor_user_group_ids: CursorUserGroupIds, pub cursor_user_ids: CursorUserIds, pub cursor_user_groups: CopyHashMap>, @@ -264,6 +247,13 @@ pub struct State { pub cpu_worker: Rc, pub ui_drag_enabled: Cell, pub ui_drag_threshold_squared: Cell, + pub animations: AnimationState, + pub layout_animations_requested: Cell, + pub layout_animations_active: Cell, + pub layout_animation_curve_override: Cell>, + pub layout_animation_style_override: Cell>, + pub(crate) layout_animation_batch: RefCell>>, + pub suppress_animations_for_next_layout: Cell, pub toplevels: CopyHashMap>, pub const_40hz_latch: EventSource, pub tray_item_ids: TrayItemIds, @@ -303,6 +293,7 @@ pub struct State { pub bo_drop_queue: Rc>>, pub virtual_outputs: VirtualOutputs, pub clean_logs_older_than: Cell>, + pub scratchpads: RefCell>>>, } // impl Drop for State { @@ -322,83 +313,25 @@ pub struct ScreenlockState { pub lock: CloneCell>>, } -pub struct XWaylandState { - pub enabled: Cell, - pub running: Cell, - pub pidfd: CloneCell>>, - pub handler: RefCell>>, - pub queue: Rc>, - pub ipc_device_ids: XIpcDeviceIds, - pub use_wire_scale: Cell, - pub wire_scale: Cell>, - pub windows: CopyHashMap>, - pub client: CloneCell>>, - pub display: CloneCell>>, +pub struct ScratchpadEntry { + node: Weak, + identifier: ToplevelIdentifier, + hidden: Cell, } -pub struct IdleState { - pub input: Cell, - pub change: AsyncEvent, - pub timeout: Cell, - pub grace_period: Cell, - pub timeout_changed: Cell, - pub inhibitors: CopyHashMap>, - pub inhibitors_changed: Cell, - pub backend_idle: Cell, - pub inhibited_idle_notifications: - CopyHashMap<(ClientId, ExtIdleNotificationV1Id), Rc>, - pub in_grace_period: Cell, -} - -impl IdleState { - pub fn set_timeout(&self, state: &State, timeout: Duration) { - self.timeout.set(timeout); - self.timeout_changed(state); +impl ScratchpadEntry { + fn alive(&self) -> bool { + self.node().is_some() } - pub fn set_grace_period(&self, state: &State, grace_period: Duration) { - self.grace_period.set(grace_period); - self.timeout_changed(state); - } - - fn timeout_changed(&self, _state: &State) { - self.timeout_changed.set(true); - self.change.trigger(); - } - - pub fn add_inhibitor(&self, state: &State, inhibitor: &Rc) { - self.inhibitors.set(inhibitor.inhibit_id, inhibitor.clone()); - self.inhibitors_changed(state); - } - - pub fn remove_inhibitor(&self, state: &State, inhibitor: &ZwpIdleInhibitorV1) { - self.inhibitors.remove(&inhibitor.inhibit_id); - self.inhibitors_changed(state); - if self.inhibitors.is_empty() { - self.resume_inhibited_notifications(); + fn node(&self) -> Option> { + let node = self.node.upgrade()?; + if node.tl_data().identifier.get() == self.identifier { + Some(node) + } else { + None } } - - fn inhibitors_changed(&self, _state: &State) { - self.inhibitors_changed.set(true); - self.change.trigger(); - } - - fn resume_inhibited_notifications(&self) { - for notification in self.inhibited_idle_notifications.lock().drain_values() { - notification.resume.trigger(); - } - } - - pub fn add_inhibited_notification(&self, n: &Rc) { - self.inhibited_idle_notifications - .set((n.client.id, n.id), n.clone()); - } - - pub fn remove_inhibited_notification(&self, n: &ExtIdleNotificationV1) { - self.inhibited_idle_notifications - .remove(&(n.client.id, n.id)); - } } pub struct InputDeviceData { @@ -424,360 +357,7 @@ pub struct DeviceHandlerData { pub mods_listener: EventListener, } -pub struct ConnectorData { - pub id: ConnectorId, - pub connector: Rc, - pub handler: Cell>>, - pub connected: Cell, - pub name: Rc, - pub description: RefCell, - pub drm_dev: Option>, - pub async_event: Rc, - pub damaged: Cell, - pub damage: RefCell>, - pub needs_vblank_emulation: Cell, - pub damage_intersect: Cell, - pub state: RefCell, - pub head_managers: HeadManagers, - pub wlr_output_heads: CopyHashMap>, -} - -pub struct OutputData { - pub connector: Rc, - pub monitor_info: Rc, - pub node: Option>, - pub lease_connectors: Rc>, -} - -pub struct DrmDevData { - pub dev: Rc, - pub handler: Cell>>, - pub connectors: CopyHashMap>, - pub syspath: Option, - pub devnode: Option, - pub vendor: Option, - pub model: Option, - pub pci_id: Option, - pub lease_global: Rc, -} - -impl ConnectorData { - pub fn damage(&self) { - if !self.damaged.replace(true) { - self.connector.damage(); - } - } - - pub fn modify_state( - &self, - state: &State, - f: impl FnOnce(&mut BackendConnectorState), - ) -> Result<(), BackendConnectorTransactionError> { - let old = self.state.borrow().clone(); - let mut s = old.clone(); - f(&mut s); - if old == s { - return Ok(()); - } - s.serial = state.backend_connector_state_serials.next(); - let mut tran = self.connector.create_transaction()?; - tran.add(&self.connector, s.clone())?; - tran.prepare()?.apply()?.commit(); - self.set_state(state, s); - Ok(()) - } - - pub fn set_state(&self, state: &State, s: BackendConnectorState) { - let old = self.state.borrow().clone(); - if old.serial >= s.serial { - return; - } - *self.state.borrow_mut() = s.clone(); - if old.enabled != s.enabled { - self.head_managers.handle_enabled_change(state, s.enabled); - } - if old.active != s.active { - self.head_managers.handle_active_change(s.active); - } - if old.non_desktop_override != s.non_desktop_override { - self.head_managers - .handle_non_desktop_override_changed(s.non_desktop_override); - } - if old.vrr != s.vrr { - self.head_managers.handle_vrr_change(s.vrr); - } - if old.tearing != s.tearing { - self.head_managers.handle_tearing_enabled_change(s.tearing); - } - if old.format != s.format { - self.head_managers.handle_format_change(s.format); - } - if (old.color_space, old.eotf) != (s.color_space, s.eotf) { - self.head_managers - .handle_colors_change(s.color_space, s.eotf); - } - if old.mode != s.mode { - self.head_managers.handle_mode_change(s.mode); - for head in self.wlr_output_heads.lock().values() { - head.handle_mode_change(s.mode); - } - } - if let Some(output) = state.outputs.get(&self.connector.id()) - && let Some(node) = &output.node - { - node.update_state(old, s); - } - } -} - -impl DrmDevData { - pub fn make_render_device(&self) { - log::info!( - "Making {} the render device", - self.devnode.as_deref().unwrap_or("unknown"), - ); - self.dev.clone().make_render_device(); - } - - pub fn set_direct_scanout_enabled(&self, _state: &State, enabled: bool) { - self.dev.set_direct_scanout_enabled(enabled); - } - - pub fn set_flip_margin(&self, _state: &State, margin: u64) { - self.dev.set_flip_margin(margin); - } -} - -struct UpdateTextTexturesVisitor; -impl NodeVisitorBase for UpdateTextTexturesVisitor { - fn visit_container(&mut self, node: &Rc) { - node.node_visit_children(self); - } - fn visit_output(&mut self, node: &Rc) { - node.schedule_update_render_data(); - node.node_visit_children(self); - } - fn visit_float(&mut self, node: &Rc) { - node.node_visit_children(self); - } - fn visit_workspace(&mut self, node: &Rc) { - node.title_texture.take(); - node.node_visit_children(self); - } - fn visit_placeholder(&mut self, node: &Rc) { - node.textures.borrow_mut().clear(); - node.schedule_update_texture(); - node.node_visit_children(self); - } -} - impl State { - pub fn create_gfx_context( - &self, - drm: &Drm, - api: Option, - ) -> Result, GfxError> { - create_gfx_context( - &self.eng, - &self.ring, - &self.eventfd_cache, - drm, - api.unwrap_or(self.default_gfx_api.get()), - self.caps_thread.as_ref(), - ) - } - - pub fn add_output_scale(&self, scale: Scale) { - if self.scales.add(scale) { - self.output_scales_changed(); - } - } - - pub fn remove_output_scale(&self, scale: Scale) { - if self.scales.remove(&scale) { - self.output_scales_changed(); - } - } - - pub fn add_cursor_size(&self, size: u32) { - if self.cursor_sizes.add(size) { - self.cursor_sizes_changed(); - } - } - - pub fn remove_cursor_size(&self, size: u32) { - if self.cursor_sizes.remove(&size) { - self.cursor_sizes_changed(); - } - } - - fn output_scales_changed(&self) { - UpdateTextTexturesVisitor.visit_display(&self.root); - self.reload_cursors(); - self.update_xwayland_wire_scale(); - self.icons.update_sizes(self); - } - - fn cursor_sizes_changed(&self) { - self.reload_cursors(); - } - - pub fn devices_enumerated(&self) { - if let Some(config) = self.config.get() { - config.devices_enumerated() - } - if self.render_ctx.is_none() { - for dev in self.drm_devs.lock().values() { - if let Ok(version) = dev.dev.version() - && version.name.contains_str("nvidia") - { - continue; - } - if dev.dev.gfx_fast_ram_access() { - continue; - } - dev.make_render_device(); - if self.render_ctx.is_some() { - break; - } - } - if self.render_ctx.is_none() - && let Some(dev) = self.drm_devs.lock().values().next() - { - dev.make_render_device(); - } - } - } - - pub fn set_render_ctx(&self, ctx: Option>) { - self.explicit_sync_supported.set(false); - self.render_ctx.set(ctx.clone()); - self.render_ctx_version.fetch_add(1); - self.cursors.set(None); - self.drm_feedback.set(None); - self.icons.clear(); - self.wait_for_syncobj - .set_ctx(ctx.as_ref().and_then(|c| c.syncobj_ctx().cloned())); - self.virtual_outputs.handle_render_ctx_change(self); - - 'handle_new_feedback: { - if let Some(ctx) = &ctx { - let feedback = match DrmFeedback::new(&self.drm_feedback_ids, &**ctx) { - Ok(fb) => fb, - Err(e) => { - log::error!("Could not create new DRM feedback: {}", ErrorFmt(e)); - break 'handle_new_feedback; - } - }; - for watcher in self.drm_feedback_consumers.lock().values() { - watcher.send_feedback(&feedback); - } - self.drm_feedback.set(Some(Rc::new(feedback))); - } - } - - { - struct Walker; - impl NodeVisitorBase for Walker { - fn visit_container(&mut self, node: &Rc) { - node.node_visit_children(self); - } - fn visit_workspace(&mut self, node: &Rc) { - node.title_texture.take(); - node.node_visit_children(self); - } - fn visit_output(&mut self, node: &Rc) { - node.render_data.borrow_mut().titles.clear(); - node.render_data.borrow_mut().status.take(); - node.set_hardware_cursor(None); - node.node_visit_children(self); - } - fn visit_float(&mut self, node: &Rc) { - node.node_visit_children(self); - } - fn visit_placeholder(&mut self, node: &Rc) { - node.textures.borrow_mut().clear(); - node.node_visit_children(self); - } - } - Walker.visit_display(&self.root); - let mut updated_buffers = AHashMap::new(); - for buffer in self.gfx_ctx_changed.iter() { - let had_buffer_texture = buffer.handle_gfx_context_change(); - updated_buffers.insert(Rc::as_ptr(&buffer), had_buffer_texture); - } - for client in self.clients.clients.borrow_mut().values() { - for surface in client.data.objects.surfaces.lock().values() { - let had_shm_texture = surface.reset_shm_textures(); - if let Some(buffer) = surface.buffer.get() { - let buf = &buffer.buffer.buf; - let had_buffer_texture = *updated_buffers.get(&Rc::as_ptr(buf)).unwrap(); - if had_shm_texture || had_buffer_texture { - buf.update_texture_or_log(surface, true); - } - } - } - } - } - - if ctx.is_some() { - self.reload_cursors(); - UpdateTextTexturesVisitor.visit_display(&self.root); - } - - for cursor_user_groups in self.cursor_user_groups.lock().values() { - cursor_user_groups.render_ctx_changed(); - } - - if let Some(ctx) = &ctx { - if let Some(ctx) = ctx.syncobj_ctx() - && ctx.supports_async_wait() - { - self.explicit_sync_supported.set(true); - } - if !self.render_ctx_ever_initialized.replace(true) - && let Some(config) = self.config.get() - { - config.graphics_initialized(); - } - } - - for watcher in self.render_ctx_watchers.lock().values() { - watcher.send_render_ctx(ctx.clone()); - } - - let mut scs = vec![]; - for client in self.clients.clients.borrow_mut().values() { - for sc in client.data.objects.screencasts.lock().values() { - scs.push(sc.clone()); - } - for sc in client.data.objects.ext_copy_sessions.lock().values() { - sc.stop(); - } - } - for sc in scs { - sc.do_destroy(); - } - - self.expose_new_singletons(); - } - - fn reload_cursors(&self) { - if let Some(ctx) = self.render_ctx.get() { - let cursors = match ServerCursors::load(&ctx, self) { - Ok(c) => c.map(Rc::new), - Err(e) => { - log::error!("Could not load the cursors: {}", ErrorFmt(e)); - None - } - }; - self.cursors.set(cursors); - for cursor_user_group in self.cursor_user_groups.lock().values() { - cursor_user_group.reload_known_cursor(); - } - } - } - pub fn add_global(&self, global: &Rc) { self.globals.add_global(self, global) } @@ -789,182 +369,6 @@ impl State { self.globals.remove(self, global) } - pub fn tree_changed(&self) { - // log::info!("state.tree_changed\n{:?}", Backtrace::new()); - if self.tree_changed_sent.replace(true) { - return; - } - let seats = self.globals.seats.lock(); - for seat in seats.values() { - seat.trigger_tree_changed(false); - } - } - - pub fn ensure_map_workspace(&self, seat: Option<&Rc>) -> Rc { - seat.cloned() - .or_else(|| self.seat_queue.last().map(|s| s.deref().clone())) - .map(|s| s.get_fallback_output()) - .or_else(|| self.root.outputs.lock().values().next().cloned()) - .or_else(|| self.dummy_output.get()) - .unwrap() - .ensure_workspace() - } - - pub fn map_tiled(self: &Rc, node: Rc) { - let seat = self.seat_queue.last(); - self.do_map_tiled(seat.as_deref(), node.clone()); - self.focus_after_map(node, seat.as_deref()); - } - - fn do_map_tiled(self: &Rc, seat: Option<&Rc>, node: Rc) { - let ws = self.ensure_map_workspace(seat); - self.map_tiled_on(node, &ws); - } - - pub fn map_tiled_on(self: &Rc, node: Rc, ws: &Rc) { - if let Some(c) = ws.container.get() { - let la = c.clone().tl_last_active_child(); - let lap = la - .tl_data() - .parent - .get() - .and_then(|n| n.node_into_container()); - if let Some(lap) = lap { - lap.add_child_after(&*la, node); - } else { - c.append_child(node); - } - } else { - let container = ContainerNode::new(self, ws, node, ContainerSplit::Horizontal); - ws.set_container(&container); - } - } - - pub fn map_floating( - self: &Rc, - node: Rc, - mut width: i32, - mut height: i32, - workspace: &Rc, - abs_pos: Option<(i32, i32)>, - ) { - width += 2 * self.theme.sizes.border_width.get(); - height += - 2 * self.theme.sizes.border_width.get() + self.theme.title_plus_underline_height(); - let output = workspace.output.get(); - let output_rect = output.global.pos.get(); - let position = if let Some((mut x1, mut y1)) = abs_pos { - if y1 <= output_rect.y1() { - y1 = output_rect.y1() + 1; - } - if y1 > output_rect.y2() { - y1 = output_rect.y2(); - } - y1 -= self.theme.sizes.border_width.get() + self.theme.title_plus_underline_height(); - x1 -= self.theme.sizes.border_width.get(); - Rect::new_sized_saturating(x1, y1, width, height) - } else { - let mut x1 = output_rect.x1(); - let mut y1 = output_rect.y1(); - if width < output_rect.width() { - x1 += (output_rect.width() - width) / 2; - } else { - width = output_rect.width(); - } - if height < output_rect.height() { - y1 += (output_rect.height() - height) / 2; - } else { - height = output_rect.height(); - } - Rect::new_sized_saturating(x1, y1, width, height) - }; - FloatNode::new(self, workspace, position, node.clone()); - self.focus_after_map(node, self.seat_queue.last().as_deref()); - } - - fn focus_after_map(&self, node: Rc, seat: Option<&Rc>) { - if !node.node_visible() { - return; - } - let Some(seat) = seat else { - return; - }; - if let Some(config) = self.config.get() - && !config.auto_focus(node.tl_data()) - { - return; - } - node.node_do_focus(&seat, Direction::Unspecified); - } - - pub fn show_workspace2( - &self, - seat: Option<&Rc>, - output: &Rc, - ws: &Rc, - ) { - let mut pinned_is_focused = false; - if let Some(seat) = seat { - for pinned in output.pinned.iter() { - pinned - .deref() - .clone() - .node_visit(&mut generic_node_visitor(|node| { - node.node_seat_state().for_each_kb_focus(|s| { - pinned_is_focused |= s.id() == seat.id(); - }); - })); - } - } - let did_change = output.show_workspace(&ws); - if !pinned_is_focused && let Some(seat) = seat { - ws.clone().node_do_focus(seat, Direction::Unspecified); - } - if !did_change { - return; - } - ws.flush_jay_workspaces(); - if !output.is_dummy { - output.schedule_update_render_data(); - self.tree_changed(); - output.workspace_switched.trigger(); - } - } - - pub fn show_workspace( - &self, - seat: &Rc, - name: &str, - output: Option>, - ) { - let output = output.unwrap_or_else(|| seat.get_fallback_output()); - let ws = match output.find_workspace(name) { - Some(ws) => ws, - _ => { - if output.is_dummy { - log::warn!("Not showing workspace because seat is on dummy output"); - return; - } - output.create_workspace(name) - } - }; - self.show_workspace2(Some(seat), &ws.output.get(), &ws); - seat.maybe_schedule_warp_mouse_to_focus(); - } - - pub fn float_map_ws(&self) -> Rc { - if let Some(seat) = self.seat_queue.last() { - let output = seat.get_fallback_output(); - if !output.is_dummy { - return output.ensure_workspace(); - } - } - if let Some(output) = self.root.outputs.lock().values().next().cloned() { - return output.ensure_workspace(); - } - self.dummy_output.get().unwrap().ensure_workspace() - } - pub fn set_status(&self, status: &str) { let status = Rc::new(status.to_owned()); self.status.set(status.clone()); @@ -974,47 +378,6 @@ impl State { } } - pub fn input_occurred(&self) { - if !self.idle.input.replace(true) { - self.idle.change.trigger(); - } - } - - pub fn start_xwayland(self: &Rc) { - if !self.xwayland.enabled.get() { - return; - } - let mut handler = self.xwayland.handler.borrow_mut(); - if handler.is_none() { - *handler = Some(self.eng.spawn("xwayland", xwayland::manage(self.clone()))); - } - } - - pub fn stop_xwayland(&self) { - if self.xwayland.running.get() { - return; - } - self.xwayland.handler.take(); - } - - pub fn set_xwayland_enabled(self: &Rc, enabled: bool) { - if self.xwayland.enabled.replace(enabled) == enabled { - return; - } - if enabled { - self.start_xwayland(); - } else { - self.stop_xwayland(); - } - } - - pub fn set_xwayland_use_wire_scale(&self, use_wire_scale: bool) { - if self.xwayland.use_wire_scale.replace(use_wire_scale) == use_wire_scale { - return; - } - self.update_xwayland_wire_scale(); - } - pub fn next_serial(&self, client: Option<&Client>) -> u64 { let serial = self.serial.fetch_add(1); if let Some(client) = client { @@ -1111,15 +474,17 @@ impl State { self.pending_output_render_data.clear(); self.pending_float_layout.clear(); self.pending_input_popup_positioning.clear(); - self.pending_toplevel_screencasts.clear(); - self.pending_screencast_reallocs_or_reconfigures.clear(); self.pending_placeholder_render_textures.clear(); self.pending_container_tab_render_textures.clear(); + self.animations.clear(); + self.layout_animations_requested.set(false); + self.layout_animations_active.set(false); + self.layout_animation_curve_override.set(None); + self.layout_animation_style_override.set(None); + self.suppress_animations_for_next_layout.set(false); self.render_ctx_watchers.clear(); self.workspace_watchers.clear(); self.toplevel_lists.clear(); - self.security_context_acceptors.clear(); - self.tagged_acceptors.clear(); self.slow_clients.clear(); for h in self.input_device_handlers.borrow_mut().drain_values() { h.async_event.clear(); @@ -1142,6 +507,7 @@ impl State { self.eng.clear(); self.ei_acceptor.take(); self.ei_acceptor_future.take(); + self.tagged_acceptors.clear(); self.ei_clients.clear(); self.slow_ei_clients.clear(); self.toplevels.clear(); @@ -1151,6 +517,7 @@ impl State { self.node_at_tree.borrow_mut().clear(); self.position_hint_requests.clear(); self.pending_warp_mouse_to_focus.clear(); + self.scratchpads.borrow_mut().clear(); self.head_managers.clear(); self.head_managers_async.clear(); self.const_40hz_latch.clear(); @@ -1216,180 +583,6 @@ impl State { } } - pub fn present_output( - &self, - output: &OutputNode, - fb: &Rc, - cd: &Rc, - acquire_sync: AcquireSync, - release_sync: ReleaseSync, - tex: &Rc, - render_hw_cursor: bool, - blend_buffer: Option<&Rc>, - blend_cd: &Rc, - ) -> Result, GfxError> { - let sync = fb.render_output( - acquire_sync, - release_sync, - cd, - output, - self, - Some(output.global.pos.get()), - output.global.persistent.scale.get(), - render_hw_cursor, - true, - blend_buffer, - blend_cd, - )?; - output.latched(false); - output.perform_screencopies( - tex, - cd, - None, - &AcquireSync::Unnecessary, - ReleaseSync::None, - !render_hw_cursor, - 0, - 0, - None, - ); - Ok(sync) - } - - pub fn perform_screencopy( - &self, - src: &Rc, - resv: Option<&Rc>, - acquire_sync: &AcquireSync, - release_sync: ReleaseSync, - src_cd: &Rc, - target: &Rc, - target_acquire_sync: AcquireSync, - target_release_sync: ReleaseSync, - target_transform: Transform, - target_cd: &Rc, - position: Rect, - render_hardware_cursors: bool, - x_off: i32, - y_off: i32, - size: Option<(i32, i32)>, - transform: Transform, - scale: Scale, - ) -> Result, GfxError> { - let mut ops = vec![]; - let mut renderer = Renderer { - base: target.renderer_base(&mut ops, scale, target_transform), - state: self, - logical_extents: position.at_point(0, 0), - pixel_extents: { - let (width, height) = target.logical_size(target_transform); - Rect::new_sized_saturating(0, 0, width, height) - }, - stretch: None, - corner_radius: None, - }; - let mut sample_rect = SampleRect::identity(); - sample_rect.buffer_transform = transform; - renderer.base.render_texture( - src, - None, - x_off, - y_off, - Some(sample_rect), - size, - scale, - None, - resv.cloned(), - acquire_sync.clone(), - release_sync, - false, - src_cd, - RenderIntent::Perceptual, - AlphaMode::PremultipliedElectrical, - ); - if render_hardware_cursors - && let Some(cursor_user_group) = self.cursor_user_group_hardware_cursor.get() - && let Some(cursor_user) = cursor_user_group.active() - && let Some(cursor) = cursor_user.get() - { - let (mut x, mut y) = cursor_user.position(); - x = x + x_off - Fixed::from_int(position.x1()); - y = y + y_off - Fixed::from_int(position.y1()); - cursor.render(&mut renderer, x, y); - } - target.render( - target_acquire_sync, - target_release_sync, - target_cd, - &ops, - Some(&Color::SOLID_BLACK), - &target_cd.linear, - None, - target_cd, - ) - } - - pub fn perform_shm_screencopy( - &self, - src: &Rc, - src_cd: &Rc, - acquire_sync: &AcquireSync, - position: Rect, - x_off: i32, - y_off: i32, - size: Option<(i32, i32)>, - capture: &Rc, - mem: &Rc, - stride: i32, - format: &'static Format, - transform: Transform, - scale: Scale, - ) -> Result, ShmScreencopyError> { - let Some(ctx) = self.render_ctx.get() else { - return Err(ShmScreencopyError::NoRenderContext); - }; - let fb = ctx - .clone() - .create_internal_fb( - &self.cpu_worker, - capture.rect.width(), - capture.rect.height(), - stride, - format, - ) - .map_err(ShmScreencopyError::CreateTemporaryFb)?; - self.perform_screencopy( - src, - None, - acquire_sync, - ReleaseSync::None, - src_cd, - &(fb.clone() as Rc), - AcquireSync::Unnecessary, - ReleaseSync::None, - transform, - self.color_manager.srgb_gamma22(), - position, - true, - x_off - capture.rect.x1(), - y_off - capture.rect.y1(), - size, - transform, - scale, - ) - .map_err(ShmScreencopyError::CopyToTemporary)?; - let staging = ctx.create_staging_buffer(fb.staging_size(), STAGING_DOWNLOAD); - let pending = fb - .download( - &staging, - capture.clone(), - mem.clone(), - Region::new(capture.rect.at_point(0, 0)), - ) - .map_err(ShmScreencopyError::ReadPixels)?; - Ok(pending) - } - pub fn create_seat(self: &Rc, name: &str) -> Rc { let global_name = self.globals.name(); let seat = WlSeatGlobal::new(global_name, name, self); @@ -1398,53 +591,6 @@ impl State { seat } - pub fn set_backend_idle(&self, idle: bool) { - if self.idle.backend_idle.replace(idle) != idle { - self.root.update_visible(self); - } - } - - pub fn root_visible(&self) -> bool { - !self.idle.backend_idle.get() - } - - pub fn find_closest_output(&self, mut x: i32, mut y: i32) -> (Rc, i32, i32) { - let mut optimal_dist = i128::MAX; - let mut optimal_output = None; - let outputs = self.root.outputs.lock(); - for output in outputs.values() { - let pos = output.global.pos.get(); - let dist = pos.dist_squared(x, y); - if dist == 0 { - if pos.contains(x, y) { - return (output.clone(), x, y); - } - } - if dist < optimal_dist { - optimal_dist = dist; - optimal_output = Some(output.clone()); - } - } - if let Some(output) = optimal_output { - let pos = output.global.pos.get(); - if pos.is_empty() { - return (output, pos.x1(), pos.y1()); - } - if x < pos.x1() { - x = pos.x1(); - } else if x >= pos.x2() { - x = pos.x2() - 1; - } - if y < pos.y1() { - y = pos.y1(); - } else if y >= pos.y2() { - y = pos.y2() - 1; - } - return (output, x, y); - } - (self.dummy_output.get().unwrap(), 0, 0) - } - pub fn now(&self) -> Time { self.eng.now() } @@ -1461,13 +607,6 @@ impl State { self.eng.now().msec() } - pub fn output_extents_changed(&self) { - self.root.update_extents(); - for seat in self.globals.seats.lock().values() { - seat.output_extents_changed(); - } - } - pub fn update_ei_acceptor(self: &Rc) { self.update_ei_acceptor2(); if let Some(forker) = self.forker.get() { @@ -1542,36 +681,6 @@ impl State { dx * dx + dy * dy > self.ui_drag_threshold_squared.get() } - pub fn update_xwayland_wire_scale(&self) { - let scale = self - .scales - .lock() - .iter() - .map(|v| v.0.round_up()) - .max() - .unwrap_or(1); - let wire_scale = match self.xwayland.use_wire_scale.get() { - true => Some(scale as i32), - false => None, - }; - self.xwayland.wire_scale.set(wire_scale); - for client in self.clients.clients.borrow().values() { - let client = &client.data; - if !client.is_xwayland { - continue; - } - if client.wire_scale.replace(wire_scale) == wire_scale { - continue; - } - for output in client.objects.outputs.lock().values() { - output.send_updates(); - } - for surface in client.objects.surfaces.lock().values() { - surface.handle_xwayland_wire_scale_change(); - } - } - } - pub fn tray_icon_size(&self) -> i32 { if !self.show_bar.get() { return 0; @@ -1588,415 +697,4 @@ impl State { }; ctx.supports_color_management() } - - pub fn initial_tile_state(&self, data: &ToplevelData) -> Option { - self.config.get()?.initial_tile_state(data) - } - - pub fn predict_tiled_body_size(&self) -> Option<(i32, i32)> { - let seat = self.seat_queue.last(); - let ws = self.ensure_map_workspace(seat.as_deref()); - let pos = ws.position.get(); - if pos.is_empty() { - return None; - } - if let Some(c) = ws.container.get() { - let la = c.clone().tl_last_active_child(); - let target = la - .tl_data() - .parent - .get() - .and_then(|n| n.node_into_container()) - .unwrap_or(c); - Some(target.predict_child_body_size()) - } else { - Some((pos.width(), pos.height())) - } - } - - pub fn update_capabilities( - &self, - data: &Rc, - bounding_caps: ClientCaps, - set_bounding_caps: bool, - ) { - if let Some(config) = self.config.get() { - config.update_capabilities(data, bounding_caps, set_bounding_caps); - } - } - - pub fn find_output_in_direction( - &self, - source_output: &OutputNode, - direction: Direction, - ) -> Option> { - if source_output.is_dummy { - return None; - } - - let outputs = self.root.outputs.lock(); - - let ref_box = source_output.global.pos.get(); - let ref_x1 = ref_box.x1(); - let ref_y1 = ref_box.y1(); - let ref_x2 = ref_box.x2(); - let ref_y2 = ref_box.y2(); - - // Use the center of the source output as the reference point (like wlroots) - let (ref_lx, ref_ly) = ref_box.center(); - - // Find the closest output in the given direction using wlroots-style algorithm - let mut min_distance = i64::MAX; - let mut closest_output = None; - - for output in outputs.values() { - if output.id == source_output.id { - continue; - } - - let box_pos = output.global.pos.get(); - let box_x1 = box_pos.x1(); - let box_y1 = box_pos.y1(); - let box_x2 = box_pos.x2(); - let box_y2 = box_pos.y2(); - - // Edge-based direction check (like wlroots) - // Test to make sure this output is in the given direction - let is_in_direction = match direction { - Direction::Left => box_x2 <= ref_x1, - Direction::Right => box_x1 >= ref_x2, - Direction::Up => box_y2 <= ref_y1, - Direction::Down => box_y1 >= ref_y2, - Direction::Unspecified => false, - }; - - if !is_in_direction { - continue; - } - - // Calculate distance from reference point to closest point on this output - // This mimics wlr_box_closest_point + squared Euclidean distance - let closest_x = ref_lx.clamp(box_x1, box_x2); - let closest_y = ref_ly.clamp(box_y1, box_y2); - - let dx = (closest_x - ref_lx) as i64; - let dy = (closest_y - ref_ly) as i64; - let distance = dx * dx + dy * dy; - - if distance < min_distance { - min_distance = distance; - closest_output = Some(output); - } - } - - closest_output.cloned() - } - - pub fn node_at(&self, x: i32, y: i32) -> FoundNode { - let mut found_tree = self.node_at_tree.borrow_mut(); - found_tree.push(FoundNode { - node: self.root.clone(), - x, - y, - }); - self.root - .node_find_tree_at(x, y, &mut found_tree, FindTreeUsecase::None); - let node = found_tree.pop().unwrap(); - found_tree.clear(); - node - } - - pub fn move_ws_to_output(&self, ws: &WorkspaceNode, output: &Rc) { - if ws.is_dummy || output.is_dummy { - return; - } - if ws.output.get().id == output.id { - return; - } - let link = match &*ws.output_link.borrow() { - None => return, - Some(l) => l.to_ref(), - }; - let config = WsMoveConfig { - make_visible_always: false, - make_visible_if_empty: true, - source_is_destroyed: false, - before: None, - }; - move_ws_to_output(&link, &output, config); - ws.desired_output.set(output.global.output_id.clone()); - self.tree_changed(); - } - - fn expose_new_singletons(&self) { - self.globals.expose_new_singletons(self); - } - - pub fn set_color_management_enabled(&self, enabled: bool) { - self.color_management_enabled.set(enabled); - self.expose_new_singletons(); - } - - pub fn set_primary_selection_enabled(&self, enabled: bool) { - self.enable_primary_selection.set(enabled); - self.expose_new_singletons(); - } - - pub fn set_explicit_sync_enabled(&self, enabled: bool) { - self.explicit_sync_enabled.set(enabled); - self.expose_new_singletons(); - } - - pub fn set_log_level(&self, level: LogLevel) { - if let Some(logger) = &self.logger { - logger.set_level(level); - } - } - - pub fn perform_clean_logs_older_than(&self) { - if let Some(time) = self.clean_logs_older_than.get() - && let Some(logger) = &self.logger - { - logger.clean_logs_older_than(time); - } - } - - fn colors_changed(&self) { - struct V; - impl NodeVisitorBase for V { - fn visit_container(&mut self, node: &Rc) { - node.on_colors_changed(); - node.node_visit_children(self); - } - - fn visit_output(&mut self, node: &Rc) { - node.on_colors_changed(); - node.node_visit_children(self); - } - - fn visit_float(&mut self, node: &Rc) { - node.on_colors_changed(); - node.node_visit_children(self); - } - } - self.root.clone().node_visit(&mut V); - self.damage(self.root.extents.get()); - self.icons.clear(); - } - - pub fn reset_colors(&self) { - self.theme.colors.reset(); - self.colors_changed(); - } - - pub fn quit(&self) { - log::info!("Quitting"); - self.ring.stop(); - } - - pub fn reload_config(self: &Rc) { - log::info!("Reloading config"); - let config = match ConfigProxy::from_config_dir(self) { - Ok(c) => c, - Err(e) => { - log::error!("Cannot reload config: {}", ErrorFmt(e)); - return; - } - }; - if let Some(config) = self.config.take() { - config.destroy(); - for seat in self.globals.seats.lock().values() { - seat.clear_shortcuts(); - } - } - config.configure(true); - self.config.set(Some(Rc::new(config))); - } - - pub fn set_ei_socket_enabled(self: &Rc, enabled: bool) { - self.enable_ei_acceptor.set(enabled); - self.update_ei_acceptor(); - } - - pub fn set_workspace_display_order(&self, order: WorkspaceDisplayOrder) { - self.workspace_display_order.set(order); - for output in self.root.outputs.lock().values() { - output.handle_workspace_display_order_update(); - } - } - - fn spaces_changed(&self) { - struct V; - impl NodeVisitorBase for V { - fn visit_container(&mut self, node: &Rc) { - node.on_spaces_changed(); - node.node_visit_children(self); - } - fn visit_output(&mut self, node: &Rc) { - node.on_spaces_changed(); - node.node_visit_children(self); - } - fn visit_float(&mut self, node: &Rc) { - node.on_spaces_changed(); - node.node_visit_children(self); - } - } - self.root.clone().node_visit(&mut V); - self.damage(self.root.extents.get()); - self.icons.update_sizes(self); - } - - pub fn set_show_bar(&self, show: bool) { - self.show_bar.set(show); - self.spaces_changed(); - } - - #[allow(dead_code)] - pub fn set_show_titles(&self, show: bool) { - self.theme.show_titles.set(show); - self.spaces_changed(); - } - - #[allow(dead_code)] - pub fn set_floating_titles(&self, floating: bool) { - self.theme.floating_titles.set(floating); - self.spaces_changed(); - } - - pub fn set_ui_drag_enabled(&self, enabled: bool) { - self.ui_drag_enabled.set(enabled); - } - - pub fn set_ui_drag_threshold(&self, threshold: i32) { - self.ui_drag_threshold_squared - .set(threshold.saturating_mul(threshold)); - } - - #[allow(dead_code)] - pub fn set_show_pin_icon(&self, show: bool) { - self.show_pin_icon.set(show); - } - - pub fn set_float_above_fullscreen(&self, v: bool) { - self.float_above_fullscreen.set(v); - for seat in self.globals.seats.lock().values() { - seat.emulate_cursor_moved(); - seat.trigger_tree_changed(false); - } - self.root.update_visible(self); - } - - pub fn reset_sizes(&self) { - self.theme.sizes.reset(); - self.spaces_changed(); - } - - fn fonts_changed(&self) { - struct V; - impl NodeVisitorBase for V { - fn visit_container(&mut self, node: &Rc) { - node.node_visit_children(self); - } - fn visit_output(&mut self, node: &Rc) { - node.schedule_update_render_data(); - node.node_visit_children(self); - } - fn visit_float(&mut self, node: &Rc) { - node.node_visit_children(self); - } - } - self.root.clone().node_visit(&mut V); - } - - pub fn reset_fonts(&self) { - let theme = &self.theme; - theme.font.set(self.theme.default_font.clone()); - theme.bar_font.set(None); - theme.title_font.set(None); - self.fonts_changed(); - } - - pub fn set_font(&self, font: &str) { - self.theme.font.set(Arc::new(font.to_string())); - self.fonts_changed(); - } - - pub fn set_bar_font(&self, font: Option<&str>) { - let font = font.map(|font| Arc::new(font.to_string())); - self.theme.bar_font.set(font); - self.fonts_changed(); - } - - #[allow(dead_code)] - pub fn set_title_font(&self, font: Option<&str>) { - let font = font.map(|font| Arc::new(font.to_string())); - self.theme.title_font.set(font); - self.fonts_changed(); - } - - pub fn set_bar_position(&self, p: BarPosition) { - self.theme.bar_position.set(p); - self.spaces_changed(); - } - - pub fn set_size(&self, sized: ThemeSized, size: i32) { - let field = sized.field(&self.theme); - field.val.set(size); - field.set.set(true); - self.spaces_changed(); - } - - pub fn set_color(&self, colored: ThemeColor, v: Color) { - colored.field(&self.theme).set(v); - self.colors_changed(); - } - - pub fn ensure_persistent_output_state( - &self, - output_id: &Rc, - ) -> Rc { - match self.persistent_output_states.get(output_id) { - Some(ds) => ds, - _ => { - let ds = self.new_persistent_output_state(); - self.persistent_output_states - .set(output_id.clone(), ds.clone()); - ds - } - } - } - - pub fn new_persistent_output_state(&self) -> Rc { - let x1 = self - .root - .outputs - .lock() - .values() - .map(|o| o.global.pos.get().x2()) - .max() - .unwrap_or(0); - Rc::new(PersistentOutputState { - transform: Default::default(), - scale: Default::default(), - pos: Cell::new((x1, 0)), - vrr_mode: Cell::new(self.default_vrr_mode.get()), - vrr_cursor_hz: Cell::new(self.default_vrr_cursor_hz.get()), - tearing_mode: Cell::new(self.default_tearing_mode.get()), - brightness: Cell::new(None), - blend_space: Cell::new(BlendSpace::Srgb), - use_native_gamut: Cell::new(false), - }) - } -} - -#[derive(Debug, Error)] -pub enum ShmScreencopyError { - #[error("There is no render context")] - NoRenderContext, - #[error("Could not create a bridge framebuffer")] - CreateTemporaryFb(#[source] GfxError), - #[error("Could not copy texture to bridge framebuffer")] - CopyToTemporary(#[source] GfxError), - #[error("Could not read pixels from texture")] - ReadPixels(#[source] GfxError), } diff --git a/src/state/animations.rs b/src/state/animations.rs new file mode 100644 index 00000000..e049c174 --- /dev/null +++ b/src/state/animations.rs @@ -0,0 +1,862 @@ +use jay_geometry::Rect; +use { + crate::{ + animation::{ + AnimationCurve, AnimationStyle, AnimationTick, RetainedExitLayer, RetainedToplevel, + expand_damage_rect, + multiphase::{ + MultiphasePhase, MultiphasePlan, MultiphasePlanFailure, MultiphaseRequest, + MultiphaseWindow, MultiphaseWindowHierarchy, partition_motion_groups, + plan_no_overlap_with_diagnostics, validate_phase_paths, + }, + spawn_in_start_rect, + }, + + tree::NodeId, + }, + std::rc::Rc, +}; + +use super::State; + +#[derive(Clone)] +pub(crate) struct LayoutAnimationCandidate { + node_id: NodeId, + old: Rect, + new: Rect, + curve: AnimationCurve, + style: AnimationStyle, + hierarchy: MultiphaseWindowHierarchy, +} + +fn coalesce_layout_animation_candidates( + candidates: Vec, +) -> Vec { + let mut merged: Vec = vec![]; + for candidate in candidates { + if let Some(existing) = merged + .iter_mut() + .find(|existing| existing.node_id == candidate.node_id) + { + existing.new = candidate.new; + existing.curve = candidate.curve; + existing.style = candidate.style; + existing.hierarchy = MultiphaseWindowHierarchy::new( + existing.hierarchy.source, + candidate.hierarchy.target, + ); + } else { + merged.push(candidate); + } + } + merged +} + +fn layout_animation_group_uses_plain( + candidates: &[LayoutAnimationCandidate], + group: &[usize], +) -> bool { + group + .iter() + .any(|&idx| candidates[idx].style == AnimationStyle::Plain) +} + +fn bridged_retarget_plan( + request: &MultiphaseRequest, + candidates: &[LayoutAnimationCandidate], + group: &[usize], + bridge_paths: &[Vec<(Rect, Rect)>], + bridge_phase_count: usize, + follow_phases: &[MultiphasePhase], +) -> Result { + let mut paths = vec![]; + for (group_pos, &idx) in group.iter().enumerate() { + let candidate = &candidates[idx]; + let window = request.windows[group_pos]; + let Some(bridge_path) = bridge_paths.get(group_pos) else { + return Err(MultiphasePlanFailure::NoPattern); + }; + let mut path = bridge_path.clone(); + let mut current = path + .last() + .map(|(_, to)| *to) + .unwrap_or(window.from); + while path.len() < bridge_phase_count { + path.push((current, current)); + } + if current != candidate.old { + return Err(MultiphasePlanFailure::NoPattern); + } + for phase in follow_phases { + match phase + .steps + .iter() + .find(|step| step.node_id == candidate.node_id) + { + Some(step) => { + if step.from != current { + return Err(MultiphasePlanFailure::NoPattern); + } + path.push((step.from, step.to)); + current = step.to; + } + None => path.push((current, current)), + } + } + if current != window.to { + return Err(MultiphasePlanFailure::NoPattern); + } + paths.push(path); + } + validate_phase_paths(request, &paths) +} + +impl State { + pub fn queue_tiled_animation( + self: &Rc, + node_id: NodeId, + old: Rect, + new: Rect, + ) { + let curve = self + .layout_animation_curve_override + .get() + .unwrap_or_else(|| self.animations.curve.get()); + self.queue_layout_animation( + node_id, + old, + new, + curve, + MultiphaseWindowHierarchy::default(), + ); + } + + pub fn queue_tiled_animation_with_hierarchy( + self: &Rc, + node_id: NodeId, + old: Rect, + new: Rect, + hierarchy: MultiphaseWindowHierarchy, + ) { + let curve = self + .layout_animation_curve_override + .get() + .unwrap_or_else(|| self.animations.curve.get()); + self.queue_layout_animation(node_id, old, new, curve, hierarchy); + } + + pub fn queue_linear_layout_animation( + self: &Rc, + node_id: NodeId, + old: Rect, + new: Rect, + ) { + self.queue_layout_animation( + node_id, + old, + new, + AnimationCurve::Linear, + MultiphaseWindowHierarchy::default(), + ); + } + + fn queue_layout_animation( + self: &Rc, + node_id: NodeId, + old: Rect, + new: Rect, + curve: AnimationCurve, + hierarchy: MultiphaseWindowHierarchy, + ) { + if !self.animations.enabled.get() + || !self.layout_animations_active.get() + || self.suppress_animations_for_next_layout.get() + { + return; + } + let (old_output, old_scale) = { + let (x, y) = old.center(); + let (output, _, _) = self.find_closest_output(x, y); + (output.id, output.global.persistent.scale.get()) + }; + let (new_output, new_scale) = { + let (x, y) = new.center(); + let (output, _, _) = self.find_closest_output(x, y); + (output.id, output.global.persistent.scale.get()) + }; + if old_output != new_output || old_scale != new_scale { + return; + } + let candidate = LayoutAnimationCandidate { + node_id, + old, + new, + curve, + style: self + .layout_animation_style_override + .get() + .unwrap_or_else(|| self.animations.style.get()), + hierarchy, + }; + if let Some(batch) = self.layout_animation_batch.borrow_mut().as_mut() { + batch.push(candidate); + return; + } + self.start_layout_animation_candidate(candidate, self.now_nsec()); + } + + fn start_layout_animation_candidate( + self: &Rc, + candidate: LayoutAnimationCandidate, + now_nsec: u64, + ) { + let started = self.animations.set_target( + candidate.node_id, + candidate.old, + candidate.new, + None, + now_nsec, + self.animations.duration_ms.get(), + candidate.curve, + ); + if started { + self.damage(expand_damage_rect( + candidate.old.union(candidate.new), + self.theme.sizes.border_width.get().max(0), + )); + self.ensure_animation_tick(); + } + } + + pub fn begin_layout_animation_batch(&self) { + self.layout_animation_batch + .borrow_mut() + .get_or_insert_with(Vec::new); + } + + pub fn finish_layout_animation_batch(self: &Rc) { + let Some(candidates) = self.layout_animation_batch.borrow_mut().take() else { + return; + }; + let candidates = coalesce_layout_animation_candidates(candidates); + if candidates.is_empty() { + return; + } + let now = self.now_nsec(); + let windows: Vec<_> = candidates + .iter() + .map(|candidate| { + MultiphaseWindow::with_hierarchy( + candidate.node_id, + self.animations + .visual_rect(candidate.node_id, candidate.old, now), + candidate.new, + candidate.hierarchy, + ) + }) + .collect(); + for group in partition_motion_groups(&windows, self.layout_animation_clearance()) { + if layout_animation_group_uses_plain(&candidates, &group) { + for idx in group { + self.start_layout_animation_candidate(candidates[idx].clone(), now); + } + continue; + } + if self.start_multiphase_layout_animation(&candidates, &windows, &group, now) { + continue; + } + for idx in group { + self.start_layout_animation_candidate(candidates[idx].clone(), now); + } + } + } + + fn layout_animation_clearance(&self) -> i32 { + let border = self.theme.sizes.border_width.get().max(0); + let gap = self.theme.sizes.gap.get().max(0); + if gap == 0 { border } else { gap + 2 * border } + } + + fn start_multiphase_layout_animation( + self: &Rc, + candidates: &[LayoutAnimationCandidate], + windows: &[MultiphaseWindow], + group: &[usize], + now_nsec: u64, + ) -> bool { + let request_windows: Vec<_> = group.iter().map(|&idx| windows[idx]).collect(); + let Some(first) = request_windows.first() else { + return false; + }; + let mut bounds = first.from.union(first.to); + for window in &request_windows[1..] { + bounds = bounds.union(window.from).union(window.to); + } + let request = MultiphaseRequest { + bounds, + windows: request_windows, + clearance: self.layout_animation_clearance(), + }; + if self.start_existing_phased_retarget(candidates, windows, group, &request, now_nsec) { + return true; + } + if self.start_bridged_phased_retarget(candidates, windows, group, &request, now_nsec) { + return true; + } + let plan = match plan_no_overlap_with_diagnostics(&request) { + Ok(plan) => plan, + Err(diagnostic) => { + log::debug!( + "falling back to plain layout animation for group {:?}: {:?}", + group, + diagnostic + ); + return false; + } + }; + self.start_multiphase_plan(candidates, windows, group, &plan.phases, now_nsec) + } + + fn start_existing_phased_retarget( + self: &Rc, + candidates: &[LayoutAnimationCandidate], + windows: &[MultiphaseWindow], + group: &[usize], + request: &MultiphaseRequest, + now_nsec: u64, + ) -> bool { + let mut paths = vec![]; + for &idx in group { + let candidate = &candidates[idx]; + let window = windows[idx]; + let Some(path) = + self.animations + .phased_route_to(candidate.node_id, window.to, now_nsec) + else { + return false; + }; + paths.push(path); + } + let plan = match validate_phase_paths(request, &paths) { + Ok(plan) => plan, + Err(error) => { + log::debug!( + "existing phased retarget rejected for group {:?}: {:?}", + group, + error + ); + return false; + } + }; + log::debug!("retargeting active phased animation for group {:?}", group); + self.start_multiphase_plan(candidates, windows, group, &plan.phases, now_nsec) + } + + fn start_bridged_phased_retarget( + self: &Rc, + candidates: &[LayoutAnimationCandidate], + windows: &[MultiphaseWindow], + group: &[usize], + request: &MultiphaseRequest, + now_nsec: u64, + ) -> bool { + let mut bridge_paths = vec![]; + let mut bridge_phase_count = 0; + let mut has_bridge = false; + for &idx in group { + let candidate = &candidates[idx]; + let window = windows[idx]; + if window.from == candidate.old { + bridge_paths.push(vec![]); + continue; + } + let Some(path) = + self.animations + .phased_route_to(candidate.node_id, candidate.old, now_nsec) + else { + return false; + }; + if !path.is_empty() { + has_bridge = true; + bridge_phase_count = bridge_phase_count.max(path.len()); + } + bridge_paths.push(path); + } + if !has_bridge { + return false; + } + + let settled_windows: Vec<_> = group + .iter() + .map(|&idx| { + let candidate = &candidates[idx]; + MultiphaseWindow::with_hierarchy( + candidate.node_id, + candidate.old, + candidate.new, + candidate.hierarchy, + ) + }) + .collect(); + let Some(first) = settled_windows.first() else { + return false; + }; + let mut bounds = first.from.union(first.to); + for window in &settled_windows[1..] { + bounds = bounds.union(window.from).union(window.to); + } + let settled_request = MultiphaseRequest { + bounds, + windows: settled_windows, + clearance: self.layout_animation_clearance(), + }; + let follow_plan = match plan_no_overlap_with_diagnostics(&settled_request) { + Ok(plan) => plan, + Err(diagnostic) => { + log::debug!( + "bridged phased retarget follow-up rejected for group {:?}: {:?}", + group, + diagnostic + ); + return false; + } + }; + let plan = match bridged_retarget_plan( + request, + candidates, + group, + &bridge_paths, + bridge_phase_count, + &follow_plan.phases, + ) { + Ok(plan) => plan, + Err(error) => { + log::debug!( + "bridged phased retarget rejected for group {:?}: {:?}", + group, + error + ); + return false; + } + }; + log::debug!("bridging active phased animation for group {:?}", group); + self.start_multiphase_plan(candidates, windows, group, &plan.phases, now_nsec) + } + + fn start_multiphase_plan( + self: &Rc, + candidates: &[LayoutAnimationCandidate], + windows: &[MultiphaseWindow], + group: &[usize], + plan_phases: &[crate::animation::multiphase::MultiphasePhase], + now_nsec: u64, + ) -> bool { + if plan_phases.is_empty() { + return false; + } + let mut entries = vec![]; + for &idx in group { + let candidate = &candidates[idx]; + let window = windows[idx]; + let mut current = window.from; + let mut damage = current.union(window.to); + let mut phases = vec![]; + for phase in plan_phases { + match phase + .steps + .iter() + .find(|step| step.node_id == candidate.node_id) + { + Some(step) => { + phases.push((step.from, step.to)); + damage = damage.union(step.from).union(step.to); + current = step.to; + } + None => phases.push((current, current)), + } + } + if current != window.to { + return false; + } + entries.push((candidate.clone(), phases, damage)); + } + let mut started_any = false; + for (candidate, phases, damage) in entries { + if self.animations.set_phased_target( + candidate.node_id, + phases, + None, + now_nsec, + self.animations.duration_ms.get(), + candidate.curve, + ) { + started_any = true; + self.damage(expand_damage_rect( + damage, + self.theme.sizes.border_width.get().max(0), + )); + } + } + if started_any { + self.ensure_animation_tick(); + } + started_any + } + + pub fn queue_spawn_in_animation( + self: &Rc, + node_id: NodeId, + target: Rect, + ) { + if !self.animations.enabled.get() || target.is_empty() { + return; + } + let start = spawn_in_start_rect(target); + let now = self.now_nsec(); + let started = self.animations.set_spawn_in( + node_id, + target, + None, + now, + self.animations.duration_ms.get(), + self.animations.curve.get(), + ); + if started { + self.damage(expand_damage_rect( + start.union(target), + self.theme.sizes.border_width.get().max(0), + )); + self.ensure_animation_tick(); + } + } + + pub fn queue_spawn_out_animation( + self: &Rc, + from: Rect, + frame_inset: i32, + retained: Rc, + active: bool, + layer: RetainedExitLayer, + ) { + if !self.animations.enabled.get() || from.is_empty() { + return; + } + let now = self.now_nsec(); + let started = self.animations.set_spawn_out( + from, + frame_inset, + retained, + active, + layer, + now, + self.animations.duration_ms.get(), + self.animations.curve.get(), + ); + if started { + self.damage(expand_damage_rect( + from, + self.theme.sizes.border_width.get().max(0), + )); + self.ensure_animation_tick(); + } + } + + pub fn set_animations_enabled(&self, enabled: bool) { + if self.animations.enabled.replace(enabled) && !enabled { + self.animations.clear(); + self.damage(self.root.extents.get()); + } + } + + pub fn set_animation_duration_ms(&self, duration_ms: u32) { + self.animations.duration_ms.set(duration_ms); + } + + pub fn set_animation_curve(&self, curve: u32) { + self.animations + .curve + .set(AnimationCurve::from_config(curve)); + } + + pub fn set_animation_style(&self, style: u32) -> bool { + let Some(style) = AnimationStyle::from_config(style) else { + return false; + }; + self.animations.style.set(style); + true + } + + pub fn set_animation_cubic_bezier(&self, x1: f32, y1: f32, x2: f32, y2: f32) -> bool { + let Some(curve) = AnimationCurve::from_cubic_bezier(x1, y1, x2, y2) else { + return false; + }; + self.animations.curve.set(curve); + true + } + + pub fn with_layout_animations(&self, f: impl FnOnce() -> T) -> T { + let prev_requested = self.layout_animations_requested.replace(true); + let prev_active = self.layout_animations_active.replace(true); + let res = f(); + self.layout_animations_requested.set(prev_requested); + self.layout_animations_active.set(prev_active); + res + } + + pub fn with_linear_layout_animations(&self, f: impl FnOnce() -> T) -> T { + let prev_requested = self.layout_animations_requested.replace(true); + let prev_active = self.layout_animations_active.replace(true); + let prev_curve = self + .layout_animation_curve_override + .replace(Some(AnimationCurve::Linear)); + let prev_style = self + .layout_animation_style_override + .replace(Some(AnimationStyle::Plain)); + let res = f(); + self.layout_animations_requested.set(prev_requested); + self.layout_animations_active.set(prev_active); + self.layout_animation_curve_override.set(prev_curve); + self.layout_animation_style_override.set(prev_style); + res + } + + fn ensure_animation_tick(self: &Rc) { + if self.animations.tick_is_active() { + return; + } + let outputs: Vec<_> = self.root.outputs.lock().values().cloned().collect(); + if outputs.is_empty() { + return; + } + let tick = Rc::new_cyclic(|weak| AnimationTick::new(self, weak)); + for output in &outputs { + tick.attach(output); + } + self.animations.set_tick(tick); + for output in &outputs { + self.damage(output.global.pos.get()); + } + } + +} + +#[cfg(test)] +mod tests { + use { + super::*, + crate::animation::multiphase::MultiphaseHierarchyPosition, + }; + + fn rect(x1: i32, y1: i32, x2: i32, y2: i32) -> Rect { + Rect::new_saturating(x1, y1, x2, y2) + } + + fn hierarchy( + source: MultiphaseHierarchyPosition, + target: MultiphaseHierarchyPosition, + ) -> MultiphaseWindowHierarchy { + MultiphaseWindowHierarchy::new(source, target) + } + + fn candidate(node_id: u32, style: AnimationStyle) -> LayoutAnimationCandidate { + candidate_rects( + node_id, + rect(0, 0, 100, 100), + rect(100, 0, 200, 100), + style, + ) + } + + fn candidate_rects( + node_id: u32, + old: Rect, + new: Rect, + style: AnimationStyle, + ) -> LayoutAnimationCandidate { + LayoutAnimationCandidate { + node_id: NodeId(node_id), + old, + new, + curve: AnimationCurve::Linear, + style, + hierarchy: MultiphaseWindowHierarchy::default(), + } + } + + #[test] + fn plain_style_candidate_forces_group_plain() { + let candidates = vec![ + candidate(1, AnimationStyle::Multiphase), + candidate(2, AnimationStyle::Plain), + ]; + + assert!(!layout_animation_group_uses_plain(&candidates, &[0])); + assert!(layout_animation_group_uses_plain(&candidates, &[0, 1])); + } + + #[test] + fn bridged_retarget_handles_second_rotation_interrupt() { + let a_left = rect(0, 0, 100, 100); + let c_mid = rect(100, 0, 200, 100); + let c_left = a_left; + let a_mid = c_mid; + let c_current = rect(150, 50, 250, 100); + let c_mid_lane = rect(100, 50, 200, 100); + let candidates = vec![ + candidate_rects(1, a_left, a_mid, AnimationStyle::Multiphase), + candidate_rects(3, c_mid, c_left, AnimationStyle::Multiphase), + ]; + let request = MultiphaseRequest { + bounds: rect(0, 0, 250, 100), + windows: vec![ + MultiphaseWindow::new(NodeId(1), a_left, a_mid), + MultiphaseWindow::new(NodeId(3), c_current, c_left), + ], + clearance: 0, + }; + let settled_request = MultiphaseRequest { + bounds: rect(0, 0, 200, 100), + windows: vec![ + MultiphaseWindow::new(NodeId(1), a_left, a_mid), + MultiphaseWindow::new(NodeId(3), c_mid, c_left), + ], + clearance: 0, + }; + let follow_plan = plan_no_overlap_with_diagnostics(&settled_request).unwrap(); + let bridge_paths = vec![vec![], vec![(c_current, c_mid_lane), (c_mid_lane, c_mid)]]; + + let plan = bridged_retarget_plan( + &request, + &candidates, + &[0, 1], + &bridge_paths, + 2, + &follow_plan.phases, + ) + .unwrap(); + + assert!(plan + .phases + .iter() + .any(|phase| phase.steps.iter().any(|step| step.node_id == NodeId(1)))); + assert!(plan + .phases + .iter() + .any(|phase| phase.steps.iter().any(|step| step.node_id == NodeId(3)))); + } + + #[test] + fn layout_animation_candidates_coalesce_duplicate_nodes() { + let source = MultiphaseHierarchyPosition { + parent: Some(NodeId(10).into()), + depth: 2, + sibling_index: Some(1), + ..Default::default() + }; + let intermediate = MultiphaseHierarchyPosition { + parent: Some(NodeId(11).into()), + depth: 1, + sibling_index: Some(0), + ..Default::default() + }; + let target = MultiphaseHierarchyPosition { + parent: Some(NodeId(12).into()), + depth: 0, + sibling_index: Some(2), + ..Default::default() + }; + let second_source = MultiphaseHierarchyPosition { + parent: Some(NodeId(20).into()), + depth: 1, + sibling_index: Some(0), + ..Default::default() + }; + let second_target = MultiphaseHierarchyPosition { + parent: Some(NodeId(20).into()), + depth: 1, + sibling_index: Some(1), + ..Default::default() + }; + + let candidates = vec![ + LayoutAnimationCandidate { + node_id: NodeId(1), + old: rect(0, 0, 100, 100), + new: rect(0, 0, 80, 100), + curve: AnimationCurve::Linear, + style: AnimationStyle::Multiphase, + hierarchy: hierarchy(source, intermediate), + }, + LayoutAnimationCandidate { + node_id: NodeId(2), + old: rect(100, 0, 200, 100), + new: rect(120, 0, 220, 100), + curve: AnimationCurve::Linear, + style: AnimationStyle::Multiphase, + hierarchy: hierarchy(second_source, second_target), + }, + LayoutAnimationCandidate { + node_id: NodeId(1), + old: rect(0, 0, 80, 100), + new: rect(0, 0, 60, 100), + curve: AnimationCurve::from_config(4), + style: AnimationStyle::Plain, + hierarchy: hierarchy(intermediate, target), + }, + ]; + + let merged = coalesce_layout_animation_candidates(candidates); + + assert_eq!(merged.len(), 2); + assert_eq!(merged[0].node_id, NodeId(1)); + assert_eq!(merged[0].old, rect(0, 0, 100, 100)); + assert_eq!(merged[0].new, rect(0, 0, 60, 100)); + assert_eq!(merged[0].curve, AnimationCurve::from_config(4)); + assert_eq!(merged[0].style, AnimationStyle::Plain); + assert_eq!(merged[0].hierarchy, hierarchy(source, target)); + assert_eq!(merged[1].node_id, NodeId(2)); + assert_eq!(merged[1].old, rect(100, 0, 200, 100)); + assert_eq!(merged[1].new, rect(120, 0, 220, 100)); + assert_eq!(merged[1].hierarchy, hierarchy(second_source, second_target)); + } + + #[test] + fn layout_animation_candidates_keep_coalesced_layout_noops() { + let hierarchy = MultiphaseWindowHierarchy::default(); + let candidates = vec![ + LayoutAnimationCandidate { + node_id: NodeId(1), + old: rect(0, 0, 100, 100), + new: rect(0, 0, 80, 100), + curve: AnimationCurve::Linear, + style: AnimationStyle::Multiphase, + hierarchy, + }, + LayoutAnimationCandidate { + node_id: NodeId(1), + old: rect(0, 0, 80, 100), + new: rect(0, 0, 100, 100), + curve: AnimationCurve::Linear, + style: AnimationStyle::Plain, + hierarchy, + }, + LayoutAnimationCandidate { + node_id: NodeId(2), + old: rect(100, 0, 200, 100), + new: rect(120, 0, 220, 100), + curve: AnimationCurve::Linear, + style: AnimationStyle::Multiphase, + hierarchy, + }, + ]; + + let merged = coalesce_layout_animation_candidates(candidates); + + assert_eq!(merged.len(), 2); + assert_eq!(merged[0].node_id, NodeId(1)); + assert_eq!(merged[0].old, rect(0, 0, 100, 100)); + assert_eq!(merged[0].new, rect(0, 0, 100, 100)); + assert_eq!(merged[0].style, AnimationStyle::Plain); + assert_eq!(merged[1].node_id, NodeId(2)); + } +} diff --git a/src/state/connectors.rs b/src/state/connectors.rs new file mode 100644 index 00000000..cc49ebe8 --- /dev/null +++ b/src/state/connectors.rs @@ -0,0 +1,154 @@ +use jay_geometry::Rect; +use { + crate::{ + backend::{ + BackendConnectorState, BackendDrmDevice, Connector, ConnectorId, MonitorInfo, + transaction::BackendConnectorTransactionError, + }, + ifs::{ + head_management::HeadManagers, + wlr_output_manager::{ + zwlr_output_head_v1::ZwlrOutputHeadV1, + zwlr_output_manager_v1::WlrOutputManagerId, + }, + wp_drm_lease_connector_v1::WpDrmLeaseConnectorV1, + wp_drm_lease_device_v1::WpDrmLeaseDeviceV1Global, + }, + + tree::OutputNode, + utils::{ + asyncevent::AsyncEvent, bindings::Bindings, copyhashmap::CopyHashMap, + }, + }, + jay_config::PciId, + std::{ + cell::{Cell, RefCell}, + rc::Rc, + }, +}; + +use super::State; + +pub struct ConnectorData { + pub id: ConnectorId, + pub connector: Rc, + pub handler: Cell>>, + pub connected: Cell, + pub name: Rc, + pub description: RefCell, + pub drm_dev: Option>, + pub async_event: Rc, + pub damaged: Cell, + pub damage: RefCell>, + pub needs_vblank_emulation: Cell, + pub damage_intersect: Cell, + pub state: RefCell, + pub head_managers: HeadManagers, + pub wlr_output_heads: CopyHashMap>, +} + +pub struct OutputData { + pub connector: Rc, + pub monitor_info: Rc, + pub node: Option>, + pub lease_connectors: Rc>, +} + +pub struct DrmDevData { + pub dev: Rc, + pub handler: Cell>>, + pub connectors: CopyHashMap>, + pub syspath: Option, + pub devnode: Option, + pub vendor: Option, + pub model: Option, + pub pci_id: Option, + pub lease_global: Rc, +} + +impl ConnectorData { + pub fn damage(&self) { + if !self.damaged.replace(true) { + self.connector.damage(); + } + } + + pub fn modify_state( + &self, + state: &State, + f: impl FnOnce(&mut BackendConnectorState), + ) -> Result<(), BackendConnectorTransactionError> { + let old = self.state.borrow().clone(); + let mut s = old.clone(); + f(&mut s); + if old == s { + return Ok(()); + } + s.serial = state.backend_connector_state_serials.next(); + let mut tran = self.connector.create_transaction()?; + tran.add(&self.connector, s.clone())?; + tran.prepare()?.apply()?.commit(); + self.set_state(state, s); + Ok(()) + } + + pub fn set_state(&self, state: &State, s: BackendConnectorState) { + let old = self.state.borrow().clone(); + if old.serial >= s.serial { + return; + } + *self.state.borrow_mut() = s.clone(); + if old.enabled != s.enabled { + self.head_managers.handle_enabled_change(state, s.enabled); + } + if old.active != s.active { + self.head_managers.handle_active_change(s.active); + } + if old.non_desktop_override != s.non_desktop_override { + self.head_managers + .handle_non_desktop_override_changed(s.non_desktop_override); + } + if old.vrr != s.vrr { + self.head_managers.handle_vrr_change(s.vrr); + } + if old.tearing != s.tearing { + self.head_managers.handle_tearing_enabled_change(s.tearing); + } + if old.format != s.format { + self.head_managers.handle_format_change(s.format); + } + if (old.color_space, old.eotf) != (s.color_space, s.eotf) { + self.head_managers + .handle_colors_change(s.color_space, s.eotf); + } + if old.mode != s.mode { + self.head_managers.handle_mode_change(s.mode); + for head in self.wlr_output_heads.lock().values() { + head.handle_mode_change(s.mode); + } + } + if let Some(output) = state.outputs.get(&self.connector.id()) + && let Some(node) = &output.node + { + node.update_state(old, s); + } + } +} + +impl DrmDevData { + pub fn make_render_device(&self) { + log::info!( + "Making {} the render device", + self.devnode.as_deref().unwrap_or("unknown"), + ); + self.dev.clone().make_render_device(); + } + + pub fn set_direct_scanout_enabled(&self, _state: &State, enabled: bool) { + self.dev.set_direct_scanout_enabled(enabled); + } + + pub fn set_flip_margin(&self, _state: &State, margin: u64) { + self.dev.set_flip_margin(margin); + } +} diff --git a/src/state/idle.rs b/src/state/idle.rs new file mode 100644 index 00000000..2637326e --- /dev/null +++ b/src/state/idle.rs @@ -0,0 +1,138 @@ +use { + crate::{ + backend::transaction::{BackendConnectorTransactionError, ConnectorTransaction}, + client::ClientId, + ifs::{ + ext_idle_notification_v1::ExtIdleNotificationV1, + wl_surface::zwp_idle_inhibitor_v1::{ + IdleInhibitorId, ZwpIdleInhibitorV1, + }, + }, + utils::{ + asyncevent::AsyncEvent, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, + hash_map_ext::HashMapExt, + }, + wire::ExtIdleNotificationV1Id, + }, + std::{cell::Cell, rc::Rc, time::Duration}, +}; + +use super::State; + +pub struct IdleState { + pub input: Cell, + pub change: AsyncEvent, + pub timeout: Cell, + pub grace_period: Cell, + pub key_press_enables_dpms: Cell, + pub mouse_move_enables_dpms: Cell, + pub timeout_changed: Cell, + pub inhibitors: CopyHashMap>, + pub inhibitors_changed: Cell, + pub backend_idle: Cell, + pub dpms_off_by_command: Cell, + pub inhibited_idle_notifications: + CopyHashMap<(ClientId, ExtIdleNotificationV1Id), Rc>, + pub in_grace_period: Cell, +} + +impl IdleState { + pub fn set_timeout(&self, state: &State, timeout: Duration) { + self.timeout.set(timeout); + self.timeout_changed(state); + } + + pub fn set_grace_period(&self, state: &State, grace_period: Duration) { + self.grace_period.set(grace_period); + self.timeout_changed(state); + } + + fn timeout_changed(&self, _state: &State) { + self.timeout_changed.set(true); + self.change.trigger(); + } + + pub fn add_inhibitor(&self, state: &State, inhibitor: &Rc) { + self.inhibitors.set(inhibitor.inhibit_id, inhibitor.clone()); + self.inhibitors_changed(state); + } + + pub fn remove_inhibitor(&self, state: &State, inhibitor: &ZwpIdleInhibitorV1) { + self.inhibitors.remove(&inhibitor.inhibit_id); + self.inhibitors_changed(state); + if self.inhibitors.is_empty() { + self.resume_inhibited_notifications(); + } + } + + fn inhibitors_changed(&self, _state: &State) { + self.inhibitors_changed.set(true); + self.change.trigger(); + } + + fn resume_inhibited_notifications(&self) { + for notification in self.inhibited_idle_notifications.lock().drain_values() { + notification.resume.trigger(); + } + } + + pub fn add_inhibited_notification(&self, n: &Rc) { + self.inhibited_idle_notifications + .set((n.client.id, n.id), n.clone()); + } + + pub fn remove_inhibited_notification(&self, n: &ExtIdleNotificationV1) { + self.inhibited_idle_notifications + .remove(&(n.client.id, n.id)); + } +} + +impl State { + pub fn input_occurred(self: &Rc, key_press: bool, mouse_move: bool) { + if self.idle.dpms_off_by_command.get() { + let enable_dpms = key_press && self.idle.key_press_enables_dpms.get() + || mouse_move && self.idle.mouse_move_enables_dpms.get(); + if enable_dpms && let Err(e) = self.set_dpms_active(true) { + log::error!("Could not enable DPMS after input: {}", ErrorFmt(e)); + } + } + if !self.idle.input.replace(true) { + self.idle.change.trigger(); + } + } + + pub fn set_backend_idle(&self, idle: bool) { + if self.idle.backend_idle.replace(idle) != idle { + self.root.update_visible(self); + } + } + + pub fn set_connectors_active( + self: &Rc, + active: bool, + ) -> Result<(), BackendConnectorTransactionError> { + let mut tran = ConnectorTransaction::new(self); + for connector in self.connectors.lock().values() { + let mut state = connector.state.borrow().clone(); + state.active = active; + tran.add(&connector.connector, state)?; + } + tran.prepare()?.apply()?.commit(); + self.set_backend_idle(!active); + Ok(()) + } + + pub fn set_dpms_active( + self: &Rc, + active: bool, + ) -> Result<(), BackendConnectorTransactionError> { + self.set_connectors_active(active)?; + self.idle.dpms_off_by_command.set(!active); + Ok(()) + } + + pub fn root_visible(&self) -> bool { + !self.idle.backend_idle.get() + } + +} diff --git a/src/state/render_context.rs b/src/state/render_context.rs new file mode 100644 index 00000000..048aa8b0 --- /dev/null +++ b/src/state/render_context.rs @@ -0,0 +1,244 @@ +use jay_units::scale::Scale; +use { + crate::{ + cursor::ServerCursors, + drm_feedback::DrmFeedback, + gfx_api::{GfxApi, GfxContext, GfxError}, + gfx_apis::create_gfx_context, + tree::{ + ContainerNode, FloatNode, Node, NodeVisitorBase, OutputNode, PlaceholderNode, + WorkspaceNode, + }, + utils::errorfmt::ErrorFmt, + video::drm::Drm, + }, + ahash::AHashMap, + bstr::ByteSlice, + std::rc::Rc, +}; + +use super::State; + +struct UpdateTextTexturesVisitor; +impl NodeVisitorBase for UpdateTextTexturesVisitor { + fn visit_container(&mut self, node: &Rc) { + node.node_visit_children(self); + } + fn visit_output(&mut self, node: &Rc) { + node.schedule_update_render_data(); + node.node_visit_children(self); + } + fn visit_float(&mut self, node: &Rc) { + node.node_visit_children(self); + } + fn visit_workspace(&mut self, node: &Rc) { + node.title_texture.take(); + node.node_visit_children(self); + } + fn visit_placeholder(&mut self, node: &Rc) { + node.textures.borrow_mut().clear(); + node.schedule_update_texture(); + node.node_visit_children(self); + } +} + +impl State { + pub fn create_gfx_context( + &self, + drm: &Drm, + api: Option, + ) -> Result, GfxError> { + create_gfx_context( + &self.eng, + &self.ring, + &self.eventfd_cache, + drm, + api.unwrap_or(self.default_gfx_api.get()), + self.caps_thread.as_ref(), + ) + } + + pub fn add_output_scale(&self, scale: Scale) { + if self.scales.add(scale) { + self.output_scales_changed(); + } + } + + pub fn remove_output_scale(&self, scale: Scale) { + if self.scales.remove(&scale) { + self.output_scales_changed(); + } + } + + pub fn add_cursor_size(&self, size: u32) { + if self.cursor_sizes.add(size) { + self.cursor_sizes_changed(); + } + } + + pub fn remove_cursor_size(&self, size: u32) { + if self.cursor_sizes.remove(&size) { + self.cursor_sizes_changed(); + } + } + + fn output_scales_changed(&self) { + UpdateTextTexturesVisitor.visit_display(&self.root); + self.reload_cursors(); + self.update_xwayland_wire_scale(); + self.icons.update_sizes(self); + } + + fn cursor_sizes_changed(&self) { + self.reload_cursors(); + } + + pub fn devices_enumerated(&self) { + if let Some(config) = self.config.get() { + config.devices_enumerated() + } + if self.render_ctx.is_none() { + for dev in self.drm_devs.lock().values() { + if let Ok(version) = dev.dev.version() + && version.name.contains_str("nvidia") + { + continue; + } + if dev.dev.gfx_fast_ram_access() { + continue; + } + dev.make_render_device(); + if self.render_ctx.is_some() { + break; + } + } + if self.render_ctx.is_none() + && let Some(dev) = self.drm_devs.lock().values().next() + { + dev.make_render_device(); + } + } + } + + pub fn set_render_ctx(&self, ctx: Option>) { + self.explicit_sync_supported.set(false); + self.render_ctx.set(ctx.clone()); + self.render_ctx_version.fetch_add(1); + self.cursors.set(None); + self.drm_feedback.set(None); + self.icons.clear(); + self.wait_for_syncobj + .set_ctx(ctx.as_ref().and_then(|c| c.syncobj_ctx().cloned())); + self.virtual_outputs.handle_render_ctx_change(self); + + 'handle_new_feedback: { + if let Some(ctx) = &ctx { + let feedback = match DrmFeedback::new(&self.drm_feedback_ids, &**ctx) { + Ok(fb) => fb, + Err(e) => { + log::error!("Could not create new DRM feedback: {}", ErrorFmt(e)); + break 'handle_new_feedback; + } + }; + for watcher in self.drm_feedback_consumers.lock().values() { + watcher.send_feedback(&feedback); + } + self.drm_feedback.set(Some(Rc::new(feedback))); + } + } + + { + struct Walker; + impl NodeVisitorBase for Walker { + fn visit_container(&mut self, node: &Rc) { + node.node_visit_children(self); + } + fn visit_workspace(&mut self, node: &Rc) { + node.title_texture.take(); + node.node_visit_children(self); + } + fn visit_output(&mut self, node: &Rc) { + node.render_data.borrow_mut().titles.clear(); + node.render_data.borrow_mut().status.take(); + node.set_hardware_cursor(None); + node.node_visit_children(self); + } + fn visit_float(&mut self, node: &Rc) { + node.node_visit_children(self); + } + fn visit_placeholder(&mut self, node: &Rc) { + node.textures.borrow_mut().clear(); + node.node_visit_children(self); + } + } + Walker.visit_display(&self.root); + let mut updated_buffers = AHashMap::new(); + for buffer in self.gfx_ctx_changed.iter() { + let had_buffer_texture = buffer.handle_gfx_context_change(); + updated_buffers.insert(Rc::as_ptr(&buffer), had_buffer_texture); + } + for client in self.clients.clients.borrow_mut().values() { + for surface in client.data.objects.surfaces.lock().values() { + let had_shm_texture = surface.reset_shm_textures(); + if let Some(buffer) = surface.buffer.get() { + let buf = &buffer.buffer.buf; + let had_buffer_texture = *updated_buffers.get(&Rc::as_ptr(buf)).unwrap(); + if had_shm_texture || had_buffer_texture { + buf.update_texture_or_log(surface, true); + } + } + } + } + } + + if ctx.is_some() { + self.reload_cursors(); + UpdateTextTexturesVisitor.visit_display(&self.root); + } + + for cursor_user_groups in self.cursor_user_groups.lock().values() { + cursor_user_groups.render_ctx_changed(); + } + + if let Some(ctx) = &ctx { + if let Some(ctx) = ctx.syncobj_ctx() + && ctx.supports_async_wait() + { + self.explicit_sync_supported.set(true); + } + if !self.render_ctx_ever_initialized.replace(true) + && let Some(config) = self.config.get() + { + config.graphics_initialized(); + } + } + + for watcher in self.render_ctx_watchers.lock().values() { + watcher.send_render_ctx(ctx.clone()); + } + + for client in self.clients.clients.borrow_mut().values() { + for sc in client.data.objects.ext_copy_sessions.lock().values() { + sc.stop(); + } + } + + self.expose_new_singletons(); + } + + fn reload_cursors(&self) { + if let Some(ctx) = self.render_ctx.get() { + let cursors = match ServerCursors::load(&ctx, self) { + Ok(c) => c.map(Rc::new), + Err(e) => { + log::error!("Could not load the cursors: {}", ErrorFmt(e)); + None + } + }; + self.cursors.set(cursors); + for cursor_user_group in self.cursor_user_groups.lock().values() { + cursor_user_group.reload_known_cursor(); + } + } + } +} diff --git a/src/state/rendering.rs b/src/state/rendering.rs new file mode 100644 index 00000000..5d87ef7b --- /dev/null +++ b/src/state/rendering.rs @@ -0,0 +1,212 @@ +use jay_theme::Color; +use jay_geometry::{Rect, Region}; +use jay_units::fixed::Fixed; +use jay_units::scale::Scale; +use { + crate::{ + clientmem::ClientMemOffset, + cmm::{cmm_description::ColorDescription, cmm_render_intent::RenderIntent}, + format::Format, + gfx_api::{ + AcquireSync, AlphaMode, BufferResv, FdSync, GfxBlendBuffer, GfxError, GfxFramebuffer, + GfxTexture, PendingShmTransfer, ReleaseSync, STAGING_DOWNLOAD, SampleRect, + }, + ifs::zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1, + + renderer::Renderer, + + tree::{OutputNode, Transform}, + }, + std::rc::Rc, + thiserror::Error, +}; + +use super::State; + +impl State { + pub fn present_output( + &self, + output: &OutputNode, + fb: &Rc, + cd: &Rc, + acquire_sync: AcquireSync, + release_sync: ReleaseSync, + tex: &Rc, + render_hw_cursor: bool, + blend_buffer: Option<&Rc>, + blend_cd: &Rc, + ) -> Result, GfxError> { + let sync = fb.render_output( + acquire_sync, + release_sync, + cd, + output, + self, + Some(output.global.pos.get()), + output.global.persistent.scale.get(), + render_hw_cursor, + true, + blend_buffer, + blend_cd, + )?; + output.latched(false); + output.perform_screencopies( + tex, + cd, + None, + &AcquireSync::Unnecessary, + ReleaseSync::None, + !render_hw_cursor, + 0, + 0, + None, + ); + Ok(sync) + } + + pub fn perform_screencopy( + &self, + src: &Rc, + resv: Option<&Rc>, + acquire_sync: &AcquireSync, + release_sync: ReleaseSync, + src_cd: &Rc, + target: &Rc, + target_acquire_sync: AcquireSync, + target_release_sync: ReleaseSync, + target_transform: Transform, + target_cd: &Rc, + position: Rect, + render_hardware_cursors: bool, + x_off: i32, + y_off: i32, + size: Option<(i32, i32)>, + transform: Transform, + scale: Scale, + ) -> Result, GfxError> { + let mut ops = vec![]; + let mut renderer = Renderer { + base: target.renderer_base(&mut ops, scale, target_transform), + state: self, + logical_extents: position.at_point(0, 0), + pixel_extents: { + let (width, height) = target.logical_size(target_transform); + Rect::new_sized_saturating(0, 0, width, height) + }, + stretch: None, + corner_radius: None, + }; + let mut sample_rect = SampleRect::identity(); + sample_rect.buffer_transform = transform; + renderer.base.render_texture( + src, + None, + x_off, + y_off, + Some(sample_rect), + size, + scale, + None, + resv.cloned(), + acquire_sync.clone(), + release_sync, + false, + src_cd, + RenderIntent::Perceptual, + AlphaMode::PremultipliedElectrical, + ); + if render_hardware_cursors + && let Some(cursor_user_group) = self.cursor_user_group_hardware_cursor.get() + && let Some(cursor_user) = cursor_user_group.active() + && let Some(cursor) = cursor_user.get() + { + let (mut x, mut y) = cursor_user.position(); + x = x + x_off - Fixed::from_int(position.x1()); + y = y + y_off - Fixed::from_int(position.y1()); + cursor.render(&mut renderer, x, y); + } + target.render( + target_acquire_sync, + target_release_sync, + target_cd, + &ops, + Some(&Color::SOLID_BLACK), + &target_cd.linear, + None, + target_cd, + ) + } + + pub fn perform_shm_screencopy( + &self, + src: &Rc, + src_cd: &Rc, + acquire_sync: &AcquireSync, + position: Rect, + x_off: i32, + y_off: i32, + size: Option<(i32, i32)>, + capture: &Rc, + mem: &Rc, + stride: i32, + format: &'static Format, + transform: Transform, + scale: Scale, + ) -> Result, ShmScreencopyError> { + let Some(ctx) = self.render_ctx.get() else { + return Err(ShmScreencopyError::NoRenderContext); + }; + let fb = ctx + .clone() + .create_internal_fb( + &self.cpu_worker, + capture.rect.width(), + capture.rect.height(), + stride, + format, + ) + .map_err(ShmScreencopyError::CreateTemporaryFb)?; + self.perform_screencopy( + src, + None, + acquire_sync, + ReleaseSync::None, + src_cd, + &(fb.clone() as Rc), + AcquireSync::Unnecessary, + ReleaseSync::None, + transform, + self.color_manager.srgb_gamma22(), + position, + true, + x_off - capture.rect.x1(), + y_off - capture.rect.y1(), + size, + transform, + scale, + ) + .map_err(ShmScreencopyError::CopyToTemporary)?; + let staging = ctx.create_staging_buffer(fb.staging_size(), STAGING_DOWNLOAD); + let pending = fb + .download( + &staging, + capture.clone(), + mem.clone(), + Region::new(capture.rect.at_point(0, 0)), + ) + .map_err(ShmScreencopyError::ReadPixels)?; + Ok(pending) + } +} + +#[derive(Debug, Error)] +pub enum ShmScreencopyError { + #[error("There is no render context")] + NoRenderContext, + #[error("Could not create a bridge framebuffer")] + CreateTemporaryFb(#[source] GfxError), + #[error("Could not copy texture to bridge framebuffer")] + CopyToTemporary(#[source] GfxError), + #[error("Could not read pixels from texture")] + ReadPixels(#[source] GfxError), +} diff --git a/src/state/settings.rs b/src/state/settings.rs new file mode 100644 index 00000000..817cae0a --- /dev/null +++ b/src/state/settings.rs @@ -0,0 +1,270 @@ +use jay_theme::{BarPosition, Color, ThemeColor, ThemeSized}; +use jay_logger::LogLevel; +use { + crate::{ + config::ConfigProxy, + ifs::wl_output::{BlendSpace, OutputId, PersistentOutputState}, + + + tree::{ContainerNode, FloatNode, Node, NodeVisitorBase, OutputNode, WorkspaceDisplayOrder}, + }, + std::{cell::Cell, rc::Rc, sync::Arc}, +}; + +use super::State; + +impl State { + pub(super) fn expose_new_singletons(&self) { + self.globals.expose_new_singletons(self); + } + + pub fn set_color_management_enabled(&self, enabled: bool) { + self.color_management_enabled.set(enabled); + self.expose_new_singletons(); + } + + pub fn set_primary_selection_enabled(&self, enabled: bool) { + self.enable_primary_selection.set(enabled); + self.expose_new_singletons(); + } + + pub fn set_explicit_sync_enabled(&self, enabled: bool) { + self.explicit_sync_enabled.set(enabled); + self.expose_new_singletons(); + } + + pub fn set_log_level(&self, level: LogLevel) { + if let Some(logger) = &self.logger { + logger.set_level(level); + } + } + + pub fn perform_clean_logs_older_than(&self) { + if let Some(time) = self.clean_logs_older_than.get() + && let Some(logger) = &self.logger + { + logger.clean_logs_older_than(time); + } + } + + fn colors_changed(&self) { + struct V; + impl NodeVisitorBase for V { + fn visit_container(&mut self, node: &Rc) { + node.on_colors_changed(); + node.node_visit_children(self); + } + + fn visit_output(&mut self, node: &Rc) { + node.on_colors_changed(); + node.node_visit_children(self); + } + + fn visit_float(&mut self, node: &Rc) { + node.on_colors_changed(); + node.node_visit_children(self); + } + } + self.root.clone().node_visit(&mut V); + self.damage(self.root.extents.get()); + self.icons.clear(); + } + + pub fn reset_colors(&self) { + self.theme.colors.reset(); + self.colors_changed(); + } + + pub fn quit(&self) { + log::info!("Quitting"); + self.ring.stop(); + } + + pub fn reload_config(self: &Rc) { + log::info!("Reloading config"); + let config = ConfigProxy::default(self); + if let Some(config) = self.config.take() { + config.destroy(); + for seat in self.globals.seats.lock().values() { + seat.clear_shortcuts(); + } + } + config.configure(true); + self.config.set(Some(Rc::new(config))); + } + + pub fn set_ei_socket_enabled(self: &Rc, enabled: bool) { + self.enable_ei_acceptor.set(enabled); + self.update_ei_acceptor(); + } + + pub fn set_workspace_display_order(&self, order: WorkspaceDisplayOrder) { + self.workspace_display_order.set(order); + for output in self.root.outputs.lock().values() { + output.handle_workspace_display_order_update(); + } + } + + fn spaces_changed(&self) { + struct V; + impl NodeVisitorBase for V { + fn visit_container(&mut self, node: &Rc) { + node.on_spaces_changed(); + node.node_visit_children(self); + } + fn visit_output(&mut self, node: &Rc) { + node.on_spaces_changed(); + node.node_visit_children(self); + } + fn visit_float(&mut self, node: &Rc) { + node.on_spaces_changed(); + node.node_visit_children(self); + } + } + self.root.clone().node_visit(&mut V); + self.damage(self.root.extents.get()); + self.icons.update_sizes(self); + } + + pub fn set_show_bar(&self, show: bool) { + self.show_bar.set(show); + self.spaces_changed(); + } + + #[allow(dead_code)] + pub fn set_show_titles(&self, show: bool) { + self.theme.show_titles.set(show); + self.spaces_changed(); + } + + #[allow(dead_code)] + pub fn set_floating_titles(&self, floating: bool) { + self.theme.floating_titles.set(floating); + self.spaces_changed(); + } + + pub fn set_ui_drag_enabled(&self, enabled: bool) { + self.ui_drag_enabled.set(enabled); + } + + pub fn set_ui_drag_threshold(&self, threshold: i32) { + self.ui_drag_threshold_squared + .set(threshold.saturating_mul(threshold)); + } + + #[allow(dead_code)] + pub fn set_show_pin_icon(&self, show: bool) { + self.show_pin_icon.set(show); + } + + pub fn set_float_above_fullscreen(&self, v: bool) { + self.float_above_fullscreen.set(v); + for seat in self.globals.seats.lock().values() { + seat.emulate_cursor_moved(); + seat.trigger_tree_changed(false); + } + self.root.update_visible(self); + } + + pub fn reset_sizes(&self) { + self.theme.sizes.reset(); + self.spaces_changed(); + } + + fn fonts_changed(&self) { + struct V; + impl NodeVisitorBase for V { + fn visit_container(&mut self, node: &Rc) { + node.node_visit_children(self); + } + fn visit_output(&mut self, node: &Rc) { + node.schedule_update_render_data(); + node.node_visit_children(self); + } + fn visit_float(&mut self, node: &Rc) { + node.node_visit_children(self); + } + } + self.root.clone().node_visit(&mut V); + } + + pub fn reset_fonts(&self) { + let theme = &self.theme; + theme.font.set(self.theme.default_font.clone()); + theme.bar_font.set(None); + theme.title_font.set(None); + self.fonts_changed(); + } + + pub fn set_font(&self, font: &str) { + self.theme.font.set(Arc::new(font.to_string())); + self.fonts_changed(); + } + + pub fn set_bar_font(&self, font: Option<&str>) { + let font = font.map(|font| Arc::new(font.to_string())); + self.theme.bar_font.set(font); + self.fonts_changed(); + } + + #[allow(dead_code)] + pub fn set_title_font(&self, font: Option<&str>) { + let font = font.map(|font| Arc::new(font.to_string())); + self.theme.title_font.set(font); + self.fonts_changed(); + } + + pub fn set_bar_position(&self, p: BarPosition) { + self.theme.bar_position.set(p); + self.spaces_changed(); + } + + pub fn set_size(&self, sized: ThemeSized, size: i32) { + let field = sized.field(&self.theme); + field.val.set(size); + field.set.set(true); + self.spaces_changed(); + } + + pub fn set_color(&self, colored: ThemeColor, v: Color) { + colored.field(&self.theme).set(v); + self.colors_changed(); + } + + pub fn ensure_persistent_output_state( + &self, + output_id: &Rc, + ) -> Rc { + match self.persistent_output_states.get(output_id) { + Some(ds) => ds, + _ => { + let ds = self.new_persistent_output_state(); + self.persistent_output_states + .set(output_id.clone(), ds.clone()); + ds + } + } + } + + pub fn new_persistent_output_state(&self) -> Rc { + let x1 = self + .root + .outputs + .lock() + .values() + .map(|o| o.global.pos.get().x2()) + .max() + .unwrap_or(0); + Rc::new(PersistentOutputState { + transform: Default::default(), + scale: Default::default(), + pos: Cell::new((x1, 0)), + vrr_mode: Cell::new(self.default_vrr_mode.get()), + vrr_cursor_hz: Cell::new(self.default_vrr_cursor_hz.get()), + tearing_mode: Cell::new(self.default_tearing_mode.get()), + brightness: Cell::new(None), + blend_space: Cell::new(BlendSpace::Srgb), + use_native_gamut: Cell::new(false), + }) + } +} diff --git a/src/state/tree_ops.rs b/src/state/tree_ops.rs new file mode 100644 index 00000000..2c3872d5 --- /dev/null +++ b/src/state/tree_ops.rs @@ -0,0 +1,539 @@ +use jay_geometry::Rect; +use { + crate::{ + ifs::wl_seat::WlSeatGlobal, + + tree::{ + ContainerNode, ContainerSplit, Direction, FindTreeUsecase, FloatNode, FoundNode, Node, + OutputNode, TileState, ToplevelData, ToplevelNode, ToplevelNodeBase, WorkspaceNode, + WsMoveConfig, generic_node_visitor, move_ws_to_output, toplevel_hide_for_scratchpad, + toplevel_restore_from_scratchpad, toplevel_set_workspace, + }, + }, + std::{cell::Cell, ops::Deref, rc::Rc}, +}; + +use super::{ScratchpadEntry, State}; + +impl State { + pub fn tree_changed(&self) { + // log::info!("state.tree_changed\n{:?}", Backtrace::new()); + if self.tree_changed_sent.replace(true) { + return; + } + let seats = self.globals.seats.lock(); + for seat in seats.values() { + seat.trigger_tree_changed(false); + } + } + + pub fn ensure_map_workspace(&self, seat: Option<&Rc>) -> Rc { + seat.cloned() + .or_else(|| self.seat_queue.last().map(|s| s.deref().clone())) + .map(|s| s.get_fallback_output()) + .or_else(|| self.root.outputs.lock().values().next().cloned()) + .or_else(|| self.dummy_output.get()) + .unwrap() + .ensure_workspace() + } + + pub fn map_tiled(self: &Rc, node: Rc) { + let seat = self.seat_queue.last(); + let animate_new_app_map = node.tl_data().parent.is_none() + && node.tl_data().kind.is_app_window() + && !node.tl_data().visible.get(); + if animate_new_app_map { + self.with_layout_animations(|| self.do_map_tiled(seat.as_deref(), node.clone(), true)); + } else { + self.do_map_tiled(seat.as_deref(), node.clone(), true); + } + self.focus_after_map(node, seat.as_deref()); + } + + pub fn map_tiled_without_autotile(self: &Rc, node: Rc) { + let seat = self.seat_queue.last(); + self.do_map_tiled(seat.as_deref(), node.clone(), false); + self.focus_after_map(node, seat.as_deref()); + } + + fn do_map_tiled( + self: &Rc, + seat: Option<&Rc>, + node: Rc, + autotile: bool, + ) { + let ws = self.ensure_map_workspace(seat); + self.map_tiled_on_(node, &ws, autotile); + } + + pub fn map_tiled_on(self: &Rc, node: Rc, ws: &Rc) { + self.map_tiled_on_(node, ws, false); + } + + fn map_tiled_on_( + self: &Rc, + node: Rc, + ws: &Rc, + autotile: bool, + ) { + if let Some(c) = ws.container.get() { + let la = c.clone().tl_last_active_child(); + let lap = la + .tl_data() + .parent + .get() + .and_then(|n| n.node_into_container()); + if let Some(lap) = lap { + if autotile { + lap.add_tiled_child_after(&*la, node); + } else { + lap.add_child_after(&*la, node); + } + } else { + c.append_child(node); + } + } else { + let container = ContainerNode::new(self, ws, node, ContainerSplit::Horizontal); + ws.set_container(&container); + } + } + + pub fn map_floating( + self: &Rc, + node: Rc, + mut width: i32, + mut height: i32, + workspace: &Rc, + abs_pos: Option<(i32, i32)>, + ) -> Rc { + width += 2 * self.theme.sizes.border_width.get(); + height += + 2 * self.theme.sizes.border_width.get() + self.theme.title_plus_underline_height(); + let output = workspace.output.get(); + let output_rect = output.global.pos.get(); + let position = if let Some((mut x1, mut y1)) = abs_pos { + if y1 <= output_rect.y1() { + y1 = output_rect.y1() + 1; + } + if y1 > output_rect.y2() { + y1 = output_rect.y2(); + } + y1 -= self.theme.sizes.border_width.get() + self.theme.title_plus_underline_height(); + x1 -= self.theme.sizes.border_width.get(); + Rect::new_sized_saturating(x1, y1, width, height) + } else { + let mut x1 = output_rect.x1(); + let mut y1 = output_rect.y1(); + if width < output_rect.width() { + x1 += (output_rect.width() - width) / 2; + } else { + width = output_rect.width(); + } + if height < output_rect.height() { + y1 += (output_rect.height() - height) / 2; + } else { + height = output_rect.height(); + } + Rect::new_sized_saturating(x1, y1, width, height) + }; + let float = FloatNode::new(self, workspace, position, node.clone()); + self.focus_after_map(node, self.seat_queue.last().as_deref()); + float + } + + pub fn send_to_scratchpad(self: &Rc, name: &str, node: Rc) { + if node.node_is_placeholder() { + return; + } + let identifier = node.tl_data().identifier.get(); + if !toplevel_hide_for_scratchpad(node.clone()) { + return; + } + let entry = Rc::new(ScratchpadEntry { + node: Rc::downgrade(&node), + identifier, + hidden: Cell::new(true), + }); + { + let mut scratchpads = self.scratchpads.borrow_mut(); + for entries in scratchpads.values_mut() { + entries.retain(|entry| entry.alive() && entry.identifier != identifier); + } + scratchpads + .entry(name.to_string()) + .or_default() + .push(entry); + } + self.tree_changed(); + } + + pub fn toggle_scratchpad(self: &Rc, seat: &Rc, name: &str) { + let entry = { + let mut scratchpads = self.scratchpads.borrow_mut(); + let Some(entries) = scratchpads.get_mut(name) else { + return; + }; + entries.retain(|entry| entry.alive()); + // Prefer the currently-shown window; otherwise act on the most recent. + entries + .iter() + .rev() + .find(|entry| !entry.hidden.get()) + .or_else(|| entries.last()) + .cloned() + }; + let Some(entry) = entry else { + return; + }; + if entry.hidden.get() { + self.show_scratchpad_entry(seat, name, &entry); + } else if entry.node().is_some_and(|node| !node.node_visible()) { + self.move_scratchpad_entry_to_current_workspace(seat, &entry); + } else { + self.hide_scratchpad_entry(&entry); + } + } + + /// Cycles through the windows of a scratchpad, one at a time: + /// nothing shown -> first window -> ... -> last window -> nothing shown. + pub fn cycle_scratchpad(self: &Rc, seat: &Rc, name: &str) { + let (current, next) = { + let mut scratchpads = self.scratchpads.borrow_mut(); + let Some(entries) = scratchpads.get_mut(name) else { + return; + }; + entries.retain(|entry| entry.alive()); + match entries.iter().position(|entry| !entry.hidden.get()) { + // Nothing shown yet: bring up the first window. + None => (None, entries.first().cloned()), + // Hide the shown window and advance; on the last window, `next` + // is `None`, so the scratchpad toggles off. + Some(i) => (entries.get(i).cloned(), entries.get(i + 1).cloned()), + } + }; + if let Some(current) = ¤t { + self.hide_scratchpad_entry(current); + } + if let Some(next) = &next { + self.show_scratchpad_entry(seat, name, next); + } + } + + fn hide_scratchpad_entry(self: &Rc, entry: &Rc) { + if entry.hidden.get() { + return; + } + let Some(node) = entry.node() else { + return; + }; + if toplevel_hide_for_scratchpad(node) { + entry.hidden.set(true); + self.tree_changed(); + } + } + + fn show_scratchpad_entry( + self: &Rc, + seat: &Rc, + name: &str, + entry: &Rc, + ) { + if !entry.hidden.get() { + return; + } + let Some(node) = entry.node() else { + return; + }; + // Only one window of a scratchpad is visible at a time. + let siblings: Vec<_> = { + let scratchpads = self.scratchpads.borrow(); + scratchpads + .get(name) + .into_iter() + .flatten() + .filter(|sibling| !Rc::ptr_eq(sibling, entry) && !sibling.hidden.get()) + .cloned() + .collect() + }; + for sibling in siblings { + self.hide_scratchpad_entry(&sibling); + } + let ws = seat.get_fallback_output().ensure_workspace(); + toplevel_restore_from_scratchpad(self, node.clone(), &ws); + entry.hidden.set(false); + node.node_do_focus(seat, Direction::Unspecified); + seat.maybe_schedule_warp_mouse_to_focus(); + self.tree_changed(); + } + + fn move_scratchpad_entry_to_current_workspace( + self: &Rc, + seat: &Rc, + entry: &Rc, + ) { + let Some(node) = entry.node() else { + return; + }; + let ws = seat.get_fallback_output().ensure_workspace(); + toplevel_set_workspace(self, node.clone(), &ws); + node.node_do_focus(seat, Direction::Unspecified); + seat.maybe_schedule_warp_mouse_to_focus(); + self.tree_changed(); + } + + fn focus_after_map(&self, node: Rc, seat: Option<&Rc>) { + if !node.node_visible() { + return; + } + let Some(seat) = seat else { + return; + }; + if let Some(config) = self.config.get() + && !config.auto_focus(node.tl_data()) + { + return; + } + node.node_do_focus(&seat, Direction::Unspecified); + } + + pub fn show_workspace2( + &self, + seat: Option<&Rc>, + output: &Rc, + ws: &Rc, + ) { + let mut pinned_is_focused = false; + if let Some(seat) = seat { + for pinned in output.pinned.iter() { + pinned + .deref() + .clone() + .node_visit(&mut generic_node_visitor(|node| { + node.node_seat_state().for_each_kb_focus(|s| { + pinned_is_focused |= s.id() == seat.id(); + }); + })); + } + } + let did_change = output.show_workspace(&ws); + if !pinned_is_focused && let Some(seat) = seat { + ws.clone().node_do_focus(seat, Direction::Unspecified); + } + if !did_change { + return; + } + ws.flush_jay_workspaces(); + if !output.is_dummy { + output.schedule_update_render_data(); + self.tree_changed(); + output.workspace_switched.trigger(); + } + } + + pub fn show_workspace( + &self, + seat: &Rc, + name: &str, + output: Option>, + ) { + let output = output.unwrap_or_else(|| seat.get_fallback_output()); + let ws = match output.find_workspace(name) { + Some(ws) => ws, + _ => { + if output.is_dummy { + log::warn!("Not showing workspace because seat is on dummy output"); + return; + } + output.create_workspace(name) + } + }; + self.show_workspace2(Some(seat), &ws.output.get(), &ws); + seat.maybe_schedule_warp_mouse_to_focus(); + } + + pub fn float_map_ws(&self) -> Rc { + if let Some(seat) = self.seat_queue.last() { + let output = seat.get_fallback_output(); + if !output.is_dummy { + return output.ensure_workspace(); + } + } + if let Some(output) = self.root.outputs.lock().values().next().cloned() { + return output.ensure_workspace(); + } + self.dummy_output.get().unwrap().ensure_workspace() + } + + pub fn output_extents_changed(&self) { + self.root.update_extents(); + for seat in self.globals.seats.lock().values() { + seat.output_extents_changed(); + } + } + + pub fn find_closest_output(&self, mut x: i32, mut y: i32) -> (Rc, i32, i32) { + let mut optimal_dist = i128::MAX; + let mut optimal_output = None; + let outputs = self.root.outputs.lock(); + for output in outputs.values() { + let pos = output.global.pos.get(); + let dist = pos.dist_squared(x, y); + if dist == 0 { + if pos.contains(x, y) { + return (output.clone(), x, y); + } + } + if dist < optimal_dist { + optimal_dist = dist; + optimal_output = Some(output.clone()); + } + } + if let Some(output) = optimal_output { + let pos = output.global.pos.get(); + if pos.is_empty() { + return (output, pos.x1(), pos.y1()); + } + if x < pos.x1() { + x = pos.x1(); + } else if x >= pos.x2() { + x = pos.x2() - 1; + } + if y < pos.y1() { + y = pos.y1(); + } else if y >= pos.y2() { + y = pos.y2() - 1; + } + return (output, x, y); + } + (self.dummy_output.get().unwrap(), 0, 0) + } + + pub fn initial_tile_state(&self, data: &ToplevelData) -> Option { + self.config.get()?.initial_tile_state(data) + } + + pub fn predict_tiled_body_size(&self) -> Option<(i32, i32)> { + let seat = self.seat_queue.last(); + let ws = self.ensure_map_workspace(seat.as_deref()); + let pos = ws.position.get(); + if pos.is_empty() { + return None; + } + if let Some(c) = ws.container.get() { + let la = c.clone().tl_last_active_child(); + let target = la + .tl_data() + .parent + .get() + .and_then(|n| n.node_into_container()) + .unwrap_or(c); + Some(target.predict_child_body_size()) + } else { + Some((pos.width(), pos.height())) + } + } + + pub fn find_output_in_direction( + &self, + source_output: &OutputNode, + direction: Direction, + ) -> Option> { + if source_output.is_dummy { + return None; + } + + let outputs = self.root.outputs.lock(); + + let ref_box = source_output.global.pos.get(); + let ref_x1 = ref_box.x1(); + let ref_y1 = ref_box.y1(); + let ref_x2 = ref_box.x2(); + let ref_y2 = ref_box.y2(); + + // Use the center of the source output as the reference point (like wlroots) + let (ref_lx, ref_ly) = ref_box.center(); + + // Find the closest output in the given direction using wlroots-style algorithm + let mut min_distance = i64::MAX; + let mut closest_output = None; + + for output in outputs.values() { + if output.id == source_output.id { + continue; + } + + let box_pos = output.global.pos.get(); + let box_x1 = box_pos.x1(); + let box_y1 = box_pos.y1(); + let box_x2 = box_pos.x2(); + let box_y2 = box_pos.y2(); + + // Edge-based direction check (like wlroots) + // Test to make sure this output is in the given direction + let is_in_direction = match direction { + Direction::Left => box_x2 <= ref_x1, + Direction::Right => box_x1 >= ref_x2, + Direction::Up => box_y2 <= ref_y1, + Direction::Down => box_y1 >= ref_y2, + Direction::Unspecified => false, + }; + + if !is_in_direction { + continue; + } + + // Calculate distance from reference point to closest point on this output + // This mimics wlr_box_closest_point + squared Euclidean distance + let closest_x = ref_lx.clamp(box_x1, box_x2); + let closest_y = ref_ly.clamp(box_y1, box_y2); + + let dx = (closest_x - ref_lx) as i64; + let dy = (closest_y - ref_ly) as i64; + let distance = dx * dx + dy * dy; + + if distance < min_distance { + min_distance = distance; + closest_output = Some(output); + } + } + + closest_output.cloned() + } + + pub fn node_at(&self, x: i32, y: i32) -> FoundNode { + let mut found_tree = self.node_at_tree.borrow_mut(); + found_tree.push(FoundNode { + node: self.root.clone(), + x, + y, + }); + self.root + .node_find_tree_at(x, y, &mut found_tree, FindTreeUsecase::None); + let node = found_tree.pop().unwrap(); + found_tree.clear(); + node + } + + pub fn move_ws_to_output(&self, ws: &WorkspaceNode, output: &Rc) { + if ws.is_dummy || output.is_dummy { + return; + } + if ws.output.get().id == output.id { + return; + } + let link = match &*ws.output_link.borrow() { + None => return, + Some(l) => l.to_ref(), + }; + let config = WsMoveConfig { + make_visible_always: false, + make_visible_if_empty: true, + source_is_destroyed: false, + before: None, + }; + move_ws_to_output(&link, &output, config); + ws.desired_output.set(output.global.output_id.clone()); + self.tree_changed(); + } + +} diff --git a/src/state/xwayland.rs b/src/state/xwayland.rs new file mode 100644 index 00000000..f28b6da4 --- /dev/null +++ b/src/state/xwayland.rs @@ -0,0 +1,102 @@ +use jay_async_engine::SpawnedFuture; +use { + crate::{ + + client::Client, + ifs::{ + data_transfer::x_data_device::XTransferDeviceIds, + wl_surface::x_surface::xwindow::{Xwindow, XwindowId}, + }, + utils::{clonecell::CloneCell, copyhashmap::CopyHashMap, queue::AsyncQueue}, + xwayland::{self, XWaylandEvent}, + }, + std::{ + cell::{Cell, RefCell}, + rc::Rc, + }, + uapi::OwnedFd, +}; + +use super::State; + +pub struct XWaylandState { + pub enabled: Cell, + pub running: Cell, + pub pidfd: CloneCell>>, + pub handler: RefCell>>, + pub queue: Rc>, + pub ipc_device_ids: XTransferDeviceIds, + pub use_wire_scale: Cell, + pub wire_scale: Cell>, + pub windows: CopyHashMap>, + pub client: CloneCell>>, + pub display: CloneCell>>, +} + +impl State { + pub fn start_xwayland(self: &Rc) { + if !self.xwayland.enabled.get() { + return; + } + let mut handler = self.xwayland.handler.borrow_mut(); + if handler.is_none() { + *handler = Some(self.eng.spawn("xwayland", xwayland::manage(self.clone()))); + } + } + + pub fn stop_xwayland(&self) { + if self.xwayland.running.get() { + return; + } + self.xwayland.handler.take(); + } + + pub fn set_xwayland_enabled(self: &Rc, enabled: bool) { + if self.xwayland.enabled.replace(enabled) == enabled { + return; + } + if enabled { + self.start_xwayland(); + } else { + self.stop_xwayland(); + } + } + + pub fn set_xwayland_use_wire_scale(&self, use_wire_scale: bool) { + if self.xwayland.use_wire_scale.replace(use_wire_scale) == use_wire_scale { + return; + } + self.update_xwayland_wire_scale(); + } + + pub fn update_xwayland_wire_scale(&self) { + let scale = self + .scales + .lock() + .iter() + .map(|v| v.0.round_up()) + .max() + .unwrap_or(1); + let wire_scale = match self.xwayland.use_wire_scale.get() { + true => Some(scale as i32), + false => None, + }; + self.xwayland.wire_scale.set(wire_scale); + for client in self.clients.clients.borrow().values() { + let client = &client.data; + if !client.is_xwayland { + continue; + } + if client.wire_scale.replace(wire_scale) == wire_scale { + continue; + } + for output in client.objects.outputs.lock().values() { + output.send_updates(); + } + for surface in client.objects.surfaces.lock().values() { + surface.handle_xwayland_wire_scale_change(); + } + } + } + +} diff --git a/src/tagged_acceptor.rs b/src/tagged_acceptor.rs index dc3ff1ce..1064ff59 100644 --- a/src/tagged_acceptor.rs +++ b/src/tagged_acceptor.rs @@ -1,8 +1,8 @@ +use jay_async_engine::SpawnedFuture; use { crate::{ - async_engine::SpawnedFuture, - client::ClientCaps, - security_context_acceptor::AcceptorMetadata, + + client::ClientMetadata, state::State, utils::{ errorfmt::ErrorFmt, @@ -50,7 +50,7 @@ struct Acceptor { socket: AllocatedSocket, tag: String, state: Rc, - metadata: Rc, + metadata: Rc, future: Cell>>, } @@ -71,13 +71,9 @@ impl TaggedAcceptors { socket: self.allocate_socket()?, tag: tag.to_owned(), state: state.clone(), - metadata: Rc::new(AcceptorMetadata { - secure: false, - sandboxed: false, - sandbox_engine: Default::default(), - app_id: Default::default(), - instance_id: Default::default(), + metadata: Rc::new(ClientMetadata { tag: Some(tag.to_owned()), + ..Default::default() }), future: Default::default(), }); @@ -115,20 +111,17 @@ impl Acceptor { } async fn accept(self: Rc) { - let s = &self.state; + let state = &self.state; loop { - let fd = match s.ring.accept(&self.socket.socket, c::SOCK_CLOEXEC).await { + let fd = match state.ring.accept(&self.socket.socket, c::SOCK_CLOEXEC).await { Ok(fd) => fd, Err(e) => { log::error!("Could not accept a client: {}", ErrorFmt(e)); break; } }; - let id = s.clients.id(); - if let Err(e) = s - .clients - .spawn(id, s, fd, ClientCaps::all(), false, &self.metadata) - { + let id = state.clients.id(); + if let Err(e) = state.clients.spawn(id, state, fd, &self.metadata) { log::error!("Could not spawn a client: {}", ErrorFmt(e)); break; } diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index aef06d91..16362e44 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -7,7 +7,7 @@ use { jay_tray_v1::JayTrayV1Global, wl_output::{BlendSpace, WlOutputGlobal}, }, - output_schedule::OutputSchedule, + output_schedule::create_output_schedule, state::{ConnectorData, OutputData, State}, tree::{OutputNode, Transform, WsMoveConfig, move_ws_to_output}, utils::{ @@ -177,7 +177,11 @@ impl ConnectorHandler { info.primaries, info.luminance, )); - let schedule = Rc::new(OutputSchedule::new(&self.state, &self.data, &desired_state)); + let schedule = Rc::new(create_output_schedule( + &self.state, + &self.data, + &desired_state, + )); let _schedule = self .state .eng @@ -309,7 +313,12 @@ impl ConnectorHandler { match event { ConnectorEvent::Disconnected => break 'outer, ConnectorEvent::HardwareCursor(hc) => { - on.schedule.set_hardware_cursor(&hc); + let hardware_cursor_damage = hc.as_ref().map(|hc| { + let hc = hc.clone(); + Rc::new(move || hc.damage()) as Rc + }); + on.schedule + .set_hardware_cursor_damage(&hardware_cursor_damage); on.set_hardware_cursor(hc); self.state.refresh_hardware_cursors(); } diff --git a/src/tasks/const_clock.rs b/src/tasks/const_clock.rs index 082b66ec..a8263228 100644 --- a/src/tasks/const_clock.rs +++ b/src/tasks/const_clock.rs @@ -1,6 +1,7 @@ +use jay_io_uring::IoUring; use { crate::{ - io_uring::IoUring, + utils::{ asyncevent::AsyncEvent, errorfmt::ErrorFmt, event_listener::EventSource, timer::TimerFd, }, diff --git a/src/tasks/idle.rs b/src/tasks/idle.rs index 5561a263..228f2a33 100644 --- a/src/tasks/idle.rs +++ b/src/tasks/idle.rs @@ -1,6 +1,6 @@ use { crate::{ - backend::transaction::{BackendConnectorTransactionError, ConnectorTransaction}, + backend::transaction::BackendConnectorTransactionError, state::State, utils::{ errorfmt::ErrorFmt, @@ -136,15 +136,7 @@ impl Idle { } fn try_set_idle(&self, idle: bool) -> Result<(), BackendConnectorTransactionError> { - let mut tran = ConnectorTransaction::new(&self.state); - for connector in self.state.connectors.lock().values() { - let mut state = connector.state.borrow().clone(); - state.active = !idle; - tran.add(&connector.connector, state)?; - } - tran.prepare()?.apply()?.commit(); - self.state.set_backend_idle(idle); - Ok(()) + self.state.set_connectors_active(!idle) } } diff --git a/src/tasks/input_device.rs b/src/tasks/input_device.rs index 61550def..64d34003 100644 --- a/src/tasks/input_device.rs +++ b/src/tasks/input_device.rs @@ -1,12 +1,12 @@ use { crate::{ - backend::{InputDevice, InputDeviceCapability}, + backend::{InputDevice, InputDeviceCapability, InputEvent, KeyState}, ifs::wl_seat::PX_PER_SCROLL, state::{DeviceHandlerData, InputDeviceData, State}, tasks::udev_utils::{UdevProps, udev_props}, utils::{asyncevent::AsyncEvent, event_listener::EventListener}, }, - jay_config::_private::DEFAULT_SEAT_NAME, + jay_config::protocol::DEFAULT_SEAT_NAME, std::{ cell::Cell, rc::{Rc, Weak}, @@ -80,13 +80,21 @@ impl DeviceHandler { } if let Some(seat) = self.data.seat.get() { let mut any_events = false; + let mut key_press = false; + let mut mouse_move = false; while let Some(event) = self.dev.event() { + let (is_key_press, is_mouse_move) = dpms_wake_triggers_for(&event); + key_press |= is_key_press; + mouse_move |= is_mouse_move; + if is_key_press || is_mouse_move { + self.state.input_occurred(is_key_press, is_mouse_move); + } seat.event(&self.data, event); any_events = true; } if any_events { seat.mark_last_active(); - self.state.input_occurred(); + self.state.input_occurred(key_press, mouse_move); } } else { while self.dev.event().is_some() { @@ -105,3 +113,16 @@ impl DeviceHandler { self.data.set_seat(&self.state, None); } } + +fn dpms_wake_triggers_for(event: &InputEvent) -> (bool, bool) { + match event { + InputEvent::Key { + state: KeyState::Pressed, + .. + } => (true, false), + InputEvent::ConnectorPosition { .. } + | InputEvent::Motion { .. } + | InputEvent::MotionAbsolute { .. } => (false, true), + _ => (false, false), + } +} diff --git a/src/text.rs b/src/text.rs index 3efd5c3d..39027b98 100644 --- a/src/text.rs +++ b/src/text.rs @@ -1,24 +1,26 @@ +use jay_udmabuf::UdmabufHolder; +use jay_theme::Color; +use jay_geometry::{Rect, Region}; +use jay_cpu_worker::{AsyncCpuWork, CpuJob, CpuWork, CpuWorker, PendingJob}; +use jay_pango::{ + CairoContext, CairoImageSurface, PangoCairoContext, PangoError, PangoFontDescription, + PangoLayout, cairo_size, + consts::{ + CAIRO_FORMAT_ARGB32, CAIRO_OPERATOR_SOURCE, CairoFormat, PANGO_ELLIPSIZE_END, PANGO_SCALE, + }, +}; use { crate::{ cmm::cmm_eotf::Eotf, - cpu_worker::{AsyncCpuWork, CpuJob, CpuWork, CpuWorker, PendingJob}, + format::{ARGB8888, Format}, gfx_api::{ AsyncShmGfxTexture, AsyncShmGfxTextureCallback, GfxBuffer, GfxContext, GfxError, GfxStagingBuffer, GfxTexture, PendingShmTransfer, STAGING_UPLOAD, }, - pango::{ - CairoContext, CairoImageSurface, PangoCairoContext, PangoError, PangoFontDescription, - PangoLayout, cairo_size, - consts::{ - CAIRO_FORMAT_ARGB32, CAIRO_OPERATOR_SOURCE, CairoFormat, PANGO_ELLIPSIZE_END, - PANGO_SCALE, - }, - }, - rect::{Rect, Region}, state::State, - theme::Color, - udmabuf::UdmabufHolder, + + utils::{ clonecell::CloneCell, double_buffered::DoubleBuffered, diff --git a/src/tools/tool_client.rs b/src/tools/tool_client.rs index 3cb77655..cf6d57a6 100644 --- a/src/tools/tool_client.rs +++ b/src/tools/tool_client.rs @@ -1,10 +1,14 @@ +use jay_wheel::{Wheel, WheelError}; +use jay_logger::{LogLevel, Logger}; +use jay_io_uring::{IoUring, IoUringError}; +use jay_async_engine::{AsyncEngine, SpawnedFuture}; use { crate::{ - async_engine::{AsyncEngine, SpawnedFuture}, + client::{EventFormatter, RequestParser}, - compositor::{LogLevel, WAYLAND_DISPLAY}, - io_uring::{IoUring, IoUringError}, - logger::Logger, + compositor::WAYLAND_DISPLAY, + + object::{ObjectId, WL_DISPLAY_ID}, utils::{ asyncevent::AsyncEvent, @@ -19,7 +23,7 @@ use { oserror::{OsError, OsErrorExt2}, xrd::xrd, }, - wheel::{Wheel, WheelError}, + wire::{ JayCompositor, JayCompositorId, JayDamageTracking, JayDamageTrackingId, JayToplevelId, JayWorkspaceId, WlCallbackId, WlRegistryId, WlSeatId, jay_compositor, @@ -28,7 +32,6 @@ use { }, }, ahash::AHashMap, - isnt::std_1::primitive::IsntSliceExt, std::{ cell::{Cell, RefCell}, collections::VecDeque, @@ -149,11 +152,7 @@ impl ToolClient { Ok(d) => d, Err(_) => return Err(ToolClientError::WaylandDisplayNotSet), }; - let mut path = format_ustr!("{}/{}", xrd, wd); - let suffix = b".jay"; - if path.not_ends_with(suffix) { - path.push(suffix.as_slice()); - } + let path = format_ustr!("{}/{}", xrd, wd); let socket = uapi::socket(c::AF_UNIX, c::SOCK_STREAM | c::SOCK_CLOEXEC, 0) .map(Rc::new) .map_os_err(ToolClientError::CreateSocket)?; @@ -330,7 +329,7 @@ impl ToolClient { self_id: s.registry, name: s.jay_compositor.0, interface: JayCompositor.name(), - version: s.jay_compositor.1.min(30), + version: s.jay_compositor.1.min(31), id: id.into(), }); self.jay_compositor.set(Some(id)); diff --git a/src/tree.rs b/src/tree.rs index 5aa601c5..75860c6a 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -1,13 +1,12 @@ +pub use jay_tree_types::*; +use jay_geometry::Rect; +use jay_keyboard::KeyboardState; +use jay_units::fixed::Fixed; use { crate::{ backend::{ButtonState, KeyState}, client::{Client, ClientId}, - fixed::Fixed, ifs::{ - wl_output::{ - TF_90, TF_180, TF_270, TF_FLIPPED, TF_FLIPPED_90, TF_FLIPPED_180, TF_FLIPPED_270, - TF_NORMAL, - }, wl_seat::{ Dnd, NodeSeatState, WlSeatGlobal, tablet::{ @@ -22,21 +21,10 @@ use { zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, }, }, - keyboard::KeyboardState, - rect::Rect, renderer::Renderer, - utils::{linkedlist::NodeRef, numcell::NumCell, static_text::StaticText}, - }, - jay_config::{ - Direction as JayDirection, video::Transform as ConfigTransform, - window::TileState as ConfigTileState, - workspace::WorkspaceDisplayOrder as ConfigWorkspaceDisplayOrder, - }, - linearize::{Linearize, LinearizeExt}, - std::{ - fmt::{Debug, Display}, - rc::Rc, + utils::{linkedlist::NodeRef, numcell::NumCell}, }, + std::{fmt::Display, rc::Rc}, }; pub use { container::*, containing::*, display::*, float::*, output::*, placeholder::*, stacked::*, @@ -55,206 +43,6 @@ mod toplevel; mod walker; mod workspace; -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default, Linearize)] -pub enum WorkspaceDisplayOrder { - #[default] - Manual, - Sorted, -} - -impl From for WorkspaceDisplayOrder { - fn from(value: ConfigWorkspaceDisplayOrder) -> Self { - match value { - ConfigWorkspaceDisplayOrder::Manual => WorkspaceDisplayOrder::Manual, - ConfigWorkspaceDisplayOrder::Sorted => WorkspaceDisplayOrder::Sorted, - } - } -} - -impl Into for WorkspaceDisplayOrder { - fn into(self) -> ConfigWorkspaceDisplayOrder { - match self { - WorkspaceDisplayOrder::Manual => ConfigWorkspaceDisplayOrder::Manual, - WorkspaceDisplayOrder::Sorted => ConfigWorkspaceDisplayOrder::Sorted, - } - } -} - -impl StaticText for WorkspaceDisplayOrder { - fn text(&self) -> &'static str { - match self { - WorkspaceDisplayOrder::Manual => "Manual", - WorkspaceDisplayOrder::Sorted => "Sorted", - } - } -} - -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default, Linearize)] -pub enum Transform { - #[default] - None, - Rotate90, - Rotate180, - Rotate270, - Flip, - FlipRotate90, - FlipRotate180, - FlipRotate270, -} - -impl StaticText for Transform { - fn text(&self) -> &'static str { - match self { - Transform::None => "none", - Transform::Rotate90 => "rotate-90", - Transform::Rotate180 => "rotate-180", - Transform::Rotate270 => "rotate-270", - Transform::Flip => "flip", - Transform::FlipRotate90 => "flip-rotate-90", - Transform::FlipRotate180 => "flip-rotate-180", - Transform::FlipRotate270 => "flip-rotate-270", - } - } -} - -impl From for Transform { - fn from(value: ConfigTransform) -> Self { - match value { - ConfigTransform::None => Transform::None, - ConfigTransform::Rotate90 => Transform::Rotate90, - ConfigTransform::Rotate180 => Transform::Rotate180, - ConfigTransform::Rotate270 => Transform::Rotate270, - ConfigTransform::Flip => Transform::Flip, - ConfigTransform::FlipRotate90 => Transform::FlipRotate90, - ConfigTransform::FlipRotate180 => Transform::FlipRotate180, - ConfigTransform::FlipRotate270 => Transform::FlipRotate270, - } - } -} - -impl Into for Transform { - fn into(self) -> ConfigTransform { - match self { - Transform::None => ConfigTransform::None, - Transform::Rotate90 => ConfigTransform::Rotate90, - Transform::Rotate180 => ConfigTransform::Rotate180, - Transform::Rotate270 => ConfigTransform::Rotate270, - Transform::Flip => ConfigTransform::Flip, - Transform::FlipRotate90 => ConfigTransform::FlipRotate90, - Transform::FlipRotate180 => ConfigTransform::FlipRotate180, - Transform::FlipRotate270 => ConfigTransform::FlipRotate270, - } - } -} - -impl Transform { - pub fn maybe_swap(self, (left, right): (T, T)) -> (T, T) { - match self { - Self::None | Self::Rotate180 | Self::Flip | Self::FlipRotate180 => (left, right), - Self::Rotate90 | Self::Rotate270 | Self::FlipRotate90 | Self::FlipRotate270 => { - (right, left) - } - } - } - - pub fn to_wl(self) -> i32 { - match self { - Self::None => TF_NORMAL, - Self::Rotate90 => TF_90, - Self::Rotate180 => TF_180, - Self::Rotate270 => TF_270, - Self::Flip => TF_FLIPPED, - Self::FlipRotate90 => TF_FLIPPED_90, - Self::FlipRotate180 => TF_FLIPPED_180, - Self::FlipRotate270 => TF_FLIPPED_270, - } - } - - pub fn from_wl(wl: i32) -> Option { - let tf = match wl { - TF_NORMAL => Self::None, - TF_90 => Self::Rotate90, - TF_180 => Self::Rotate180, - TF_270 => Self::Rotate270, - TF_FLIPPED => Self::Flip, - TF_FLIPPED_90 => Self::FlipRotate90, - TF_FLIPPED_180 => Self::FlipRotate180, - TF_FLIPPED_270 => Self::FlipRotate270, - _ => return None, - }; - Some(tf) - } - - pub fn apply_point(self, width: i32, height: i32, (x, y): (i32, i32)) -> (i32, i32) { - match self { - Self::None => (x, y), - Self::Rotate90 => (y, height - x), - Self::Rotate180 => (width - x, height - y), - Self::Rotate270 => (width - y, x), - Self::Flip => (width - x, y), - Self::FlipRotate90 => (y, x), - Self::FlipRotate180 => (x, height - y), - Self::FlipRotate270 => (width - y, height - x), - } - } - - pub fn inverse(self) -> Self { - match self { - Self::Rotate90 => Self::Rotate270, - Self::Rotate270 => Self::Rotate90, - _ => self, - } - } -} - -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Linearize)] -pub enum TileState { - Tiled, - Floating, -} - -impl TryFrom for TileState { - type Error = (); - - fn try_from(value: ConfigTileState) -> Result { - let v = match value { - ConfigTileState::Tiled => TileState::Tiled, - ConfigTileState::Floating => TileState::Floating, - _ => return Err(()), - }; - Ok(v) - } -} - -impl Into for TileState { - fn into(self) -> ConfigTileState { - match self { - TileState::Tiled => ConfigTileState::Tiled, - TileState::Floating => ConfigTileState::Floating, - } - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum Direction { - Unspecified, - Left, - Down, - Up, - Right, -} - -impl From for Direction { - fn from(d: JayDirection) -> Self { - match d { - JayDirection::Left => Self::Left, - JayDirection::Down => Self::Down, - JayDirection::Up => Self::Up, - JayDirection::Right => Self::Right, - } - } -} - pub struct NodeIds { next: NumCell, } @@ -277,7 +65,6 @@ impl NodeIds { pub struct NodeId(pub u32); impl NodeId { - #[expect(dead_code)] pub fn raw(&self) -> u32 { self.0 } @@ -289,49 +76,12 @@ impl Display for NodeId { } } -#[derive(Copy, Clone, Eq, PartialEq)] -pub enum FindTreeResult { - AcceptsInput, - Other, -} - -impl FindTreeResult { - pub fn accepts_input(self) -> bool { - self == Self::AcceptsInput - } -} - -#[derive(Copy, Clone)] -pub enum FindTreeUsecase { - None, - SelectToplevel, - SelectToplevelOrPopup, - SelectWorkspace, -} - #[derive(Copy, Clone)] pub enum NodeLocation { Workspace(OutputNodeId, WorkspaceNodeId), Output(OutputNodeId), } -#[derive(Copy, Clone, Linearize, Eq, PartialEq, Debug)] -pub enum NodeLayer { - Display, - Layer0, - Layer1, - Output, - Workspace, - Tiled, - Fullscreen, - Stacked, - Layer2, - Layer3, - StackedAboveLayers, - Lock, - InputMethod, -} - pub enum NodeLayerLink { Display, Layer0(NodeRef>), @@ -377,19 +127,6 @@ impl NodeLayerLink { } } -impl NodeLayer { - pub fn prev(self) -> Self { - if self == NodeLayer::Display { - return NodeLayer::InputMethod; - } - Self::from_linear(self.linearize() - 1).unwrap_or(NodeLayer::InputMethod) - } - - pub fn next(self) -> Self { - Self::from_linear(self.linearize() + 1).unwrap_or(NodeLayer::Display) - } -} - pub trait Node: 'static { fn node_id(&self) -> NodeId; fn node_seat_state(&self) -> &NodeSeatState; diff --git a/src/tree/container.rs b/src/tree/container.rs index 61ec00d1..6712ec01 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -1,19 +1,25 @@ +mod drag_destination; +mod layout; +mod tasks; + +pub use drag_destination::default_tile_drag_destination; +pub use tasks::{container_layout, container_render_positions, container_tab_render_textures}; + +use jay_geometry::Rect; +use jay_units::fixed::Fixed; use { crate::{ backend::ButtonState, cursor::KnownCursor, cursor_user::CursorUser, - fixed::Fixed, ifs::wl_seat::{ BTN_LEFT, NodeSeatState, SeatId, WlSeatGlobal, collect_kb_foci, collect_kb_foci2, tablet::{TabletTool, TabletToolChanges, TabletToolId}, wl_pointer::PendingScroll, }, - rect::Rect, + renderer::Renderer, - scale::Scale, state::State, - text::TextTexture, tree::{ ContainingNode, Direction, FindTreeResult, FindTreeUsecase, FloatNode, FoundNode, Node, NodeId, NodeLayerLink, NodeLocation, OutputNode, TddType, TileDragDestination, @@ -25,24 +31,24 @@ use { }, utils::{ clonecell::CloneCell, - errorfmt::ErrorFmt, event_listener::LazyEventSource, hash_map_ext::HashMapExt, linkedlist::{LinkedList, LinkedNode, NodeRef}, numcell::NumCell, - on_drop_event::OnDropEvent, rc_eq::rc_eq, + scroller::Scroller, threshold_counter::ThresholdCounter, }, }, ahash::AHashMap, + drag_destination::direction_to_split, jay_config::Axis, smallvec::SmallVec, std::{ cell::{Cell, RefCell}, fmt::{Debug, Formatter}, mem, - ops::{Deref, DerefMut, Sub}, + ops::{Deref, DerefMut}, rc::Rc, }, }; @@ -131,6 +137,8 @@ pub struct ContainerNode { pub content_height: Cell, pub sum_factors: Cell, pub layout_scheduled: Cell, + animate_next_layout: Cell, + pub mono_transition_animation_pending: Cell, compute_render_positions_scheduled: Cell, num_children: NumCell, pub children: LinkedList, @@ -148,6 +156,7 @@ pub struct ContainerNode { pub child_removed: Rc, pub all_children_resized: Rc, pub tab_bar: RefCell>, + scroll: Scroller, pub update_tab_textures_scheduled: Cell, pub ephemeral: Cell, } @@ -185,23 +194,6 @@ struct CursorState { op: Option, } -impl ContainerChild { - fn position_content(&self) { - let mut content = self.content.get(); - let body = self.body.get(); - let width = content.width(); - let height = content.height(); - // let x1 = body.x1() + (body.width() - width) / 2; - // let y1 = body.y1() + (body.height() - height) / 2; - let x1 = body.x1(); - let y1 = body.y1(); - content = Rect::new_sized_saturating(x1, y1, width, height); - // log::debug!("body: {:?}", body); - // log::debug!("content: {:?}", content); - self.content.set(content); - } -} - impl ContainerNode { pub fn new( state: &Rc, @@ -238,6 +230,8 @@ impl ContainerNode { content_height: Cell::new(0), sum_factors: Cell::new(1.0), layout_scheduled: Cell::new(false), + animate_next_layout: Cell::new(false), + mono_transition_animation_pending: Cell::new(false), compute_render_positions_scheduled: Cell::new(false), num_children: NumCell::new(1), children, @@ -262,6 +256,7 @@ impl ContainerNode { child_removed: state.lazy_event_sources.create_source(), all_children_resized: state.post_layout_event_sources.create_source(), tab_bar: RefCell::new(None), + scroll: Default::default(), update_tab_textures_scheduled: Cell::new(false), ephemeral: Cell::new(Ephemeral::Off), }); @@ -286,6 +281,47 @@ impl ContainerNode { self.add_child_x(prev, new, |prev, new| self.add_child_after_(prev, new)); } + pub fn add_tiled_child_after(self: &Rc, prev: &dyn Node, new: Rc) { + if !self.state.theme.autotile_enabled.get() + || self.mono_child.is_some() + || self.num_children.get() <= 1 + { + self.add_child_after(prev, new); + return; + } + let focused = self + .child_nodes + .borrow() + .get(&prev.node_id()) + .map(|n| n.to_ref()); + let Some(focused) = focused else { + log::error!( + "Tried to autotile a child into a container but the preceding node is not in the container" + ); + return; + }; + let focused_node = focused.node.clone(); + let focused_active = focused_node.tl_data().active(); + let sub = ContainerNode::new( + &self.state, + &self.workspace.get(), + focused_node.clone(), + self.split.get().other(), + ); + // Autotile-created groups are structural and collapse once only one + // child remains. Explicit make-group commands control their own + // grouping through the regular manual paths. + sub.ephemeral.set(Ephemeral::On); + sub.append_child(new); + let sub_id = sub.node_id(); + self.clone().cnode_replace_child(&*focused_node, sub); + if focused_active + && let Some(group) = self.child_nodes.borrow().get(&sub_id).map(|n| n.to_ref()) + { + self.update_child_active(&group, true, 1); + } + } + pub fn add_child_before(self: &Rc, prev: &dyn Node, new: Rc) { self.add_child_x(prev, new, |prev, new| self.add_child_before_(prev, new)); } @@ -384,212 +420,6 @@ impl ContainerNode { } } - pub fn predict_child_body_size(&self) -> (i32, i32) { - if self.mono_child.is_some() { - let mb = self.mono_body.get(); - return (mb.width(), mb.height()); - } - let nc = self.num_children.get() as i32 + 1; - match self.split.get() { - ContainerSplit::Horizontal => { - let spacing = self.child_spacing(); - let content_w = self.width.get().sub((nc - 1) * spacing).max(0); - (content_w / nc, self.height.get()) - } - ContainerSplit::Vertical => { - let spacing = self.child_spacing(); - let content_h = self.height.get().sub((nc - 1) * spacing).max(0); - (self.width.get(), content_h / nc) - } - } - } - - pub fn on_spaces_changed(self: &Rc) { - self.update_content_size(); - // log::info!("on_spaces_changed"); - self.schedule_layout(); - self.schedule_compute_render_positions(); - } - - pub fn on_colors_changed(self: &Rc) { - self.schedule_compute_render_positions(); - } - - fn damage(&self) { - let bw = if self.state.theme.sizes.gap.get() != 0 { - self.state.theme.sizes.border_width.get() - } else { - 0 - }; - self.state.damage(Rect::new_sized_saturating( - self.abs_x1.get() - bw, - self.abs_y1.get() - bw, - self.width.get() + 2 * bw, - self.height.get() + 2 * bw, - )); - } - - fn child_spacing(&self) -> i32 { - let gap = self.state.theme.sizes.gap.get(); - let bw = self.state.theme.sizes.border_width.get(); - if gap == 0 { bw } else { gap + 2 * bw } - } - - fn schedule_layout(self: &Rc) { - if !self.layout_scheduled.replace(true) { - self.state.pending_container_layout.push(self.clone()); - } - } - - fn schedule_layout_immediate(self: &Rc) { - self.schedule_layout(); - if self.toplevel_data.visible.get() { - self.damage(); - } - } - - fn all_children_match_body(&self) -> bool { - if let Some(mono) = self.mono_child.get() { - let body = self.mono_body.get(); - let content = mono.content.get(); - return content.width() == body.width() && content.height() == body.height(); - } - for child in self.children.iter() { - let body = child.body.get(); - let content = child.content.get(); - if content.width() != body.width() || content.height() != body.height() { - return false; - } - } - true - } - - fn perform_layout(self: &Rc) { - self.layout_scheduled.set(false); - if self.num_children.get() == 0 { - return; - } - if let Some(child) = self.mono_child.get() { - self.perform_mono_layout(&child); - } else { - self.perform_split_layout(); - } - self.state.tree_changed(); - // log::info!("perform_layout"); - self.schedule_compute_render_positions(); - self.layout_complete.trigger(); - if self.all_children_match_body() { - self.all_children_resized.trigger(); - if self.toplevel_data.visible.get() { - self.damage(); - } - } - } - - fn perform_mono_layout(self: &Rc, child: &ContainerChild) { - let mb = self.mono_body.get(); - child - .node - .clone() - .tl_change_extents(&mb.move_(self.abs_x1.get(), self.abs_y1.get())); - self.mono_content - .set(child.content.get().at_point(mb.x1(), mb.y1())); - } - - fn perform_split_layout(self: &Rc) { - let sum_factors = self.sum_factors.get(); - let split = self.split.get(); - let spacing = self.child_spacing(); - let (content_size, other_content_size) = match split { - ContainerSplit::Horizontal => (self.content_width.get(), self.content_height.get()), - ContainerSplit::Vertical => (self.content_height.get(), self.content_width.get()), - }; - let num_children = self.num_children.get(); - if num_children == 0 { - return; - } - let mut pos = 0; - let mut remaining_content_size = content_size; - for child in self.children.iter() { - let factor = child.factor.get() / sum_factors; - child.factor.set(factor); - let mut body_size = (content_size as f64 * factor).round() as i32; - body_size = body_size.min(remaining_content_size); - remaining_content_size -= body_size; - let (x1, y1, width, height) = match split { - ContainerSplit::Horizontal => (pos, 0, body_size, other_content_size), - _ => (0, pos, other_content_size, body_size), - }; - let body = Rect::new_sized_saturating(x1, y1, width, height); - child.body.set(body); - pos += body_size + spacing; - } - if remaining_content_size > 0 { - let size_per = remaining_content_size / num_children as i32; - let mut rem = remaining_content_size % num_children as i32; - pos = 0; - for child in self.children.iter() { - let mut body = child.body.get(); - let mut add = size_per; - if rem > 0 { - rem -= 1; - add += 1; - } - let (x1, y1, width, height, size) = match split { - ContainerSplit::Horizontal => { - let width = body.width() + add; - (pos, 0, width, other_content_size, width) - } - _ => { - let height = body.height() + add; - (0, pos, other_content_size, height, height) - } - }; - body = Rect::new_sized_saturating(x1, y1, width, height); - child.body.set(body); - pos += size + spacing; - } - } - self.sum_factors.set(1.0); - for child in self.children.iter() { - let body = child.body.get(); - let body = body.move_(self.abs_x1.get(), self.abs_y1.get()); - child.node.clone().tl_change_extents(&body); - child.position_content(); - } - } - - fn update_content_size(&self) { - let nc = self.num_children.get(); - let spacing = self.child_spacing(); - match self.split.get() { - ContainerSplit::Horizontal => { - let new_content_size = self.width.get().sub((nc - 1) as i32 * spacing).max(0); - self.content_width.set(new_content_size); - self.content_height.set(self.height.get()); - } - ContainerSplit::Vertical => { - let new_content_size = self.height.get().sub((nc - 1) as i32 * spacing).max(0); - self.content_height.set(new_content_size); - self.content_width.set(self.width.get()); - } - } - let tab_bar_height = if self.mono_child.is_some() { - // Tab bar sits above the window with a configurable gap. - let tbh = self.state.theme.sizes.tab_bar_height.get(); - let gap = self.state.theme.sizes.tab_bar_gap.get(); - tbh + gap - } else { - 0 - }; - self.mono_body.set(Rect::new_sized_saturating( - 0, - tab_bar_height, - self.width.get(), - (self.height.get() - tab_bar_height).max(0), - )); - } - fn pointer_move( self: &Rc, _seat: &Rc, @@ -656,6 +486,7 @@ impl ContainerNode { op.child.factor.set(child_factor); self.sum_factors.set(sum_factors); // log::info!("pointer_move"); + self.state.suppress_animations_for_next_layout.set(true); self.schedule_layout_immediate(); } } @@ -741,6 +572,18 @@ impl ContainerNode { self.activate_child2(child, false); } + fn activate_child_from_input( + self: &Rc, + child: &NodeRef, + seat: &Rc, + ) { + self.activate_child(child); + child + .node + .clone() + .node_do_focus(seat, Direction::Unspecified); + } + fn activate_child2(self: &Rc, child: &NodeRef, preserve_focus: bool) { if let Some(mc) = self.mono_child.get() { if mc.node.node_id() == child.node.node_id() { @@ -816,6 +659,7 @@ impl ContainerNode { } } self.mono_child.set(child.clone()); + self.mono_transition_animation_pending.set(true); if child.is_some() { self.rebuild_tab_bar(); } else { @@ -1357,42 +1201,6 @@ impl ContainerNode { } pub fn insert_child(self: &Rc, node: Rc, direction: Direction) { - // Autotile: if the container would become too narrow/tall, wrap the - // focused child and new node in a perpendicular sub-container. - if self.state.theme.autotile_enabled.get() && self.mono_child.is_none() { - let (pw, ph) = self.predict_child_body_size(); - let opposite = match self.split.get() { - ContainerSplit::Horizontal if pw > 0 && ph > 0 && pw < ph => { - Some(ContainerSplit::Vertical) - } - ContainerSplit::Vertical if pw > 0 && ph > 0 && ph < pw => { - Some(ContainerSplit::Horizontal) - } - _ => None, - }; - if let Some(opp_split) = opposite { - if let Some(focused) = self.focus_history.last() { - if self.num_children.get() <= 1 { - // Single child, autotile not applicable. - } else { - let focused_node = focused.node.clone(); - let was_ephemeral = self.ephemeral.replace(Ephemeral::Off); - self.clone().cnode_remove_child2(&*focused_node, true); - self.ephemeral.set(was_ephemeral); - let sub = ContainerNode::new( - &self.state, - &self.workspace.get(), - focused_node, - opp_split, - ); - sub.ephemeral.set(Ephemeral::On); - sub.append_child(node); - self.append_child(sub); - return; - } - } - } - } let (split, right) = direction_to_split(direction); if split != self.split.get() || right { self.append_child(node); @@ -1502,7 +1310,7 @@ impl ContainerNode { fn button( self: Rc, id: CursorType, - _seat: &Rc, + seat: &Rc, _time_usec: u64, pressed: bool, button: u32, @@ -1532,7 +1340,7 @@ impl ContainerNode { if let Some(child) = children.get(&child_id) { let child_ref = child.to_ref(); drop(children); - self.activate_child(&child_ref); + self.activate_child_from_input(&child_ref, seat); } return; } @@ -1757,118 +1565,6 @@ enum SeatOpKind { Resize { dist_left: i32, dist_right: i32 }, } -pub async fn container_layout(state: Rc) { - loop { - let container = state.pending_container_layout.pop().await; - if container.layout_scheduled.get() { - container.perform_layout(); - } - } -} - -pub async fn container_render_positions(state: Rc) { - loop { - let container = state.pending_container_render_positions.pop().await; - if container.compute_render_positions_scheduled.get() { - container.compute_render_positions(); - } - } -} - -pub async fn container_tab_render_textures(state: Rc) { - loop { - let container = state.pending_container_tab_render_textures.pop().await; - container.update_tab_textures_scheduled.set(false); - let (event, textures) = container.update_tab_textures_phase1(); - event.triggered().await; - container.update_tab_textures_phase2(&textures); - } -} - -impl ContainerNode { - fn update_tab_textures_phase1( - self: &Rc, - ) -> ( - Rc, - Vec>>>, - ) { - let on_completed = Rc::new(OnDropEvent::default()); - let (entries, bar_height, render_scale) = { - let tab_bar = self.tab_bar.borrow(); - let Some(tb) = tab_bar.as_ref() else { - return (on_completed.event(), vec![]); - }; - let entries: Vec<_> = tb - .entries - .iter() - .map(|e| { - ( - e.title.clone(), - TabBar::entry_colors(&self.state, e), - e.title_texture.clone(), - e.width.get(), - ) - }) - .collect(); - (entries, tb.height, tb.render_scale) - }; - let Some(ctx) = self.state.render_ctx.get() else { - log::warn!("tab text phase1: no render context"); - return (on_completed.event(), vec![]); - }; - let font = self.state.theme.title_font(); - let scale = if render_scale != Scale::from_int(1) { - Some(render_scale.to_f64()) - } else { - None - }; - let mut texture_height = bar_height; - if let Some(s) = scale { - texture_height = (bar_height as f64 * s).round() as _; - } - let mut texture_refs = Vec::new(); - let text_padding = self.state.theme.sizes.tab_bar_text_padding.get(); - let border_width = self.state.theme.sizes.tab_bar_border_width.get(); - for (title, (_, _, text_color), title_texture, tab_width) in &entries { - let max_width = (*tab_width - 2 * (text_padding + border_width)).max(0); - let max_width = if let Some(s) = scale { - (max_width as f64 * s).round() as i32 - } else { - max_width - }; - if max_width <= 0 { - continue; - } - let mut tex_ref = title_texture.borrow_mut(); - let tex = tex_ref.get_or_insert_with(|| TextTexture::new(&self.state, &ctx)); - tex.schedule_render_fitting_or_ellipsized( - on_completed.clone(), - Some(texture_height), - max_width, - &font, - title, - *text_color, - false, - scale, - ); - texture_refs.push(title_texture.clone()); - } - (on_completed.event(), texture_refs) - } - - fn update_tab_textures_phase2(&self, textures: &[Rc>>]) { - for title_texture in textures { - let tex_ref = title_texture.borrow(); - if let Some(tex) = tex_ref.as_ref() { - if let Err(e) = tex.flip() { - log::warn!("Could not render tab text: {}", ErrorFmt(e)); - } - } - } - self.damage(); - } -} - impl Node for ContainerNode { fn node_id(&self) -> NodeId { self.id.into() @@ -2017,31 +1713,33 @@ impl Node for ContainerNode { self.button(id, seat, time_usec, state == ButtonState::Pressed, button); } - fn node_on_axis_event(self: Rc, _seat: &Rc, event: &PendingScroll) { + fn node_on_axis_event(self: Rc, seat: &Rc, event: &PendingScroll) { if self.mono_child.is_none() { return; } - // Use vertical scroll (index 1) to switch tabs. - let v = match event.v120[1].get() { - Some(v) if v != 0 => v, + let steps = match self.scroll.handle(event) { + Some(steps) => steps, _ => return, }; - let mono = match self.mono_child.get() { + let mut target = match self.mono_child.get() { Some(m) => m, None => return, }; - let next = if v > 0 { - // Scroll down → next tab. - mono.next().or_else(|| self.children.first()) - } else { - // Scroll up → previous tab. - mono.prev().or_else(|| self.children.last()) - }; - if let Some(next) = next { - if next.node.node_id() != mono.node.node_id() { - self.activate_child(&next); + let current_id = target.node.node_id(); + for _ in 0..steps.abs() { + let next = if steps > 0 { + target.next().or_else(|| self.children.first()) + } else { + target.prev().or_else(|| self.children.last()) + }; + match next { + Some(next) => target = next, + None => break, } } + if target.node.node_id() != current_id { + self.activate_child_from_input(&target, seat); + } } fn node_on_leave(&self, seat: &WlSeatGlobal) { @@ -2259,6 +1957,11 @@ impl ContainingNode for ContainerNode { } // log::info!("cnode_remove_child2"); self.rebuild_tab_bar(); + if self.state.animations.enabled.get() + && !self.state.suppress_animations_for_next_layout.get() + { + self.animate_next_layout.set(true); + } self.schedule_layout(); self.cancel_seat_ops(); self.child_removed.trigger(); @@ -2616,128 +2319,3 @@ impl ToplevelNodeBase for ContainerNode { } } } - -fn direction_to_split(dir: Direction) -> (ContainerSplit, bool) { - match dir { - Direction::Left => (ContainerSplit::Horizontal, true), - Direction::Down => (ContainerSplit::Vertical, false), - Direction::Up => (ContainerSplit::Vertical, true), - Direction::Right => (ContainerSplit::Horizontal, false), - Direction::Unspecified => (ContainerSplit::Horizontal, true), - } -} - -fn tile_drag_destination_in_mono( - tl: Rc, - abs_bounds: Rect, - abs_x: i32, - abs_y: i32, -) -> TileDragDestination { - let mut x1 = abs_bounds.x1(); - let mut x2 = abs_bounds.x2(); - let mut y1 = abs_bounds.y1(); - let mut y2 = abs_bounds.y2(); - let dx = (x2 - x1) / 3; - let dy = (y2 - y1) / 3; - let mut split_before = true; - let mut split = ContainerSplit::Horizontal; - if abs_x < x1 + dx { - x2 = x1 + dx; - } else if abs_x > x2 - dx { - split_before = false; - x1 = x2 - dx; - } else { - split = ContainerSplit::Vertical; - x1 += dx; - x2 -= dx; - if abs_y < y1 + dy { - y2 = y1 + dy; - } else if abs_y > y2 - dy { - split_before = false; - y1 = y2 - dy; - } else { - let rect = Rect::new_saturating(x1, y1 + dy, x2, y2 - dy); - return TileDragDestination { - highlight: rect, - ty: TddType::Replace(tl), - }; - } - } - let rect = Rect::new_saturating(x1, y1, x2, y2); - TileDragDestination { - highlight: rect, - ty: TddType::Split { - node: tl, - split, - before: split_before, - }, - } -} - -fn tile_drag_destination_in_split( - tl: Rc, - split: ContainerSplit, - abs_bounds: Rect, - mut abs_x: i32, - mut abs_y: i32, -) -> TileDragDestination { - let mut x1 = abs_bounds.x1(); - let mut x2 = abs_bounds.x2(); - let mut y1 = abs_bounds.y1(); - let mut y2 = abs_bounds.y2(); - macro_rules! swap { - () => { - if split == ContainerSplit::Horizontal { - mem::swap(&mut x1, &mut y1); - mem::swap(&mut x2, &mut y2); - mem::swap(&mut abs_x, &mut abs_y); - } - }; - } - swap!(); - let mut split_before = false; - let mut split_after = false; - let dx = (x2 - x1) / 3; - if abs_x < x1 + dx { - split_before = true; - x2 = x1 + dx; - } else if abs_x < x2 - dx { - x1 += dx; - x2 -= dx; - } else { - split_after = true; - x1 = x2 - dx; - } - swap!(); - let rect = Rect::new_saturating(x1, y1, x2, y2); - let ty = if split_before || split_after { - TddType::Split { - node: tl, - split: split.other(), - before: split_before, - } - } else { - TddType::Replace(tl) - }; - TileDragDestination { - highlight: rect, - ty, - } -} - -pub fn default_tile_drag_destination( - tl: Rc, - source: NodeId, - split: Option, - abs_bounds: Rect, - abs_x: i32, - abs_y: i32, -) -> Option { - if tl.node_id() == source { - return None; - } - Some(match split { - None => tile_drag_destination_in_mono(tl, abs_bounds, abs_x, abs_y), - Some(s) => tile_drag_destination_in_split(tl, s, abs_bounds, abs_x, abs_y), - }) -} diff --git a/src/tree/container/drag_destination.rs b/src/tree/container/drag_destination.rs new file mode 100644 index 00000000..dca8135c --- /dev/null +++ b/src/tree/container/drag_destination.rs @@ -0,0 +1,134 @@ +use jay_geometry::Rect; +use { + super::ContainerSplit, + crate::{ + + tree::{Direction, NodeId, TddType, TileDragDestination, ToplevelNode}, + }, + std::{mem, rc::Rc}, +}; + +pub(super) fn direction_to_split(dir: Direction) -> (ContainerSplit, bool) { + match dir { + Direction::Left => (ContainerSplit::Horizontal, true), + Direction::Down => (ContainerSplit::Vertical, false), + Direction::Up => (ContainerSplit::Vertical, true), + Direction::Right => (ContainerSplit::Horizontal, false), + Direction::Unspecified => (ContainerSplit::Horizontal, true), + } +} + +fn tile_drag_destination_in_mono( + tl: Rc, + abs_bounds: Rect, + abs_x: i32, + abs_y: i32, +) -> TileDragDestination { + let mut x1 = abs_bounds.x1(); + let mut x2 = abs_bounds.x2(); + let mut y1 = abs_bounds.y1(); + let mut y2 = abs_bounds.y2(); + let dx = (x2 - x1) / 3; + let dy = (y2 - y1) / 3; + let mut split_before = true; + let mut split = ContainerSplit::Horizontal; + if abs_x < x1 + dx { + x2 = x1 + dx; + } else if abs_x > x2 - dx { + split_before = false; + x1 = x2 - dx; + } else { + split = ContainerSplit::Vertical; + x1 += dx; + x2 -= dx; + if abs_y < y1 + dy { + y2 = y1 + dy; + } else if abs_y > y2 - dy { + split_before = false; + y1 = y2 - dy; + } else { + let rect = Rect::new_saturating(x1, y1 + dy, x2, y2 - dy); + return TileDragDestination { + highlight: rect, + ty: TddType::Replace(tl), + }; + } + } + let rect = Rect::new_saturating(x1, y1, x2, y2); + TileDragDestination { + highlight: rect, + ty: TddType::Split { + node: tl, + split, + before: split_before, + }, + } +} + +fn tile_drag_destination_in_split( + tl: Rc, + split: ContainerSplit, + abs_bounds: Rect, + mut abs_x: i32, + mut abs_y: i32, +) -> TileDragDestination { + let mut x1 = abs_bounds.x1(); + let mut x2 = abs_bounds.x2(); + let mut y1 = abs_bounds.y1(); + let mut y2 = abs_bounds.y2(); + macro_rules! swap { + () => { + if split == ContainerSplit::Horizontal { + mem::swap(&mut x1, &mut y1); + mem::swap(&mut x2, &mut y2); + mem::swap(&mut abs_x, &mut abs_y); + } + }; + } + swap!(); + let mut split_before = false; + let mut split_after = false; + let dx = (x2 - x1) / 3; + if abs_x < x1 + dx { + split_before = true; + x2 = x1 + dx; + } else if abs_x < x2 - dx { + x1 += dx; + x2 -= dx; + } else { + split_after = true; + x1 = x2 - dx; + } + swap!(); + let rect = Rect::new_saturating(x1, y1, x2, y2); + let ty = if split_before || split_after { + TddType::Split { + node: tl, + split: split.other(), + before: split_before, + } + } else { + TddType::Replace(tl) + }; + TileDragDestination { + highlight: rect, + ty, + } +} + +pub fn default_tile_drag_destination( + tl: Rc, + source: NodeId, + split: Option, + abs_bounds: Rect, + abs_x: i32, + abs_y: i32, +) -> Option { + if tl.node_id() == source { + return None; + } + Some(match split { + None => tile_drag_destination_in_mono(tl, abs_bounds, abs_x, abs_y), + Some(s) => tile_drag_destination_in_split(tl, s, abs_bounds, abs_x, abs_y), + }) +} diff --git a/src/tree/container/layout.rs b/src/tree/container/layout.rs new file mode 100644 index 00000000..0ff32e07 --- /dev/null +++ b/src/tree/container/layout.rs @@ -0,0 +1,236 @@ +use { + super::{ContainerChild, ContainerNode, ContainerSplit}, + jay_geometry::Rect, + std::{ops::Sub, rc::Rc}, +}; + +impl ContainerChild { + pub(super) fn position_content(&self) { + let mut content = self.content.get(); + let body = self.body.get(); + let width = content.width(); + let height = content.height(); + // let x1 = body.x1() + (body.width() - width) / 2; + // let y1 = body.y1() + (body.height() - height) / 2; + let x1 = body.x1(); + let y1 = body.y1(); + content = Rect::new_sized_saturating(x1, y1, width, height); + // log::debug!("body: {:?}", body); + // log::debug!("content: {:?}", content); + self.content.set(content); + } +} + +impl ContainerNode { + pub fn predict_child_body_size(&self) -> (i32, i32) { + if self.mono_child.is_some() { + let mb = self.mono_body.get(); + return (mb.width(), mb.height()); + } + let nc = self.num_children.get() as i32 + 1; + match self.split.get() { + ContainerSplit::Horizontal => { + let spacing = self.child_spacing(); + let content_w = self.width.get().sub((nc - 1) * spacing).max(0); + (content_w / nc, self.height.get()) + } + ContainerSplit::Vertical => { + let spacing = self.child_spacing(); + let content_h = self.height.get().sub((nc - 1) * spacing).max(0); + (self.width.get(), content_h / nc) + } + } + } + + pub fn on_spaces_changed(self: &Rc) { + self.update_content_size(); + // log::info!("on_spaces_changed"); + self.schedule_layout(); + self.schedule_compute_render_positions(); + } + + pub fn on_colors_changed(self: &Rc) { + self.schedule_compute_render_positions(); + } + + pub(super) fn damage(&self) { + let bw = if self.state.theme.sizes.gap.get() != 0 { + self.state.theme.sizes.border_width.get() + } else { + 0 + }; + self.state.damage(Rect::new_sized_saturating( + self.abs_x1.get() - bw, + self.abs_y1.get() - bw, + self.width.get() + 2 * bw, + self.height.get() + 2 * bw, + )); + } + + pub(super) fn child_spacing(&self) -> i32 { + let gap = self.state.theme.sizes.gap.get(); + let bw = self.state.theme.sizes.border_width.get(); + if gap == 0 { bw } else { gap + 2 * bw } + } + + pub(super) fn schedule_layout(self: &Rc) { + if self.state.layout_animations_requested.get() || self.state.layout_animations_active.get() + { + self.animate_next_layout.set(true); + } + if !self.layout_scheduled.replace(true) { + self.state.pending_container_layout.push(self.clone()); + } + } + + pub(super) fn schedule_layout_immediate(self: &Rc) { + self.schedule_layout(); + if self.toplevel_data.visible.get() { + self.damage(); + } + } + + pub(super) fn all_children_match_body(&self) -> bool { + if let Some(mono) = self.mono_child.get() { + let body = self.mono_body.get(); + let content = mono.content.get(); + return content.width() == body.width() && content.height() == body.height(); + } + for child in self.children.iter() { + let body = child.body.get(); + let content = child.content.get(); + if content.width() != body.width() || content.height() != body.height() { + return false; + } + } + true + } + + pub(super) fn perform_layout(self: &Rc) { + self.layout_scheduled.set(false); + if self.num_children.get() == 0 { + self.mono_transition_animation_pending.set(false); + return; + } + if let Some(child) = self.mono_child.get() { + self.perform_mono_layout(&child); + } else { + self.perform_split_layout(); + } + self.state.tree_changed(); + // log::info!("perform_layout"); + self.schedule_compute_render_positions(); + self.layout_complete.trigger(); + if self.all_children_match_body() { + self.all_children_resized.trigger(); + if self.toplevel_data.visible.get() { + self.damage(); + } + } + self.mono_transition_animation_pending.set(false); + } + + fn perform_mono_layout(self: &Rc, child: &ContainerChild) { + let mb = self.mono_body.get(); + child + .node + .clone() + .tl_change_extents(&mb.move_(self.abs_x1.get(), self.abs_y1.get())); + self.mono_content + .set(child.content.get().at_point(mb.x1(), mb.y1())); + } + + fn perform_split_layout(self: &Rc) { + let sum_factors = self.sum_factors.get(); + let split = self.split.get(); + let spacing = self.child_spacing(); + let (content_size, other_content_size) = match split { + ContainerSplit::Horizontal => (self.content_width.get(), self.content_height.get()), + ContainerSplit::Vertical => (self.content_height.get(), self.content_width.get()), + }; + let num_children = self.num_children.get(); + if num_children == 0 { + return; + } + let mut pos = 0; + let mut remaining_content_size = content_size; + for child in self.children.iter() { + let factor = child.factor.get() / sum_factors; + child.factor.set(factor); + let mut body_size = (content_size as f64 * factor).round() as i32; + body_size = body_size.min(remaining_content_size); + remaining_content_size -= body_size; + let (x1, y1, width, height) = match split { + ContainerSplit::Horizontal => (pos, 0, body_size, other_content_size), + _ => (0, pos, other_content_size, body_size), + }; + let body = Rect::new_sized_saturating(x1, y1, width, height); + child.body.set(body); + pos += body_size + spacing; + } + if remaining_content_size > 0 { + let size_per = remaining_content_size / num_children as i32; + let mut rem = remaining_content_size % num_children as i32; + pos = 0; + for child in self.children.iter() { + let mut body = child.body.get(); + let mut add = size_per; + if rem > 0 { + rem -= 1; + add += 1; + } + let (x1, y1, width, height, size) = match split { + ContainerSplit::Horizontal => { + let width = body.width() + add; + (pos, 0, width, other_content_size, width) + } + _ => { + let height = body.height() + add; + (0, pos, other_content_size, height, height) + } + }; + body = Rect::new_sized_saturating(x1, y1, width, height); + child.body.set(body); + pos += size + spacing; + } + } + self.sum_factors.set(1.0); + for child in self.children.iter() { + let body = child.body.get(); + let body = body.move_(self.abs_x1.get(), self.abs_y1.get()); + child.node.clone().tl_change_extents(&body); + child.position_content(); + } + } + + pub(super) fn update_content_size(&self) { + let nc = self.num_children.get(); + let spacing = self.child_spacing(); + match self.split.get() { + ContainerSplit::Horizontal => { + let new_content_size = self.width.get().sub((nc - 1) as i32 * spacing).max(0); + self.content_width.set(new_content_size); + self.content_height.set(self.height.get()); + } + ContainerSplit::Vertical => { + let new_content_size = self.height.get().sub((nc - 1) as i32 * spacing).max(0); + self.content_height.set(new_content_size); + self.content_width.set(self.width.get()); + } + } + let tab_bar_height = if self.mono_child.is_some() { + // Tab bar sits above the window with a configurable gap. + let tbh = self.state.theme.sizes.tab_bar_height.get(); + let gap = self.state.theme.sizes.tab_bar_gap.get(); + tbh + gap + } else { + 0 + }; + self.mono_body.set(Rect::new_sized_saturating( + 0, + tab_bar_height, + self.width.get(), + (self.height.get() - tab_bar_height).max(0), + )); + } +} diff --git a/src/tree/container/tasks.rs b/src/tree/container/tasks.rs new file mode 100644 index 00000000..779a2238 --- /dev/null +++ b/src/tree/container/tasks.rs @@ -0,0 +1,156 @@ +use jay_units::scale::Scale; +use { + crate::{ + state::State, + text::TextTexture, + tree::tab_bar::TabBar, + utils::{errorfmt::ErrorFmt, on_drop_event::OnDropEvent}, + }, + std::{cell::RefCell, rc::Rc}, +}; + +use super::ContainerNode; + +pub async fn container_layout(state: Rc) { + loop { + let first = state.pending_container_layout.pop().await; + let mut containers = vec![first]; + while let Some(container) = state.pending_container_layout.try_pop() { + containers.push(container); + } + let mut animated = vec![]; + let mut immediate = vec![]; + for container in containers { + if !container.layout_scheduled.get() { + continue; + } + let animate = container.animate_next_layout.replace(false) + && !state.suppress_animations_for_next_layout.get(); + if animate { + animated.push(container); + } else { + immediate.push(container); + } + } + if !animated.is_empty() { + let prev_active = state.layout_animations_active.replace(true); + state.begin_layout_animation_batch(); + for container in animated { + container.perform_layout(); + } + state.finish_layout_animation_batch(); + state.layout_animations_active.set(prev_active); + } + if !immediate.is_empty() { + let prev_active = state.layout_animations_active.replace(false); + for container in immediate { + container.perform_layout(); + } + state.layout_animations_active.set(prev_active); + } + state.suppress_animations_for_next_layout.set(false); + } +} + +pub async fn container_render_positions(state: Rc) { + loop { + let container = state.pending_container_render_positions.pop().await; + if container.compute_render_positions_scheduled.get() { + container.compute_render_positions(); + } + } +} + +pub async fn container_tab_render_textures(state: Rc) { + loop { + let container = state.pending_container_tab_render_textures.pop().await; + container.update_tab_textures_scheduled.set(false); + let (event, textures) = container.update_tab_textures_phase1(); + event.triggered().await; + container.update_tab_textures_phase2(&textures); + } +} + +impl ContainerNode { + fn update_tab_textures_phase1( + self: &Rc, + ) -> ( + Rc, + Vec>>>, + ) { + let on_completed = Rc::new(OnDropEvent::default()); + let (entries, bar_height, render_scale) = { + let tab_bar = self.tab_bar.borrow(); + let Some(tb) = tab_bar.as_ref() else { + return (on_completed.event(), vec![]); + }; + let entries: Vec<_> = tb + .entries + .iter() + .map(|e| { + ( + e.title.clone(), + TabBar::entry_colors(&self.state, e), + e.title_texture.clone(), + e.width.get(), + ) + }) + .collect(); + (entries, tb.height, tb.render_scale) + }; + let Some(ctx) = self.state.render_ctx.get() else { + log::warn!("tab text phase1: no render context"); + return (on_completed.event(), vec![]); + }; + let font = self.state.theme.title_font(); + let scale = if render_scale != Scale::from_int(1) { + Some(render_scale.to_f64()) + } else { + None + }; + let mut texture_height = bar_height; + if let Some(s) = scale { + texture_height = (bar_height as f64 * s).round() as _; + } + let mut texture_refs = Vec::new(); + let text_padding = self.state.theme.sizes.tab_bar_text_padding.get(); + let border_width = self.state.theme.sizes.tab_bar_border_width.get(); + for (title, (_, _, text_color), title_texture, tab_width) in &entries { + let max_width = (*tab_width - 2 * (text_padding + border_width)).max(0); + let max_width = if let Some(s) = scale { + (max_width as f64 * s).round() as i32 + } else { + max_width + }; + if max_width <= 0 { + continue; + } + let mut tex_ref = title_texture.borrow_mut(); + let tex = tex_ref.get_or_insert_with(|| TextTexture::new(&self.state, &ctx)); + tex.schedule_render_fitting_or_ellipsized( + on_completed.clone(), + Some(texture_height), + max_width, + &font, + title, + *text_color, + false, + scale, + ); + texture_refs.push(title_texture.clone()); + } + (on_completed.event(), texture_refs) + } + + fn update_tab_textures_phase2(&self, textures: &[Rc>>]) { + for title_texture in textures { + let tex_ref = title_texture.borrow(); + if let Some(tex) = tex_ref.as_ref() { + if let Err(e) = tex.flip() { + log::warn!("Could not render tab text: {}", ErrorFmt(e)); + } + } + } + self.damage(); + } +} diff --git a/src/tree/display.rs b/src/tree/display.rs index 440916bf..aa00c448 100644 --- a/src/tree/display.rs +++ b/src/tree/display.rs @@ -1,25 +1,33 @@ +use jay_geometry::Rect; +use jay_units::fixed::Fixed; use { crate::{ backend::ConnectorId, cursor::KnownCursor, - fixed::Fixed, ifs::wl_seat::{NodeSeatState, WlSeatGlobal, tablet::TabletTool}, - rect::Rect, + renderer::Renderer, state::State, tree::{ - FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeLayerLink, NodeLocation, - OutputNode, StackedNode, TileDragDestination, WorkspaceDragDestination, + Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeLayerLink, + NodeLocation, OutputNode, StackedNode, TileDragDestination, WorkspaceDragDestination, WorkspaceNodeId, walker::NodeVisitor, }, utils::{copyhashmap::CopyHashMap, linkedlist::LinkedList}, }, - std::{cell::Cell, ops::Deref, rc::Rc}, + std::{ + cell::{Cell, RefCell}, + mem, + ops::Deref, + rc::{Rc, Weak}, + }, }; pub struct DisplayNode { pub id: NodeId, pub extents: Cell, + visible: Cell, + suspend_restore_kb_foci: RefCell, Weak)>>, pub outputs: CopyHashMap>, pub stacked: Rc>>, pub stacked_above_layers: Rc>>, @@ -31,6 +39,8 @@ impl DisplayNode { let slf = Self { id, extents: Default::default(), + visible: Default::default(), + suspend_restore_kb_foci: Default::default(), outputs: Default::default(), stacked: Default::default(), stacked_above_layers: Default::default(), @@ -71,6 +81,17 @@ impl DisplayNode { pub fn update_visible(&self, state: &State) { let visible = state.root_visible(); + let was_visible = self.visible.replace(visible); + if !visible && was_visible { + let mut foci = self.suspend_restore_kb_foci.borrow_mut(); + foci.clear(); + for seat in state.globals.seats.lock().values() { + let node = seat.get_keyboard_node(); + if node.node_id() != self.id { + foci.push((seat.clone(), Rc::downgrade(&node))); + } + } + } for output in self.outputs.lock().values() { output.update_visible(); } @@ -82,6 +103,20 @@ impl DisplayNode { for seat in state.globals.seats.lock().values() { seat.set_visible(visible); } + if visible && !was_visible { + for (seat, node) in mem::take(&mut *self.suspend_restore_kb_foci.borrow_mut()) { + if seat.get_keyboard_node().node_id() == self.id { + if let Some(node) = node.upgrade() + && node.node_visible() + { + seat.focus_node(node); + } else { + seat.get_fallback_output() + .take_keyboard_navigation_focus(&seat, Direction::Unspecified); + } + } + } + } if visible { state.damage(self.extents.get()); } diff --git a/src/tree/float.rs b/src/tree/float.rs index dc0b44f4..4b7705d8 100644 --- a/src/tree/float.rs +++ b/src/tree/float.rs @@ -1,14 +1,15 @@ +use jay_geometry::Rect; +use jay_units::fixed::Fixed; use { crate::{ backend::ButtonState, cursor::KnownCursor, cursor_user::CursorUser, - fixed::Fixed, ifs::wl_seat::{ BTN_LEFT, BTN_RIGHT, NodeSeatState, SeatId, WlSeatGlobal, tablet::{TabletTool, TabletToolChanges, TabletToolId}, }, - rect::Rect, + renderer::Renderer, state::State, tree::{ @@ -31,6 +32,9 @@ use { }; tree_id!(FloatNodeId); + +const COMMAND_MOVE_DELTA: i32 = 100; + pub struct FloatNode { pub id: FloatNodeId, pub state: Rc, @@ -153,6 +157,13 @@ impl FloatNode { _ => return, }; let pos = self.position.get(); + let spawn_in_pending = { + let data = child.tl_data(); + data.spawn_in_pending.get() && data.kind.is_app_window() && !data.is_fullscreen.get() + }; + if spawn_in_pending && self.visible.get() { + self.state.queue_spawn_in_animation(self.id.into(), pos); + } let theme = &self.state.theme; let bw = theme.sizes.border_width.get(); let cpos = Rect::new_sized_saturating( @@ -363,6 +374,50 @@ impl FloatNode { y2 += y1 - pos.y1(); } let new_pos = Rect::new_saturating(x1, y1, x2, y2); + self.set_position(new_pos); + } + + pub fn move_by_direction(self: &Rc, direction: Direction) { + let (dx, dy) = match direction { + Direction::Left => (-COMMAND_MOVE_DELTA, 0), + Direction::Down => (0, COMMAND_MOVE_DELTA), + Direction::Up => (0, -COMMAND_MOVE_DELTA), + Direction::Right => (COMMAND_MOVE_DELTA, 0), + Direction::Unspecified => return, + }; + self.set_position(self.position.get().move_(dx, dy)); + } + + fn body_for_outer(&self, outer: Rect) -> Rect { + let bw = self.state.theme.sizes.border_width.get(); + Rect::new_sized_saturating( + outer.x1() + bw, + outer.y1() + bw, + outer.width() - 2 * bw, + outer.height() - 2 * bw, + ) + } + + fn queue_position_animation(&self, old_pos: Rect, new_pos: Rect) { + self.state + .clone() + .queue_tiled_animation(self.id.into(), old_pos, new_pos); + let Some(child) = self.child.get() else { + return; + }; + self.state.clone().queue_tiled_animation( + child.node_id(), + self.body_for_outer(old_pos), + self.body_for_outer(new_pos), + ); + } + + fn set_position(self: &Rc, new_pos: Rect) { + let pos = self.position.get(); + if new_pos == pos { + return; + } + self.queue_position_animation(pos, new_pos); self.position.set(new_pos); if self.visible.get() { self.state.damage(pos); @@ -791,13 +846,7 @@ impl ContainingNode for FloatNode { let bw = theme.sizes.border_width.get(); let (x, y) = (x - bw, y - bw); let pos = self.position.get(); - if pos.position() != (x, y) { - let new_pos = pos.at_point(x, y); - self.position.set(new_pos); - self.state.damage(pos); - self.state.damage(new_pos); - self.schedule_layout(); - } + self.set_position(pos.at_point(x, y)); } fn cnode_resize_child( @@ -828,14 +877,7 @@ impl ContainingNode for FloatNode { y2 = (v + bw).max(y1 + bw + bw); } let new_pos = Rect::new_saturating(x1, y1, x2, y2); - if new_pos != pos { - self.position.set(new_pos); - if self.visible.get() { - self.state.damage(pos); - self.state.damage(new_pos); - } - self.schedule_layout(); - } + self.set_position(new_pos); } fn cnode_pinned(&self) -> bool { diff --git a/src/tree/output.rs b/src/tree/output.rs index d21b3e5e..48dd792c 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -1,3 +1,21 @@ +mod captures; +mod policy; +mod render_data; +mod workspaces; + +#[allow(unused_imports)] +pub use { + policy::{ + TearingMode, TearingSurfaceRequirements, VrrContentTypeRequirements, VrrMode, + VrrSurfaceRequirements, calculate_logical_size, + }, + render_data::{OutputRenderData, OutputStatus, OutputTitle, OutputWorkspaceRenderData}, +}; + +use jay_theme::BarPosition; +use jay_geometry::Rect; +use jay_units::fixed::Fixed; +use jay_units::scale::Scale; use { crate::{ backend::{ @@ -5,18 +23,14 @@ use { HardwareCursor, Mode, transaction::BackendConnectorTransactionError, }, client::ClientId, - cmm::cmm_description::ColorDescription, cursor::KnownCursor, - fixed::Fixed, - gfx_api::{AcquireSync, BufferResv, GfxTexture, ReleaseSync}, ifs::{ ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1, jay_output::JayOutput, jay_screencast::JayScreencast, - wl_buffer::WlBufferStorage, wl_output::{BlendSpace, WlOutputGlobal}, wl_seat::{ - BTN_LEFT, NodeSeatState, SeatId, WlSeatGlobal, collect_kb_foci2, + BTN_LEFT, NodeSeatState, SeatId, WlSeatGlobal, tablet::{TabletTool, TabletToolChanges, TabletToolId}, wl_pointer::PendingScroll, }, @@ -38,12 +52,11 @@ use { zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1, }, output_schedule::OutputSchedule, - rect::Rect, + renderer::Renderer, - scale::Scale, state::State, text::TextTexture, - theme::BarPosition, + tree::{ Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeLayerLink, NodeLocation, PinnedNode, StackedNode, TddType, TileDragDestination, Transform, @@ -57,8 +70,7 @@ use { copyhashmap::CopyHashMap, errorfmt::ErrorFmt, event_listener::{EventSource, LazyEventSource}, - hash_map_ext::HashMapExt, - linkedlist::{LinkedList, NodeRef}, + linkedlist::LinkedList, on_drop_event::OnDropEvent, scroller::Scroller, }, @@ -66,10 +78,7 @@ use { ExtImageCopyCaptureSessionV1Id, JayOutputId, JayScreencastId, ZwlrScreencopyFrameV1Id, }, }, - ahash::AHashMap, - jay_config::video::{TearingMode as ConfigTearingMode, VrrMode as ConfigVrrMode}, numeric_sort::cmp, - smallvec::SmallVec, std::{ cell::{Cell, RefCell}, fmt::{Debug, Formatter}, @@ -271,186 +280,6 @@ impl OutputNode { } } - pub fn add_screencast(&self, sc: &Rc) { - self.screencasts.set((sc.client.id, sc.id), sc.clone()); - self.screencast_changed(); - } - - pub fn remove_screencast(&self, sc: &JayScreencast) { - self.screencasts.remove(&(sc.client.id, sc.id)); - self.screencast_changed(); - } - - pub fn screencast_changed(&self) { - for ws in self.workspaces.iter() { - ws.update_has_captures(); - } - } - - pub fn perform_screencopies( - &self, - tex: &Rc, - cd: &Rc, - resv: Option<&Rc>, - acquire_sync: &AcquireSync, - release_sync: ReleaseSync, - render_hardware_cursor: bool, - x_off: i32, - y_off: i32, - size: Option<(i32, i32)>, - ) { - if let Some(workspace) = self.workspace.get() { - if !workspace.may_capture.get() { - return; - } - } - self.perform_wlr_screencopies( - tex, - cd, - resv, - acquire_sync, - release_sync, - render_hardware_cursor, - x_off, - y_off, - size, - ); - for sc in self.screencasts.lock().values() { - sc.copy_texture( - self, - tex, - cd, - resv, - acquire_sync, - release_sync, - render_hardware_cursor, - x_off, - y_off, - size, - ); - } - for sc in self.ext_copy_sessions.lock().values() { - sc.copy_texture( - self, - tex, - cd, - resv, - acquire_sync, - release_sync, - render_hardware_cursor, - x_off, - y_off, - size, - ); - } - } - - pub fn perform_wlr_screencopies( - &self, - tex: &Rc, - cd: &Rc, - resv: Option<&Rc>, - acquire_sync: &AcquireSync, - release_sync: ReleaseSync, - render_hardware_cursors: bool, - x_off: i32, - y_off: i32, - size: Option<(i32, i32)>, - ) { - if self.screencopies.is_empty() { - return; - } - let now = self.state.now(); - for capture in self.screencopies.lock().drain_values() { - let wl_buffer = match capture.buffer.take() { - Some(b) => b, - _ => { - log::warn!("Capture frame is pending but has no buffer attached"); - capture.send_failed(); - continue; - } - }; - if wl_buffer.destroyed() { - capture.send_failed(); - continue; - } - let mut ready = true; - if let Some(storage) = wl_buffer.storage.borrow_mut().deref() { - match storage { - WlBufferStorage::Shm { mem, stride, .. } => { - let res = self.state.perform_shm_screencopy( - tex, - cd, - acquire_sync, - self.global.pos.get(), - x_off, - y_off, - size, - &capture, - mem, - *stride, - wl_buffer.format, - self.global.persistent.transform.get(), - self.global.persistent.scale.get(), - ); - match res { - Ok(p) => { - ready = p.is_none(); - capture.pending.set(p); - } - Err(e) => { - log::warn!("Could not perform shm screencopy: {}", ErrorFmt(e)); - capture.send_failed(); - continue; - } - } - } - WlBufferStorage::Dmabuf { fb, .. } => { - let fb = match fb { - Some(fb) => fb, - _ => { - log::warn!("Capture buffer has no framebuffer"); - capture.send_failed(); - continue; - } - }; - let res = self.state.perform_screencopy( - tex, - resv, - acquire_sync, - release_sync, - cd, - &fb, - AcquireSync::Implicit, - ReleaseSync::Implicit, - self.global.persistent.transform.get(), - self.state.color_manager.srgb_gamma22(), - self.global.pos.get(), - render_hardware_cursors, - x_off - capture.rect.x1(), - y_off - capture.rect.y1(), - size, - self.global.persistent.transform.get(), - self.global.persistent.scale.get(), - ); - if let Err(e) = res { - log::warn!("Could not perform screencopy: {}", ErrorFmt(e)); - capture.send_failed(); - continue; - } - } - } - } - if capture.with_damage.get() { - capture.send_damage(); - } - if ready { - capture.send_ready(now.0.tv_sec as _, now.0.tv_nsec as _); - } - } - self.screencast_changed(); - } - pub fn clear(&self) { self.global.clear(); self.workspace.set(None); @@ -664,117 +493,6 @@ impl OutputNode { } } - pub fn ensure_workspace(self: &Rc) -> Rc { - if let Some(ws) = self.workspace.get() { - if !ws.is_dummy { - return ws; - } - } - self.generate_workspace() - } - - pub fn generate_workspace(self: &Rc) -> Rc { - let name = 'name: { - for i in 1.. { - let name = i.to_string(); - if self.find_workspace(&name).is_none() { - break 'name name; - } - } - unreachable!(); - }; - self.create_workspace(&name) - } - - pub fn find_workspace(&self, name: &str) -> Option> { - self.workspaces - .iter() - .find(|ws| ws.name.as_str() == name) - .map(|ws| (*ws).clone()) - } - - pub fn show_workspace(&self, ws: &Rc) -> bool { - let mut seats = SmallVec::new(); - if let Some(old) = self.workspace.set(Some(ws.clone())) { - if old.id == ws.id { - return false; - } - collect_kb_foci2(old.clone(), &mut seats); - for pinned in self.pinned.iter() { - pinned.deref().clone().set_workspace(ws, false); - } - if old.is_empty() { - for jw in old.jay_workspaces.lock().values() { - jw.send_destroyed(); - jw.workspace.set(None); - } - for wh in old.ext_workspaces.lock().values() { - wh.handle_destroyed(); - } - old.clear(); - self.state.workspaces.remove(&old.id); - } else { - old.set_visible(false); - old.flush_jay_workspaces(); - } - } - self.update_visible(); - self.update_presentation_type(); - if let Some(fs) = ws.fullscreen.get() { - fs.tl_change_extents(&self.global.pos.get()); - } - ws.change_extents(&self.workspace_rect.get()); - for seat in seats { - ws.clone().node_do_focus(&seat, Direction::Unspecified); - } - if self.node_visible() { - self.state.damage(self.global.pos.get()); - } - true - } - - pub fn find_workspace_insertion_point(&self, name: &str) -> Option>> { - if self.state.workspace_display_order.get() == WorkspaceDisplayOrder::Sorted { - for existing_ws in self.workspaces.iter() { - if cmp(name, &existing_ws.name) == std::cmp::Ordering::Less { - return Some(existing_ws); - } - } - } - None - } - - pub fn create_workspace(self: &Rc, name: &str) -> Rc { - let ws = WorkspaceNode::new(self, name, false); - ws.opt.set(Some(ws.clone())); - ws.update_has_captures(); - let link = if let Some(before) = self.find_workspace_insertion_point(name) { - before.prepend(ws.clone()) - } else { - self.workspaces.add_last(ws.clone()) - }; - *ws.output_link.borrow_mut() = Some(link); - self.state.workspaces.set(ws.id, ws.clone()); - if self.workspace.is_none() { - self.show_workspace(&ws); - } - let mut clients_to_kill = AHashMap::new(); - for watcher in self.state.workspace_watchers.lock().values() { - if let Err(e) = watcher.send_workspace(&ws) { - clients_to_kill.insert(watcher.client.id, (watcher.client.clone(), e)); - } - } - for (client, e) in clients_to_kill.values() { - client.error(e); - } - self.state.workspace_managers.announce_workspace(self, &ws); - self.state - .workspace_managers - .update_workspace_coordinates(self); - self.schedule_update_render_data(); - ws - } - pub fn update_rects(self: &Rc) { let rect = self.global.pos.get(); let bh = self.state.theme.sizes.bar_height(); @@ -885,9 +603,6 @@ impl OutputNode { self.change_extents_(&self.calculate_extents()); if (old_width, old_height) != (new_width, new_height) { - for sc in self.screencasts.lock().values() { - sc.schedule_realloc_or_reconfigure(); - } for sc in self.ext_copy_sessions.lock().values() { sc.buffer_size_changed(); } @@ -1573,45 +1288,6 @@ impl OutputNode { } } -pub struct OutputTitle { - pub x1: i32, - pub x2: i32, - pub tex_x: i32, - pub tex_y: i32, - pub tex: Rc, - pub ws: Rc, -} - -pub struct OutputStatus { - pub tex_x: i32, - pub tex: TextTexture, -} - -#[derive(Copy, Clone)] -pub struct OutputWorkspaceRenderData { - pub rect: Rect, - pub captured: bool, -} - -#[derive(Default)] -pub struct OutputRenderData { - pub full_area: Rect, - pub active_workspace: Option, - pub bar_separator: Rect, - pub inactive_workspaces: Vec, - pub attention_requested_workspaces: Vec, - pub captured_inactive_workspaces: Vec, - pub titles: Vec, - pub status: Option, -} - -impl OutputRenderData { - fn clear(&mut self) { - self.titles.clear(); - self.status.take(); - } -} - impl Debug for OutputNode { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("OutputNode").finish_non_exhaustive() @@ -1920,154 +1596,3 @@ impl Node for OutputNode { } } } - -pub fn calculate_logical_size( - mode: (i32, i32), - transform: Transform, - scale: crate::scale::Scale, -) -> (i32, i32) { - let (mut width, mut height) = transform.maybe_swap(mode); - if scale != 1 { - let scale = scale.to_f64(); - width = (width as f64 / scale).round() as _; - height = (height as f64 / scale).round() as _; - } - (width, height) -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] -pub enum VrrMode { - #[default] - Never, - Always, - Fullscreen { - surface: Option, - }, -} - -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] -pub struct VrrSurfaceRequirements { - pub content_type: Option, -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct VrrContentTypeRequirements { - pub photo: bool, - pub video: bool, - pub game: bool, -} - -impl Default for VrrContentTypeRequirements { - fn default() -> Self { - Self { - photo: true, - video: true, - game: true, - } - } -} - -impl VrrMode { - pub const NEVER: &'static Self = &Self::Never; - pub const ALWAYS: &'static Self = &Self::Always; - pub const VARIANT_1: &'static Self = &Self::Fullscreen { surface: None }; - pub const VARIANT_2: &'static Self = &Self::Fullscreen { - surface: Some(VrrSurfaceRequirements { content_type: None }), - }; - pub const VARIANT_3: &'static Self = &Self::Fullscreen { - surface: Some(VrrSurfaceRequirements { - content_type: Some(VrrContentTypeRequirements { - photo: false, - video: true, - game: true, - }), - }), - }; - - pub fn from_config(mode: ConfigVrrMode) -> Option<&'static Self> { - let res = match mode { - ConfigVrrMode::NEVER => Self::NEVER, - ConfigVrrMode::ALWAYS => Self::ALWAYS, - ConfigVrrMode::VARIANT_1 => Self::VARIANT_1, - ConfigVrrMode::VARIANT_2 => Self::VARIANT_2, - ConfigVrrMode::VARIANT_3 => Self::VARIANT_3, - _ => return None, - }; - Some(res) - } - - pub fn to_config(&self) -> ConfigVrrMode { - match self { - Self::NEVER => ConfigVrrMode::NEVER, - Self::ALWAYS => ConfigVrrMode::ALWAYS, - Self::VARIANT_1 => ConfigVrrMode::VARIANT_1, - Self::VARIANT_2 => ConfigVrrMode::VARIANT_2, - Self::VARIANT_3 => ConfigVrrMode::VARIANT_3, - _ => { - log::error!("VRR mode {self:?} has no config representation"); - ConfigVrrMode::NEVER - } - } - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] -pub enum TearingMode { - #[default] - Never, - Always, - Fullscreen { - surface: Option, - }, -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct TearingSurfaceRequirements { - pub tearing_requested: bool, -} - -impl Default for TearingSurfaceRequirements { - fn default() -> Self { - Self { - tearing_requested: true, - } - } -} - -impl TearingMode { - pub const NEVER: &'static Self = &Self::Never; - pub const ALWAYS: &'static Self = &Self::Always; - pub const VARIANT_1: &'static Self = &Self::Fullscreen { surface: None }; - pub const VARIANT_2: &'static Self = &Self::Fullscreen { - surface: Some(TearingSurfaceRequirements { - tearing_requested: false, - }), - }; - pub const VARIANT_3: &'static Self = &Self::Fullscreen { - surface: Some(TearingSurfaceRequirements { - tearing_requested: true, - }), - }; - - pub fn from_config(mode: ConfigTearingMode) -> Option<&'static Self> { - let res = match mode { - ConfigTearingMode::NEVER => Self::NEVER, - ConfigTearingMode::ALWAYS => Self::ALWAYS, - ConfigTearingMode::VARIANT_1 => Self::VARIANT_1, - ConfigTearingMode::VARIANT_2 => Self::VARIANT_2, - ConfigTearingMode::VARIANT_3 => Self::VARIANT_3, - _ => return None, - }; - Some(res) - } - - pub fn to_config(&self) -> ConfigTearingMode { - match self { - Self::NEVER => ConfigTearingMode::NEVER, - Self::ALWAYS => ConfigTearingMode::ALWAYS, - Self::VARIANT_1 => ConfigTearingMode::VARIANT_1, - Self::VARIANT_2 => ConfigTearingMode::VARIANT_2, - Self::VARIANT_3 => ConfigTearingMode::VARIANT_3, - } - } -} diff --git a/src/tree/output/captures.rs b/src/tree/output/captures.rs new file mode 100644 index 00000000..e5ba1a71 --- /dev/null +++ b/src/tree/output/captures.rs @@ -0,0 +1,196 @@ +use { + super::OutputNode, + crate::{ + cmm::cmm_description::ColorDescription, + gfx_api::{AcquireSync, BufferResv, GfxTexture, ReleaseSync}, + ifs::{jay_screencast::JayScreencast, wl_buffer::WlBufferStorage}, + utils::{errorfmt::ErrorFmt, hash_map_ext::HashMapExt}, + }, + std::{ops::Deref, rc::Rc}, +}; + +impl OutputNode { + pub fn captures_changed(&self) { + for ws in self.workspaces.iter() { + ws.update_has_captures(); + } + } + + pub fn screencast_changed(&self) { + self.captures_changed(); + } + + pub fn add_screencast(&self, sc: &Rc) { + self.screencasts.set((sc.client.id, sc.id), sc.clone()); + self.captures_changed(); + } + + pub fn remove_screencast(&self, sc: &JayScreencast) { + self.screencasts.remove(&(sc.client.id, sc.id)); + self.captures_changed(); + } + + pub fn perform_screencopies( + &self, + tex: &Rc, + cd: &Rc, + resv: Option<&Rc>, + acquire_sync: &AcquireSync, + release_sync: ReleaseSync, + render_hardware_cursor: bool, + x_off: i32, + y_off: i32, + size: Option<(i32, i32)>, + ) { + if let Some(workspace) = self.workspace.get() { + if !workspace.may_capture.get() { + return; + } + } + self.perform_wlr_screencopies( + tex, + cd, + resv, + acquire_sync, + release_sync, + render_hardware_cursor, + x_off, + y_off, + size, + ); + for sc in self.screencasts.lock().values() { + sc.copy_texture( + self, + tex, + cd, + resv, + acquire_sync, + release_sync, + render_hardware_cursor, + x_off, + y_off, + size, + ); + } + for sc in self.ext_copy_sessions.lock().values() { + sc.copy_texture( + self, + tex, + cd, + resv, + acquire_sync, + release_sync, + render_hardware_cursor, + x_off, + y_off, + size, + ); + } + } + + pub fn perform_wlr_screencopies( + &self, + tex: &Rc, + cd: &Rc, + resv: Option<&Rc>, + acquire_sync: &AcquireSync, + release_sync: ReleaseSync, + render_hardware_cursors: bool, + x_off: i32, + y_off: i32, + size: Option<(i32, i32)>, + ) { + if self.screencopies.is_empty() { + return; + } + let now = self.state.now(); + for capture in self.screencopies.lock().drain_values() { + let wl_buffer = match capture.buffer.take() { + Some(b) => b, + _ => { + log::warn!("Capture frame is pending but has no buffer attached"); + capture.send_failed(); + continue; + } + }; + if wl_buffer.destroyed() { + capture.send_failed(); + continue; + } + let mut ready = true; + if let Some(storage) = wl_buffer.storage.borrow_mut().deref() { + match storage { + WlBufferStorage::Shm { mem, stride, .. } => { + let res = self.state.perform_shm_screencopy( + tex, + cd, + acquire_sync, + self.global.pos.get(), + x_off, + y_off, + size, + &capture, + mem, + *stride, + wl_buffer.format, + self.global.persistent.transform.get(), + self.global.persistent.scale.get(), + ); + match res { + Ok(p) => { + ready = p.is_none(); + capture.pending.set(p); + } + Err(e) => { + log::warn!("Could not perform shm screencopy: {}", ErrorFmt(e)); + capture.send_failed(); + continue; + } + } + } + WlBufferStorage::Dmabuf { fb, .. } => { + let fb = match fb { + Some(fb) => fb, + _ => { + log::warn!("Capture buffer has no framebuffer"); + capture.send_failed(); + continue; + } + }; + let res = self.state.perform_screencopy( + tex, + resv, + acquire_sync, + release_sync, + cd, + &fb, + AcquireSync::Implicit, + ReleaseSync::Implicit, + self.global.persistent.transform.get(), + self.state.color_manager.srgb_gamma22(), + self.global.pos.get(), + render_hardware_cursors, + x_off - capture.rect.x1(), + y_off - capture.rect.y1(), + size, + self.global.persistent.transform.get(), + self.global.persistent.scale.get(), + ); + if let Err(e) = res { + log::warn!("Could not perform screencopy: {}", ErrorFmt(e)); + capture.send_failed(); + continue; + } + } + } + } + if capture.with_damage.get() { + capture.send_damage(); + } + if ready { + capture.send_ready(now.0.tv_sec as _, now.0.tv_nsec as _); + } + } + self.captures_changed(); + } +} diff --git a/src/tree/output/policy.rs b/src/tree/output/policy.rs new file mode 100644 index 00000000..341879c1 --- /dev/null +++ b/src/tree/output/policy.rs @@ -0,0 +1,155 @@ +use { + crate::tree::Transform, + jay_config::video::{TearingMode as ConfigTearingMode, VrrMode as ConfigVrrMode}, +}; + +pub fn calculate_logical_size( + mode: (i32, i32), + transform: Transform, + scale: jay_units::scale::Scale, +) -> (i32, i32) { + let (mut width, mut height) = transform.maybe_swap(mode); + if scale != 1 { + let scale = scale.to_f64(); + width = (width as f64 / scale).round() as _; + height = (height as f64 / scale).round() as _; + } + (width, height) +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] +pub enum VrrMode { + #[default] + Never, + Always, + Fullscreen { + surface: Option, + }, +} + +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +pub struct VrrSurfaceRequirements { + pub content_type: Option, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct VrrContentTypeRequirements { + pub photo: bool, + pub video: bool, + pub game: bool, +} + +impl Default for VrrContentTypeRequirements { + fn default() -> Self { + Self { + photo: true, + video: true, + game: true, + } + } +} + +impl VrrMode { + pub const NEVER: &'static Self = &Self::Never; + pub const ALWAYS: &'static Self = &Self::Always; + pub const VARIANT_1: &'static Self = &Self::Fullscreen { surface: None }; + pub const VARIANT_2: &'static Self = &Self::Fullscreen { + surface: Some(VrrSurfaceRequirements { content_type: None }), + }; + pub const VARIANT_3: &'static Self = &Self::Fullscreen { + surface: Some(VrrSurfaceRequirements { + content_type: Some(VrrContentTypeRequirements { + photo: false, + video: true, + game: true, + }), + }), + }; + + pub fn from_config(mode: ConfigVrrMode) -> Option<&'static Self> { + let res = match mode { + ConfigVrrMode::NEVER => Self::NEVER, + ConfigVrrMode::ALWAYS => Self::ALWAYS, + ConfigVrrMode::VARIANT_1 => Self::VARIANT_1, + ConfigVrrMode::VARIANT_2 => Self::VARIANT_2, + ConfigVrrMode::VARIANT_3 => Self::VARIANT_3, + _ => return None, + }; + Some(res) + } + + pub fn to_config(&self) -> ConfigVrrMode { + match self { + Self::NEVER => ConfigVrrMode::NEVER, + Self::ALWAYS => ConfigVrrMode::ALWAYS, + Self::VARIANT_1 => ConfigVrrMode::VARIANT_1, + Self::VARIANT_2 => ConfigVrrMode::VARIANT_2, + Self::VARIANT_3 => ConfigVrrMode::VARIANT_3, + _ => { + log::error!("VRR mode {self:?} has no config representation"); + ConfigVrrMode::NEVER + } + } + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] +pub enum TearingMode { + #[default] + Never, + Always, + Fullscreen { + surface: Option, + }, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct TearingSurfaceRequirements { + pub tearing_requested: bool, +} + +impl Default for TearingSurfaceRequirements { + fn default() -> Self { + Self { + tearing_requested: true, + } + } +} + +impl TearingMode { + pub const NEVER: &'static Self = &Self::Never; + pub const ALWAYS: &'static Self = &Self::Always; + pub const VARIANT_1: &'static Self = &Self::Fullscreen { surface: None }; + pub const VARIANT_2: &'static Self = &Self::Fullscreen { + surface: Some(TearingSurfaceRequirements { + tearing_requested: false, + }), + }; + pub const VARIANT_3: &'static Self = &Self::Fullscreen { + surface: Some(TearingSurfaceRequirements { + tearing_requested: true, + }), + }; + + pub fn from_config(mode: ConfigTearingMode) -> Option<&'static Self> { + let res = match mode { + ConfigTearingMode::NEVER => Self::NEVER, + ConfigTearingMode::ALWAYS => Self::ALWAYS, + ConfigTearingMode::VARIANT_1 => Self::VARIANT_1, + ConfigTearingMode::VARIANT_2 => Self::VARIANT_2, + ConfigTearingMode::VARIANT_3 => Self::VARIANT_3, + _ => return None, + }; + Some(res) + } + + pub fn to_config(&self) -> ConfigTearingMode { + match self { + Self::NEVER => ConfigTearingMode::NEVER, + Self::ALWAYS => ConfigTearingMode::ALWAYS, + Self::VARIANT_1 => ConfigTearingMode::VARIANT_1, + Self::VARIANT_2 => ConfigTearingMode::VARIANT_2, + Self::VARIANT_3 => ConfigTearingMode::VARIANT_3, + } + } +} diff --git a/src/tree/output/render_data.rs b/src/tree/output/render_data.rs new file mode 100644 index 00000000..4077e31d --- /dev/null +++ b/src/tree/output/render_data.rs @@ -0,0 +1,44 @@ +use { + crate::{gfx_api::GfxTexture, text::TextTexture, tree::WorkspaceNode}, + jay_geometry::Rect, + std::rc::Rc, +}; + +pub struct OutputTitle { + pub x1: i32, + pub x2: i32, + pub tex_x: i32, + pub tex_y: i32, + pub tex: Rc, + pub ws: Rc, +} + +pub struct OutputStatus { + pub tex_x: i32, + pub tex: TextTexture, +} + +#[derive(Copy, Clone)] +pub struct OutputWorkspaceRenderData { + pub rect: Rect, + pub captured: bool, +} + +#[derive(Default)] +pub struct OutputRenderData { + pub full_area: Rect, + pub active_workspace: Option, + pub bar_separator: Rect, + pub inactive_workspaces: Vec, + pub attention_requested_workspaces: Vec, + pub captured_inactive_workspaces: Vec, + pub titles: Vec, + pub status: Option, +} + +impl OutputRenderData { + pub(super) fn clear(&mut self) { + self.titles.clear(); + self.status.take(); + } +} diff --git a/src/tree/output/workspaces.rs b/src/tree/output/workspaces.rs new file mode 100644 index 00000000..5b83fde5 --- /dev/null +++ b/src/tree/output/workspaces.rs @@ -0,0 +1,125 @@ +use { + super::OutputNode, + crate::{ + ifs::wl_seat::collect_kb_foci2, + tree::{Direction, Node, WorkspaceDisplayOrder, WorkspaceNode}, + utils::linkedlist::NodeRef, + }, + ahash::AHashMap, + numeric_sort::cmp, + smallvec::SmallVec, + std::{ops::Deref, rc::Rc}, +}; + +impl OutputNode { + pub fn ensure_workspace(self: &Rc) -> Rc { + if let Some(ws) = self.workspace.get() { + if !ws.is_dummy { + return ws; + } + } + self.generate_workspace() + } + + pub fn generate_workspace(self: &Rc) -> Rc { + let name = 'name: { + for i in 1.. { + let name = i.to_string(); + if self.find_workspace(&name).is_none() { + break 'name name; + } + } + unreachable!(); + }; + self.create_workspace(&name) + } + + pub fn find_workspace(&self, name: &str) -> Option> { + self.workspaces + .iter() + .find(|ws| ws.name.as_str() == name) + .map(|ws| (*ws).clone()) + } + + pub fn show_workspace(&self, ws: &Rc) -> bool { + let mut seats = SmallVec::new(); + if let Some(old) = self.workspace.set(Some(ws.clone())) { + if old.id == ws.id { + return false; + } + collect_kb_foci2(old.clone(), &mut seats); + for pinned in self.pinned.iter() { + pinned.deref().clone().set_workspace(ws, false); + } + if old.is_empty() { + for jw in old.jay_workspaces.lock().values() { + jw.send_destroyed(); + jw.workspace.set(None); + } + for wh in old.ext_workspaces.lock().values() { + wh.handle_destroyed(); + } + old.clear(); + self.state.workspaces.remove(&old.id); + } else { + old.set_visible(false); + old.flush_jay_workspaces(); + } + } + self.update_visible(); + self.update_presentation_type(); + if let Some(fs) = ws.fullscreen.get() { + fs.tl_change_extents(&self.global.pos.get()); + } + ws.change_extents(&self.workspace_rect.get()); + for seat in seats { + ws.clone().node_do_focus(&seat, Direction::Unspecified); + } + if self.node_visible() { + self.state.damage(self.global.pos.get()); + } + true + } + + pub fn find_workspace_insertion_point(&self, name: &str) -> Option>> { + if self.state.workspace_display_order.get() == WorkspaceDisplayOrder::Sorted { + for existing_ws in self.workspaces.iter() { + if cmp(name, &existing_ws.name) == std::cmp::Ordering::Less { + return Some(existing_ws); + } + } + } + None + } + + pub fn create_workspace(self: &Rc, name: &str) -> Rc { + let ws = WorkspaceNode::new(self, name, false); + ws.opt.set(Some(ws.clone())); + ws.update_has_captures(); + let link = if let Some(before) = self.find_workspace_insertion_point(name) { + before.prepend(ws.clone()) + } else { + self.workspaces.add_last(ws.clone()) + }; + *ws.output_link.borrow_mut() = Some(link); + self.state.workspaces.set(ws.id, ws.clone()); + if self.workspace.is_none() { + self.show_workspace(&ws); + } + let mut clients_to_kill = AHashMap::new(); + for watcher in self.state.workspace_watchers.lock().values() { + if let Err(e) = watcher.send_workspace(&ws) { + clients_to_kill.insert(watcher.client.id, (watcher.client.clone(), e)); + } + } + for (client, e) in clients_to_kill.values() { + client.error(e); + } + self.state.workspace_managers.announce_workspace(self, &ws); + self.state + .workspace_managers + .update_workspace_coordinates(self); + self.schedule_update_render_data(); + ws + } +} diff --git a/src/tree/placeholder.rs b/src/tree/placeholder.rs index 264cce3d..92351974 100644 --- a/src/tree/placeholder.rs +++ b/src/tree/placeholder.rs @@ -1,12 +1,13 @@ +use jay_geometry::Rect; +use jay_units::fixed::Fixed; +use jay_units::scale::Scale; use { crate::{ client::Client, cursor::KnownCursor, - fixed::Fixed, ifs::wl_seat::{NodeSeatState, WlSeatGlobal}, - rect::Rect, + renderer::Renderer, - scale::Scale, state::State, text::TextTexture, tree::{ diff --git a/src/tree/tab_bar.rs b/src/tree/tab_bar.rs index 4dd1f485..01ee95bd 100644 --- a/src/tree/tab_bar.rs +++ b/src/tree/tab_bar.rs @@ -1,5 +1,7 @@ +use jay_units::scale::Scale; +use jay_theme::Color; use { - crate::{scale::Scale, state::State, text::TextTexture, theme::Color, tree::NodeId}, + crate::{state::State, text::TextTexture, tree::NodeId}, std::{ cell::{Cell, RefCell}, rc::Rc, diff --git a/src/tree/toplevel.rs b/src/tree/toplevel.rs index 02bba848..9b42c0d5 100644 --- a/src/tree/toplevel.rs +++ b/src/tree/toplevel.rs @@ -1,8 +1,15 @@ +use jay_geometry::Rect; use { crate::{ + animation::{ + RetainedExitLayer, RetainedToplevel, + multiphase::{ + MultiphaseHierarchyPosition, MultiphaseHierarchyTransition, + MultiphaseWindowHierarchy, PhaseAxis, + }, + }, client::{Client, ClientId}, criteria::{ - CritDestroyListener, CritMatcherId, tlm::{ TL_CHANGED_APP_ID, TL_CHANGED_CONTENT_TY, TL_CHANGED_DESTROYED, TL_CHANGED_FLOATING, TL_CHANGED_FULLSCREEN, TL_CHANGED_NEW, TL_CHANGED_TITLE, @@ -25,7 +32,7 @@ use { zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1, zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1, }, - rect::Rect, + state::State, tree::{ ContainerNode, ContainerSplit, ContainingNode, Direction, FloatNode, Node, NodeId, @@ -42,6 +49,7 @@ use { }, }, jay_config::{window, window::WindowType}, + jay_criteria::{CritDestroyListener, CritMatcherId}, std::{ borrow::Borrow, cell::{Cell, OnceCell, RefCell}, @@ -117,6 +125,7 @@ impl ToplevelNode for T { let parent_was_none = data.parent.set(Some(parent.clone())).is_none(); if parent_was_none { data.mapped_during_iteration.set(data.state.eng.iteration()); + data.spawn_in_pending.set(data.kind.is_app_window()); data.property_changed(TL_CHANGED_NEW); } let was_floating = data.parent_is_float.get(); @@ -184,6 +193,57 @@ impl ToplevelNode for T { fn tl_change_extents(self: Rc, rect: &Rect) { let data = self.tl_data(); let prev = data.desired_extents.replace(*rect); + let target_hierarchy = self.tl_multiphase_hierarchy_position(); + let hierarchy = MultiphaseWindowHierarchy::new( + data.layout_animation_position.replace(target_hierarchy), + target_hierarchy, + ); + let spawn_in_pending = data.spawn_in_pending.get(); + let spawn_in_eligible = spawn_in_pending + && !rect.is_empty() + && data.visible.get() + && !data.is_fullscreen.get() + && data.kind.is_app_window() + && !self.node_is_container(); + let parent_container = data + .parent + .get() + .and_then(|parent| parent.node_into_container()); + let parent_is_mono = parent_container + .as_ref() + .is_some_and(|container| container.mono_child.is_some()); + let parent_mono_transition = parent_container + .as_ref() + .is_some_and(|container| container.mono_transition_animation_pending.get()); + let active_mono_boundary = matches!( + hierarchy.transition, + MultiphaseHierarchyTransition::EnteringMono + | MultiphaseHierarchyTransition::ExitingMono + ) && parent_mono_transition + && (hierarchy.source.mono_active || hierarchy.target.mono_active); + if prev != *rect + && !prev.is_empty() + && !rect.is_empty() + && data.visible.get() + && !data.parent_is_float.get() + && !self.node_is_container() + && (!parent_is_mono || active_mono_boundary) + { + data.state.clone().queue_tiled_animation_with_hierarchy( + data.node_id, + prev, + *rect, + hierarchy, + ); + } + if spawn_in_eligible { + data.state + .clone() + .queue_spawn_in_animation(data.node_id, *rect); + } + if spawn_in_eligible { + data.spawn_in_pending.set(false); + } if prev.size() != rect.size() { for sc in data.jay_screencasts.lock().values() { sc.schedule_realloc_or_reconfigure(); @@ -275,6 +335,35 @@ pub trait ToplevelNodeBase: Node { true } + fn tl_multiphase_hierarchy_position(&self) -> MultiphaseHierarchyPosition { + let data = self.tl_data(); + let Some(parent) = data.parent.get() else { + return Default::default(); + }; + let mut position = MultiphaseHierarchyPosition { + parent: Some(parent.node_id().into()), + ..Default::default() + }; + populate_multiphase_ancestor_splits(&mut position, Some(parent.clone())); + if let Some(container) = parent.node_into_container() { + position.split_axis = Some(match container.split.get() { + ContainerSplit::Horizontal => PhaseAxis::Horizontal, + ContainerSplit::Vertical => PhaseAxis::Vertical, + }); + if let Some(mono) = container.mono_child.get() { + position.parent_is_mono = true; + position.mono_active = mono.node.node_id() == data.node_id; + } + for (idx, child) in container.children.iter().enumerate() { + if child.node.node_id() == data.node_id { + position.sibling_index = Some(idx.min(u16::MAX as usize) as u16); + break; + } + } + } + position + } + fn tl_set_active(&self, active: bool) { let _ = active; } @@ -299,6 +388,11 @@ pub trait ToplevelNodeBase: Node { fn tl_scanout_surface(&self) -> Option> { None } + + fn tl_animation_snapshot(&self) -> Option> { + None + } + fn tl_restack_popups(&self) { // nothing } @@ -339,6 +433,31 @@ pub trait ToplevelNodeBase: Node { } } +fn populate_multiphase_ancestor_splits( + position: &mut MultiphaseHierarchyPosition, + mut parent: Option>, +) { + let mut depth = 0u16; + while let Some(node) = parent { + let Some(toplevel) = node.clone().node_into_toplevel() else { + break; + }; + depth = depth.saturating_add(1); + if let Some(container) = node.node_into_container() { + match container.split.get() { + ContainerSplit::Horizontal => { + position.nearest_horizontal_split_depth.get_or_insert(depth); + } + ContainerSplit::Vertical => { + position.nearest_vertical_split_depth.get_or_insert(depth); + } + } + } + parent = toplevel.tl_data().parent.get(); + } + position.depth = depth; +} + pub struct FullscreenedData { pub placeholder: Rc, pub workspace: Rc, @@ -377,6 +496,13 @@ impl ToplevelType { ToplevelType::XWindow { .. } => window::X_WINDOW, } } + + pub fn is_app_window(&self) -> bool { + matches!( + self, + ToplevelType::XdgToplevel(_) | ToplevelType::XWindow(_) + ) + } } pub struct ToplevelData { @@ -399,8 +525,10 @@ pub struct ToplevelData { pub title: RefCell, pub parent: CloneCell>>, pub mapped_during_iteration: Cell, + pub spawn_in_pending: Cell, pub pos: Cell, pub desired_extents: Cell, + pub layout_animation_position: Cell, pub seat_state: NodeSeatState, pub wants_attention: Cell, pub requested_attention: Cell, @@ -462,8 +590,10 @@ impl ToplevelData { title: RefCell::new(title), parent: Default::default(), mapped_during_iteration: Cell::new(0), + spawn_in_pending: Cell::new(false), pos: Default::default(), desired_extents: Default::default(), + layout_animation_position: Default::default(), seat_state: Default::default(), wants_attention: Cell::new(false), requested_attention: Cell::new(false), @@ -554,8 +684,8 @@ impl ToplevelData { for screencast in self.jay_screencasts.lock().drain_values() { screencast.do_destroy(); } - for screencast in self.ext_copy_sessions.lock().drain_values() { - screencast.stop(); + for capture in self.ext_copy_sessions.lock().drain_values() { + capture.stop(); } { let id = toplevel_identifier(); @@ -850,7 +980,7 @@ impl ToplevelData { } fd.workspace.remove_fullscreen_node(); if fd.placeholder.is_destroyed() { - state.map_tiled(node); + state.map_tiled_without_autotile(node); return; } let parent = fd.placeholder.tl_data().parent.take().unwrap(); @@ -935,6 +1065,62 @@ impl ToplevelData { self.mapped_during_iteration.get() == self.state.eng.iteration() } + pub fn queue_spawn_out(&self, node: &dyn ToplevelNode, retained: Option>) { + if !self.kind.is_app_window() + || !self.visible.get() + || self.is_fullscreen.get() + || node.node_is_container() + { + return; + } + let Some(retained) = retained else { + return; + }; + let bw = self.state.theme.sizes.border_width.get().max(0); + let now = self.state.now_nsec(); + let (outer, frame_inset, layer) = if self.parent_is_float.get() { + let Some(float) = self.float.get() else { + return; + }; + ( + self.state + .animations + .visual_rect(float.node_id(), float.position.get(), now), + bw, + RetainedExitLayer::Floating, + ) + } else { + let body = + self.state + .animations + .visual_rect(self.node_id, node.node_absolute_position(), now); + if body.is_empty() { + return; + } + if self.state.theme.sizes.gap.get() != 0 { + ( + Rect::new_sized_saturating( + body.x1() - bw, + body.y1() - bw, + body.width() + 2 * bw, + body.height() + 2 * bw, + ), + bw, + RetainedExitLayer::Tiled, + ) + } else { + (body, 0, RetainedExitLayer::Tiled) + } + }; + self.state.clone().queue_spawn_out_animation( + outer, + frame_inset, + retained, + self.active(), + layer, + ); + } + pub fn set_content_type(&self, content_type: Option) { if self.content_type.replace(content_type) != content_type { self.property_changed(TL_CHANGED_CONTENT_TY); @@ -1043,6 +1229,26 @@ pub fn toplevel_create_split(state: &Rc, tl: Rc, axis: } } +fn float_outer_for_body(state: &State, body: Rect) -> Rect { + let bw = state.theme.sizes.border_width.get(); + Rect::new_sized_saturating( + body.x1() - bw, + body.y1() - bw, + body.width() + 2 * bw, + body.height() + 2 * bw, + ) +} + +fn float_body_for_outer(state: &State, outer: Rect) -> Rect { + let bw = state.theme.sizes.border_width.get(); + Rect::new_sized_saturating( + outer.x1() + bw, + outer.y1() + bw, + outer.width() - 2 * bw, + outer.height() - 2 * bw, + ) +} + pub fn toplevel_set_floating(state: &Rc, tl: Rc, floating: bool) { let data = tl.tl_data(); if data.is_fullscreen.get() { @@ -1057,11 +1263,21 @@ pub fn toplevel_set_floating(state: &Rc, tl: Rc, floati }; if !floating { parent.cnode_remove_child2(&*tl, true); - state.map_tiled(tl); + state.map_tiled_without_autotile(tl); } else if let Some(ws) = data.workspace.get() { + let node_id = data.node_id; + let old_body = + state + .animations + .visual_rect(node_id, tl.node_absolute_position(), state.now_nsec()); + let old_outer = float_outer_for_body(state, old_body); parent.cnode_remove_child2(&*tl, true); let (width, height) = data.float_size(&ws); - state.map_floating(tl, width, height, &ws, None); + let floater = state.map_floating(tl, width, height, &ws, None); + let new_outer = floater.position.get(); + let new_body = float_body_for_outer(state, new_outer); + state.queue_linear_layout_animation(floater.node_id(), old_outer, new_outer); + state.queue_linear_layout_animation(node_id, old_body, new_body); } } @@ -1108,3 +1324,54 @@ pub fn toplevel_set_workspace(state: &Rc, tl: Rc, ws: & tl.tl_set_fullscreen(true, Some(ws.clone())); } } + +/// Removes a toplevel from the tree so it can be parked in a scratchpad. +/// +/// Returns `true` if the window was hidden. A placeholder, a window without a +/// parent, or a window that refuses to leave fullscreen cannot be parked. +pub fn toplevel_hide_for_scratchpad(tl: Rc) -> bool { + if tl.node_is_placeholder() { + return false; + } + let data = tl.tl_data(); + let workspace = data.workspace.get(); + if data.is_fullscreen.get() { + tl.clone().tl_set_fullscreen(false, None); + if data.is_fullscreen.get() { + return false; + } + } + let Some(parent) = data.parent.get() else { + return false; + }; + let kb_foci = collect_kb_foci(tl.clone()); + parent.cnode_remove_child2(&*tl, true); + data.parent.take(); + data.float.take(); + if data.parent_is_float.replace(false) { + data.property_changed(TL_CHANGED_FLOATING); + } + if data.workspace.take().is_some() { + data.property_changed(TL_CHANGED_WORKSPACE); + } + tl.tl_set_visible(false); + if let Some(workspace) = &workspace { + for seat in kb_foci { + workspace + .clone() + .node_do_focus(&seat, Direction::Unspecified); + } + } + true +} + +/// Maps a parked scratchpad window back onto `ws`. Scratchpad windows always +/// return floating, regardless of how they were laid out before parking. +pub fn toplevel_restore_from_scratchpad( + state: &Rc, + tl: Rc, + ws: &Rc, +) { + let (width, height) = tl.tl_data().float_size(ws); + state.map_floating(tl.clone(), width, height, ws, None); +} diff --git a/src/tree/workspace.rs b/src/tree/workspace.rs index f60354a4..77ca575e 100644 --- a/src/tree/workspace.rs +++ b/src/tree/workspace.rs @@ -1,8 +1,9 @@ +use jay_geometry::Rect; +use jay_units::fixed::Fixed; use { crate::{ client::ClientId, cursor::KnownCursor, - fixed::Fixed, ifs::{ jay_workspace::JayWorkspace, wl_output::OutputId, @@ -15,7 +16,7 @@ use { ext_workspace_manager_v1::WorkspaceManagerId, }, }, - rect::Rect, + renderer::Renderer, state::State, text::TextTexture, @@ -197,10 +198,10 @@ impl WorkspaceNode { } self.pull_child_properties(&**container); let pos = self.position.get(); - container.clone().tl_change_extents(&pos); container.tl_set_parent(self.clone()); container.tl_set_visible(self.container_visible()); self.container.set(Some(container.clone())); + container.clone().tl_change_extents(&pos); self.state.damage(self.position.get()); } diff --git a/src/utils.rs b/src/utils.rs index 1dbf2bac..9fa30371 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,69 +1,113 @@ -pub mod array; -pub mod array_to_tuple; -pub mod asyncevent; -pub mod atomic_enum; -pub mod binary_search_map; +macro_rules! reexport_utils { + ($($name:ident,)*) => { + $( + pub mod $name { + #[allow(unused_imports)] + pub use jay_utils::$name::*; + } + )* + }; +} + +reexport_utils! { + array, + array_to_tuple, + asyncevent, + atomic_enum, + binary_search_map, + bitfield, + bitflags, + buf, + cell_ext, + compat, + copyhashmap, + double_buffered, + errorfmt, + fdcloser, + free_list, + geometric_decay, + hash_map_ext, + log_on_drop, + mmap, + nice, + nonblock, + num_cpus, + numcell, + on_change, + on_drop_event, + once, + opaque, + opaque_cell, + opt, + option_ext, + ordered_float, + oserror, + page_size, + pid_info, + pidfd_send_signal, + pipe, + process_name, + ptr_ext, + queue, + rc_eq, + refcounted, + smallmap, + stack, + static_text, + string_ext, + syncqueue, + threshold_counter, + tri, + unlink_on_drop, + vec_ext, + vecdeque_ext, + vecset, + vecstorage, + windows, + xrd, +} + pub mod bindings; -pub mod bitfield; -pub mod bitflags; -pub mod buf; -pub mod buffd; -pub mod bufio; -pub mod cell_ext; +pub mod buffd { + pub use jay_wire_buf::*; +} +pub mod bufio { + pub use jay_bufio::*; +} pub mod clone3; -pub mod clonecell; -pub mod compat; -pub mod copyhashmap; -pub mod double_buffered; pub mod double_click_state; -pub mod errorfmt; -pub mod event_listener; -pub mod fdcloser; -pub mod free_list; -pub mod geometric_decay; -pub mod hash_map_ext; -pub mod line_logger; +pub mod event_listener { + pub use jay_utils::event_listener::*; + + use {crate::state::State, std::rc::Rc}; + + pub async fn handle_lazy_event_sources(state: Rc) { + handle_lazy_event_sources_of(&state.lazy_event_sources).await; + } + + pub async fn handle_post_layout_event_sources(state: Rc) { + handle_lazy_event_sources_of(&state.post_layout_event_sources).await; + } +} pub mod linkedlist; -pub mod log_on_drop; -pub mod mmap; -pub mod nice; -pub mod nonblock; -pub mod num_cpus; -pub mod numcell; -pub mod object_drop_queue; -pub mod on_change; -pub mod on_drop_event; -pub mod once; -pub mod opaque; -pub mod opaque_cell; -pub mod opt; -pub mod option_ext; -pub mod ordered_float; -pub mod oserror; -pub mod page_size; +pub mod line_logger { + pub use jay_io_uring::line_logger::*; +} +pub mod object_drop_queue { + pub use jay_io_uring::object_drop_queue::*; +} pub mod pending_serial; -pub mod pid_info; -pub mod pidfd_send_signal; -pub mod pipe; -pub mod process_name; -pub mod ptr_ext; -pub mod queue; -pub mod rc_eq; -pub mod refcounted; -pub mod run_toplevel; +pub mod run_toplevel { + pub use jay_async_engine::RunToplevel; +} pub mod scroller; -pub mod smallmap; -pub mod stack; -pub mod static_text; -pub mod string_ext; -pub mod syncqueue; -pub mod threshold_counter; -pub mod timer; -pub mod tri; -pub mod unlink_on_drop; -pub mod vec_ext; -pub mod vecdeque_ext; -pub mod vecset; -pub mod vecstorage; -pub mod windows; -pub mod xrd; +pub mod timer { + pub use jay_io_uring::timer::*; +} + +pub mod clonecell { + pub use jay_utils::clonecell::*; + + unsafe impl UnsafeCellCloneSafe for crate::utils::linkedlist::NodeRef {} + unsafe impl UnsafeCellCloneSafe for crate::tree::NodeId {} +} diff --git a/src/utils/clone3.rs b/src/utils/clone3.rs index 08397694..778a6285 100644 --- a/src/utils/clone3.rs +++ b/src/utils/clone3.rs @@ -1,7 +1,8 @@ +use jay_pr_caps::drop_all_pr_caps; use { crate::{ forker::ForkerError, - pr_caps::drop_all_pr_caps, + utils::{errorfmt::ErrorFmt, oserror::OsErrorExt2, process_name::set_process_name}, }, run_on_drop::on_drop, diff --git a/src/utils/linkedlist.rs b/src/utils/linkedlist.rs index cd976314..817a26dc 100644 --- a/src/utils/linkedlist.rs +++ b/src/utils/linkedlist.rs @@ -340,6 +340,7 @@ impl LinkedNode { LinkedNode { data: node.into() } } + #[allow(dead_code)] pub fn detached(t: T) -> Self { Self::new(Some(t)) } diff --git a/src/video.rs b/src/video.rs index 9f8906fe..3257093b 100644 --- a/src/video.rs +++ b/src/video.rs @@ -1,11 +1,4 @@ -pub mod dmabuf; pub mod drm; pub mod gbm; -pub type Modifier = u64; - -pub const INVALID_MODIFIER: Modifier = 0x00ff_ffff_ffff_ffff; -pub const LINEAR_MODIFIER: Modifier = 0; - -// This is required by AMD and therefore everyone else uses this too. -pub const LINEAR_STRIDE_ALIGN: u64 = 256; +pub use jay_video_types::*; diff --git a/src/video/drm.rs b/src/video/drm.rs index 53ca3ae5..5fa8a25c 100644 --- a/src/video/drm.rs +++ b/src/video/drm.rs @@ -2,11 +2,12 @@ pub mod syncobj; mod sys; pub mod wait_for_syncobj; +use jay_io_uring::{IoUring, IoUringError}; use { crate::{ backend, format::Format, - io_uring::{IoUring, IoUringError}, + utils::{ buf::Buf, errorfmt::ErrorFmt, @@ -21,17 +22,15 @@ use { drm::sys::{ DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP, DRM_CAP_CURSOR_HEIGHT, DRM_CAP_CURSOR_WIDTH, DRM_DISPLAY_MODE_LEN, DRM_MODE_ATOMIC_TEST_ONLY, DRM_MODE_FB_MODIFIERS, - DRM_MODE_OBJECT_BLOB, DRM_MODE_OBJECT_CONNECTOR, DRM_MODE_OBJECT_CRTC, - DRM_MODE_OBJECT_ENCODER, DRM_MODE_OBJECT_FB, DRM_MODE_OBJECT_PLANE, - DRM_MODE_OBJECT_PROPERTY, FORMAT_BLOB_CURRENT, auth_magic, create_lease, drm_event, + FORMAT_BLOB_CURRENT, auth_magic, create_lease, drm_event, drm_event_crtc_sequence, drm_event_vblank, drm_format_modifier, - drm_format_modifier_blob, drop_master, gem_close, get_cap, - get_device_name_from_fd2, get_minor_name_from_fd, get_node_type_from_fd, get_nodes, - get_version, mode_addfb2, mode_atomic, mode_create_blob, mode_destroy_blob, - mode_get_resources, mode_getconnector, mode_getencoder, mode_getplane, - mode_getplaneresources, mode_getprobblob, mode_getproperty, mode_obj_getproperties, - mode_rmfb, mode_supports_get_resources, prime_fd_to_handle, queue_sequence, - revoke_lease, set_client_cap, + drm_format_modifier_blob, drop_master, gem_close, get_cap, get_device_name_from_fd2, + get_minor_name_from_fd, get_node_type_from_fd, get_nodes, get_version, mode_addfb2, + mode_atomic, mode_create_blob, mode_destroy_blob, mode_get_resources, + mode_getconnector, mode_getencoder, mode_getplane, mode_getplaneresources, + mode_getprobblob, mode_getproperty, mode_obj_getproperties, mode_rmfb, + mode_supports_get_resources, prime_fd_to_handle, queue_sequence, revoke_lease, + set_client_cap, }, }, }, @@ -51,6 +50,9 @@ use { }; pub use { consts::*, + jay_video_types::drm::{ + DrmBlob, DrmConnector, DrmCrtc, DrmEncoder, DrmFb, DrmObject, DrmPlane, DrmProperty, + }, sys::{ DRM_CLIENT_CAP_ATOMIC, DRM_MODE_ATOMIC_ALLOW_MODESET, DRM_MODE_ATOMIC_NONBLOCK, DRM_MODE_PAGE_FLIP_ASYNC, DRM_MODE_PAGE_FLIP_EVENT, drm_mode_modeinfo, @@ -719,46 +721,6 @@ pub struct DrmPropertyValue { pub value: u64, } -pub trait DrmObject { - const TYPE: u32; - const NONE: Self; - fn id(&self) -> u32; - fn is_some(&self) -> bool; - fn is_none(&self) -> bool; -} - -macro_rules! drm_obj { - ($name:ident, $ty:expr) => { - #[repr(transparent)] - #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default, Ord, PartialOrd)] - pub struct $name(pub u32); - - impl DrmObject for $name { - const TYPE: u32 = $ty; - const NONE: Self = Self(0); - - fn id(&self) -> u32 { - self.0 - } - - fn is_some(&self) -> bool { - self.0 != 0 - } - - fn is_none(&self) -> bool { - self.0 == 0 - } - } - }; -} -drm_obj!(DrmCrtc, DRM_MODE_OBJECT_CRTC); -drm_obj!(DrmConnector, DRM_MODE_OBJECT_CONNECTOR); -drm_obj!(DrmEncoder, DRM_MODE_OBJECT_ENCODER); -drm_obj!(DrmProperty, DRM_MODE_OBJECT_PROPERTY); -drm_obj!(DrmFb, DRM_MODE_OBJECT_FB); -drm_obj!(DrmBlob, DRM_MODE_OBJECT_BLOB); -drm_obj!(DrmPlane, DRM_MODE_OBJECT_PLANE); - #[derive(Debug, Default)] pub struct DrmCardResources { pub _min_width: u32, @@ -1115,15 +1077,28 @@ num!(i64); num!(u64); num!(bool); -impl ObjectChangeValue for T -where - T: DrmObject, -{ - fn into_u64(self) -> u64 { - self.id() as u64 - } +macro_rules! drm_object_value { + ($($ty:ty,)*) => { + $( + impl ObjectChangeValue for $ty { + fn into_u64(self) -> u64 { + self.id() as u64 + } + } + )* + }; } +drm_object_value!( + DrmBlob, + DrmConnector, + DrmCrtc, + DrmEncoder, + DrmFb, + DrmPlane, + DrmProperty, +); + #[expect(non_camel_case_types)] #[derive(Copy, Clone, Debug)] pub enum ConnectorType { diff --git a/src/video/drm/sys.rs b/src/video/drm/sys.rs index 715882dc..61f2a766 100644 --- a/src/video/drm/sys.rs +++ b/src/video/drm/sys.rs @@ -408,13 +408,6 @@ pub fn mode_obj_getproperties( Ok(props) } -pub const DRM_MODE_OBJECT_CRTC: u32 = 0xcccccccc; -pub const DRM_MODE_OBJECT_CONNECTOR: u32 = 0xc0c0c0c0; -pub const DRM_MODE_OBJECT_ENCODER: u32 = 0xe0e0e0e0; -pub const DRM_MODE_OBJECT_PROPERTY: u32 = 0xb0b0b0b0; -pub const DRM_MODE_OBJECT_FB: u32 = 0xfbfbfbfb; -pub const DRM_MODE_OBJECT_BLOB: u32 = 0xbbbbbbbb; -pub const DRM_MODE_OBJECT_PLANE: u32 = 0xeeeeeeee; #[expect(dead_code)] pub const DRM_MODE_OBJECT_ANY: u32 = 0; diff --git a/src/video/drm/wait_for_syncobj.rs b/src/video/drm/wait_for_syncobj.rs index f13ed366..900bc3bc 100644 --- a/src/video/drm/wait_for_syncobj.rs +++ b/src/video/drm/wait_for_syncobj.rs @@ -1,7 +1,9 @@ +use jay_io_uring::IoUring; +use jay_async_engine::{AsyncEngine, SpawnedFuture}; use { crate::{ - async_engine::{AsyncEngine, SpawnedFuture}, - io_uring::IoUring, + + utils::{ asyncevent::AsyncEvent, buf::Buf, clonecell::CloneCell, copyhashmap::CopyHashMap, hash_map_ext::HashMapExt, numcell::NumCell, oserror::OsErrorExt2, stack::Stack, diff --git a/src/video/gbm.rs b/src/video/gbm.rs index eb34ca71..ec924508 100644 --- a/src/video/gbm.rs +++ b/src/video/gbm.rs @@ -3,7 +3,7 @@ use { crate::{ allocator::{ - Allocator, AllocatorError, BO_USE_CURSOR, BO_USE_LINEAR, BO_USE_PROTECTED, + Allocator, AllocatorDrm, AllocatorError, BO_USE_CURSOR, BO_USE_LINEAR, BO_USE_PROTECTED, BO_USE_RENDERING, BO_USE_SCANOUT, BO_USE_WRITE, BufferObject, BufferUsage, MappedBuffer, }, @@ -323,7 +323,7 @@ impl GbmDevice { } impl Allocator for GbmDevice { - fn drm(&self) -> Option<&Drm> { + fn drm(&self) -> Option<&dyn AllocatorDrm> { Some(&self.drm) } diff --git a/src/virtual_output.rs b/src/virtual_output.rs index 281c1d65..b9e1f937 100644 --- a/src/virtual_output.rs +++ b/src/virtual_output.rs @@ -1,10 +1,13 @@ +use std::time::Duration; +use jay_geometry::Region; +use jay_async_engine::{Phase, SpawnedFuture}; use { crate::{ allocator::{AllocatorError, BO_USE_RENDERING, BufferObject, BufferUsage}, - async_engine::{Phase, SpawnedFuture}, + backend::{ BackendConnectorState, Connector, ConnectorEvent, ConnectorId, ConnectorKernelId, - DrmDeviceId, HardwareCursor, HardwareCursorUpdate, Mode, MonitorInfo, + DrmDeviceId, HardwareCursor, HardwareCursorUpdate, Mode, MonitorInfo, OutputId, transaction::{ BackendAppliedConnectorTransaction, BackendConnectorTransaction, BackendConnectorTransactionError, BackendConnectorTransactionType, @@ -18,12 +21,12 @@ use { GfxError, GfxFramebuffer, GfxRenderPass, GfxTexture, ReleaseSync, create_render_pass, }, ifs::{ - wl_output::{BlendSpace, OutputId}, + wl_output::BlendSpace, wp_presentation_feedback::{ KIND_HW_CLOCK, KIND_HW_COMPLETION, KIND_VSYNC, KIND_ZERO_COPY, }, }, - rect::Region, + state::State, tasks::handle_connector, tree::OutputNode, @@ -43,7 +46,7 @@ use { fmt::{Debug, Formatter}, mem, rc::Rc, - time::Duration, + }, thiserror::Error, uapi::c, diff --git a/src/vulkan_core.rs b/src/vulkan_core.rs index 108f0b51..f249da4a 100644 --- a/src/vulkan_core.rs +++ b/src/vulkan_core.rs @@ -1,5 +1,5 @@ use { - crate::{eventfd_cache::EventfdError, video::drm::DrmError}, + crate::video::drm::DrmError, ahash::{AHashMap, AHashSet}, ash::{ Entry, Instance, LoadingError, @@ -16,6 +16,7 @@ use { api_version_patch, api_version_variant, }, }, + jay_eventfd_cache::EventfdError, isnt::std_1::collections::IsntHashMapExt, log::Level, run_on_drop::on_drop, diff --git a/src/vulkan_core/device.rs b/src/vulkan_core/device.rs index 83a4c73f..664a50a8 100644 --- a/src/vulkan_core/device.rs +++ b/src/vulkan_core/device.rs @@ -1,6 +1,7 @@ +use jay_eventfd_cache::EventfdCache; use { crate::{ - eventfd_cache::EventfdCache, video::drm::syncobj::SyncobjCtx, + video::drm::syncobj::SyncobjCtx, vulkan_core::VulkanCoreInstance, }, ash::{ diff --git a/src/wl_usr.rs b/src/wl_usr.rs index 06249a4a..398caf80 100644 --- a/src/wl_usr.rs +++ b/src/wl_usr.rs @@ -1,11 +1,14 @@ pub mod usr_ifs; pub mod usr_object; +use jay_wheel::Wheel; +use jay_io_uring::{IoUring, IoUringError}; +use jay_async_engine::{AsyncEngine, SpawnedFuture}; use { crate::{ - async_engine::{AsyncEngine, SpawnedFuture}, + client::{EventFormatter, MIN_SERVER_ID, RequestParser}, - io_uring::{IoUring, IoUringError}, + object::{Interface, ObjectId, Version, WL_DISPLAY_ID}, utils::{ asyncevent::AsyncEvent, @@ -21,7 +24,7 @@ use { oserror::{OsError, OsErrorExt2}, }, video::dmabuf::DmaBufIds, - wheel::Wheel, + wire::wl_display, wl_usr::{ usr_ifs::{ diff --git a/src/wl_usr/usr_ifs/usr_wp_viewport.rs b/src/wl_usr/usr_ifs/usr_wp_viewport.rs index b336b4a1..a018932d 100644 --- a/src/wl_usr/usr_ifs/usr_wp_viewport.rs +++ b/src/wl_usr/usr_ifs/usr_wp_viewport.rs @@ -1,6 +1,6 @@ +use jay_units::fixed::Fixed; use { crate::{ - fixed::Fixed, object::Version, wire::{WpViewportId, wp_viewport::*}, wl_usr::{UsrCon, usr_object::UsrObject}, diff --git a/src/xcon.rs b/src/xcon.rs index 0cd9ebc2..394f7cb7 100644 --- a/src/xcon.rs +++ b/src/xcon.rs @@ -1,21 +1,19 @@ -pub use crate::xcon::{ - formatter::Formatter, - parser::Parser, - wire_type::{Message, Request, XEvent}, +pub use jay_xcon::{ + consts, xauthority, Formatter, Message, Parser, Request, SendEvent, XEvent, XconError, }; +use jay_async_engine::{Phase, SpawnedFuture}; use { crate::{ - async_engine::{Phase, SpawnedFuture}, + compositor::DISPLAY, - io_uring::IoUringError, state::State, utils::{ buf::DynamicBuf, - bufio::{BufIo, BufIoError, BufIoMessage}, + bufio::{BufIo, BufIoMessage}, clonecell::CloneCell, errorfmt::ErrorFmt, numcell::NumCell, - oserror::{OsError, OsErrorExt2}, + oserror::OsErrorExt2, queue::AsyncQueue, stack::Stack, vec_ext::VecExt, @@ -29,7 +27,6 @@ use { consts::{IMAGE_FORMAT_Z_PIXMAP, RENDER_PICT_TYPE_DIRECT}, incoming::handle_incoming, outgoing::handle_outgoing, - wire_type::SendEvent, xauthority::{LOCAL, MIT_MAGIC_COOKIE, XAuthority}, }, }, @@ -49,93 +46,11 @@ use { rc::{Rc, Weak}, task::{Context, Poll, Waker}, }, - thiserror::Error, uapi::{OwnedFd, c}, }; -pub mod consts; -mod formatter; mod incoming; mod outgoing; -mod parser; -mod wire_type; -mod xauthority; - -#[derive(Debug, Error)] -pub enum XconError { - #[error("Unexpected EOF")] - UnexpectedEof, - #[error("Buffer slice is not properly aligned")] - UnalignedSlice, - #[error("Neither XAUTHORITY nor HOME is set")] - HomeNotSet, - #[error("Could not read Xauthority file")] - ReadXAuthority(#[source] std::io::Error), - #[error("Display field in Xauthority could not be parsed")] - InvalidAuthorityDisplay, - #[error("The DISPLAY is not set")] - DisplayNotSet, - #[error("DISPLAY contains an invalid value")] - InvalidDisplayFormat, - #[error("Could not create a unix socket")] - CreateSocket(#[source] OsError), - #[error("Could not connect to Xserver")] - ConnectSocket(#[source] IoUringError), - #[error("Could not retrive the hostname")] - Hostname(#[source] OsError), - #[error("Server did not send enough fds")] - NotEnoughFds, - #[error("Server rejected our connection attempt: {0}")] - Connect(BString), - #[error("Server requires additional authentication: {0}")] - Authenticate(BString), - #[error(transparent)] - BufIoError(#[from] BufIoError), - #[error("The server did not send a reply to a request")] - MissingReply, - #[error("The server did not send fds with a reply")] - MissingFds, - #[error("The server sent a message with an excessive size")] - ExcessiveMessageSize, - #[error(transparent)] - XconError(Rc), - #[error("The server does not support the `{0}` extension")] - ExtensionUnavailable(&'static str), - #[error("The server returned error {0}")] - CoreError(u8), - #[error("The extension `{}` returned error {}", .0.name(), .1)] - ExtensionError(Extension, u8), - #[error("The connection to the server has already been closed")] - Dead, - #[error("Could not query the `{0}` extension")] - QueryExtension(BString, #[source] Box), - #[error("All available xids have been used")] - XidExhausted, - #[error("Enum contains an unknown variant")] - UnknownEnumVariant, - #[error("Could not query the render pict formats")] - QueryPictFormats(#[source] Box), - #[error("The server does not support the picture format for cursors")] - CursorFormatNotSupported, - #[error("Could not create a pixmap")] - CreatePixmap(#[source] Box), - #[error("Could not create a graphics context")] - CreateGc(#[source] Box), - #[error("Could not upload an image")] - PutImage(#[source] Box), - #[error("Could not create a picture")] - CreatePicture(#[source] Box), - #[error("Could not create a cursor")] - CreateCursor(#[source] Box), - #[error("Property has an invalid type")] - InvalidPropertyType, - #[error("Property has an invalid format. Expected: {0}; Actual: {1}")] - InvalidPropertyFormat(u8, u8), - #[error("Length of the property data is not a multiple of its format")] - IrregularPropertyLength, - #[error("The property is not set")] - PropertyUnavailable, -} #[derive(Debug)] struct ExtensionIdRange { diff --git a/src/xcon/incoming.rs b/src/xcon/incoming.rs index 540bb4cd..47965304 100644 --- a/src/xcon/incoming.rs +++ b/src/xcon/incoming.rs @@ -99,7 +99,7 @@ impl Incoming { break 'handle_error; }; let e = match ext { - Some(e) => XconError::ExtensionError(e, code), + Some(e) => XconError::ExtensionError(e.name(), code), _ => XconError::CoreError(code), }; if let Some(first) = reply_handlers.front() diff --git a/src/xwayland.rs b/src/xwayland.rs index a60e34a4..0b39aa2a 100644 --- a/src/xwayland.rs +++ b/src/xwayland.rs @@ -1,22 +1,21 @@ mod xsocket; mod xwm; +use jay_io_uring::IoUringError; use { crate::{ - client::{ClientCaps, ClientError}, + client::{ClientError, ClientMetadata}, compositor::DISPLAY, forker::{ForkerError, ForkerProxy}, ifs::{ - ipc::{DataOfferId, DataSourceId, IpcLocation, x_data_offer::XDataOffer}, + data_transfer::{DataOfferId, DataSourceId, TransferLocation, x_data_offer::XDataOffer}, wl_seat::SeatId, wl_surface::x_surface::xwindow::{Xwindow, XwindowData}, }, - io_uring::IoUringError, - security_context_acceptor::AcceptorMetadata, + state::State, user_session::import_environment, utils::{ - buf::Buf, errorfmt::ErrorFmt, line_logger::log_lines, oserror::{OsError, OsErrorExt2}, @@ -192,10 +191,8 @@ async fn run( Rc::new(client1), uapi::getuid(), pid, - ClientCaps::all(), - false, true, - &Rc::new(AcceptorMetadata::default()), + &Rc::new(ClientMetadata::default()), ); let client = match client { Ok(c) => c, @@ -226,11 +223,11 @@ async fn run( } const PROG: &str = "Xwayland"; -const ENABLE_EI_PORTAL: &str = "-enable-ei-portal"; pub async fn build_args(state: &State, forker: &ForkerProxy) -> (String, Vec) { + let _ = (state, forker); let prog = PROG.to_string(); - let mut args = vec![ + let args = vec![ "-terminate".to_string(), "-rootless".to_string(), "-verbose".to_string(), @@ -242,45 +239,9 @@ pub async fn build_args(state: &State, forker: &ForkerProxy) -> (String, Vec XwaylandFeatures { - let mut features = Default::default(); - let Ok(Pipe { read, write }) = pipe() else { - return features; - }; - forker.spawn( - PROG.to_string(), - vec!["-help".to_string()], - vec![], - vec![(2, Rc::new(write))], - ); - let read = Rc::new(read); - let mut help = Vec::new(); - let mut buf = Buf::new(1024); - loop { - match state.ring.read(&read, buf.clone()).await { - Ok(0) => break, - Ok(n) => help.extend_from_slice(&buf[..n]), - Err(_) => return features, - } - } - if help.as_bstr().contains_str(ENABLE_EI_PORTAL) { - features.ei_portal = true; - } - features -} - async fn log_xwayland(state: Rc, stderr: OwnedFd) { let stderr = Rc::new(stderr); let res = log_lines(&state.ring, &stderr, |left, right| { @@ -303,30 +264,30 @@ pub enum XWaylandEvent { #[expect(dead_code)] SeatChanged, - IpcCancelSource { - location: IpcLocation, + DataTransferCancelSource { + location: TransferLocation, seat: SeatId, source: DataSourceId, }, - IpcSendSource { - location: IpcLocation, + DataTransferSendSource { + location: TransferLocation, seat: SeatId, source: DataSourceId, mime_type: String, fd: Rc, }, - IpcSetOffer { - location: IpcLocation, + DataTransferSetOffer { + location: TransferLocation, seat: SeatId, offer: Rc, }, - IpcSetSelection { - location: IpcLocation, + DataTransferSetSelection { + location: TransferLocation, seat: SeatId, offer: Option>, }, - IpcAddOfferMimeType { - location: IpcLocation, + DataTransferAddOfferMimeType { + location: TransferLocation, seat: SeatId, offer: DataOfferId, mime_type: String, diff --git a/src/xwayland/xwm.rs b/src/xwayland/xwm.rs index b55312fe..4f3098b2 100644 --- a/src/xwayland/xwm.rs +++ b/src/xwayland/xwm.rs @@ -1,27 +1,33 @@ #![allow(clippy::await_holding_refcell_ref)] // all borrows are to data that is only used by this task +mod selection; +mod properties; +mod transfer; + +use jay_geometry::Rect; +use jay_async_engine::SpawnedFuture; +use selection::SelectionData; + use { crate::{ - async_engine::SpawnedFuture, + client::Client, - criteria::tlm::{TL_CHANGED_CLASS_INST, TL_CHANGED_ROLE}, ifs::{ - ipc::{ - DataOfferId, DataSourceId, DynDataOffer, DynDataSource, IpcLocation, IpcVtable, + data_transfer::{ + DataOfferId, DataSourceId, DynDataOffer, DynDataSource, TransferLocation, TransferVtable, SourceData, add_data_source_mime_type, destroy_data_device, destroy_data_offer, destroy_data_source, receive_data_offer, - x_data_device::{XClipboardIpc, XIpc, XIpcDevice, XPrimarySelectionIpc}, + x_data_device::{XClipboardTransfer, XTransfer, XTransferDevice, XPrimarySelectionTransfer}, x_data_offer::XDataOffer, x_data_source::XDataSource, }, wl_seat::{SeatId, WlSeatGlobal}, wl_surface::{ WlSurface, - x_surface::xwindow::{XInputModel, Xwindow, XwindowData}, + x_surface::xwindow::{Xwindow, XwindowData}, }, }, - io_uring::{IoUring, IoUringError}, - rect::Rect, + state::State, tree::{Node, ToplevelNode}, utils::{ @@ -34,7 +40,6 @@ use { hash_map_ext::HashMapExt, linkedlist::LinkedList, numcell::NumCell, - oserror::OsError, pipe::{Pipe, pipe}, rc_eq::rc_eq, }, @@ -43,7 +48,7 @@ use { ChangeProperty, ChangeWindowAttributes, ClientMessage, CompositeRedirectSubwindows, ConfigureNotify, ConfigureRequest, ConfigureWindow, ConfigureWindowValues, ConvertSelection, CreateNotify, CreateWindow, CreateWindowValues, DestroyNotify, - Extension, FocusIn, GetAtomName, GetGeometry, InternAtom, KillClient, MapNotify, + Extension, FocusIn, GetGeometry, InternAtom, KillClient, MapNotify, MapRequest, MapWindow, PropertyNotify, ResClientIdSpec, ResQueryClientIds, SelectSelectionInput, SelectionNotify, SelectionRequest, SetInputFocus, SetSelectionOwner, UnmapNotify, XfixesQueryVersion, XfixesSelectionNotify, @@ -53,23 +58,21 @@ use { consts::{ _NET_WM_STATE_ADD, _NET_WM_STATE_REMOVE, _NET_WM_STATE_TOGGLE, ATOM_ATOM, ATOM_NONE, ATOM_STRING, ATOM_WINDOW, ATOM_WM_CLASS, ATOM_WM_NAME, - ATOM_WM_SIZE_HINTS, ATOM_WM_TRANSIENT_FOR, COMPOSITE_REDIRECT_MANUAL, - CONFIG_WINDOW_HEIGHT, CONFIG_WINDOW_WIDTH, CONFIG_WINDOW_X, CONFIG_WINDOW_Y, - EVENT_MASK_FOCUS_CHANGE, EVENT_MASK_PROPERTY_CHANGE, - EVENT_MASK_SUBSTRUCTURE_NOTIFY, EVENT_MASK_SUBSTRUCTURE_REDIRECT, - ICCCM_WM_HINT_INPUT, ICCCM_WM_STATE_ICONIC, ICCCM_WM_STATE_NORMAL, - ICCCM_WM_STATE_WITHDRAWN, INPUT_FOCUS_POINTER_ROOT, MWM_HINTS_DECORATIONS_FIELD, - MWM_HINTS_FLAGS_FIELD, NOTIFY_DETAIL_POINTER, NOTIFY_MODE_GRAB, NOTIFY_MODE_UNGRAB, - PROP_MODE_APPEND, PROP_MODE_REPLACE, RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID, - SELECTION_CLIENT_CLOSE_MASK, SELECTION_WINDOW_DESTROY_MASK, - SET_SELECTION_OWNER_MASK, STACK_MODE_ABOVE, STACK_MODE_BELOW, - WINDOW_CLASS_INPUT_OUTPUT, + ATOM_WM_TRANSIENT_FOR, COMPOSITE_REDIRECT_MANUAL, CONFIG_WINDOW_HEIGHT, + CONFIG_WINDOW_WIDTH, CONFIG_WINDOW_X, CONFIG_WINDOW_Y, EVENT_MASK_FOCUS_CHANGE, + EVENT_MASK_PROPERTY_CHANGE, EVENT_MASK_SUBSTRUCTURE_NOTIFY, + EVENT_MASK_SUBSTRUCTURE_REDIRECT, ICCCM_WM_STATE_ICONIC, ICCCM_WM_STATE_NORMAL, + ICCCM_WM_STATE_WITHDRAWN, INPUT_FOCUS_POINTER_ROOT, NOTIFY_DETAIL_POINTER, + NOTIFY_MODE_GRAB, NOTIFY_MODE_UNGRAB, PROP_MODE_REPLACE, + RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID, SELECTION_CLIENT_CLOSE_MASK, + SELECTION_WINDOW_DESTROY_MASK, SET_SELECTION_OWNER_MASK, STACK_MODE_ABOVE, + STACK_MODE_BELOW, WINDOW_CLASS_INPUT_OUTPUT, }, }, xwayland::{XWaylandError, XWaylandEvent}, }, ahash::{AHashMap, AHashSet}, - bstr::{ByteSlice, ByteVec}, + bstr::ByteSlice, futures_util::{FutureExt, select}, smallvec::SmallVec, std::{ @@ -79,9 +82,8 @@ use { mem::{self}, ops::{Deref, DerefMut}, rc::Rc, - time::Duration, }, - uapi::{OwnedFd, c}, + uapi::OwnedFd, }; atoms! { @@ -161,54 +163,12 @@ atoms! { XdndTypeList, } -struct EnhancedOffer { - offer: Rc, - mime_types: RefCell>, - active: Cell, -} - -#[derive(Default)] -struct SelectionData { - sources: CopyHashMap>, - offers: CopyHashMap>, - active_offer: CloneCell>>, - win: Cell, - selection: Cell, - pending_transfers: RefCell>, - _phantom: PhantomData, -} - -impl SelectionData { - fn destroy(&self) { - for offer in self.offers.lock().drain_values() { - destroy_data_offer::(&offer.offer); - } - self.active_offer.take(); - self.destroy_sources(); - } - - fn destroy_sources(&self) { - for source in self.sources.lock().drain_values() { - destroy_data_source::(&source); - } - } - - fn seat_removed(&self, id: SeatId) { - if let Some(offer) = self.active_offer.get() - && offer.offer.get_seat().id() == id - { - self.active_offer.take(); - } - self.offers.remove(&id); - self.sources.remove(&id); - } -} #[derive(Default)] pub struct XwmShared { - devices: CopyHashMap>, - data: SelectionData, - primary_selection: SelectionData, + devices: CopyHashMap>, + data: SelectionData, + primary_selection: SelectionData, transfers: CopyHashMap>, } @@ -217,8 +177,8 @@ impl Drop for XwmShared { self.data.destroy(); self.primary_selection.destroy(); for device in self.devices.lock().drain_values() { - destroy_data_device::(&device); - destroy_data_device::(&device); + destroy_data_device::(&device); + destroy_data_device::(&device); device.seat.unset_x_data_device(device.id); } self.transfers.clear(); @@ -253,13 +213,6 @@ pub struct Wm { num_mapped: usize, } -struct PendingTransfer { - mime_type: u32, - fd: Rc, -} - -const TEXT_PLAIN_UTF_8: &str = "text/plain;charset=utf-8"; -const TEXT_PLAIN: &str = "text/plain"; #[derive(Copy, Clone, Debug, Eq, PartialEq)] enum Initiator { @@ -566,7 +519,7 @@ impl Wm { self.shared.devices.remove(&seat); } for seat in new_seats { - let dd = Rc::new(XIpcDevice { + let dd = Rc::new(XTransferDevice { id: self.state.xwayland.ipc_device_ids.next(), clipboard: Default::default(), primary_selection: Default::default(), @@ -609,29 +562,29 @@ impl Wm { XWaylandEvent::ActivateRoot => self.activate_window(None, Initiator::Wayland).await, XWaylandEvent::Close(window) => self.close_window(&window).await, XWaylandEvent::SeatChanged => self.seats_changed(), - XWaylandEvent::IpcCancelSource { + XWaylandEvent::DataTransferCancelSource { location, seat, source, } => match location { - IpcLocation::Clipboard => { - self.dd_cancel_source::(&self.shared.clone().data, seat, source) + TransferLocation::Clipboard => { + self.dd_cancel_source::(&self.shared.clone().data, seat, source) } - IpcLocation::PrimarySelection => self.dd_cancel_source::( + TransferLocation::PrimarySelection => self.dd_cancel_source::( &self.shared.clone().primary_selection, seat, source, ), }, - XWaylandEvent::IpcSendSource { + XWaylandEvent::DataTransferSendSource { location, seat, source, mime_type, fd, } => match location { - IpcLocation::Clipboard => { - self.dd_send_source::( + TransferLocation::Clipboard => { + self.dd_send_source::( &self.shared.clone().data, seat, source, @@ -640,8 +593,8 @@ impl Wm { ) .await } - IpcLocation::PrimarySelection => { - self.dd_send_source::( + TransferLocation::PrimarySelection => { + self.dd_send_source::( &self.shared.clone().primary_selection, seat, source, @@ -651,17 +604,17 @@ impl Wm { .await } }, - XWaylandEvent::IpcSetOffer { + XWaylandEvent::DataTransferSetOffer { location, seat, offer, } => match location { - IpcLocation::Clipboard => { - self.dd_set_offer::(&self.shared.clone().data, seat, offer) + TransferLocation::Clipboard => { + self.dd_set_offer::(&self.shared.clone().data, seat, offer) .await } - IpcLocation::PrimarySelection => { - self.dd_set_offer::( + TransferLocation::PrimarySelection => { + self.dd_set_offer::( &self.shared.clone().primary_selection, seat, offer, @@ -669,17 +622,17 @@ impl Wm { .await } }, - XWaylandEvent::IpcSetSelection { + XWaylandEvent::DataTransferSetSelection { seat, location, offer, } => match location { - IpcLocation::Clipboard => { - self.dd_set_selection::(&self.shared.clone().data, seat, offer) + TransferLocation::Clipboard => { + self.dd_set_selection::(&self.shared.clone().data, seat, offer) .await } - IpcLocation::PrimarySelection => { - self.dd_set_selection::( + TransferLocation::PrimarySelection => { + self.dd_set_selection::( &self.shared.clone().primary_selection, seat, offer, @@ -687,14 +640,14 @@ impl Wm { .await } }, - XWaylandEvent::IpcAddOfferMimeType { + XWaylandEvent::DataTransferAddOfferMimeType { location, seat, offer, mime_type, } => match location { - IpcLocation::Clipboard => { - self.dd_add_offer_mime_type::( + TransferLocation::Clipboard => { + self.dd_add_offer_mime_type::( &self.shared.clone().data, seat, offer, @@ -702,8 +655,8 @@ impl Wm { ) .await } - IpcLocation::PrimarySelection => { - self.dd_add_offer_mime_type::( + TransferLocation::PrimarySelection => { + self.dd_add_offer_mime_type::( &self.shared.clone().primary_selection, seat, offer, @@ -715,202 +668,6 @@ impl Wm { } } - async fn dd_add_offer_mime_type( - &mut self, - sd: &SelectionData, - seat: SeatId, - offer: DataOfferId, - mt: String, - ) { - let enhanced = match sd.offers.get(&seat) { - Some(r) if r.offer.offer_id != offer => { - return; - } - None => { - return; - } - Some(r) => r, - }; - let mt = match self.mime_type_to_atom(mt).await { - Ok(mt) => mt, - Err(e) => { - log::error!("Could not get mime type atom: {}", ErrorFmt(e)); - return; - } - }; - enhanced.mime_types.borrow_mut().push(mt); - } - - async fn dd_set_offer( - &mut self, - sd: &SelectionData, - seat: SeatId, - offer: Rc, - ) { - let mut mime_types = vec![]; - if let Some(offer) = sd.offers.remove(&seat) { - destroy_data_offer::(&offer.offer); - mime_types = mem::take(offer.mime_types.borrow_mut().deref_mut()); - } - sd.offers.set( - seat, - Rc::new(EnhancedOffer { - offer, - mime_types: RefCell::new(mime_types), - active: Cell::new(false), - }), - ); - } - - async fn dd_set_selection( - &mut self, - sd: &SelectionData, - seat: SeatId, - offer: Option>, - ) { - let offer = match offer { - None => { - if let Some(offer) = sd.offers.remove(&seat) { - destroy_data_offer::(&offer.offer); - if offer.active.get() { - sd.active_offer.take(); - } - } - return; - } - Some(offer) => offer, - }; - let enhanced = match sd.offers.get(&seat) { - None => { - destroy_data_offer::(&offer); - return; - } - Some(e) => e, - }; - if !rc_eq(&enhanced.offer, &offer) { - destroy_data_offer::(&offer); - return; - } - if !enhanced.active.replace(true) - && let Some(old) = sd.active_offer.set(Some(enhanced)) - { - old.active.set(false); - } - let so = SetSelectionOwner { - owner: sd.win.get(), - selection: sd.selection.get(), - time: 0, - }; - if let Err(err) = self.c.call(&so).await { - log::error!("Could not set primary selection owner: {}", ErrorFmt(err)); - } - } - - async fn get_atom_name(&mut self, atom: u32) -> Result { - if let Some(name) = self.atom_name_cache.get(&atom) { - return Ok(name.clone()); - } - let gan = GetAtomName { atom }; - match self.c.call(&gan).await { - Ok(name) => { - let name = name.get().name.to_string(); - self.atom_name_cache.insert(atom, name.clone()); - Ok(name) - } - Err(e) => Err(e), - } - } - - async fn get_atom(&mut self, name: String) -> Result { - if let Some(atom) = self.atom_cache.get(&name) { - return Ok(*atom); - } - let ia = InternAtom { - only_if_exists: 0, - name: name.as_bytes().as_bstr(), - }; - match self.c.call(&ia).await { - Ok(id) => { - let atom = id.get().atom; - self.atom_cache.insert(name, atom); - Ok(atom) - } - Err(e) => Err(e), - } - } - - async fn mime_type_to_atom(&mut self, mime_type: String) -> Result { - match mime_type.as_str() { - TEXT_PLAIN_UTF_8 => Ok(self.atoms.UTF8_STRING), - TEXT_PLAIN => Ok(ATOM_STRING), - _ => self.get_atom(mime_type).await, - } - } - - async fn atom_to_mime_type(&mut self, atom: u32) -> Result { - if atom == self.atoms.UTF8_STRING { - Ok(TEXT_PLAIN_UTF_8.to_string()) - } else if atom == ATOM_STRING { - Ok(TEXT_PLAIN.to_string()) - } else { - self.get_atom_name(atom).await - } - } - - async fn dd_send_source( - &mut self, - sd: &SelectionData, - seat: SeatId, - src: DataSourceId, - mime_type: String, - fd: Rc, - ) { - let actual_src = match sd.sources.get(&seat) { - None => return, - Some(src) => src, - }; - if actual_src.source_data().id != src { - return; - } - let mime_type = match self.mime_type_to_atom(mime_type).await { - Ok(mt) => mt, - Err(e) => { - log::error!("Could not intern mime type: {}", ErrorFmt(e)); - return; - } - }; - let cs = ConvertSelection { - requestor: sd.win.get(), - selection: sd.selection.get(), - target: mime_type, - property: self.atoms._WL_SELECTION, - time: 0, - }; - if let Err(e) = self.c.call(&cs).await { - log::error!( - "Could not perform convert selection request: {}", - ErrorFmt(e) - ); - return; - } - sd.pending_transfers - .borrow_mut() - .push(PendingTransfer { mime_type, fd }); - } - - fn dd_cancel_source( - &mut self, - sd: &SelectionData, - seat: SeatId, - source: DataSourceId, - ) { - if let Some(cur) = sd.sources.get(&seat) - && cur.source_data().id == source - { - sd.sources.remove(&seat); - destroy_data_source::(&cur); - } - } async fn handle_xwayland_configure(&mut self, window: Rc) { if window.data.destroyed.get() { @@ -1082,398 +839,6 @@ impl Wm { } } - fn compute_input_model(&self, data: &Rc) { - let has_wm_take_focus = data.info.protocols.contains(&self.atoms.WM_TAKE_FOCUS); - let accepts_input = data.info.icccm_hints.input.get(); - let model = match (accepts_input, has_wm_take_focus) { - (false, false) => XInputModel::None, - (true, false) => XInputModel::Passive, - (true, true) => XInputModel::Local, - (false, true) => XInputModel::Global, - }; - data.info.input_model.set(model); - } - - async fn load_window_wm_window_role(&self, data: &Rc) { - let property_changed = || { - if let Some(window) = data.window.get() { - window.toplevel_data.property_changed(TL_CHANGED_ROLE); - } - }; - let mut buf = vec![]; - match self - .c - .get_property::(data.window_id, self.atoms.WM_WINDOW_ROLE, 0, &mut buf) - .await - { - Ok(ty) if ty == ATOM_STRING => {} - Ok(ty) if ty == self.atoms.UTF8_STRING => {} - Ok(ty) => { - self.unexpected_type(data.window_id, "WM_WINDOW_ROLE", ty) - .await; - return; - } - Err(XconError::PropertyUnavailable) => { - data.info.role.borrow_mut().take(); - property_changed(); - return; - } - Err(e) => { - log::error!( - "Could not retrieve WM_WINDOW_ROLE property: {}", - ErrorFmt(e) - ); - return; - } - } - // log::info!("{} role {}", data.window_id, buf.as_bstr()); - *data.info.role.borrow_mut() = Some(buf.into_string_lossy()); - property_changed(); - } - - async fn load_window_wm_class(&self, data: &Rc) { - let mut buf = vec![]; - let property_changed = || { - if let Some(window) = data.window.get() { - let class = data.info.class.borrow(); - for handle in window.toplevel_data.manager_handles.lock().values() { - handle.send_app_id(class.as_deref().unwrap_or_default()); - handle.send_done(); - } - window.toplevel_data.property_changed(TL_CHANGED_CLASS_INST); - } - }; - match self - .c - .get_property::(data.window_id, ATOM_WM_CLASS, 0, &mut buf) - .await - { - Ok(ty) if ty == ATOM_STRING => {} - Ok(ty) if ty == self.atoms.UTF8_STRING => {} - Ok(ty) => { - self.unexpected_type(data.window_id, "WM_CLASS", ty).await; - return; - } - Err(XconError::PropertyUnavailable) => { - data.info.instance.borrow_mut().take(); - data.info.class.borrow_mut().take(); - property_changed(); - return; - } - Err(e) => { - log::error!("Could not retrieve WM_CLASS property: {}", ErrorFmt(e)); - return; - } - } - let mut iter = buf.split(|c| *c == 0); - let mut map = || Some(iter.next().unwrap_or(&[]).to_str_lossy().into_owned()); - *data.info.instance.borrow_mut() = map(); - *data.info.class.borrow_mut() = map(); - property_changed(); - } - - async fn load_window_wm_name2(&self, data: &Rc, prop: u32, name: &str) { - let mut buf = vec![]; - match self - .c - .get_property::(data.window_id, prop, 0, &mut buf) - .await - { - Ok(ty) if ty == ATOM_STRING && data.info.utf8_title.get() => return, - Ok(ty) if ty == ATOM_STRING => {} - Ok(ty) if ty == self.atoms.COMPOUND_TEXT => return, // used by java. - Ok(ty) if ty == self.atoms.UTF8_STRING => { - data.info.utf8_title.set(true); - } - Ok(ty) => { - self.unexpected_type(data.window_id, name, ty).await; - return; - } - Err(XconError::PropertyUnavailable) => return, - Err(e) => { - log::error!("Could not retrieve {} property: {}", name, ErrorFmt(e)); - return; - } - } - let title = buf.as_bstr().to_string(); - if let Some(window) = data.window.get() { - window.toplevel_data.set_title(&title); - window.tl_title_changed(); - } - *data.info.title.borrow_mut() = Some(title); - data.title_changed(); - } - - async fn unexpected_type(&self, window: u32, prop: &str, ty: u32) { - let mut ty_name = "unknown".as_bytes().as_bstr(); - let res = self.c.call(&GetAtomName { atom: ty }).await; - if let Ok(res) = &res { - ty_name = res.get().name; - } - log::error!( - "Property {} of window {} has unexpected type {} ({})", - prop, - window, - ty_name, - ty - ); - } - - async fn load_window_wm_name(&self, data: &Rc) { - self.load_window_wm_name2(data, ATOM_WM_NAME, "WM_NAME") - .await; - } - - async fn load_window_net_wm_name(&self, data: &Rc) { - self.load_window_wm_name2(data, self.atoms._NET_WM_NAME, "_NET_WM_NAME") - .await; - } - - async fn load_window_wm_transient_for(&self, data: &Rc) { - let mut buf = vec![]; - if let Err(e) = self - .c - .get_property::(data.window_id, ATOM_WM_TRANSIENT_FOR, ATOM_WINDOW, &mut buf) - .await - { - if not_matches!(e, XconError::PropertyUnavailable) { - log::error!( - "Could not retrieve WM_TRANSIENT_FOR property: {}", - ErrorFmt(e) - ); - } - } - if let Some(old) = data.parent.take() { - old.children.remove(&data.window_id); - } - if let Some(w) = buf.first() - && let Some(w) = self.windows.get(w) - { - if data.is_ancestor_of(w.clone()) { - log::error!("Cannot set WM_TRANSIENT_FOR because it would create a cycle"); - return; - } - w.children.set(data.window_id, data.clone()); - data.parent.set(Some(w.clone())); - } - } - - async fn load_window_wm_protocols(&self, data: &Rc) { - let mut buf = vec![]; - if let Err(e) = self - .c - .get_property::(data.window_id, self.atoms.WM_PROTOCOLS, ATOM_ATOM, &mut buf) - .await - { - if not_matches!(e, XconError::PropertyUnavailable) { - log::error!("Could not retrieve WM_PROTOCOLS property: {}", ErrorFmt(e)); - } - return; - } - data.info.protocols.clear(); - data.info - .protocols - .lock() - .extend(buf.iter().copied().map(|v| (v, ()))); - self.compute_input_model(data); - } - - async fn load_window_wm_hints(&self, data: &Rc) { - let mut buf = vec![]; - if let Err(e) = self - .c - .get_property::(data.window_id, self.atoms.WM_HINTS, 0, &mut buf) - .await - { - if not_matches!(e, XconError::PropertyUnavailable) { - log::error!("Could not retrieve WM_HINTS property: {}", ErrorFmt(e)); - } - data.info.icccm_hints.input.set(true); - self.compute_input_model(data); - return; - } - let mut values = [0; 9]; - let len = values.len().min(buf.len()); - values[..len].copy_from_slice(&buf[..len]); - data.info.icccm_hints.flags.set(values[0] as i32); - data.info.icccm_hints.input.set(values[1] != 0); - data.info.icccm_hints.initial_state.set(values[2] as i32); - data.info.icccm_hints.icon_pixmap.set(values[3]); - data.info.icccm_hints.icon_window.set(values[4]); - data.info.icccm_hints.icon_x.set(values[5] as i32); - data.info.icccm_hints.icon_y.set(values[6] as i32); - data.info.icccm_hints.icon_mask.set(values[7]); - data.info.icccm_hints.window_group.set(values[8]); - if data - .info - .icccm_hints - .flags - .get() - .not_contains(ICCCM_WM_HINT_INPUT) - { - data.info.icccm_hints.input.set(true); - } - self.compute_input_model(data); - } - - async fn load_window_wm_normal_hints(&self, data: &Rc) { - let mut buf = vec![]; - if let Err(e) = self - .c - .get_property::( - data.window_id, - self.atoms.WM_NORMAL_HINTS, - ATOM_WM_SIZE_HINTS, - &mut buf, - ) - .await - { - if not_matches!(e, XconError::PropertyUnavailable) { - log::error!( - "Could not retrieve WM_NORMAL_HINTS property: {}", - ErrorFmt(e) - ); - } - return; - } - let mut values = [0; 18]; - let len = values.len().min(buf.len()); - values[..len].copy_from_slice(&buf[..len]); - data.info.normal_hints.flags.set(values[0]); - data.info.normal_hints.x.set(values[1] as i32); - data.info.normal_hints.y.set(values[2] as i32); - data.info.normal_hints.width.set(values[3] as i32); - data.info.normal_hints.height.set(values[4] as i32); - data.info.normal_hints.min_width.set(values[5] as i32); - data.info.normal_hints.min_height.set(values[6] as i32); - data.info.normal_hints.max_width.set(values[7] as i32); - data.info.normal_hints.max_height.set(values[8] as i32); - data.info.normal_hints.width_inc.set(values[9] as i32); - data.info.normal_hints.height_inc.set(values[10] as i32); - data.info.normal_hints.min_aspect_num.set(values[11] as i32); - data.info.normal_hints.min_aspect_den.set(values[12] as i32); - data.info.normal_hints.max_aspect_num.set(values[13] as i32); - data.info.normal_hints.max_aspect_den.set(values[14] as i32); - data.info.normal_hints.base_width.set(values[15] as i32); - data.info.normal_hints.base_height.set(values[16] as i32); - data.info.normal_hints.win_gravity.set(values[17]); - self.update_wants_floating(data); - } - - async fn load_window_motif_wm_hints(&self, data: &Rc) { - let mut buf = vec![]; - if let Err(e) = self - .c - .get_property::(data.window_id, self.atoms._MOTIF_WM_HINTS, 0, &mut buf) - .await - { - if not_matches!(e, XconError::PropertyUnavailable) { - log::error!( - "Could not retrieve _MOTIF_WM_HINTS property: {}", - ErrorFmt(e) - ); - } - return; - } - let mut values = [0; 5]; - let len = values.len().min(buf.len()); - values[..len].copy_from_slice(&buf[..len]); - data.info - .motif_hints - .flags - .set(values[MWM_HINTS_FLAGS_FIELD]); - data.info - .motif_hints - .decorations - .set(values[MWM_HINTS_DECORATIONS_FIELD]); - } - - async fn load_window_net_startup_id(&self, data: &Rc) { - let mut buf = vec![]; - match self - .c - .get_property::(data.window_id, self.atoms._NET_STARTUP_ID, 0, &mut buf) - .await - { - Ok(ty) if ty == ATOM_STRING => {} - Ok(ty) if ty == self.atoms.UTF8_STRING => {} - Ok(ty) => { - self.unexpected_type(data.window_id, "_NET_STARTUP_ID", ty) - .await; - return; - } - Err(XconError::PropertyUnavailable) => return, - Err(e) => { - log::error!( - "Could not retrieve _NET_STARTUP_ID property: {}", - ErrorFmt(e) - ); - return; - } - } - *data.info.startup_id.borrow_mut() = Some(buf.into()); - } - - async fn load_window_net_wm_state(&self, data: &Rc) { - data.info.fullscreen.set(false); - let mut buf = vec![]; - if let Err(e) = self - .c - .get_property::(data.window_id, self.atoms._NET_WM_STATE, 0, &mut buf) - .await - { - if not_matches!(e, XconError::PropertyUnavailable) { - log::error!("Could not retrieve _NET_WM_STATE property: {}", ErrorFmt(e)); - } - return; - } - for prop in buf { - if prop == self.atoms._NET_WM_STATE_MODAL { - data.info.modal.set(true); - self.update_wants_floating(data); - } else if prop == self.atoms._NET_WM_STATE_FULLSCREEN { - data.info.fullscreen.set(true); - } else if prop == self.atoms._NET_WM_STATE_MAXIMIZED_VERT { - data.info.maximized_vert.set(true); - } else if prop == self.atoms._NET_WM_STATE_MAXIMIZED_HORZ { - data.info.maximized_horz.set(true); - } else if prop == self.atoms._NET_WM_STATE_HIDDEN { - data.info.minimized.set(true); - } - } - } - - async fn load_window_net_wm_window_type(&self, data: &Rc) { - let mut buf = vec![]; - if let Err(e) = self - .c - .get_property::( - data.window_id, - self.atoms._NET_WM_WINDOW_TYPE, - ATOM_ATOM, - &mut buf, - ) - .await - { - if not_matches!(e, XconError::PropertyUnavailable) { - log::error!( - "Could not retrieve _NET_WM_WINDOW_TYPE property: {}", - ErrorFmt(e) - ); - } - return; - } - data.info - .never_focus - .set(buf.iter().any(|t| self.never_focus.contains(t))); - data.info.window_types.clear(); - data.info - .window_types - .lock() - .extend(buf.iter().copied().map(|v| (v, ()))); - self.update_wants_floating(data); - } - async fn create_window(&mut self, data: &Rc, surface: Rc) { if data.window.is_some() { log::error!("The xwindow has already been constructed"); @@ -1583,48 +948,6 @@ impl Wm { } } - async fn handle_xfixes_event(&mut self, event: &Event) -> Result<(), XWaylandError> { - match event.code() { - XfixesSelectionNotify::OPCODE => self.handle_xfixes_selection_notify(event).await, - _ => Ok(()), - } - } - - async fn handle_xfixes_selection_notify(&mut self, event: &Event) -> Result<(), XWaylandError> { - let event: XfixesSelectionNotify = event.parse()?; - let shared = self.shared.clone(); - if event.selection == self.atoms.PRIMARY { - self.handle_xfixes_selection_notify_(&shared.primary_selection, &event) - .await - } else if event.selection == self.atoms.CLIPBOARD { - self.handle_xfixes_selection_notify_(&shared.data, &event) - .await - } else { - Ok(()) - } - } - - async fn handle_xfixes_selection_notify_( - &mut self, - sd: &SelectionData, - event: &XfixesSelectionNotify, - ) -> Result<(), XWaylandError> { - if event.owner == sd.win.get() { - return Ok(()); - } - sd.destroy_sources(); - let cs = ConvertSelection { - requestor: sd.win.get(), - selection: sd.selection.get(), - target: self.atoms.TARGETS, - property: self.atoms._WL_SELECTION, - time: event.timestamp, - }; - if let Err(e) = self.c.call(&cs).await { - log::error!("Could not convert selection: {}", ErrorFmt(e)); - } - Ok(()) - } async fn handle_core_event(&mut self, event: &Event) -> Result<(), XWaylandError> { match event.code() { @@ -1644,227 +967,6 @@ impl Wm { } } - async fn handle_selection_request(&mut self, event: &Event) -> Result<(), XWaylandError> { - let event: SelectionRequest = event.parse()?; - let shared = self.shared.clone(); - if event.selection == self.atoms.PRIMARY { - self.handle_selection_request_(&shared.primary_selection, &event) - .await - } else if event.selection == self.atoms.CLIPBOARD { - self.handle_selection_request_(&shared.data, &event).await - } else { - log::warn!("Unknown selection request"); - Ok(()) - } - } - - async fn handle_selection_request_( - &mut self, - sd: &SelectionData, - event: &SelectionRequest, - ) -> Result<(), XWaylandError> { - let mut success = Some(false); - if let Some(offer) = sd.active_offer.get() { - let mt = offer.mime_types.borrow_mut(); - if event.target == self.atoms.TARGETS { - let cp = ChangeProperty { - mode: PROP_MODE_REPLACE, - window: event.requestor, - property: event.property, - ty: ATOM_ATOM, - format: 32, - data: uapi::as_bytes(&mt[..]), - }; - match self.c.call(&cp).await { - Ok(_) => success = Some(true), - Err(e) => { - log::error!("Could not set selection property: {}", ErrorFmt(e)); - } - } - } else { - 'convert: { - let present = mt.contains(&event.target); - drop(mt); - let mt = match self.atom_to_mime_type(event.target).await { - Ok(mt) => mt, - Err(e) => { - log::error!("Could not get mime type name: {}", ErrorFmt(e)); - break 'convert; - } - }; - if !present { - log::error!("Peer requested unavailable target {}", mt); - break 'convert; - } - let Pipe { - read: rx, - write: tx, - } = match pipe() { - Ok(p) => p, - Err(e) => { - log::error!("Could not create pipe: {}", e); - break 'convert; - } - }; - success = None; - receive_data_offer::(&offer.offer, &mt, Rc::new(tx)); - let id = self.transfer_ids.fetch_add(1); - let wtx = WaylandToXTransfer { - id, - fd: Rc::new(rx), - ring: self.state.ring.clone(), - c: self.c.clone(), - window: event.requestor, - time: event.time, - property: event.property, - ty: event.target, - selection: sd.selection.get(), - shared: self.shared.clone(), - }; - self.shared - .transfers - .set(id, self.state.eng.spawn("wayland to X transfer", wtx.run())); - } - } - } - if let Some(success) = success { - let target = match success { - true => event.target, - false => ATOM_NONE, - }; - let sn = SelectionNotify { - time: event.time, - requestor: event.requestor, - selection: sd.selection.get(), - target, - property: event.property, - }; - if let Err(e) = self.c.send_event(false, event.requestor, 0, &sn).await { - log::error!("Could not send event: {}", ErrorFmt(e)); - } - } - Ok(()) - } - - async fn handle_selection_notify(&mut self, event: &Event) -> Result<(), XWaylandError> { - let event: SelectionNotify = event.parse()?; - if event.property != self.atoms._WL_SELECTION { - return Ok(()); - } - let shared = self.shared.clone(); - if event.selection == self.atoms.PRIMARY { - self.handle_selection_notify_(&shared.primary_selection, &event) - .await - } else if event.selection == self.atoms.CLIPBOARD { - self.handle_selection_notify_(&shared.data, &event).await - } else { - Ok(()) - } - } - - async fn handle_selection_notify_( - &mut self, - sd: &SelectionData, - event: &SelectionNotify, - ) -> Result<(), XWaylandError> { - if event.property != self.atoms._WL_SELECTION { - return Ok(()); - } - if event.target == ATOM_NONE { - return Ok(()); - } - if event.target == self.atoms.TARGETS { - let targets = self.get_selection_mime_types(sd.win.get()).await?; - for dev in self.shared.devices.lock().values() { - let seat = T::get_device_seat(dev); - if !seat.may_modify_primary_selection(&self.client, None) { - continue; - } - let source = Rc::new(XDataSource { - state: self.state.clone(), - device: dev.clone(), - data: SourceData::new(&self.client), - location: T::LOCATION, - }); - for target in &targets { - add_data_source_mime_type::(&source, target); - } - let res = match source.location { - IpcLocation::Clipboard => seat.set_selection(Some(source.clone())), - IpcLocation::PrimarySelection => { - seat.set_primary_selection(Some(source.clone())) - } - }; - if let Err(e) = res { - log::error!("Could not set selection: {}", ErrorFmt(e)); - return Ok(()); - } - sd.sources.set(seat.id(), source); - } - } else { - let mut transfers = sd.pending_transfers.borrow_mut(); - let transfers = transfers.drain(..); - let mut data = vec![]; - let gp = self - .c - .get_property( - sd.win.get(), - self.atoms._WL_SELECTION, - event.target, - &mut data, - ) - .await; - if let Err(e) = gp { - log::error!("Could not get converted property: {}", e); - return Ok(()); - } - let mut data = Buf::from_slice(&data); - for transfer in transfers { - if event.target != transfer.mime_type { - log::error!("Conversion yielded an incompatible mime type"); - continue; - } - let id = self.transfer_ids.fetch_add(1); - let transfer = XToWaylandTransfer { - id, - data: data.clone(), - fd: transfer.fd, - state: self.state.clone(), - shared: self.shared.clone(), - }; - self.shared.transfers.set( - id, - self.state - .eng - .spawn("X to wayland transfer", transfer.run()), - ); - } - } - - Ok(()) - } - - async fn get_selection_mime_types( - &mut self, - window: u32, - ) -> Result, XWaylandError> { - let mut buf = vec![]; - self.c - .get_property3::(window, self.atoms._WL_SELECTION, ATOM_ATOM, true, &mut buf) - .await?; - let mut res = vec![]; - for atom in buf { - let name = match self.atom_to_mime_type(atom).await { - Ok(n) => n, - Err(e) => { - log::error!("Could not get atom name: {}", ErrorFmt(e)); - continue; - } - }; - res.push(name); - } - Ok(res) - } async fn handle_unmap_notify(&mut self, revent: &Event) -> Result<(), XWaylandError> { let event: UnmapNotify = revent.parse()?; @@ -2034,6 +1136,7 @@ impl Wm { self.windows_by_surface_serial.remove(&serial); } if let Some(window) = data.window.take() { + window.queue_spawn_out(); window.destroy(); } if let Some(parent) = data.parent.take() { @@ -2567,97 +1670,3 @@ impl Wm { data.info.wants_floating.set(res); } } - -struct XToWaylandTransfer { - id: u64, - data: Buf, - fd: Rc, - state: Rc, - shared: Rc, -} - -impl XToWaylandTransfer { - async fn run(mut self) { - let timeout = self.state.now() + Duration::from_millis(5000); - let mut pos = 0; - while pos < self.data.len() { - let res = self - .state - .ring - .write(&self.fd, self.data.slice(pos..), Some(timeout)); - match res.await { - Ok(n) => pos += n, - Err(IoUringError::OsError(OsError(c::ECANCELED))) => { - log::error!("Transfer timed out"); - break; - } - Err(e) => { - log::error!("Could not write to wayland client: {}", ErrorFmt(e)); - break; - } - } - } - self.shared.transfers.remove(&self.id); - } -} - -struct WaylandToXTransfer { - id: u64, - fd: Rc, - ring: Rc, - c: Rc, - window: u32, - time: u32, - property: u32, - ty: u32, - selection: u32, - shared: Rc, -} - -impl WaylandToXTransfer { - async fn run(self) { - let mut success = false; - let mut buf = Buf::new(1024); - loop { - match self.ring.read(&self.fd, buf.clone()).await { - Ok(0) => { - success = true; - break; - } - Ok(n) => { - let cp = ChangeProperty { - mode: PROP_MODE_APPEND, - window: self.window, - property: self.property, - ty: self.ty, - format: 8, - data: &buf[..n], - }; - if let Err(e) = self.c.call(&cp).await { - log::error!("Could not append data to property: {}", ErrorFmt(e)); - break; - } - } - Err(e) => { - log::error!("Could not read from wayland client: {}", ErrorFmt(e)); - break; - } - } - } - let target = match success { - true => self.ty, - false => ATOM_NONE, - }; - let sn = SelectionNotify { - time: self.time, - requestor: self.window, - selection: self.selection, - target, - property: self.property, - }; - if let Err(e) = self.c.send_event(false, self.window, 0, &sn).await { - log::error!("Could not send event: {}", ErrorFmt(e)); - } - self.shared.transfers.remove(&self.id); - } -} diff --git a/src/xwayland/xwm/properties.rs b/src/xwayland/xwm/properties.rs new file mode 100644 index 00000000..fc8cf48d --- /dev/null +++ b/src/xwayland/xwm/properties.rs @@ -0,0 +1,413 @@ +use { + super::Wm, + crate::{ + criteria::tlm::{TL_CHANGED_CLASS_INST, TL_CHANGED_ROLE}, + ifs::wl_surface::x_surface::xwindow::{XInputModel, XwindowData}, + tree::ToplevelNode, + utils::{bitflags::BitflagsExt, errorfmt::ErrorFmt}, + wire_xcon::GetAtomName, + xcon::{ + XconError, + consts::{ + ATOM_ATOM, ATOM_STRING, ATOM_WINDOW, ATOM_WM_CLASS, ATOM_WM_NAME, + ATOM_WM_SIZE_HINTS, ATOM_WM_TRANSIENT_FOR, ICCCM_WM_HINT_INPUT, + MWM_HINTS_DECORATIONS_FIELD, MWM_HINTS_FLAGS_FIELD, + }, + }, + }, + bstr::{ByteSlice, ByteVec}, + std::rc::Rc, +}; + +impl Wm { + pub(super) fn compute_input_model(&self, data: &Rc) { + let has_wm_take_focus = data.info.protocols.contains(&self.atoms.WM_TAKE_FOCUS); + let accepts_input = data.info.icccm_hints.input.get(); + let model = match (accepts_input, has_wm_take_focus) { + (false, false) => XInputModel::None, + (true, false) => XInputModel::Passive, + (true, true) => XInputModel::Local, + (false, true) => XInputModel::Global, + }; + data.info.input_model.set(model); + } + + pub(super) async fn load_window_wm_window_role(&self, data: &Rc) { + let property_changed = || { + if let Some(window) = data.window.get() { + window.toplevel_data.property_changed(TL_CHANGED_ROLE); + } + }; + let mut buf = vec![]; + match self + .c + .get_property::(data.window_id, self.atoms.WM_WINDOW_ROLE, 0, &mut buf) + .await + { + Ok(ty) if ty == ATOM_STRING => {} + Ok(ty) if ty == self.atoms.UTF8_STRING => {} + Ok(ty) => { + self.unexpected_type(data.window_id, "WM_WINDOW_ROLE", ty) + .await; + return; + } + Err(XconError::PropertyUnavailable) => { + data.info.role.borrow_mut().take(); + property_changed(); + return; + } + Err(e) => { + log::error!( + "Could not retrieve WM_WINDOW_ROLE property: {}", + ErrorFmt(e) + ); + return; + } + } + *data.info.role.borrow_mut() = Some(buf.into_string_lossy()); + property_changed(); + } + + pub(super) async fn load_window_wm_class(&self, data: &Rc) { + let mut buf = vec![]; + let property_changed = || { + if let Some(window) = data.window.get() { + let class = data.info.class.borrow(); + for handle in window.toplevel_data.manager_handles.lock().values() { + handle.send_app_id(class.as_deref().unwrap_or_default()); + handle.send_done(); + } + window.toplevel_data.property_changed(TL_CHANGED_CLASS_INST); + } + }; + match self + .c + .get_property::(data.window_id, ATOM_WM_CLASS, 0, &mut buf) + .await + { + Ok(ty) if ty == ATOM_STRING => {} + Ok(ty) if ty == self.atoms.UTF8_STRING => {} + Ok(ty) => { + self.unexpected_type(data.window_id, "WM_CLASS", ty).await; + return; + } + Err(XconError::PropertyUnavailable) => { + data.info.instance.borrow_mut().take(); + data.info.class.borrow_mut().take(); + property_changed(); + return; + } + Err(e) => { + log::error!("Could not retrieve WM_CLASS property: {}", ErrorFmt(e)); + return; + } + } + let mut iter = buf.split(|c| *c == 0); + let mut map = || Some(iter.next().unwrap_or(&[]).to_str_lossy().into_owned()); + *data.info.instance.borrow_mut() = map(); + *data.info.class.borrow_mut() = map(); + property_changed(); + } + + async fn load_window_wm_name2(&self, data: &Rc, prop: u32, name: &str) { + let mut buf = vec![]; + match self + .c + .get_property::(data.window_id, prop, 0, &mut buf) + .await + { + Ok(ty) if ty == ATOM_STRING && data.info.utf8_title.get() => return, + Ok(ty) if ty == ATOM_STRING => {} + Ok(ty) if ty == self.atoms.COMPOUND_TEXT => return, + Ok(ty) if ty == self.atoms.UTF8_STRING => { + data.info.utf8_title.set(true); + } + Ok(ty) => { + self.unexpected_type(data.window_id, name, ty).await; + return; + } + Err(XconError::PropertyUnavailable) => return, + Err(e) => { + log::error!("Could not retrieve {} property: {}", name, ErrorFmt(e)); + return; + } + } + let title = buf.as_bstr().to_string(); + if let Some(window) = data.window.get() { + window.toplevel_data.set_title(&title); + window.tl_title_changed(); + } + *data.info.title.borrow_mut() = Some(title); + data.title_changed(); + } + + async fn unexpected_type(&self, window: u32, prop: &str, ty: u32) { + let mut ty_name = "unknown".as_bytes().as_bstr(); + let res = self.c.call(&GetAtomName { atom: ty }).await; + if let Ok(res) = &res { + ty_name = res.get().name; + } + log::error!( + "Property {} of window {} has unexpected type {} ({})", + prop, + window, + ty_name, + ty + ); + } + + pub(super) async fn load_window_wm_name(&self, data: &Rc) { + self.load_window_wm_name2(data, ATOM_WM_NAME, "WM_NAME") + .await; + } + + pub(super) async fn load_window_net_wm_name(&self, data: &Rc) { + self.load_window_wm_name2(data, self.atoms._NET_WM_NAME, "_NET_WM_NAME") + .await; + } + + pub(super) async fn load_window_wm_transient_for(&self, data: &Rc) { + let mut buf = vec![]; + if let Err(e) = self + .c + .get_property::(data.window_id, ATOM_WM_TRANSIENT_FOR, ATOM_WINDOW, &mut buf) + .await + { + if not_matches!(e, XconError::PropertyUnavailable) { + log::error!( + "Could not retrieve WM_TRANSIENT_FOR property: {}", + ErrorFmt(e) + ); + } + } + if let Some(old) = data.parent.take() { + old.children.remove(&data.window_id); + } + if let Some(w) = buf.first() + && let Some(w) = self.windows.get(w) + { + if data.is_ancestor_of(w.clone()) { + log::error!("Cannot set WM_TRANSIENT_FOR because it would create a cycle"); + return; + } + w.children.set(data.window_id, data.clone()); + data.parent.set(Some(w.clone())); + } + } + + pub(super) async fn load_window_wm_protocols(&self, data: &Rc) { + let mut buf = vec![]; + if let Err(e) = self + .c + .get_property::(data.window_id, self.atoms.WM_PROTOCOLS, ATOM_ATOM, &mut buf) + .await + { + if not_matches!(e, XconError::PropertyUnavailable) { + log::error!("Could not retrieve WM_PROTOCOLS property: {}", ErrorFmt(e)); + } + return; + } + data.info.protocols.clear(); + data.info + .protocols + .lock() + .extend(buf.iter().copied().map(|v| (v, ()))); + self.compute_input_model(data); + } + + pub(super) async fn load_window_wm_hints(&self, data: &Rc) { + let mut buf = vec![]; + if let Err(e) = self + .c + .get_property::(data.window_id, self.atoms.WM_HINTS, 0, &mut buf) + .await + { + if not_matches!(e, XconError::PropertyUnavailable) { + log::error!("Could not retrieve WM_HINTS property: {}", ErrorFmt(e)); + } + data.info.icccm_hints.input.set(true); + self.compute_input_model(data); + return; + } + let mut values = [0; 9]; + let len = values.len().min(buf.len()); + values[..len].copy_from_slice(&buf[..len]); + data.info.icccm_hints.flags.set(values[0] as i32); + data.info.icccm_hints.input.set(values[1] != 0); + data.info.icccm_hints.initial_state.set(values[2] as i32); + data.info.icccm_hints.icon_pixmap.set(values[3]); + data.info.icccm_hints.icon_window.set(values[4]); + data.info.icccm_hints.icon_x.set(values[5] as i32); + data.info.icccm_hints.icon_y.set(values[6] as i32); + data.info.icccm_hints.icon_mask.set(values[7]); + data.info.icccm_hints.window_group.set(values[8]); + if data + .info + .icccm_hints + .flags + .get() + .not_contains(ICCCM_WM_HINT_INPUT) + { + data.info.icccm_hints.input.set(true); + } + self.compute_input_model(data); + } + + pub(super) async fn load_window_wm_normal_hints(&self, data: &Rc) { + let mut buf = vec![]; + if let Err(e) = self + .c + .get_property::( + data.window_id, + self.atoms.WM_NORMAL_HINTS, + ATOM_WM_SIZE_HINTS, + &mut buf, + ) + .await + { + if not_matches!(e, XconError::PropertyUnavailable) { + log::error!( + "Could not retrieve WM_NORMAL_HINTS property: {}", + ErrorFmt(e) + ); + } + return; + } + let mut values = [0; 18]; + let len = values.len().min(buf.len()); + values[..len].copy_from_slice(&buf[..len]); + data.info.normal_hints.flags.set(values[0]); + data.info.normal_hints.x.set(values[1] as i32); + data.info.normal_hints.y.set(values[2] as i32); + data.info.normal_hints.width.set(values[3] as i32); + data.info.normal_hints.height.set(values[4] as i32); + data.info.normal_hints.min_width.set(values[5] as i32); + data.info.normal_hints.min_height.set(values[6] as i32); + data.info.normal_hints.max_width.set(values[7] as i32); + data.info.normal_hints.max_height.set(values[8] as i32); + data.info.normal_hints.width_inc.set(values[9] as i32); + data.info.normal_hints.height_inc.set(values[10] as i32); + data.info.normal_hints.min_aspect_num.set(values[11] as i32); + data.info.normal_hints.min_aspect_den.set(values[12] as i32); + data.info.normal_hints.max_aspect_num.set(values[13] as i32); + data.info.normal_hints.max_aspect_den.set(values[14] as i32); + data.info.normal_hints.base_width.set(values[15] as i32); + data.info.normal_hints.base_height.set(values[16] as i32); + data.info.normal_hints.win_gravity.set(values[17]); + self.update_wants_floating(data); + } + + pub(super) async fn load_window_motif_wm_hints(&self, data: &Rc) { + let mut buf = vec![]; + if let Err(e) = self + .c + .get_property::(data.window_id, self.atoms._MOTIF_WM_HINTS, 0, &mut buf) + .await + { + if not_matches!(e, XconError::PropertyUnavailable) { + log::error!( + "Could not retrieve _MOTIF_WM_HINTS property: {}", + ErrorFmt(e) + ); + } + return; + } + let mut values = [0; 5]; + let len = values.len().min(buf.len()); + values[..len].copy_from_slice(&buf[..len]); + data.info + .motif_hints + .flags + .set(values[MWM_HINTS_FLAGS_FIELD]); + data.info + .motif_hints + .decorations + .set(values[MWM_HINTS_DECORATIONS_FIELD]); + } + + pub(super) async fn load_window_net_startup_id(&self, data: &Rc) { + let mut buf = vec![]; + match self + .c + .get_property::(data.window_id, self.atoms._NET_STARTUP_ID, 0, &mut buf) + .await + { + Ok(ty) if ty == ATOM_STRING => {} + Ok(ty) if ty == self.atoms.UTF8_STRING => {} + Ok(ty) => { + self.unexpected_type(data.window_id, "_NET_STARTUP_ID", ty) + .await; + return; + } + Err(XconError::PropertyUnavailable) => return, + Err(e) => { + log::error!( + "Could not retrieve _NET_STARTUP_ID property: {}", + ErrorFmt(e) + ); + return; + } + } + *data.info.startup_id.borrow_mut() = Some(buf.into()); + } + + pub(super) async fn load_window_net_wm_state(&self, data: &Rc) { + data.info.fullscreen.set(false); + let mut buf = vec![]; + if let Err(e) = self + .c + .get_property::(data.window_id, self.atoms._NET_WM_STATE, 0, &mut buf) + .await + { + if not_matches!(e, XconError::PropertyUnavailable) { + log::error!("Could not retrieve _NET_WM_STATE property: {}", ErrorFmt(e)); + } + return; + } + for prop in buf { + if prop == self.atoms._NET_WM_STATE_MODAL { + data.info.modal.set(true); + self.update_wants_floating(data); + } else if prop == self.atoms._NET_WM_STATE_FULLSCREEN { + data.info.fullscreen.set(true); + } else if prop == self.atoms._NET_WM_STATE_MAXIMIZED_VERT { + data.info.maximized_vert.set(true); + } else if prop == self.atoms._NET_WM_STATE_MAXIMIZED_HORZ { + data.info.maximized_horz.set(true); + } else if prop == self.atoms._NET_WM_STATE_HIDDEN { + data.info.minimized.set(true); + } + } + } + + pub(super) async fn load_window_net_wm_window_type(&self, data: &Rc) { + let mut buf = vec![]; + if let Err(e) = self + .c + .get_property::( + data.window_id, + self.atoms._NET_WM_WINDOW_TYPE, + ATOM_ATOM, + &mut buf, + ) + .await + { + if not_matches!(e, XconError::PropertyUnavailable) { + log::error!( + "Could not retrieve _NET_WM_WINDOW_TYPE property: {}", + ErrorFmt(e) + ); + } + return; + } + data.info + .never_focus + .set(buf.iter().any(|t| self.never_focus.contains(t))); + data.info.window_types.clear(); + data.info + .window_types + .lock() + .extend(buf.iter().copied().map(|v| (v, ()))); + self.update_wants_floating(data); + } +} diff --git a/src/xwayland/xwm/selection.rs b/src/xwayland/xwm/selection.rs new file mode 100644 index 00000000..7102cd09 --- /dev/null +++ b/src/xwayland/xwm/selection.rs @@ -0,0 +1,527 @@ +use { + super::*, + super::transfer::{WaylandToXTransfer, XToWaylandTransfer}, + crate::wire_xcon::GetAtomName, +}; + +pub(super) struct EnhancedOffer { + offer: Rc, + mime_types: RefCell>, + active: Cell, +} + +#[derive(Default)] +pub(super) struct SelectionData { + pub(super) sources: CopyHashMap>, + pub(super) offers: CopyHashMap>, + pub(super) active_offer: CloneCell>>, + pub(super) win: Cell, + pub(super) selection: Cell, + pub(super) pending_transfers: RefCell>, + pub(super) _phantom: PhantomData, +} + +impl SelectionData { + pub(super) fn destroy(&self) { + for offer in self.offers.lock().drain_values() { + destroy_data_offer::(&offer.offer); + } + self.active_offer.take(); + self.destroy_sources(); + } + + fn destroy_sources(&self) { + for source in self.sources.lock().drain_values() { + destroy_data_source::(&source); + } + } + + pub(super) fn seat_removed(&self, id: SeatId) { + if let Some(offer) = self.active_offer.get() + && offer.offer.get_seat().id() == id + { + self.active_offer.take(); + } + self.offers.remove(&id); + self.sources.remove(&id); + } +} + +pub(super) struct PendingTransfer { + mime_type: u32, + fd: Rc, +} + +const TEXT_PLAIN_UTF_8: &str = "text/plain;charset=utf-8"; +const TEXT_PLAIN: &str = "text/plain"; + +impl Wm { + pub(super) async fn dd_add_offer_mime_type( + &mut self, + sd: &SelectionData, + seat: SeatId, + offer: DataOfferId, + mt: String, + ) { + let enhanced = match sd.offers.get(&seat) { + Some(r) if r.offer.offer_id != offer => { + return; + } + None => { + return; + } + Some(r) => r, + }; + let mt = match self.mime_type_to_atom(mt).await { + Ok(mt) => mt, + Err(e) => { + log::error!("Could not get mime type atom: {}", ErrorFmt(e)); + return; + } + }; + enhanced.mime_types.borrow_mut().push(mt); + } + + pub(super) async fn dd_set_offer( + &mut self, + sd: &SelectionData, + seat: SeatId, + offer: Rc, + ) { + let mut mime_types = vec![]; + if let Some(offer) = sd.offers.remove(&seat) { + destroy_data_offer::(&offer.offer); + mime_types = mem::take(offer.mime_types.borrow_mut().deref_mut()); + } + sd.offers.set( + seat, + Rc::new(EnhancedOffer { + offer, + mime_types: RefCell::new(mime_types), + active: Cell::new(false), + }), + ); + } + + pub(super) async fn dd_set_selection( + &mut self, + sd: &SelectionData, + seat: SeatId, + offer: Option>, + ) { + let offer = match offer { + None => { + if let Some(offer) = sd.offers.remove(&seat) { + destroy_data_offer::(&offer.offer); + if offer.active.get() { + sd.active_offer.take(); + } + } + return; + } + Some(offer) => offer, + }; + let enhanced = match sd.offers.get(&seat) { + None => { + destroy_data_offer::(&offer); + return; + } + Some(e) => e, + }; + if !rc_eq(&enhanced.offer, &offer) { + destroy_data_offer::(&offer); + return; + } + if !enhanced.active.replace(true) + && let Some(old) = sd.active_offer.set(Some(enhanced)) + { + old.active.set(false); + } + let so = SetSelectionOwner { + owner: sd.win.get(), + selection: sd.selection.get(), + time: 0, + }; + if let Err(err) = self.c.call(&so).await { + log::error!("Could not set primary selection owner: {}", ErrorFmt(err)); + } + } + + async fn get_atom_name(&mut self, atom: u32) -> Result { + if let Some(name) = self.atom_name_cache.get(&atom) { + return Ok(name.clone()); + } + let gan = GetAtomName { atom }; + match self.c.call(&gan).await { + Ok(name) => { + let name = name.get().name.to_string(); + self.atom_name_cache.insert(atom, name.clone()); + Ok(name) + } + Err(e) => Err(e), + } + } + + async fn get_atom(&mut self, name: String) -> Result { + if let Some(atom) = self.atom_cache.get(&name) { + return Ok(*atom); + } + let ia = InternAtom { + only_if_exists: 0, + name: name.as_bytes().as_bstr(), + }; + match self.c.call(&ia).await { + Ok(id) => { + let atom = id.get().atom; + self.atom_cache.insert(name, atom); + Ok(atom) + } + Err(e) => Err(e), + } + } + + async fn mime_type_to_atom(&mut self, mime_type: String) -> Result { + match mime_type.as_str() { + TEXT_PLAIN_UTF_8 => Ok(self.atoms.UTF8_STRING), + TEXT_PLAIN => Ok(ATOM_STRING), + _ => self.get_atom(mime_type).await, + } + } + + async fn atom_to_mime_type(&mut self, atom: u32) -> Result { + if atom == self.atoms.UTF8_STRING { + Ok(TEXT_PLAIN_UTF_8.to_string()) + } else if atom == ATOM_STRING { + Ok(TEXT_PLAIN.to_string()) + } else { + self.get_atom_name(atom).await + } + } + + pub(super) async fn dd_send_source( + &mut self, + sd: &SelectionData, + seat: SeatId, + src: DataSourceId, + mime_type: String, + fd: Rc, + ) { + let actual_src = match sd.sources.get(&seat) { + None => return, + Some(src) => src, + }; + if actual_src.source_data().id != src { + return; + } + let mime_type = match self.mime_type_to_atom(mime_type).await { + Ok(mt) => mt, + Err(e) => { + log::error!("Could not intern mime type: {}", ErrorFmt(e)); + return; + } + }; + let cs = ConvertSelection { + requestor: sd.win.get(), + selection: sd.selection.get(), + target: mime_type, + property: self.atoms._WL_SELECTION, + time: 0, + }; + if let Err(e) = self.c.call(&cs).await { + log::error!( + "Could not perform convert selection request: {}", + ErrorFmt(e) + ); + return; + } + sd.pending_transfers + .borrow_mut() + .push(PendingTransfer { mime_type, fd }); + } + + pub(super) fn dd_cancel_source( + &mut self, + sd: &SelectionData, + seat: SeatId, + source: DataSourceId, + ) { + if let Some(cur) = sd.sources.get(&seat) + && cur.source_data().id == source + { + sd.sources.remove(&seat); + destroy_data_source::(&cur); + } + } + pub(super) async fn handle_xfixes_event( + &mut self, + event: &Event, + ) -> Result<(), XWaylandError> { + match event.code() { + XfixesSelectionNotify::OPCODE => self.handle_xfixes_selection_notify(event).await, + _ => Ok(()), + } + } + + async fn handle_xfixes_selection_notify(&mut self, event: &Event) -> Result<(), XWaylandError> { + let event: XfixesSelectionNotify = event.parse()?; + let shared = self.shared.clone(); + if event.selection == self.atoms.PRIMARY { + self.handle_xfixes_selection_notify_(&shared.primary_selection, &event) + .await + } else if event.selection == self.atoms.CLIPBOARD { + self.handle_xfixes_selection_notify_(&shared.data, &event) + .await + } else { + Ok(()) + } + } + + async fn handle_xfixes_selection_notify_( + &mut self, + sd: &SelectionData, + event: &XfixesSelectionNotify, + ) -> Result<(), XWaylandError> { + if event.owner == sd.win.get() { + return Ok(()); + } + sd.destroy_sources(); + let cs = ConvertSelection { + requestor: sd.win.get(), + selection: sd.selection.get(), + target: self.atoms.TARGETS, + property: self.atoms._WL_SELECTION, + time: event.timestamp, + }; + if let Err(e) = self.c.call(&cs).await { + log::error!("Could not convert selection: {}", ErrorFmt(e)); + } + Ok(()) + } + pub(super) async fn handle_selection_request( + &mut self, + event: &Event, + ) -> Result<(), XWaylandError> { + let event: SelectionRequest = event.parse()?; + let shared = self.shared.clone(); + if event.selection == self.atoms.PRIMARY { + self.handle_selection_request_(&shared.primary_selection, &event) + .await + } else if event.selection == self.atoms.CLIPBOARD { + self.handle_selection_request_(&shared.data, &event).await + } else { + log::warn!("Unknown selection request"); + Ok(()) + } + } + + async fn handle_selection_request_( + &mut self, + sd: &SelectionData, + event: &SelectionRequest, + ) -> Result<(), XWaylandError> { + let mut success = Some(false); + if let Some(offer) = sd.active_offer.get() { + let mt = offer.mime_types.borrow_mut(); + if event.target == self.atoms.TARGETS { + let cp = ChangeProperty { + mode: PROP_MODE_REPLACE, + window: event.requestor, + property: event.property, + ty: ATOM_ATOM, + format: 32, + data: uapi::as_bytes(&mt[..]), + }; + match self.c.call(&cp).await { + Ok(_) => success = Some(true), + Err(e) => { + log::error!("Could not set selection property: {}", ErrorFmt(e)); + } + } + } else { + 'convert: { + let present = mt.contains(&event.target); + drop(mt); + let mt = match self.atom_to_mime_type(event.target).await { + Ok(mt) => mt, + Err(e) => { + log::error!("Could not get mime type name: {}", ErrorFmt(e)); + break 'convert; + } + }; + if !present { + log::error!("Peer requested unavailable target {}", mt); + break 'convert; + } + let Pipe { + read: rx, + write: tx, + } = match pipe() { + Ok(p) => p, + Err(e) => { + log::error!("Could not create pipe: {}", e); + break 'convert; + } + }; + success = None; + receive_data_offer::(&offer.offer, &mt, Rc::new(tx)); + let id = self.transfer_ids.fetch_add(1); + let wtx = WaylandToXTransfer { + id, + fd: Rc::new(rx), + ring: self.state.ring.clone(), + c: self.c.clone(), + window: event.requestor, + time: event.time, + property: event.property, + ty: event.target, + selection: sd.selection.get(), + shared: self.shared.clone(), + }; + self.shared + .transfers + .set(id, self.state.eng.spawn("wayland to X transfer", wtx.run())); + } + } + } + if let Some(success) = success { + let target = match success { + true => event.target, + false => ATOM_NONE, + }; + let sn = SelectionNotify { + time: event.time, + requestor: event.requestor, + selection: sd.selection.get(), + target, + property: event.property, + }; + if let Err(e) = self.c.send_event(false, event.requestor, 0, &sn).await { + log::error!("Could not send event: {}", ErrorFmt(e)); + } + } + Ok(()) + } + + pub(super) async fn handle_selection_notify( + &mut self, + event: &Event, + ) -> Result<(), XWaylandError> { + let event: SelectionNotify = event.parse()?; + if event.property != self.atoms._WL_SELECTION { + return Ok(()); + } + let shared = self.shared.clone(); + if event.selection == self.atoms.PRIMARY { + self.handle_selection_notify_(&shared.primary_selection, &event) + .await + } else if event.selection == self.atoms.CLIPBOARD { + self.handle_selection_notify_(&shared.data, &event).await + } else { + Ok(()) + } + } + + async fn handle_selection_notify_( + &mut self, + sd: &SelectionData, + event: &SelectionNotify, + ) -> Result<(), XWaylandError> { + if event.property != self.atoms._WL_SELECTION { + return Ok(()); + } + if event.target == ATOM_NONE { + return Ok(()); + } + if event.target == self.atoms.TARGETS { + let targets = self.get_selection_mime_types(sd.win.get()).await?; + for dev in self.shared.devices.lock().values() { + let seat = T::get_device_seat(dev); + if !seat.may_modify_primary_selection(&self.client, None) { + continue; + } + let source = Rc::new(XDataSource { + state: self.state.clone(), + device: dev.clone(), + data: SourceData::new(&self.client), + location: T::LOCATION, + }); + for target in &targets { + add_data_source_mime_type::(&source, target); + } + let res = match source.location { + TransferLocation::Clipboard => seat.set_selection(Some(source.clone())), + TransferLocation::PrimarySelection => { + seat.set_primary_selection(Some(source.clone())) + } + }; + if let Err(e) = res { + log::error!("Could not set selection: {}", ErrorFmt(e)); + return Ok(()); + } + sd.sources.set(seat.id(), source); + } + } else { + let mut transfers = sd.pending_transfers.borrow_mut(); + let transfers = transfers.drain(..); + let mut data = vec![]; + let gp = self + .c + .get_property( + sd.win.get(), + self.atoms._WL_SELECTION, + event.target, + &mut data, + ) + .await; + if let Err(e) = gp { + log::error!("Could not get converted property: {}", e); + return Ok(()); + } + let mut data = Buf::from_slice(&data); + for transfer in transfers { + if event.target != transfer.mime_type { + log::error!("Conversion yielded an incompatible mime type"); + continue; + } + let id = self.transfer_ids.fetch_add(1); + let transfer = XToWaylandTransfer { + id, + data: data.clone(), + fd: transfer.fd, + state: self.state.clone(), + shared: self.shared.clone(), + }; + self.shared.transfers.set( + id, + self.state + .eng + .spawn("X to wayland transfer", transfer.run()), + ); + } + } + + Ok(()) + } + + async fn get_selection_mime_types( + &mut self, + window: u32, + ) -> Result, XWaylandError> { + let mut buf = vec![]; + self.c + .get_property3::(window, self.atoms._WL_SELECTION, ATOM_ATOM, true, &mut buf) + .await?; + let mut res = vec![]; + for atom in buf { + let name = match self.atom_to_mime_type(atom).await { + Ok(n) => n, + Err(e) => { + log::error!("Could not get atom name: {}", ErrorFmt(e)); + continue; + } + }; + res.push(name); + } + Ok(res) + } +} diff --git a/src/xwayland/xwm/transfer.rs b/src/xwayland/xwm/transfer.rs new file mode 100644 index 00000000..d80e7749 --- /dev/null +++ b/src/xwayland/xwm/transfer.rs @@ -0,0 +1,110 @@ +use jay_io_uring::{IoUring, IoUringError}; +use { + super::XwmShared, + crate::{ + + state::State, + utils::{buf::Buf, errorfmt::ErrorFmt, oserror::OsError}, + wire_xcon::{ChangeProperty, SelectionNotify}, + xcon::{ + Xcon, + consts::{ATOM_NONE, PROP_MODE_APPEND}, + }, + }, + std::{rc::Rc, time::Duration}, + uapi::{OwnedFd, c}, +}; + +pub(super) struct XToWaylandTransfer { + pub(super) id: u64, + pub(super) data: Buf, + pub(super) fd: Rc, + pub(super) state: Rc, + pub(super) shared: Rc, +} + +impl XToWaylandTransfer { + pub(super) async fn run(mut self) { + let timeout = self.state.now() + Duration::from_millis(5000); + let mut pos = 0; + while pos < self.data.len() { + let res = self + .state + .ring + .write(&self.fd, self.data.slice(pos..), Some(timeout)); + match res.await { + Ok(n) => pos += n, + Err(IoUringError::OsError(OsError(c::ECANCELED))) => { + log::error!("Transfer timed out"); + break; + } + Err(e) => { + log::error!("Could not write to wayland client: {}", ErrorFmt(e)); + break; + } + } + } + self.shared.transfers.remove(&self.id); + } +} + +pub(super) struct WaylandToXTransfer { + pub(super) id: u64, + pub(super) fd: Rc, + pub(super) ring: Rc, + pub(super) c: Rc, + pub(super) window: u32, + pub(super) time: u32, + pub(super) property: u32, + pub(super) ty: u32, + pub(super) selection: u32, + pub(super) shared: Rc, +} + +impl WaylandToXTransfer { + pub(super) async fn run(self) { + let mut success = false; + let mut buf = Buf::new(1024); + loop { + match self.ring.read(&self.fd, buf.clone()).await { + Ok(0) => { + success = true; + break; + } + Ok(n) => { + let cp = ChangeProperty { + mode: PROP_MODE_APPEND, + window: self.window, + property: self.property, + ty: self.ty, + format: 8, + data: &buf[..n], + }; + if let Err(e) = self.c.call(&cp).await { + log::error!("Could not append data to property: {}", ErrorFmt(e)); + break; + } + } + Err(e) => { + log::error!("Could not read from wayland client: {}", ErrorFmt(e)); + break; + } + } + } + let target = match success { + true => self.ty, + false => ATOM_NONE, + }; + let sn = SelectionNotify { + time: self.time, + requestor: self.window, + selection: self.selection, + target, + property: self.property, + }; + if let Err(e) = self.c.send_event(false, self.window, 0, &sn).await { + log::error!("Could not send event: {}", ErrorFmt(e)); + } + self.shared.transfers.remove(&self.id); + } +} diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs deleted file mode 100644 index ba71c585..00000000 --- a/toml-config/src/config.rs +++ /dev/null @@ -1,653 +0,0 @@ -mod context; -pub mod error; -mod extractor; -mod keycodes; -mod parser; -mod parsers; -mod spanned; -mod value; - -pub use crate::config::parsers::input_mode::InputMode; -use { - crate::{ - config::{ - context::Context, - parsers::{ - color_management::ColorManagement, - config::{ConfigParser, ConfigParserError}, - float::Float, - focus_history::FocusHistory, - }, - }, - toml::{self}, - }, - ahash::AHashMap, - jay_config::{ - Direction, Workspace, - client::ClientCapabilities, - input::{ - FallbackOutputMode, LayerDirection, SwitchEvent, Timeline, acceleration::AccelProfile, - clickmethod::ClickMethod, - }, - keyboard::{Keymap, ModifiedKeySym, mods::Modifiers, syms::KeySym}, - logging::LogLevel, - status::MessageFormat, - theme::{BarPosition, Color}, - video::{BlendSpace, ColorSpace, Eotf, Format, GfxApi, TearingMode, Transform, VrrMode}, - window::{ContentType, TileState, WindowType}, - workspace::WorkspaceDisplayOrder, - xwayland::XScalingMode, - }, - std::{ - cell::RefCell, - error::Error, - fmt::{Display, Formatter}, - rc::Rc, - time::Duration, - }, - thiserror::Error, - toml::toml_parser, -}; - -#[derive(Debug, Copy, Clone)] -pub enum SimpleCommand { - Close, - DisablePointerConstraint, - Focus(Direction), - FocusParent, - Move(Direction), - None, - Quit, - ReloadConfigSo, - ReloadConfigToml, - ToggleFloating, - SetFloating(bool), - ToggleFullscreen, - SetFullscreen(bool), - Forward(bool), - EnableWindowManagement(bool), - SetFloatAboveFullscreen(bool), - ToggleFloatAboveFullscreen, - SetFloatPinned(bool), - ToggleFloatPinned, - KillClient, - ShowBar(bool), - ToggleBar, - ShowTitles(bool), - ToggleTitles, - FloatTitles(bool), - ToggleFloatTitles, - FocusHistory(Timeline), - FocusLayerRel(LayerDirection), - FocusTiles, - ToggleFocusFloatTiled, - CreateMark, - JumpToMark, - PopMode(bool), - EnableSimpleIm(bool), - ToggleSimpleImEnabled, - ReloadSimpleIm, - EnableUnicodeInput, - WarpMouseToFocus, - ToggleTab, - MakeGroupH, - MakeGroupV, - MakeGroupTab, - ChangeGroupOpposite, - Equalize, - EqualizeRecursive, - MoveTabLeft, - MoveTabRight, - SetAutotile(bool), - ToggleAutotile, -} - -#[derive(Debug, Clone)] -#[expect(clippy::enum_variant_names)] -pub enum Action { - ConfigureConnector { - con: ConfigConnector, - }, - ConfigureDirectScanout { - enabled: bool, - }, - ConfigureDrmDevice { - dev: ConfigDrmDevice, - }, - ConfigureIdle { - idle: Option, - grace_period: Option, - }, - ConfigureInput { - input: Box, - }, - ConfigureOutput { - out: Output, - }, - Exec { - exec: Exec, - }, - MoveToWorkspace { - name: String, - }, - Multi { - actions: Vec, - }, - SetEnv { - env: Vec<(String, String)>, - }, - SetGfxApi { - api: GfxApi, - }, - SetKeymap { - map: ConfigKeymap, - }, - SetLogLevel { - level: LogLevel, - }, - SetRenderDevice { - dev: Box, - }, - SetStatus { - status: Option, - }, - SetTheme { - theme: Box, - }, - ShowWorkspace { - name: String, - output: Option, - }, - SimpleCommand { - cmd: SimpleCommand, - }, - SwitchToVt { - num: u32, - }, - UnsetEnv { - env: Vec, - }, - MoveToOutput { - workspace: Option, - output: Option, - direction: Option, - }, - SetRepeatRate { - rate: RepeatRate, - }, - DefineAction { - name: String, - action: Box, - }, - UndefineAction { - name: String, - }, - NamedAction { - name: String, - }, - CreateMark(u32), - JumpToMark(u32), - CopyMark(u32, u32), - SetMode { - name: String, - latch: bool, - }, - CreateVirtualOutput { - name: String, - }, - RemoveVirtualOutput { - name: String, - }, - Resize { - dx1: i32, - dy1: i32, - dx2: i32, - dy2: i32, - }, -} - -#[derive(Debug, Clone, Default)] -pub struct Theme { - pub attention_requested_bg_color: Option, - pub bg_color: Option, - pub bar_bg_color: Option, - pub bar_status_text_color: Option, - pub border_color: Option, - pub active_border_color: Option, - pub captured_focused_title_bg_color: Option, - pub captured_unfocused_title_bg_color: Option, - pub focused_inactive_title_bg_color: Option, - pub focused_inactive_title_text_color: Option, - pub focused_title_bg_color: Option, - pub focused_title_text_color: Option, - pub separator_color: Option, - pub unfocused_title_bg_color: Option, - pub unfocused_title_text_color: Option, - pub highlight_color: Option, - pub border_width: Option, - pub title_height: Option, - pub bar_height: Option, - pub font: Option, - pub title_font: Option, - pub bar_font: Option, - pub bar_position: Option, - pub bar_separator_width: Option, - pub gap: Option, - pub floating_titles: Option, - pub title_gap: Option, - pub corner_radius: Option, - pub tab_active_bg_color: Option, - pub tab_active_border_color: Option, - pub tab_inactive_bg_color: Option, - pub tab_inactive_border_color: Option, - pub tab_active_text_color: Option, - pub tab_inactive_text_color: Option, - pub tab_bar_bg_color: Option, - pub tab_attention_bg_color: Option, - pub tab_bar_height: Option, - pub tab_bar_padding: Option, - pub tab_bar_radius: Option, - pub tab_bar_border_width: Option, - pub tab_bar_text_padding: Option, - pub tab_bar_gap: Option, - pub tab_title_align: Option, -} - -#[derive(Debug, Clone)] -pub struct Status { - pub format: MessageFormat, - pub exec: Exec, - pub separator: Option, -} - -#[derive(Debug, Clone, Default)] -pub struct UiDrag { - pub enabled: Option, - pub threshold: Option, -} - -#[derive(Debug, Clone)] -pub enum OutputMatch { - Any(Vec), - All { - name: Option, - connector: Option, - serial_number: Option, - manufacturer: Option, - model: Option, - }, -} - -#[derive(Default, Debug, Clone)] -pub struct GenericMatch { - pub name: Option, - pub not: Option>, - pub all: Option>, - pub any: Option>, - pub exactly: Option>, -} - -#[derive(Debug, Clone)] -pub struct MatchExactly { - pub num: usize, - pub list: Vec, -} - -#[derive(Debug, Clone)] -pub struct ClientRule { - pub name: Option, - pub match_: ClientMatch, - pub action: Option, - pub latch: Option, - pub capabilities: Option, - pub bounding_capabilities: Option, -} - -#[derive(Default, Debug, Clone)] -pub struct ClientMatch { - pub generic: GenericMatch, - pub sandbox_engine: Option, - pub sandbox_engine_regex: Option, - pub sandbox_app_id: Option, - pub sandbox_app_id_regex: Option, - pub sandbox_instance_id: Option, - pub sandbox_instance_id_regex: Option, - pub sandboxed: Option, - pub uid: Option, - pub pid: Option, - pub is_xwayland: Option, - pub comm: Option, - pub comm_regex: Option, - pub exe: Option, - pub exe_regex: Option, - pub tag: Option, - pub tag_regex: Option, -} - -#[derive(Debug, Clone)] -pub struct WindowRule { - pub name: Option, - pub match_: WindowMatch, - pub action: Option, - pub latch: Option, - pub auto_focus: Option, - pub initial_tile_state: Option, -} - -#[derive(Default, Debug, Clone)] -pub struct WindowMatch { - pub generic: GenericMatch, - pub types: Option, - pub client: Option, - pub title: Option, - pub title_regex: Option, - pub app_id: Option, - pub app_id_regex: Option, - pub floating: Option, - pub visible: Option, - pub urgent: Option, - pub focused: Option, - pub fullscreen: Option, - pub just_mapped: Option, - pub tag: Option, - pub tag_regex: Option, - pub x_class: Option, - pub x_class_regex: Option, - pub x_instance: Option, - pub x_instance_regex: Option, - pub x_role: Option, - pub x_role_regex: Option, - pub workspace: Option, - pub workspace_regex: Option, - pub content_types: Option, -} - -#[derive(Debug, Clone)] -pub enum DrmDeviceMatch { - Any(Vec), - All { - name: Option, - syspath: Option, - vendor: Option, - vendor_name: Option, - model: Option, - model_name: Option, - devnode: Option, - }, -} - -#[derive(Debug, Clone)] -pub struct Mode { - pub width: i32, - pub height: i32, - pub refresh_rate: Option, -} - -impl Display for Mode { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{} x {}", self.width, self.height)?; - if let Some(rr) = self.refresh_rate { - write!(f, " @ {rr}")?; - } - Ok(()) - } -} - -#[derive(Debug, Clone)] -pub struct Output { - pub name: Option, - pub match_: OutputMatch, - pub x: Option, - pub y: Option, - pub scale: Option, - pub transform: Option, - pub mode: Option, - pub vrr: Option, - pub tearing: Option, - pub format: Option, - pub color_space: Option, - pub eotf: Option, - pub brightness: Option>, - pub blend_space: Option, - pub use_native_gamut: Option, -} - -#[derive(Debug, Clone)] -pub enum ConnectorMatch { - Any(Vec), - All { connector: Option }, -} - -#[derive(Debug, Clone)] -pub enum InputMatch { - Any(Vec), - All { - tag: Option, - name: Option, - syspath: Option, - devnode: Option, - is_keyboard: Option, - is_pointer: Option, - is_touch: Option, - is_tablet_tool: Option, - is_tablet_pad: Option, - is_gesture: Option, - is_switch: Option, - }, -} - -#[derive(Debug, Clone)] -pub struct Input { - pub tag: Option, - pub match_: InputMatch, - pub accel_profile: Option, - pub accel_speed: Option, - pub tap_enabled: Option, - pub tap_drag_enabled: Option, - pub tap_drag_lock_enabled: Option, - pub left_handed: Option, - pub natural_scrolling: Option, - pub click_method: Option, - pub middle_button_emulation: Option, - pub px_per_wheel_scroll: Option, - pub transform_matrix: Option<[[f64; 2]; 2]>, - pub keymap: Option, - pub switch_actions: AHashMap, - pub output: Option>, - pub calibration_matrix: Option<[[f32; 3]; 2]>, -} - -#[derive(Debug, Clone)] -pub struct Exec { - pub prog: String, - pub args: Vec, - pub envs: Vec<(String, String)>, - pub privileged: bool, - pub tag: Option, -} - -#[derive(Debug, Clone)] -pub struct ConfigConnector { - pub match_: ConnectorMatch, - pub enabled: bool, -} - -#[derive(Debug, Clone)] -pub struct ConfigDrmDevice { - pub name: Option, - pub match_: DrmDeviceMatch, - pub gfx_api: Option, - pub direct_scanout_enabled: Option, - pub flip_margin_ms: Option, -} - -#[derive(Debug, Clone)] -pub enum ConfigKeymap { - Named(String), - Literal(Keymap), - Defined { name: String, map: Keymap }, -} - -#[derive(Debug, Clone)] -pub struct RepeatRate { - pub rate: i32, - pub delay: i32, -} - -#[derive(Debug, Clone)] -pub struct Vrr { - pub mode: Option, - pub cursor_hz: Option, -} - -#[derive(Debug, Clone)] -pub struct SimpleIm { - pub enabled: Option, -} - -#[derive(Debug, Clone)] -pub struct Xwayland { - pub enabled: Option, - pub scaling_mode: Option, -} - -#[derive(Debug, Clone)] -pub struct Tearing { - pub mode: Option, -} - -#[derive(Debug, Clone, Default)] -pub struct Libei { - pub enable_socket: Option, -} - -#[derive(Debug, Clone)] -pub struct Shortcut { - pub mask: Modifiers, - pub keysym: ModifiedKeySym, - pub action: Action, - pub latch: Option, -} - -#[derive(Debug, Clone)] -pub struct NamedAction { - pub name: Rc, - pub action: Action, -} - -#[derive(Debug, Clone)] -pub struct Config { - pub keymap: Option, - pub repeat_rate: Option, - pub shortcuts: Vec, - pub on_graphics_initialized: Option, - pub on_idle: Option, - pub status: Option, - pub connectors: Vec, - pub outputs: Vec, - pub workspace_capture: bool, - pub env: Vec<(String, String)>, - pub on_startup: Option, - pub keymaps: Vec, - pub auto_reload: Option, - pub log_level: Option, - pub clean_logs_older_than: Option, - pub theme: Theme, - pub gfx_api: Option, - pub direct_scanout_enabled: Option, - pub drm_devices: Vec, - pub render_device: Option, - pub inputs: Vec, - pub idle: Option, - pub grace_period: Option, - pub explicit_sync_enabled: Option, - pub focus_follows_mouse: bool, - pub window_management_key: Option, - pub vrr: Option, - pub tearing: Option, - pub libei: Libei, - pub ui_drag: UiDrag, - pub xwayland: Option, - pub color_management: Option, - pub float: Option, - pub named_actions: Vec, - pub max_action_depth: u64, - pub client_rules: Vec, - pub window_rules: Vec, - pub pointer_revert_key: Option, - pub use_hardware_cursor: Option, - pub show_bar: Option, - pub show_titles: Option, - pub focus_history: Option, - pub middle_click_paste: Option, - pub input_modes: AHashMap, - pub workspace_display_order: Option, - pub simple_im: Option, - pub fallback_output_mode: Option, - pub mouse_follows_focus: Option, -} - -#[derive(Debug, Error)] -pub enum ConfigError { - #[error("Could not parse the toml document")] - Toml(#[from] toml_parser::ParserError), - #[error("Could not interpret the toml as a config document")] - Parser(#[from] ConfigParserError), -} - -pub fn parse_config( - input: &[u8], - mark_names: &RefCell>, - handle_error: F, -) -> Option -where - F: FnOnce(&dyn Error), -{ - let cx = Context { - input, - used: Default::default(), - mark_names, - }; - macro_rules! fatal { - ($e:expr) => {{ - let e = ConfigError::from($e.value); - let e = cx.error2($e.span, e); - handle_error(&e); - return None; - }}; - } - let toml = match toml_parser::parse(input, &cx) { - Ok(t) => t, - Err(e) => fatal!(e), - }; - let config = match toml.parse(&mut ConfigParser(&cx)) { - Ok(c) => c, - Err(e) => fatal!(e), - }; - let used = cx.used.take(); - macro_rules! check_defined { - ($name:expr, $used:ident, $defined:ident) => { - for spanned in &used.$used { - if !used.$defined.contains(spanned) { - log::warn!( - "{} {} used but not defined: {}", - $name, - spanned.value, - cx.error3(spanned.span), - ); - } - } - }; - } - check_defined!("Keymap", keymaps, defined_keymaps); - check_defined!("DRM device", drm_devices, defined_drm_devices); - check_defined!("Output", outputs, defined_outputs); - check_defined!("Input", inputs, defined_inputs); - Some(config) -} - -#[test] -fn default_config_parses() { - let input = include_bytes!("default-config.toml"); - parse_config(input, &Default::default(), |_| ()).unwrap(); -} diff --git a/toml-config/src/config/parsers/capabilities.rs b/toml-config/src/config/parsers/capabilities.rs deleted file mode 100644 index 8477ef9a..00000000 --- a/toml-config/src/config/parsers/capabilities.rs +++ /dev/null @@ -1,70 +0,0 @@ -use { - crate::{ - config::parser::{DataType, ParseResult, Parser, UnexpectedDataType}, - toml::{ - toml_span::{Span, Spanned, SpannedExt}, - toml_value::Value, - }, - }, - jay_config::client::{ - CC_DATA_CONTROL, CC_DRM_LEASE, CC_FOREIGN_TOPLEVEL_GEOMETRY_TRACKING, - CC_FOREIGN_TOPLEVEL_LIST, CC_FOREIGN_TOPLEVEL_MANAGER, CC_GAMMA_CONTROL_MANAGER, - CC_HEAD_MANAGER, CC_IDLE_NOTIFIER, CC_INPUT_METHOD, CC_LAYER_SHELL, CC_SCREENCOPY, - CC_SEAT_MANAGER, CC_SESSION_LOCK, CC_VIRTUAL_KEYBOARD, CC_VIRTUAL_POINTER, - CC_WORKSPACE_MANAGER, ClientCapabilities, - }, - thiserror::Error, -}; - -#[derive(Debug, Error)] -pub enum CapabilitiesParserError { - #[error(transparent)] - Expected(#[from] UnexpectedDataType), - #[error("Unknown capability `{}`", .0)] - UnknownCapability(String), -} - -pub struct CapabilitiesParser; - -impl Parser for CapabilitiesParser { - type Value = ClientCapabilities; - type Error = CapabilitiesParserError; - const EXPECTED: &'static [DataType] = &[DataType::Array, DataType::String]; - - fn parse_string(&mut self, span: Span, string: &str) -> ParseResult { - let ty = match string { - "none" => ClientCapabilities(0), - "all" => ClientCapabilities(!0), - "data-control" => CC_DATA_CONTROL, - "virtual-keyboard" => CC_VIRTUAL_KEYBOARD, - "foreign-toplevel-list" => CC_FOREIGN_TOPLEVEL_LIST, - "idle-notifier" => CC_IDLE_NOTIFIER, - "session-lock" => CC_SESSION_LOCK, - "layer-shell" => CC_LAYER_SHELL, - "screencopy" => CC_SCREENCOPY, - "seat-manager" => CC_SEAT_MANAGER, - "drm-lease" => CC_DRM_LEASE, - "input-method" => CC_INPUT_METHOD, - "workspace-manager" => CC_WORKSPACE_MANAGER, - "foreign-toplevel-manager" => CC_FOREIGN_TOPLEVEL_MANAGER, - "head-manager" => CC_HEAD_MANAGER, - "gamma-control-manager" => CC_GAMMA_CONTROL_MANAGER, - "virtual-pointer" => CC_VIRTUAL_POINTER, - "foreign-toplevel-geometry-tracking" => CC_FOREIGN_TOPLEVEL_GEOMETRY_TRACKING, - _ => { - return Err( - CapabilitiesParserError::UnknownCapability(string.to_owned()).spanned(span), - ); - } - }; - Ok(ty) - } - - fn parse_array(&mut self, _span: Span, array: &[Spanned]) -> ParseResult { - let mut ty = ClientCapabilities(0); - for el in array { - ty |= el.parse(&mut CapabilitiesParser)?; - } - Ok(ty) - } -} diff --git a/wire/jay_acceptor_request.txt b/wire/jay_acceptor_request.txt deleted file mode 100644 index e3b0105e..00000000 --- a/wire/jay_acceptor_request.txt +++ /dev/null @@ -1,9 +0,0 @@ -request destroy { } - -event done { - name: str, -} - -event failed { - msg: str, -} diff --git a/wire/jay_client_query.txt b/wire/jay_client_query.txt index f2f97d05..ef841292 100644 --- a/wire/jay_client_query.txt +++ b/wire/jay_client_query.txt @@ -47,7 +47,3 @@ event comm { event exe { exe: str, } - -event tag (since = 25) { - tag: str, -} diff --git a/wire/jay_compositor.txt b/wire/jay_compositor.txt index 019f9ea3..25200404 100644 --- a/wire/jay_compositor.txt +++ b/wire/jay_compositor.txt @@ -121,11 +121,6 @@ request create_tree_query (since = 18) { id: id(jay_tree_query), } -request get_tagged_acceptor (since = 25) { - id: id(jay_acceptor_request), - tag: str, -} - request get_sync_file_surface (since = 26) { id: id(jay_sync_file_surface), surface: id(wl_surface), @@ -135,6 +130,10 @@ request get_pid (since = 27) { } +request set_dpms (since = 31) { + active: u32, +} + # events event client_id { diff --git a/wire/wp_security_context_manager_v1.txt b/wire/wp_security_context_manager_v1.txt deleted file mode 100644 index 6d351344..00000000 --- a/wire/wp_security_context_manager_v1.txt +++ /dev/null @@ -1,8 +0,0 @@ -request destroy (destructor) { -} - -request create_listener { - id: id(wp_security_context_v1) (new), - listen_fd: fd, - close_fd: fd, -} diff --git a/wire/wp_security_context_v1.txt b/wire/wp_security_context_v1.txt deleted file mode 100644 index 1a037aee..00000000 --- a/wire/wp_security_context_v1.txt +++ /dev/null @@ -1,17 +0,0 @@ -request destroy (destructor) { -} - -request set_sandbox_engine { - name: str, -} - -request set_app_id { - app_id: str, -} - -request set_instance_id { - instance_id: str, -} - -request commit { -}