1
0
Fork 0
forked from wry/wry

Compare commits

...

7 commits

736 changed files with 35487 additions and 40313 deletions

4
.gitmodules vendored
View file

@ -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

486
Cargo.lock generated
View file

@ -630,6 +630,16 @@ dependencies = [
"smallvec",
]
[[package]]
name = "jay-allocator"
version = "0.1.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 = "0.1.0"
dependencies = [
"jay-time",
"jay-tracy",
"jay-utils",
]
[[package]]
name = "jay-bufio"
version = "0.1.0"
dependencies = [
"jay-io-uring",
"jay-utils",
"thiserror",
"uapi",
]
[[package]]
name = "jay-bugs"
version = "0.1.0"
dependencies = [
"ahash",
]
[[package]]
name = "jay-clientmem"
version = "0.1.0"
dependencies = [
"jay-cpu-worker",
"jay-gfx-types",
"jay-tracy",
"jay-utils",
"log",
"thiserror",
"uapi",
]
[[package]]
name = "jay-cmm"
version = "0.1.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",
@ -699,7 +792,6 @@ name = "jay-config"
version = "1.10.0"
dependencies = [
"backtrace",
"bincode",
"bstr",
"error_reporter",
"futures-util",
@ -711,6 +803,279 @@ dependencies = [
"uapi",
]
[[package]]
name = "jay-config-schema"
version = "0.1.0"
dependencies = [
"ahash",
"jay-config",
]
[[package]]
name = "jay-cpu-worker"
version = "0.1.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 = "0.1.0"
dependencies = [
"ahash",
"jay-utils",
"linearize",
"regex",
]
[[package]]
name = "jay-damage"
version = "0.1.0"
dependencies = [
"jay-geometry",
"jay-tree-types",
"jay-units",
]
[[package]]
name = "jay-dbus-core"
version = "0.1.0"
dependencies = [
"bstr",
"jay-bufio",
"jay-io-uring",
"jay-utils",
"thiserror",
"uapi",
]
[[package]]
name = "jay-drm-feedback"
version = "0.1.0"
dependencies = [
"ahash",
"byteorder",
"jay-video-types",
"thiserror",
"uapi",
]
[[package]]
name = "jay-edid"
version = "0.1.0"
dependencies = [
"bstr",
"thiserror",
]
[[package]]
name = "jay-eventfd-cache"
version = "0.1.0"
dependencies = [
"jay-async-engine",
"jay-io-uring",
"jay-utils",
"log",
"thiserror",
"uapi",
]
[[package]]
name = "jay-formats"
version = "0.1.0"
dependencies = [
"ahash",
"clap",
"jay-ash",
"jay-config",
]
[[package]]
name = "jay-geometry"
version = "0.1.0"
dependencies = [
"jay-algorithms",
"smallvec",
]
[[package]]
name = "jay-gfx-types"
version = "0.1.0"
dependencies = [
"uapi",
]
[[package]]
name = "jay-input-types"
version = "0.1.0"
dependencies = [
"jay-output-types",
"jay-units",
"jay-utils",
"linearize",
]
[[package]]
name = "jay-io-uring"
version = "0.1.0"
dependencies = [
"jay-async-engine",
"jay-time",
"jay-utils",
"log",
"run-on-drop",
"thiserror",
"uapi",
]
[[package]]
name = "jay-keyboard"
version = "0.1.0"
dependencies = [
"blake3",
"jay-input-types",
"jay-utils",
"kbvm",
"thiserror",
"uapi",
]
[[package]]
name = "jay-layout-animation"
version = "0.1.0"
dependencies = [
"jay-geometry",
]
[[package]]
name = "jay-libinput"
version = "0.1.0"
dependencies = [
"anyhow",
"bstr",
"isnt 0.2.0",
"jay-utils",
"libloading",
"log",
"repc",
"thiserror",
"uapi",
]
[[package]]
name = "jay-logger"
version = "0.1.0"
dependencies = [
"backtrace",
"bstr",
"clap",
"dirs",
"humantime",
"jay-config",
"jay-utils",
"linearize",
"log",
"parking_lot",
"thiserror",
"uapi",
]
[[package]]
name = "jay-output-schedule"
version = "0.1.0"
dependencies = [
"futures-util",
"jay-async-engine",
"jay-io-uring",
"jay-utils",
"log",
"num-traits",
]
[[package]]
name = "jay-output-types"
version = "0.1.0"
dependencies = [
"blake3",
"jay-cmm",
"jay-formats",
"jay-utils",
"linearize",
"uapi",
]
[[package]]
name = "jay-pango"
version = "0.1.0"
dependencies = [
"anyhow",
"jay-geometry",
"repc",
"thiserror",
"uapi",
]
[[package]]
name = "jay-pr-caps"
version = "0.1.0"
dependencies = [
"jay-utils",
"opera",
"parking_lot",
"uapi",
]
[[package]]
name = "jay-sighand"
version = "0.1.0"
dependencies = [
"jay-async-engine",
"jay-io-uring",
"jay-utils",
"log",
"thiserror",
"uapi",
]
[[package]]
name = "jay-theme"
version = "0.1.0"
dependencies = [
"jay-cmm",
"jay-config",
"jay-gfx-types",
"jay-utils",
"linearize",
"num-traits",
]
[[package]]
name = "jay-time"
version = "0.1.0"
dependencies = [
"uapi",
]
[[package]]
name = "jay-toml"
version = "0.1.0"
dependencies = [
"bstr",
"indexmap",
"serde_json",
"thiserror",
"walkdir",
]
[[package]]
name = "jay-toml-config"
version = "0.12.0"
@ -720,15 +1085,126 @@ dependencies = [
"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 = "0.1.0"
dependencies = [
"ahash",
"parking_lot",
"rustc-demangle",
"tracy-client-sys",
]
[[package]]
name = "jay-tree-types"
version = "0.1.0"
dependencies = [
"jay-config",
"jay-utils",
"linearize",
]
[[package]]
name = "jay-udmabuf"
version = "0.1.0"
dependencies = [
"jay-allocator",
"jay-formats",
"jay-utils",
"jay-video-types",
"log",
"thiserror",
"uapi",
]
[[package]]
name = "jay-units"
version = "0.1.0"
[[package]]
name = "jay-utils"
version = "0.1.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 = "0.1.0"
dependencies = [
"arrayvec",
"jay-formats",
"jay-utils",
"uapi",
]
[[package]]
name = "jay-wheel"
version = "0.1.0"
dependencies = [
"jay-async-engine",
"jay-io-uring",
"jay-time",
"jay-utils",
"log",
"thiserror",
"uapi",
]
[[package]]
name = "jay-wire-buf"
version = "0.1.0"
dependencies = [
"bstr",
"jay-io-uring",
"jay-time",
"jay-units",
"jay-utils",
"jay-wire-types",
"smallvec",
"thiserror",
"uapi",
]
[[package]]
name = "jay-wire-types"
version = "0.1.0"
[[package]]
name = "jay-xcon"
version = "0.1.0"
dependencies = [
"bstr",
"jay-bufio",
"jay-io-uring",
"jay-utils",
"log",
"thiserror",
"uapi",
]
[[package]]

View file

@ -13,7 +13,56 @@ 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",
]
[profile.release]
panic = "abort"
@ -23,9 +72,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 = { version = "1.10.0", path = "crates/jay-config" }
jay-toml-config = { version = "0.12.0", path = "crates/toml-config" }
jay-algorithms = { version = "0.4.0", path = "crates/algorithms" }
jay-geometry = { version = "0.1.0", path = "crates/geometry" }
jay-layout-animation = { version = "0.1.0", path = "crates/layout-animation" }
jay-formats = { version = "0.1.0", path = "crates/formats" }
jay-edid = { version = "0.1.0", path = "crates/edid" }
jay-units = { version = "0.1.0", path = "crates/units" }
jay-utils = { version = "0.1.0", path = "crates/utils" }
jay-criteria = { version = "0.1.0", path = "crates/criteria" }
jay-cmm = { version = "0.1.0", path = "crates/cmm" }
jay-time = { version = "0.1.0", path = "crates/time" }
jay-tracy = { version = "0.1.0", path = "crates/tracy" }
jay-async-engine = { version = "0.1.0", path = "crates/async-engine" }
jay-io-uring = { version = "0.1.0", path = "crates/io-uring" }
jay-bufio = { version = "0.1.0", path = "crates/bufio" }
jay-dbus-core = { version = "0.1.0", path = "crates/dbus-core" }
jay-xcon = { version = "0.1.0", path = "crates/xcon" }
jay-wire-types = { version = "0.1.0", path = "crates/wire-types" }
jay-wire-buf = { version = "0.1.0", path = "crates/wire-buf" }
jay-tree-types = { version = "0.1.0", path = "crates/tree-types" }
jay-eventfd-cache = { version = "0.1.0", path = "crates/eventfd-cache" }
jay-wheel = { version = "0.1.0", path = "crates/wheel" }
jay-cpu-worker = { version = "0.1.0", path = "crates/cpu-worker" }
jay-sighand = { version = "0.1.0", path = "crates/sighand" }
jay-pr-caps = { version = "0.1.0", path = "crates/pr-caps" }
jay-bugs = { version = "0.1.0", path = "crates/bugs" }
jay-logger = { version = "0.1.0", path = "crates/logger" }
jay-video-types = { version = "0.1.0", path = "crates/video-types" }
jay-output-types = { version = "0.1.0", path = "crates/output-types" }
jay-input-types = { version = "0.1.0", path = "crates/input-types" }
jay-keyboard = { version = "0.1.0", path = "crates/keyboard" }
jay-gfx-types = { version = "0.1.0", path = "crates/gfx-types" }
jay-theme = { version = "0.1.0", path = "crates/theme" }
jay-clientmem = { version = "0.1.0", path = "crates/clientmem" }
jay-allocator = { version = "0.1.0", path = "crates/allocator" }
jay-output-schedule = { version = "0.1.0", path = "crates/output-schedule" }
jay-drm-feedback = { version = "0.1.0", path = "crates/drm-feedback" }
jay-udmabuf = { version = "0.1.0", path = "crates/udmabuf" }
jay-damage = { version = "0.1.0", path = "crates/damage" }
jay-pango = { version = "0.1.0", path = "crates/pango" }
jay-libinput = { version = "0.1.0", path = "crates/libinput" }
uapi = "0.2.13"
thiserror = "2.0.11"
@ -58,8 +146,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 +176,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"]

View file

@ -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)

View file

@ -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.

View file

@ -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

View file

@ -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:

View file

@ -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,
},
}
```

View file

@ -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

View file

@ -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,
},
}
```

View file

@ -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,
},
},
]

View file

@ -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

View file

@ -87,5 +87,5 @@ Xwayland client itself:
```toml
[[clients]]
match.is-xwayland = true
# ... grant capabilities, etc.
# ... configure client-specific behavior
```

View file

@ -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 | |

View file

@ -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`).

View file

@ -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.

View file

@ -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

View file

@ -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`

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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<W: Write>(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")?;

View file

@ -274,47 +274,15 @@ fn write_message<W: Write>(f: &mut W, obj: &str, message: &Message) -> Result<()
Ok(())
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum RequestHandlerDirection {
Request,
Event,
}
fn write_request_handler<W: Write>(
f: &mut W,
camel_obj_name: &str,
messages: &[Lined<Message>],
direction: RequestHandlerDirection,
) -> Result<()> {
let snake_direction;
let camel_direction;
let parent;
let parser;
let error;
let param;
writeln!(f)?;
match direction {
RequestHandlerDirection::Request => {
snake_direction = "request";
camel_direction = "Request";
parent = "crate::object::Object";
parser = "crate::client::Client";
error = "crate::client::ClientError";
param = "req";
}
RequestHandlerDirection::Event => {
snake_direction = "event";
camel_direction = "Event";
parent = "crate::wl_usr::usr_object::UsrObject";
parser = "crate::wl_usr::UsrCon";
error = "crate::wl_usr::UsrConError";
param = "ev";
writeln!(f, " #[allow(clippy::allow_attributes, dead_code)]")?;
}
}
writeln!(
f,
" pub trait {camel_obj_name}{camel_direction}Handler: {parent} + Sized {{"
" pub trait {camel_obj_name}RequestHandler: crate::object::Object + Sized {{"
)?;
writeln!(f, " type Error: std::error::Error;")?;
for message in messages {
@ -326,24 +294,24 @@ fn write_request_handler<W: Write>(
writeln!(f)?;
writeln!(
f,
" fn {}(&self, {param}: {}{lt}, _slf: &Rc<Self>) -> Result<(), Self::Error>;",
" fn {}(&self, req: {}{lt}, _slf: &Rc<Self>) -> Result<(), Self::Error>;",
msg.safe_name, msg.camel_name
)?;
}
writeln!(f)?;
writeln!(f, " #[inline(always)]")?;
writeln!(f, " fn handle_{snake_direction}_impl(")?;
writeln!(f, " fn handle_request_impl(")?;
writeln!(f, " self: Rc<Self>,")?;
writeln!(f, " client: &{parser},")?;
writeln!(f, " client: &crate::client::Client,")?;
writeln!(f, " req: u32,")?;
writeln!(
f,
" parser: crate::utils::buffd::MsgParser<'_, '_>,"
)?;
writeln!(f, " ) -> Result<(), {error}> {{")?;
writeln!(f, " ) -> Result<(), crate::client::ClientError> {{")?;
if messages.is_empty() {
writeln!(f, " #![allow(unused_variables)]")?;
writeln!(f, " Err({error}::InvalidMethod)")?;
writeln!(f, " Err(crate::client::ClientError::InvalidMethod)")?;
} else {
writeln!(f, " let method;")?;
writeln!(
@ -379,10 +347,10 @@ fn write_request_handler<W: Write>(
}
writeln!(
f,
" _ => return Err({error}::InvalidMethod),"
" _ => return Err(crate::client::ClientError::InvalidMethod),"
)?;
writeln!(f, " }};")?;
writeln!(f, " Err({error}::MethodError {{")?;
writeln!(f, " Err(crate::client::ClientError::MethodError {{")?;
writeln!(f, " interface: {camel_obj_name},")?;
writeln!(f, " id: self.id(),")?;
writeln!(f, " method,")?;
@ -417,6 +385,7 @@ fn write_file<W: Write>(
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)?;
@ -425,13 +394,6 @@ fn write_file<W: Write>(
f,
&camel_obj_name,
&messages.requests,
RequestHandlerDirection::Request,
)?;
write_request_handler(
f,
&camel_obj_name,
&messages.events,
RequestHandlerDirection::Event,
)?;
writeln!(f, "}}")?;
Ok(())

View file

@ -0,0 +1,12 @@
[package]
name = "jay-allocator"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
jay-formats = { version = "0.1.0", path = "../formats" }
jay-video-types = { version = "0.1.0", path = "../video-types" }
thiserror = "2.0.11"
uapi = "0.2.13"

View file

@ -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<dyn Error + Send>);
#[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<Rc<dyn BufferObject>, AllocatorError>;
fn import_dmabuf(
&self,
dmabuf: &DmaBuf,
usage: BufferUsage,
) -> Result<Rc<dyn BufferObject>, AllocatorError>;
}
pub trait AllocatorDrm {
fn dev(&self) -> c::dev_t;
fn dup_render_fd(&self) -> Result<Rc<OwnedFd>, AllocatorError>;
}
pub trait BufferObject {
fn dmabuf(&self) -> &DmaBuf;
fn map_read(self: Rc<Self>) -> Result<Box<dyn MappedBuffer>, AllocatorError>;
fn map_write(self: Rc<Self>) -> Result<Box<dyn MappedBuffer>, AllocatorError>;
}
pub trait MappedBuffer {
unsafe fn data(&self) -> &[u8];
fn data_ptr(&self) -> *mut u8;
fn stride(&self) -> i32;
}

View file

@ -0,0 +1,14 @@
[package]
name = "jay-async-engine"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
jay-time = { version = "0.1.0", path = "../time" }
jay-tracy = { version = "0.1.0", path = "../tracy" }
jay-utils = { version = "0.1.0", path = "../utils" }
[features]
it = []
tracy = ["jay-tracy/tracy"]

View file

@ -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<T, F: Future<Output = T>> Task<T, F> {
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 {

View file

@ -1,5 +1,5 @@
use {
crate::async_engine::AsyncEngine,
crate::AsyncEngine,
std::{
future::Future,
pin::Pin,

View file

@ -0,0 +1,169 @@
mod ae_task;
mod ae_yield;
mod run_toplevel;
pub use {ae_task::SpawnedFuture, ae_yield::Yield, run_toplevel::*};
use {
crate::ae_task::Runnable,
jay_time::Time,
jay_utils::{array, numcell::NumCell, syncqueue::SyncQueue},
std::{
cell::{Cell, RefCell},
collections::VecDeque,
future::Future,
rc::Rc,
task::Waker,
},
};
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum Phase {
EventHandling,
Layout,
PostLayout,
Present,
}
const NUM_PHASES: usize = 4;
pub struct AsyncEngine {
num_queued: NumCell<usize>,
queues: [SyncQueue<Runnable>; NUM_PHASES],
iteration: NumCell<u64>,
yields: SyncQueue<Waker>,
stash: RefCell<VecDeque<Runnable>>,
yield_stash: RefCell<VecDeque<Waker>>,
stopped: Cell<bool>,
now: Cell<Option<Time>>,
#[cfg(feature = "it")]
idle: Cell<Option<Waker>>,
}
impl AsyncEngine {
pub fn new() -> Rc<Self> {
Rc::new(Self {
num_queued: Default::default(),
queues: array::from_fn(|_| Default::default()),
iteration: Default::default(),
yields: Default::default(),
stash: Default::default(),
yield_stash: Default::default(),
stopped: Cell::new(false),
now: Default::default(),
#[cfg(feature = "it")]
idle: Default::default(),
})
}
pub fn stop(&self) {
self.stopped.set(true);
}
pub fn clear(&self) {
self.stash.borrow_mut().clear();
self.yield_stash.borrow_mut().clear();
self.yields.take();
for queue in &self.queues {
queue.take();
}
}
pub fn spawn<T, F: Future<Output = T> + 'static>(
self: &Rc<Self>,
name: &str,
f: F,
) -> SpawnedFuture<T> {
self.spawn_(name, Phase::EventHandling, f)
}
pub fn spawn2<T, F: Future<Output = T> + 'static>(
self: &Rc<Self>,
name: &str,
phase: Phase,
f: F,
) -> SpawnedFuture<T> {
self.spawn_(name, phase, f)
}
pub fn yield_now(self: &Rc<Self>) -> Yield {
Yield {
iteration: self.iteration(),
queue: self.clone(),
}
}
pub fn dispatch(&self) {
let mut stash = self.stash.borrow_mut();
let mut yield_stash = self.yield_stash.borrow_mut();
loop {
if self.num_queued.get() == 0 {
#[cfg(feature = "it")]
if let Some(idle) = self.idle.take() {
idle.wake();
continue;
}
break;
}
self.now.take();
let mut phase = 0;
while phase < NUM_PHASES {
self.queues[phase].swap(&mut *stash);
if stash.is_empty() {
phase += 1;
continue;
}
self.num_queued.fetch_sub(stash.len());
while let Some(runnable) = stash.pop_front() {
runnable.run();
if self.stopped.get() {
return;
}
}
}
self.iteration.fetch_add(1);
self.yields.swap(&mut *yield_stash);
while let Some(waker) = yield_stash.pop_front() {
waker.wake();
}
}
}
#[cfg(feature = "it")]
pub async fn idle(&self) {
use std::{future::poll_fn, task::Poll};
let mut register = true;
poll_fn(|ctx| {
if register {
self.idle.set(Some(ctx.waker().clone()));
register = false;
Poll::Pending
} else {
Poll::Ready(())
}
})
.await
}
fn push(&self, runnable: Runnable, phase: Phase) {
self.queues[phase as usize].push(runnable);
self.num_queued.fetch_add(1);
}
fn push_yield(&self, waker: Waker) {
self.yields.push(waker);
}
pub fn iteration(&self) -> u64 {
self.iteration.get()
}
pub fn now(&self) -> Time {
match self.now.get() {
Some(t) => t,
None => {
let now = Time::now_unchecked();
self.now.set(Some(now));
now
}
}
}
}

View file

@ -1,8 +1,6 @@
use {
crate::{
async_engine::{AsyncEngine, SpawnedFuture},
utils::queue::AsyncQueue,
},
crate::{AsyncEngine, SpawnedFuture},
jay_utils::queue::AsyncQueue,
std::rc::Rc,
};

12
crates/bufio/Cargo.toml Normal file
View file

@ -0,0 +1,12 @@
[package]
name = "jay-bufio"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
jay-io-uring = { version = "0.1.0", path = "../io-uring" }
jay-utils = { version = "0.1.0", path = "../utils" }
thiserror = "2.0.11"
uapi = "0.2.13"

View file

@ -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,

8
crates/bugs/Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
[package]
name = "jay-bugs"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
ahash = "0.8.7"

38
crates/bugs/src/lib.rs Normal file
View file

@ -0,0 +1,38 @@
use {ahash::AHashMap, std::sync::LazyLock};
static BUGS: LazyLock<AHashMap<&'static str, Bugs>> = LazyLock::new(|| {
let mut map = AHashMap::new();
map.insert(
"chromium",
Bugs {
respect_min_max_size: true,
..Default::default()
},
);
map.insert(
"Alacritty",
Bugs {
min_width: Some(100),
min_height: Some(100),
..Default::default()
},
);
map
});
pub fn get(app_id: &str) -> &'static Bugs {
BUGS.get(app_id).unwrap_or(&NONE)
}
pub static NONE: Bugs = Bugs {
respect_min_max_size: false,
min_width: None,
min_height: None,
};
#[derive(Default, Debug)]
pub struct Bugs {
pub respect_min_max_size: bool,
pub min_width: Option<i32>,
pub min_height: Option<i32>,
}

View file

@ -0,0 +1,18 @@
[package]
name = "jay-clientmem"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
jay-cpu-worker = { version = "0.1.0", path = "../cpu-worker" }
jay-gfx-types = { version = "0.1.0", path = "../gfx-types" }
jay-tracy = { version = "0.1.0", path = "../tracy" }
jay-utils = { version = "0.1.0", path = "../utils" }
log = "0.4.20"
thiserror = "2.0.11"
uapi = "0.2.13"
[features]
tracy = ["jay-tracy/tracy"]

331
crates/clientmem/src/lib.rs Normal file
View file

@ -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<Rc<OwnedFd>>,
failed: Cell<bool>,
sigbus_impossible: bool,
data: *const [Cell<u8>],
cpu: Option<Rc<CpuWorker>>,
}
#[derive(Clone)]
pub struct ClientMemOffset {
mem: Rc<ClientMem>,
offset: usize,
data: *const [Cell<u8>],
}
impl ClientMem {
pub fn new(
fd: &Rc<OwnedFd>,
len: usize,
read_only: bool,
client: Option<ClientMemClient<'_>>,
cpu: Option<&Rc<CpuWorker>>,
is_udmabuf: bool,
) -> Result<Self, ClientMemError> {
Self::new2(fd, len, read_only, client, cpu, c::MAP_SHARED, is_udmabuf)
}
pub fn new_private(
fd: &Rc<OwnedFd>,
len: usize,
read_only: bool,
client: Option<ClientMemClient<'_>>,
cpu: Option<&Rc<CpuWorker>>,
) -> Result<Self, ClientMemError> {
Self::new2(fd, len, read_only, client, cpu, c::MAP_PRIVATE, false)
}
fn new2(
fd: &Rc<OwnedFd>,
len: usize,
read_only: bool,
client: Option<ClientMemClient<'_>>,
cpu: Option<&Rc<CpuWorker>>,
flags: c::c_int,
is_udmabuf: bool,
) -> Result<Self, ClientMemError> {
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<u8>, 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<Self>, offset: usize, len: usize) -> ClientMemOffset {
let mem = unsafe { &*self.data };
ClientMemOffset {
mem: self.clone(),
offset,
data: &mem[offset..][..len],
}
}
pub fn fd(&self) -> &Rc<OwnedFd> {
&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<u8>] {
self.data
}
pub fn access<T, F: FnOnce(&[Cell<u8>]) -> T>(&self, f: F) -> Result<T, ClientMemError> {
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<T: Pod>(&self, dst: &mut Vec<T>) -> Result<(), ClientMemError> {
if self.data.len().checked_rem(std::mem::size_of::<T>()) != Some(0) {
return Err(ClientMemError::InvalidLength);
}
self.access(|v| {
let len_elements = v.len() / std::mem::size_of::<T>();
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<OwnedFd>,
data: *const [Cell<u8>],
}
unsafe impl Send for CloseMemWork {}
impl CpuJob for CloseMemWork {
fn work(&mut self) -> &mut dyn CpuWork {
self
}
fn completed(self: Box<Self>) {
// nothing
}
}
impl CpuWork for CloseMemWork {
fn run(&mut self) -> Option<Box<dyn AsyncCpuWork>> {
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<u8>])) -> Result<(), Box<dyn Error + Sync + Send>> {
self.access(f).map_err(|e| e.into())
}
}

8
crates/cmm/Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
[package]
name = "jay-cmm"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
jay-utils = { version = "0.1.0", path = "../utils" }

View file

@ -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,
};

View file

@ -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 {

View file

@ -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),

View file

@ -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},
};

View file

@ -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 {

View file

@ -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<Self> {
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,

View file

@ -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,
};

View file

@ -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<T, U> Mul<[f64; 3]> for ColorMatrix<T, U> {
}
}
impl<T, U> Mul<Color> for ColorMatrix<T, U> {
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<T, U> ColorMatrix<T, U> {
pub const fn new(m: [[f64; 4]; 3]) -> Self {
let m = [

53
crates/cmm/src/lib.rs Normal file
View file

@ -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;

View file

@ -0,0 +1,24 @@
[package]
name = "jay-cpu-worker"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
jay-async-engine = { version = "0.1.0", path = "../async-engine" }
jay-geometry = { version = "0.1.0", path = "../geometry" }
jay-io-uring = { version = "0.1.0", path = "../io-uring" }
jay-tracy = { version = "0.1.0", path = "../tracy" }
jay-utils = { version = "0.1.0", 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 = { version = "0.1.0", path = "../wheel" }
[features]
it = []
tracy = ["jay-tracy/tracy", "jay-async-engine/tracy"]

View file

@ -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<Box<dyn AsyncCpuWork>> {
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 {

View file

@ -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,

View file

@ -0,0 +1,509 @@
pub mod jobs;
#[cfg(test)]
mod tests;
use {
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::{
any::Any,
cell::{Cell, RefCell},
collections::VecDeque,
mem,
ptr::NonNull,
rc::Rc,
sync::Arc,
thread,
},
thiserror::Error,
uapi::{OwnedFd, c},
};
pub trait CpuJob {
fn work(&mut self) -> &mut dyn CpuWork;
fn completed(self: Box<Self>);
}
pub trait CpuWork: Send {
fn run(&mut self) -> Option<Box<dyn AsyncCpuWork>>;
fn cancel_async(&mut self, ring: &Rc<IoUring>) {
let _ = ring;
unreachable!();
}
fn async_work_done(&mut self, work: Box<dyn AsyncCpuWork>) {
let _ = work;
unreachable!();
}
}
pub trait AsyncCpuWork: Any {
fn run(
self: Box<Self>,
eng: &Rc<AsyncEngine>,
ring: &Rc<IoUring>,
completion: WorkCompletion,
) -> SpawnedFuture<CompletedWork>;
}
pub struct WorkCompletion {
worker: Rc<Worker>,
id: CpuJobId,
}
pub struct CompletedWork(());
impl WorkCompletion {
pub fn complete(self, work: Box<dyn AsyncCpuWork>) -> CompletedWork {
let job = self.worker.async_jobs.remove(&self.id).unwrap();
unsafe {
job.work.deref_mut().async_work_done(work);
}
self.worker.send_completion(self.id);
CompletedWork(())
}
}
pub struct CpuWorker {
data: Rc<CpuWorkerData>,
_completions_listener: SpawnedFuture<()>,
_job_enqueuer: SpawnedFuture<()>,
}
#[must_use]
pub struct PendingJob {
id: CpuJobId,
thread_data: Rc<CpuWorkerData>,
job_data: Rc<PendingJobData>,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
enum PendingJobState {
#[default]
Waiting,
Abandoned,
Completed,
}
#[derive(Default)]
struct PendingJobData {
job: Cell<Option<NonNull<dyn CpuJob>>>,
state: Cell<PendingJobState>,
}
enum Job {
New {
id: CpuJobId,
work: *mut dyn CpuWork,
},
Cancel {
id: CpuJobId,
},
}
unsafe impl Send for Job {}
#[derive(Default)]
struct CompletedJobsExchange {
queue: VecDeque<CpuJobId>,
condvar: Option<Arc<Condvar>>,
}
struct CpuWorkerData {
next: CpuJobIds,
jobs_to_enqueue: AsyncQueue<Job>,
new_jobs: Arc<Mutex<VecDeque<Job>>>,
have_new_jobs: Rc<OwnedFd>,
completed_jobs_remote: Arc<Mutex<CompletedJobsExchange>>,
completed_jobs_local: RefCell<VecDeque<CpuJobId>>,
have_completed_jobs: Rc<OwnedFd>,
pending_jobs: CopyHashMap<CpuJobId, Rc<PendingJobData>>,
ring: Rc<IoUring>,
_stop: OwnedFd,
pending_job_data_cache: Stack<Rc<PendingJobData>>,
sync_wake_condvar: Arc<Condvar>,
}
#[derive(Debug)]
struct CpuJobIds {
next: NumCell<u64>,
}
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 {
#[error("Could not create a pipe")]
Pipe(#[source] OsError),
#[error("Could not create an eventfd")]
EventFd(#[source] OsError),
#[error("Could not dup an eventfd")]
Dup(#[source] OsError),
}
impl PendingJob {
pub fn detach(self) {
match self.job_data.state.get() {
PendingJobState::Waiting => {
self.job_data.state.set(PendingJobState::Abandoned);
}
PendingJobState::Abandoned => {
unreachable!();
}
PendingJobState::Completed => {}
}
}
}
impl Drop for CpuWorker {
fn drop(&mut self) {
self.data.do_equeue_jobs();
if self.data.pending_jobs.is_not_empty() {
log::warn!("CpuWorker dropped with pending jobs. Completed jobs will not be triggered.")
}
}
}
impl Drop for PendingJob {
fn drop(&mut self) {
match self.job_data.state.get() {
PendingJobState::Waiting => {
log::warn!("PendingJob dropped before completion. Blocking.");
let data = &self.thread_data;
let id = self.id;
self.job_data.state.set(PendingJobState::Abandoned);
data.jobs_to_enqueue.push(Job::Cancel { id });
data.do_equeue_jobs();
loop {
data.dispatch_completions();
if !data.pending_jobs.contains(&id) {
break;
}
let mut remote = data.completed_jobs_remote.lock();
while remote.queue.is_empty() {
remote.condvar = Some(data.sync_wake_condvar.clone());
data.sync_wake_condvar.wait(&mut remote);
}
}
}
PendingJobState::Abandoned => {}
PendingJobState::Completed => {
self.thread_data
.pending_job_data_cache
.push(self.job_data.clone());
}
}
}
}
impl CpuWorkerData {
fn clear(&self) {
self.jobs_to_enqueue.clear();
self.new_jobs.lock().clear();
self.completed_jobs_remote.lock().queue.clear();
self.completed_jobs_local.borrow_mut().clear();
self.pending_jobs.clear();
self.pending_job_data_cache.take();
}
async fn wait_for_completions(self: Rc<Self>) {
let mut buf = TypedBuf::<u64>::new();
loop {
if let Err(e) = self.ring.read(&self.have_completed_jobs, buf.buf()).await {
log::error!("Could not wait for job completions: {}", ErrorFmt(e));
return;
}
self.dispatch_completions();
}
}
fn dispatch_completions(&self) {
let completions = &mut *self.completed_jobs_local.borrow_mut();
mem::swap(completions, &mut self.completed_jobs_remote.lock().queue);
while let Some(id) = completions.pop_front() {
let job_data = self.pending_jobs.remove(&id).unwrap();
let job = job_data.job.take().unwrap();
let job = unsafe { Box::from_raw(job.as_ptr()) };
match job_data.state.get() {
PendingJobState::Waiting => {
job_data.state.set(PendingJobState::Completed);
job.completed();
}
PendingJobState::Abandoned => {
self.pending_job_data_cache.push(job_data);
}
PendingJobState::Completed => {
unreachable!();
}
}
}
}
async fn equeue_jobs(self: Rc<Self>) {
loop {
self.jobs_to_enqueue.non_empty().await;
self.do_equeue_jobs();
}
}
fn do_equeue_jobs(&self) {
self.jobs_to_enqueue.move_to(&mut self.new_jobs.lock());
if let Err(e) = uapi::eventfd_write(self.have_new_jobs.raw(), 1) {
panic!("Could not signal eventfd: {}", ErrorFmt(e));
}
}
}
impl CpuWorker {
pub fn new(ring: &Rc<IoUring>, eng: &Rc<AsyncEngine>) -> Result<Self, CpuWorkerError> {
let new_jobs: Arc<Mutex<VecDeque<Job>>> = Default::default();
let completed_jobs: Arc<Mutex<CompletedJobsExchange>> = Default::default();
let Pipe {
read: stop_read,
write: stop_write,
} = pipe().map_err(CpuWorkerError::Pipe)?;
let have_new_jobs = uapi::eventfd(0, c::EFD_CLOEXEC).map_os_err(CpuWorkerError::EventFd)?;
let have_completed_jobs =
uapi::eventfd(0, c::EFD_CLOEXEC).map_os_err(CpuWorkerError::EventFd)?;
thread::Builder::new()
.name("cpu worker".to_string())
.spawn({
let new_jobs = new_jobs.clone();
let completed_jobs = completed_jobs.clone();
let have_new_jobs = uapi::fcntl_dupfd_cloexec(have_new_jobs.raw(), 0)
.map_os_err(CpuWorkerError::Dup)?;
let have_completed_jobs = uapi::fcntl_dupfd_cloexec(have_completed_jobs.raw(), 0)
.map_os_err(CpuWorkerError::Dup)?;
move || {
work(
new_jobs,
completed_jobs,
stop_write,
have_new_jobs,
have_completed_jobs,
)
}
})
.unwrap();
let data = Rc::new(CpuWorkerData {
next: Default::default(),
jobs_to_enqueue: Default::default(),
new_jobs,
have_new_jobs: Rc::new(have_new_jobs),
completed_jobs_remote: completed_jobs,
completed_jobs_local: Default::default(),
have_completed_jobs: Rc::new(have_completed_jobs),
pending_jobs: Default::default(),
ring: ring.clone(),
_stop: stop_read,
pending_job_data_cache: Default::default(),
sync_wake_condvar: Arc::new(Condvar::new()),
});
Ok(Self {
_completions_listener: eng.spawn(
"cpu worker completions",
data.clone().wait_for_completions(),
),
_job_enqueuer: eng.spawn("cpu worker enqueue", data.clone().equeue_jobs()),
data,
})
}
pub fn clear(&self) {
self.data.clear();
}
pub fn submit(&self, job: Box<dyn CpuJob>) -> PendingJob {
let mut job = NonNull::from(Box::leak(job));
let id = self.data.next.next();
self.data.jobs_to_enqueue.push(Job::New {
id,
work: unsafe { job.as_mut().work() },
});
let job_data = self.data.pending_job_data_cache.pop().unwrap_or_default();
job_data.job.set(Some(job));
job_data.state.set(PendingJobState::Waiting);
self.data.pending_jobs.set(id, job_data.clone());
PendingJob {
id,
thread_data: self.data.clone(),
job_data,
}
}
#[cfg(feature = "it")]
pub fn wait_idle(&self) -> bool {
let was_idle = self.data.pending_jobs.is_empty();
loop {
self.data.dispatch_completions();
if self.data.pending_jobs.is_empty() {
break;
}
let mut remote = self.data.completed_jobs_remote.lock();
while remote.queue.is_empty() {
remote.condvar = Some(self.data.sync_wake_condvar.clone());
self.data.sync_wake_condvar.wait(&mut remote);
}
}
was_idle
}
}
fn work(
new_jobs: Arc<Mutex<VecDeque<Job>>>,
completed_jobs: Arc<Mutex<CompletedJobsExchange>>,
stop: OwnedFd,
have_new_jobs: OwnedFd,
have_completed_jobs: OwnedFd,
) {
let eng = AsyncEngine::new();
let ring = IoUring::new(&eng, 32).unwrap();
let worker = Rc::new(Worker {
eng,
ring,
completed_jobs,
have_completed_jobs,
async_jobs: Default::default(),
stopped: Cell::new(false),
});
let _stop_listener = worker
.eng
.spawn("stop listener", worker.clone().handle_stop(stop));
let _new_job_listener = worker.eng.spawn(
"new job listener",
worker.clone().handle_new_jobs(new_jobs, have_new_jobs),
);
if let Err(e) = worker.ring.run() {
panic!("io_uring failed: {}", ErrorFmt(e));
}
}
struct Worker {
eng: Rc<AsyncEngine>,
ring: Rc<IoUring>,
completed_jobs: Arc<Mutex<CompletedJobsExchange>>,
have_completed_jobs: OwnedFd,
async_jobs: CopyHashMap<CpuJobId, AsyncJob>,
stopped: Cell<bool>,
}
struct AsyncJob {
_future: SpawnedFuture<CompletedWork>,
work: *mut dyn CpuWork,
}
impl Worker {
async fn handle_stop(self: Rc<Self>, stop: OwnedFd) {
let stop = Rc::new(stop);
if let Err(e) = self.ring.poll(&stop, 0).await {
log::error!(
"Could not wait for stop fd to become readable: {}",
ErrorFmt(e)
);
} else {
assert!(self.async_jobs.is_empty());
self.stopped.set(true);
self.ring.stop();
}
}
async fn handle_new_jobs(
self: Rc<Self>,
jobs_remote: Arc<Mutex<VecDeque<Job>>>,
new_jobs: OwnedFd,
) {
let mut buf = TypedBuf::<u64>::new();
let new_jobs = Rc::new(new_jobs);
let mut jobs = VecDeque::new();
loop {
if let Err(e) = self.ring.read(&new_jobs, buf.buf()).await {
if self.stopped.get() {
return;
}
panic!(
"Could not wait for new jobs fd to be signaled: {}",
ErrorFmt(e),
);
}
mem::swap(&mut jobs, &mut *jobs_remote.lock());
while let Some(job) = jobs.pop_front() {
self.handle_new_job(job);
}
}
}
fn handle_new_job(self: &Rc<Self>, job: Job) {
match job {
Job::Cancel { id } => {
let mut jobs = self.async_jobs.lock();
if let Some(job) = jobs.get_mut(&id) {
unsafe {
job.work.deref_mut().cancel_async(&self.ring);
}
}
}
Job::New { id, work } => match unsafe { work.deref_mut() }.run() {
None => {
self.send_completion(id);
return;
}
Some(w) => {
let completion = WorkCompletion {
worker: self.clone(),
id,
};
let future = w.run(&self.eng, &self.ring, completion);
self.async_jobs.set(
id,
AsyncJob {
_future: future,
work,
},
);
}
},
}
}
fn send_completion(&self, id: CpuJobId) {
let cv = {
let mut exchange = self.completed_jobs.lock();
exchange.queue.push_back(id);
exchange.condvar.take()
};
if let Some(cv) = cv {
cv.notify_all();
}
if let Err(e) = uapi::eventfd_write(self.have_completed_jobs.raw(), 1) {
panic!("Could not signal job completion: {}", ErrorFmt(e));
}
}
}

View file

@ -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},
};

View file

@ -0,0 +1,12 @@
[package]
name = "jay-criteria"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
jay-utils = { version = "0.1.0", path = "../utils" }
ahash = "0.8.7"
linearize = { version = "0.1.3", features = ["derive"] }
regex = "1.11.1"

View file

@ -1,5 +1,5 @@
use {
crate::criteria::{
crate::{
CritMatcherId,
crit_graph::{CritTarget, crit_upstream::CritUpstreamNode},
},

View file

@ -1,5 +1,5 @@
use {
crate::criteria::{
crate::{
CritUpstreamNode,
crit_graph::{
CritDownstream, CritDownstreamData, CritTarget, CritUpstreamData,

View file

@ -1,5 +1,5 @@
use {
crate::criteria::{
crate::{
CritMatcherId, CritUpstreamNode, FixedRootMatcher, RootMatcherMap,
crit_graph::{
CritTarget, CritUpstreamData,

View file

@ -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},

View file

@ -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,

View file

@ -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<AsyncQueue<CritLeafEvent<Target>>>,
}
pub(in crate::criteria) struct NodeHolder<Target>
pub struct NodeHolder<Target>
where
Target: CritTarget,
{
@ -77,7 +75,7 @@ impl<Target> CritLeafMatcher<Target>
where
Target: CritTarget,
{
pub(in crate::criteria) fn new(
pub(crate) fn new(
mgr: &Target::Mgr,
upstream: &Rc<dyn CritUpstreamNode<Target>>,
on_match: impl Fn(Target::LeafData) -> Box<dyn FnOnce()> + 'static,

View file

@ -1,5 +1,5 @@
use {
crate::criteria::crit_graph::{CritMiddleCriterion, CritTarget, CritUpstreamNode},
crate::crit_graph::{CritMiddleCriterion, CritTarget, CritUpstreamNode},
std::{marker::PhantomData, rc::Rc},
};

View file

@ -1,5 +1,5 @@
use {
crate::criteria::{
crate::{
CritMatcherIds, FixedRootMatcher,
crit_graph::{
CritFixedRootCriterion, CritFixedRootCriterionBase, CritMgr, CritRoot, CritRootFixed,

View file

@ -1,5 +1,5 @@
use {
crate::criteria::crit_graph::{CritMiddleCriterion, CritTarget, CritUpstreamNode},
crate::crit_graph::{CritMiddleCriterion, CritTarget, CritUpstreamNode},
std::{marker::PhantomData, rc::Rc},
};

View file

@ -1,5 +1,5 @@
use {
crate::criteria::{
crate::{
CritLiteralOrRegex, RootMatcherMap,
crit_graph::{CritRootCriterion, CritTarget},
},

View file

@ -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<Target>: 'static
pub trait CritDestroyListenerBase<Target>: 'static
where
Target: CritTarget,
{

131
crates/criteria/src/lib.rs Normal file
View file

@ -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<u64>,
}
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<Target, T> = CopyHashMap<CritMatcherId, Weak<CritRoot<Target, T>>>;
pub type FixedRootMatcher<Target, T> =
StaticMap<bool, Rc<CritRoot<Target, CritRootFixed<Target, T>>>>;
#[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<dyn CritUpstreamNode<Self::Target>>],
all: bool,
) -> Rc<dyn CritUpstreamNode<Self::Target>> {
if upstream.is_empty() {
return self.match_constant()[all].clone();
}
CritMiddle::new(self, upstream, CritMatchAnyOrAll::new(upstream, all))
}
fn exactly(
&self,
upstream: &[Rc<dyn CritUpstreamNode<Self::Target>>],
num: usize,
) -> Rc<dyn CritUpstreamNode<Self::Target>> {
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<dyn CritUpstreamNode<Self::Target>>,
on_match: impl Fn(<Self::Target as CritTarget>::LeafData) -> Box<dyn FnOnce()> + 'static,
) -> Rc<CritLeafMatcher<Self::Target>> {
CritLeafMatcher::new(self, upstream, on_match)
}
fn not(
&self,
upstream: &Rc<dyn CritUpstreamNode<Self::Target>>,
) -> Rc<dyn CritUpstreamNode<Self::Target>> {
upstream.not(self)
}
fn root<T>(&self, criterion: T) -> Rc<dyn CritUpstreamNode<Self::Target>>
where
T: CritRootCriterion<Self::Target>,
{
CritRoot::new(self.roots(), self.id(), criterion)
}
}
impl<T> CritMgrExt for T where T: CritMgr {}

10
crates/damage/Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "jay-damage"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
jay-geometry = { version = "0.1.0", path = "../geometry" }
jay-tree-types = { version = "0.1.0", path = "../tree-types" }
jay-units = { version = "0.1.0", path = "../units" }

116
crates/damage/src/lib.rs Normal file
View file

@ -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 _,
}
}
}

View file

@ -0,0 +1,14 @@
[package]
name = "jay-dbus-core"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
jay-bufio = { version = "0.1.0", path = "../bufio" }
jay-io-uring = { version = "0.1.0", path = "../io-uring" }
jay-utils = { version = "0.1.0", path = "../utils" }
bstr = { version = "1.9.0", default-features = false, features = ["std"] }
thiserror = "2.0.11"
uapi = "0.2.13"

View file

@ -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)?);
}

View file

@ -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},
};

232
crates/dbus-core/src/lib.rs Normal file
View file

@ -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<String>,
}
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<IoUringError>),
#[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<DbusError>),
}
impl From<IoUringError> 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<DynamicType>),
DictEntry(Box<DynamicType>, Box<DynamicType>),
Struct(Vec<DynamicType>),
}
pub struct Parser<'a> {
pub(crate) buf: &'a [u8],
pub(crate) pos: usize,
pub(crate) fds: &'a [Rc<OwnedFd>],
}
pub struct Formatter<'a> {
fds: &'a mut Vec<Rc<OwnedFd>>,
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<Self, DbusError>;
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<Self, DbusError> {
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<u8>);
fn marshal(&self, fmt: &mut Formatter);
fn unmarshal(parser: &mut Parser<'a>) -> Result<Self, DbusError>;
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,
};
}

View file

@ -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<OwnedFd>]) -> Self {
Self { buf, pos: 0, fds }
Self::new_at(buf, 0, fds)
}
pub fn new_at(buf: &'a [u8], pos: usize, fds: &'a [Rc<OwnedFd>]) -> 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() {

View file

@ -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},
};

View file

@ -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> {

View file

@ -0,0 +1,13 @@
[package]
name = "jay-drm-feedback"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
jay-video-types = { version = "0.1.0", path = "../video-types" }
ahash = "0.8.7"
byteorder = "1.5.0"
thiserror = "2.0.11"
uapi = "0.2.13"

View file

@ -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<u64>,
}
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<OwnedFd>,
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<DrmFeedbackShared>,
pub tranches: Vec<DrmFeedbackTranche>,
}
#[derive(Clone, Debug)]
pub struct DrmFeedbackTranche {
pub device: c::dev_t,
pub indices: Vec<u16>,
pub scanout: bool,
}
impl DrmFeedback {
pub fn new<C: DrmFeedbackContext + ?Sized>(
ids: &DrmFeedbackIds,
render_ctx: &C,
) -> Result<Self, DrmFeedbackError> {
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<Option<Self>, 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<c::dev_t>;
fn for_each_read_format(&self, f: &mut dyn FnMut(u32, Modifier));
}
fn create_fd_data<C: DrmFeedbackContext + ?Sized>(
ctx: &C,
) -> (Vec<u8>, 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::<NativeEndian>(format).unwrap();
vec.write_u32::<NativeEndian>(0).unwrap();
vec.write_u64::<NativeEndian>(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,
}

11
crates/edid/Cargo.toml Normal file
View file

@ -0,0 +1,11 @@
[package]
name = "jay-edid"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
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"

1312
crates/edid/src/lib.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,14 @@
[package]
name = "jay-eventfd-cache"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
jay-async-engine = { version = "0.1.0", path = "../async-engine" }
jay-io-uring = { version = "0.1.0", path = "../io-uring" }
jay-utils = { version = "0.1.0", path = "../utils" }
log = { version = "0.4.20", features = ["std"] }
thiserror = "2.0.11"
uapi = "0.2.13"

View file

@ -0,0 +1,157 @@
use {
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,
uapi::{OwnedFd, c},
};
#[cfg(test)]
mod tests;
#[derive(Debug, Error)]
pub enum EventfdError {
#[error("Could not create an eventfd")]
CreateEventfd(#[source] OsError),
}
pub struct EventfdCache {
inner: Rc<Inner>,
_task: SpawnedFuture<()>,
}
struct Inner {
ring: Rc<IoUring>,
fds: Stack<Rc<OwnedFd>>,
recycle: AsyncQueue<Rc<OwnedFd>>,
}
pub struct Eventfd {
cache: Rc<Inner>,
pub fd: Rc<OwnedFd>,
signaled: Cell<bool>,
}
impl EventfdCache {
pub fn new(ring: &Rc<IoUring>, eng: &Rc<AsyncEngine>) -> Rc<Self> {
let inner = Rc::new(Inner {
ring: ring.clone(),
fds: Default::default(),
recycle: Default::default(),
});
let task = eng.spawn("eventfd-cache", inner.clone().recycle());
Rc::new(Self { inner, _task: task })
}
pub fn acquire(&self) -> Result<Eventfd, EventfdError> {
let fd = match self.inner.fds.pop() {
Some(fd) => fd,
_ => uapi::eventfd(0, c::EFD_CLOEXEC)
.map(Rc::new)
.map_os_err(EventfdError::CreateEventfd)?,
};
Ok(Eventfd {
cache: self.inner.clone(),
fd,
signaled: Default::default(),
})
}
}
impl Eventfd {
pub fn is_signaled(&self) -> bool {
self.signaled.get()
}
pub async fn signaled(&self) -> Result<(), IoUringError> {
if self.signaled.get() {
return Ok(());
}
self.cache.ring.readable(&self.fd).await?;
self.signaled.set(true);
Ok(())
}
pub fn signaled_blocking(&self) -> Result<(), OsError> {
if self.signaled.get() {
return Ok(());
}
let mut pollfd = c::pollfd {
fd: self.fd.raw(),
events: c::POLLIN,
revents: 0,
};
uapi::poll(slice::from_mut(&mut pollfd), -1).to_os_error()?;
self.signaled.set(true);
Ok(())
}
}
impl Inner {
async fn recycle(self: Rc<Self>) {
let slf = &*self;
let mut fds = vec![];
let mut bufs = vec![];
let mut tasks = vec![];
let mut todo = vec![];
loop {
fds.clear();
tasks.clear();
todo.clear();
slf.recycle.non_empty().await;
while let Some(fd) = slf.recycle.try_pop() {
fds.push(fd);
}
for (idx, fd) in fds.iter().enumerate() {
if idx >= bufs.len() {
bufs.push(Buf::new(size_of::<u64>()));
}
let fd = fd.clone();
let buf = bufs[idx].clone();
tasks.push(async move { slf.ring.read(&fd, buf).await });
todo.push(idx);
}
poll_fn(|ctx| {
let mut i = 0;
while i < todo.len() {
let idx = todo[i];
let task = unsafe { Pin::new_unchecked(&mut tasks[idx]) };
if let Poll::Ready(res) = task.poll(ctx) {
todo.swap_remove(i);
match res {
Ok(_) => {
self.fds.push(fds[idx].clone());
}
Err(e) => {
log::error!("Could not read from eventfd: {}", ErrorFmt(e));
}
}
} else {
i += 1;
}
}
if todo.is_empty() {
Poll::Ready(())
} else {
Poll::Pending
}
})
.await;
}
}
}
impl Drop for Eventfd {
fn drop(&mut self) {
if self.signaled.get() {
self.cache.recycle.push(self.fd.clone());
}
}
}

View file

@ -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,
};

13
crates/formats/Cargo.toml Normal file
View file

@ -0,0 +1,13 @@
[package]
name = "jay-formats"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
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 = { version = "1.10.0", path = "../jay-config" }

559
crates/formats/src/lib.rs Normal file
View file

@ -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<u32>,
pub external_only_guess: bool,
pub has_alpha: bool,
pub opaque: Option<&'static Format>,
pub shm_info: Option<FormatShmInfo>,
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<PossibleValue> {
Some(PossibleValue::new(self.name))
}
}
static FORMATS_MAP: LazyLock<AHashMap<u32, &'static Format>> = LazyLock::new(|| {
let mut map = AHashMap::new();
for format in FORMATS {
assert!(map.insert(format.drm, format).is_none());
}
map
});
static FORMATS_REFS: LazyLock<Vec<&'static Format>> = LazyLock::new(|| FORMATS.iter().collect());
static FORMATS_NAMES: LazyLock<AHashMap<&'static str, &'static Format>> = LazyLock::new(|| {
let mut map = AHashMap::new();
for format in FORMATS {
assert!(map.insert(format.name, format).is_none());
}
map
});
static FORMATS_CONFIG: LazyLock<AHashMap<ConfigFormat, &'static Format>> = 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<u32, &'static Format> {
&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<ConfigFormat, &'static Format> {
&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,
];

View file

@ -0,0 +1,11 @@
[package]
name = "jay-geometry"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
description = "Geometry primitives for Jay"
repository = "https://github.com/mahkoh/jay"
[dependencies]
jay-algorithms = { version = "0.4.0", path = "../algorithms" }
smallvec = { version = "1.11.1", features = ["const_generics", "const_new", "union"] }

365
crates/geometry/src/lib.rs Normal file
View file

@ -0,0 +1,365 @@
mod region;
#[cfg(test)]
mod tests;
pub use region::{DamageQueue, RegionBuilder};
use {
jay_algorithms::rect::{NoTag, RectRaw, Tag},
smallvec::SmallVec,
std::fmt::{Debug, Formatter},
};
#[derive(Copy, Clone, Eq, PartialEq, Default)]
#[repr(transparent)]
pub struct Rect<T = NoTag>
where
T: Tag,
{
raw: RectRaw<T>,
}
#[derive(Clone, Eq, PartialEq, Debug, Default)]
pub struct Region<T = NoTag>
where
T: Tag,
{
rects: SmallVec<[RectRaw<T>; 1]>,
extents: Rect,
}
impl Debug for Rect {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Debug::fmt(&self.raw, f)
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
pub struct RectOverflow {
pub left: i32,
pub right: i32,
pub top: i32,
pub bottom: i32,
}
impl RectOverflow {
pub fn is_contained(&self) -> bool {
self.left <= 0 && self.right <= 0 && self.top <= 0 && self.bottom <= 0
}
pub fn x_overflow(&self) -> bool {
self.left > 0 || self.right > 0
}
pub fn y_overflow(&self) -> bool {
self.top > 0 || self.bottom > 0
}
}
impl<T> Rect<T>
where
T: Tag,
{
pub fn untag(&self) -> Rect {
Rect {
raw: RectRaw {
x1: self.raw.x1,
y1: self.raw.y1,
x2: self.raw.x2,
y2: self.raw.y2,
tag: NoTag,
},
}
}
}
impl Rect {
pub fn new_empty(x: i32, y: i32) -> Self {
Self {
raw: RectRaw {
x1: x,
y1: y,
x2: x,
y2: y,
tag: NoTag,
},
}
}
pub fn new(x1: i32, y1: i32, x2: i32, y2: i32) -> Option<Self> {
if x2 < x1 || y2 < y1 {
return None;
}
Some(Self {
raw: RectRaw {
x1,
y1,
x2,
y2,
tag: NoTag,
},
})
}
#[cfg_attr(not(test), expect(dead_code))]
fn new_unchecked_danger(x1: i32, y1: i32, x2: i32, y2: i32) -> Self {
Self {
raw: RectRaw {
x1,
y1,
x2,
y2,
tag: NoTag,
},
}
}
pub fn new_sized(x1: i32, y1: i32, width: i32, height: i32) -> Option<Self> {
if width < 0 || height < 0 {
return None;
}
Self::new(x1, y1, x1 + width, y1 + height)
}
pub fn new_saturating(x1: i32, y1: i32, x2: i32, y2: i32) -> Self {
Self {
raw: RectRaw {
x1,
y1,
x2: x2.max(x1),
y2: y2.max(y1),
tag: NoTag,
},
}
}
pub fn new_sized_saturating(x1: i32, y1: i32, width: i32, height: i32) -> Self {
Self {
raw: RectRaw {
x1,
y1,
x2: x1.saturating_add(width.max(0)),
y2: y1.saturating_add(height.max(0)),
tag: NoTag,
},
}
}
pub fn union(&self, other: Self) -> Self {
Self {
raw: RectRaw {
x1: self.raw.x1.min(other.raw.x1),
y1: self.raw.y1.min(other.raw.y1),
x2: self.raw.x2.max(other.raw.x2),
y2: self.raw.y2.max(other.raw.y2),
tag: NoTag,
},
}
}
pub fn intersect(&self, other: Self) -> Self {
let x1 = self.raw.x1.max(other.raw.x1);
let y1 = self.raw.y1.max(other.raw.y1);
let x2 = self.raw.x2.min(other.raw.x2).max(x1);
let y2 = self.raw.y2.min(other.raw.y2).max(y1);
Self {
raw: RectRaw {
x1,
y1,
x2,
y2,
tag: NoTag,
},
}
}
pub fn with_size_saturating(&self, width: i32, height: i32) -> Self {
Self::new_sized_saturating(self.raw.x1, self.raw.y1, width, height)
}
pub fn with_tag(&self, tag: u32) -> Rect<u32> {
Rect {
raw: RectRaw {
x1: self.raw.x1,
y1: self.raw.y1,
x2: self.raw.x2,
y2: self.raw.y2,
tag,
},
}
}
}
impl<T> Rect<T>
where
T: Tag,
{
#[cfg_attr(not(test), expect(dead_code))]
fn new_unchecked_danger_tagged(x1: i32, y1: i32, x2: i32, y2: i32, tag: T) -> Self {
Self {
raw: RectRaw {
x1,
y1,
x2,
y2,
tag,
},
}
}
pub fn intersects(&self, other: &Self) -> bool {
self.raw.x1 < other.raw.x2
&& other.raw.x1 < self.raw.x2
&& self.raw.y1 < other.raw.y2
&& other.raw.y1 < self.raw.y2
}
pub fn contains(&self, x: i32, y: i32) -> bool {
self.raw.x1 <= x && self.raw.y1 <= y && self.raw.x2 > x && self.raw.y2 > y
}
pub fn not_contains(&self, x: i32, y: i32) -> bool {
!self.contains(x, y)
}
pub fn dist_squared(&self, x: i32, y: i32) -> i128 {
let x = x as i64;
let y = y as i64;
let x1 = self.raw.x1 as i64;
let x2 = self.raw.x2 as i64;
let y1 = self.raw.y1 as i64;
let y2 = self.raw.y2 as i64;
let mut dx = 0;
if x1 > x {
dx = x1 - x;
} else if x2 < x {
dx = x - x2;
}
let mut dy = 0;
if y1 > y {
dy = y1 - y;
} else if y2 < y {
dy = y - y2;
}
let dx = dx as i128;
let dy = dy as i128;
dx * dx + dy * dy
}
pub fn contains_rect<U>(&self, rect: &Rect<U>) -> bool
where
U: Tag,
{
self.raw.x1 <= rect.raw.x1
&& self.raw.y1 <= rect.raw.x1
&& rect.raw.x2 <= self.raw.x2
&& rect.raw.y2 <= self.raw.y2
}
pub fn get_overflow<U>(&self, child: &Rect<U>) -> RectOverflow
where
U: Tag,
{
RectOverflow {
left: self.raw.x1 - child.raw.x1,
right: child.raw.x2 - self.raw.x2,
top: self.raw.y1 - child.raw.y1,
bottom: child.raw.y2 - self.raw.y2,
}
}
pub fn is_empty(&self) -> bool {
self.raw.x1 == self.raw.x2 || self.raw.y1 == self.raw.y2
}
pub fn is_not_empty(&self) -> bool {
!self.is_empty()
}
pub fn to_origin(&self) -> Self {
Self {
raw: RectRaw {
x1: 0,
y1: 0,
x2: self.raw.x2 - self.raw.x1,
y2: self.raw.y2 - self.raw.y1,
tag: self.raw.tag,
},
}
}
pub fn move_(&self, dx: i32, dy: i32) -> Self {
Self {
raw: RectRaw {
x1: self.raw.x1.saturating_add(dx),
y1: self.raw.y1.saturating_add(dy),
x2: self.raw.x2.saturating_add(dx),
y2: self.raw.y2.saturating_add(dy),
tag: self.raw.tag,
},
}
}
pub fn at_point(&self, x1: i32, y1: i32) -> Self {
Self {
raw: RectRaw {
x1,
y1,
x2: x1 + self.raw.x2 - self.raw.x1,
y2: y1 + self.raw.y2 - self.raw.y1,
tag: self.raw.tag,
},
}
}
pub fn translate(&self, x: i32, y: i32) -> (i32, i32) {
(x.wrapping_sub(self.raw.x1), y.wrapping_sub(self.raw.y1))
}
pub fn translate_inv(&self, x: i32, y: i32) -> (i32, i32) {
(x.wrapping_add(self.raw.x1), y.wrapping_add(self.raw.y1))
}
pub fn x1(&self) -> i32 {
self.raw.x1
}
pub fn x2(&self) -> i32 {
self.raw.x2
}
pub fn y1(&self) -> i32 {
self.raw.y1
}
pub fn y2(&self) -> i32 {
self.raw.y2
}
pub fn width(&self) -> i32 {
self.raw.x2 - self.raw.x1
}
pub fn height(&self) -> i32 {
self.raw.y2 - self.raw.y1
}
pub fn position(&self) -> (i32, i32) {
(self.raw.x1, self.raw.y1)
}
pub fn size(&self) -> (i32, i32) {
(self.width(), self.height())
}
pub fn center(&self) -> (i32, i32) {
(
self.raw.x1 + self.width() / 2,
self.raw.y1 + self.height() / 2,
)
}
pub fn tag(&self) -> T {
self.raw.tag
}
}

View file

@ -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)
}
}

View file

@ -1,5 +1,5 @@
use {
crate::rect::{Rect, Region},
crate::{Rect, Region},
jay_algorithms::rect::{NoTag, RectRaw},
};

View file

@ -0,0 +1,8 @@
[package]
name = "jay-gfx-types"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
[dependencies]
uapi = "0.2.13"

View file

@ -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<u8>])) -> Result<(), Box<dyn Error + Sync + Send>>;
}
pub enum ShmMemoryBacking {
Ptr(*const [Cell<u8>]),
Fd(Rc<OwnedFd>, usize),
}
impl ShmMemory for Vec<Cell<u8>> {
fn len(&self) -> usize {
self.len()
}
fn safe_access(&self) -> ShmMemoryBacking {
ShmMemoryBacking::Ptr(&**self)
}
fn access(&self, f: &mut dyn FnMut(&[Cell<u8>])) -> Result<(), Box<dyn Error + Sync + Send>> {
f(self);
Ok(())
}
}

View file

@ -0,0 +1,14 @@
[package]
name = "jay-input-types"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
description = "Input data types for the Jay compositor"
repository = "https://github.com/mahkoh/jay"
[dependencies]
jay-output-types = { version = "0.1.0", path = "../output-types" }
jay-units = { version = "0.1.0", path = "../units" }
jay-utils = { version = "0.1.0", path = "../utils" }
linearize = { version = "0.1.3", features = ["derive"] }

Some files were not shown because too many files have changed in this diff Show more