1
0
Fork 0
forked from wry/wry

Compare commits

...

124 commits

Author SHA1 Message Date
c5dd462a6e
wl-seat: split focus navigation 2026-05-29 21:58:14 -04:00
c4e9011714
wl-seat: split window management 2026-05-29 21:51:40 -04:00
8f7997e270
wl-seat: split selection handling 2026-05-29 21:50:09 -04:00
4074cee365
tree: split output captures 2026-05-29 21:48:05 -04:00
fa1ad20864
tree: split output workspaces 2026-05-29 21:46:18 -04:00
112b1a8e5f
copy-device: split copy execution 2026-05-29 21:44:06 -04:00
100c657050
tree: split container layout 2026-05-29 21:39:35 -04:00
9437a56807
vulkan: split renderer pipelines 2026-05-29 21:35:40 -04:00
ea7251d6e0
xwayland: split window property loading 2026-05-29 21:32:44 -04:00
7531c8f791
metal: split video discovery 2026-05-29 21:27:55 -04:00
556e4214c4
xwayland: remove transfer wildcard import 2026-05-29 21:21:56 -04:00
4e4de21c2e
metal: remove video property wildcard import 2026-05-29 21:21:52 -04:00
7a49da0a48
metal: split video object model 2026-05-29 21:19:57 -04:00
774177390e
tree: split container drag destinations 2026-05-29 21:13:54 -04:00
795cc7d117
vulkan: split renderer pipeline caches 2026-05-29 21:12:22 -04:00
ed0dc3fbad
vulkan: split renderer operations 2026-05-29 21:11:23 -04:00
959f23f61b
vulkan: split renderer paint regions 2026-05-29 21:09:25 -04:00
8c61a3150b
metal: split copy device holder 2026-05-29 21:06:28 -04:00
1e9ed0e568
metal: split drm property helpers 2026-05-29 21:05:27 -04:00
097eb2e8d3
metal: split video leases and cursors 2026-05-29 21:03:23 -04:00
3c7ba1a7b7
vulkan: split renderer color caches 2026-05-29 21:01:31 -04:00
1a43bd55e1
xwayland: split selection transfers 2026-05-29 20:47:04 -04:00
ff04218023
wl-seat: split device and object handlers 2026-05-29 20:44:28 -04:00
054a3a919f
wl-surface: split interface handlers 2026-05-29 20:42:15 -04:00
d36ec3b7d3
copy-device: split support modules 2026-05-29 20:39:13 -04:00
c482a2b99d
tree: split output helpers 2026-05-29 20:35:58 -04:00
7d9ee6e696
tree: split container tasks 2026-05-29 19:50:08 -04:00
16c402825f
state: split render context handling 2026-05-29 19:48:26 -04:00
c4655d4641
state: split tree operations 2026-05-29 19:46:42 -04:00
36eb811f25
state: split settings mutations 2026-05-29 19:45:30 -04:00
c2d86dcd7f
state: split xwayland handling 2026-05-29 19:44:20 -04:00
cd1f049a3e
state: split idle handling 2026-05-29 19:43:08 -04:00
27750a31c4
state: split rendering helpers 2026-05-29 19:41:55 -04:00
19c2265400
state: split animation orchestration 2026-05-29 19:39:44 -04:00
fb31d5115d
state: split connector records 2026-05-29 19:36:43 -04:00
b387c57d57
forker: split proxy boundary 2026-05-29 19:34:06 -04:00
b0603f0639
forker: split worker implementation 2026-05-29 19:31:48 -04:00
e78e9bcd4c
forker: split protocol messages 2026-05-29 19:23:28 -04:00
08b37552e2
config: split runtime handling 2026-05-29 19:22:09 -04:00
d74c56fc76
config: split keymap handling 2026-05-29 19:21:15 -04:00
32ad879a65
config: split option handling 2026-05-29 19:20:24 -04:00
ec9ce08c77
config: split seat handling 2026-05-29 19:19:03 -04:00
05c40cb46d
config: move window operations 2026-05-29 19:15:36 -04:00
274e7c0602
config: split resource handling 2026-05-29 19:10:36 -04:00
b08a8b63c9
config: split workspace handling 2026-05-29 19:08:40 -04:00
814ba579bf
config: split input device handling 2026-05-29 19:05:55 -04:00
988a1e6965
config: split theme handling 2026-05-29 19:03:07 -04:00
1d739ac4b6
config: split output handling 2026-05-29 19:00:12 -04:00
6393fdf3c0
workspace: move crates under crates 2026-05-29 18:55:59 -04:00
0016bc8cf0
config: split window handling 2026-05-29 18:48:33 -04:00
f4fdf750ec
config: split matcher handling 2026-05-29 18:47:25 -04:00
899d39a320
config: split request dispatch 2026-05-29 18:46:17 -04:00
b5b690d9cf
config: split toml parser crate 2026-05-29 18:42:50 -04:00
e3f122e903
config: expose runtime protocol 2026-05-29 18:36:27 -04:00
bcc85c8b1b
renderer: remove unused helper methods 2026-05-29 18:30:30 -04:00
864f506b1e
ifs: remove dead interface markers 2026-05-29 18:28:38 -04:00
9135834cc7
utils: inline buffer module reexports 2026-05-29 18:27:36 -04:00
869a2c2428
tree: rename capture notifications 2026-05-29 18:25:57 -04:00
3bc3d7ac2a
docs: remove security context global 2026-05-29 18:25:10 -04:00
5f02f22c8b
wire: remove unused user client layer 2026-05-29 18:24:36 -04:00
ce03990ea4
ifs: remove private screencast interface 2026-05-29 18:20:10 -04:00
698110c265
globals: remove client capability permissions 2026-05-29 18:16:06 -04:00
8a35bc3ca4
security: remove context protocol 2026-05-29 18:13:14 -04:00
6f913d5f69
ifs: rename data control protocol traits 2026-05-29 18:09:44 -04:00
5cb7b5973f
ifs: rename data transfer interfaces 2026-05-29 18:08:56 -04:00
f9e8d614ce
config: rename criterion payload types 2026-05-29 18:07:30 -04:00
a8176b96c3
config: rename private command messages 2026-05-29 18:06:35 -04:00
87de5fcca3
config: remove bincode command bridge 2026-05-29 18:05:41 -04:00
d8920ed7a0
ifs: rename data transfer module 2026-05-29 17:57:38 -04:00
a038855895
config: move parsed model into schema crate 2026-05-29 17:15:14 -04:00
c8a6b69bf1
config: move input mode model into config module 2026-05-29 17:11:14 -04:00
36b5a831fc
config: move simple command schema into schema crate 2026-05-29 17:10:00 -04:00
e21670f3f6
config: move keymap schema into schema crate 2026-05-29 17:08:55 -04:00
d9261414c2
config: move rule match schema into schema crate 2026-05-29 17:08:06 -04:00
f0e7bd31cb
config: move command schema into schema crate 2026-05-29 17:06:48 -04:00
81a1a865a1
config: move input match schema into schema crate 2026-05-29 17:05:46 -04:00
fb65585bfa
config: move output schema into schema crate 2026-05-29 17:04:27 -04:00
e94d8fec1f
config: move theme schema into schema crate 2026-05-29 17:02:33 -04:00
41e7fcc290
config: move parser option structs into schema crate 2026-05-29 17:01:28 -04:00
b550bb1025
config: move simple options into schema crate 2026-05-29 17:00:24 -04:00
902853955b
video: move drm object ids into type crate 2026-05-29 13:01:44 -04:00
524836bef3
input: move backend events into type crate 2026-05-29 12:58:17 -04:00
03f35d7944
output: move connector state into type crate 2026-05-29 12:54:24 -04:00
be1511a7be
backend: move interface ids into type crates 2026-05-29 12:51:31 -04:00
46d39becd4
input: move capability conversion to libinput boundary 2026-05-29 12:48:47 -04:00
a20deb0628
output: decouple identity from wayland output 2026-05-29 12:45:39 -04:00
59e4e6dfb7
input: decouple tablet contracts from wayland seat 2026-05-29 12:42:59 -04:00
e996d9528a
io_uring: move fd utility helpers into crate 2026-05-29 12:11:41 -04:00
9606e0892c
async_engine: move toplevel scheduler into crate 2026-05-29 12:10:40 -04:00
7d9cd198ba
keyboard: move keymap state into workspace crate 2026-05-29 12:09:20 -04:00
151dc313ba
input: move shared types into workspace crate 2026-05-29 12:03:45 -04:00
54aefd8c41
damage: move transform matrix into workspace crate 2026-05-29 11:58:22 -04:00
db94c9167f
animation: move curve primitives into layout crate 2026-05-29 11:56:39 -04:00
1558666601
udmabuf: move allocator implementation into workspace crate 2026-05-29 11:53:32 -04:00
81a718aa74
drm_feedback: move table generation into workspace crate 2026-05-29 11:51:32 -04:00
6d569bd4b7
output_schedule: move cursor scheduler into workspace crate 2026-05-29 11:48:49 -04:00
11940fb6a5
allocator: move buffer allocation traits into workspace crate 2026-05-29 11:45:26 -04:00
663cfb3ca3
clientmem: move shm mapping into workspace crate 2026-05-29 11:41:55 -04:00
bf3859a026
gfx: move shared memory contract into types crate 2026-05-29 11:39:18 -04:00
37ec1a4a3f
theme: move shared state into workspace crate 2026-05-29 11:37:04 -04:00
854e0474be
build: move fontconfig constants out of src 2026-05-29 11:33:08 -04:00
f5bcfaf73c
libinput: move bindings into workspace crate 2026-05-29 11:32:34 -04:00
4562a34890
pango: move bindings into workspace crate 2026-05-29 11:29:14 -04:00
ebaccd8762
video: move dma-buf types into workspace crate 2026-05-29 11:27:02 -04:00
f456905231
logger: move logging into workspace crate 2026-05-29 11:25:35 -04:00
061991218f
bugs: move application quirks into workspace crate 2026-05-29 11:21:42 -04:00
d50863bbd8
pr_caps: move process capabilities into workspace crate 2026-05-29 11:20:59 -04:00
c709ff997f
sighand: move signal handling into workspace crate 2026-05-29 11:20:12 -04:00
5a3b2a483b
cpu_worker: move runtime into workspace crate 2026-05-29 11:18:35 -04:00
1c24ca26fe
wheel: move timer wheel into workspace crate 2026-05-29 11:15:29 -04:00
aa70204881
eventfd: move cache into workspace crate 2026-05-29 11:14:42 -04:00
89dc6c91cf
tree: move shared tree types into workspace crate 2026-05-29 11:11:54 -04:00
a1e4641e82
wire: move message buffers into workspace crates 2026-05-29 11:07:43 -04:00
d8380b3dce
xcon: move wire core into workspace crate 2026-05-29 11:02:40 -04:00
61ec13def0
dbus: move protocol core into workspace crate 2026-05-29 10:58:23 -04:00
9e428510ca
dbus: remove unused server-side object support 2026-05-29 09:29:29 -04:00
6489c2821c
bufio: move socket buffering into workspace crate 2026-05-29 09:24:04 -04:00
c3b17db151
io_uring: move runtime into workspace crate 2026-05-29 09:22:59 -04:00
03d3876888
async_engine: move scheduler into workspace crate 2026-05-29 09:19:14 -04:00
b7ecf700fa
tracy: move profiler facade into workspace crate 2026-05-29 09:17:36 -04:00
657e7ce2f7
all: split reusable components into workspace crates 2026-05-29 09:14:53 -04:00
2a079ed800
feat: add window animations 2026-05-29 00:02:13 -04:00
eece44a59c
add config options for waking dpms on mouse and keyboard interaction 2026-05-25 23:58:23 -04:00
2167484861
add dpms on/off command 2026-05-25 21:56:48 -04:00
702 changed files with 34186 additions and 40630 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

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

View file

@ -0,0 +1,482 @@
use {
jay_output_types::ConnectorId,
jay_units::Fixed,
jay_utils::{numcell::NumCell, static_text::StaticText},
linearize::Linearize,
std::{
fmt::{Display, Formatter},
ops::{BitOr, BitOrAssign},
},
};
macro_rules! linear_ids {
($ids:ident, $id:ident $(,)?) => {
linear_ids!($ids, $id, u32);
};
($ids:ident, $id:ident, $ty:ty $(,)?) => {
#[derive(Debug)]
pub struct $ids {
next: NumCell<$ty>,
}
impl Default for $ids {
fn default() -> Self {
Self {
next: NumCell::new(1),
}
}
}
impl $ids {
pub fn next(&self) -> $id {
$id(self.next.fetch_add(1))
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct $id($ty);
impl $id {
pub fn raw(&self) -> $ty {
self.0
}
pub fn from_raw(id: $ty) -> Self {
Self(id)
}
}
impl Display for $id {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.0, f)
}
}
};
}
#[derive(Debug, Copy, Clone, PartialEq, Linearize)]
pub enum InputDeviceAccelProfile {
Flat,
Adaptive,
}
impl StaticText for InputDeviceAccelProfile {
fn text(&self) -> &'static str {
match self {
InputDeviceAccelProfile::Flat => "Flat",
InputDeviceAccelProfile::Adaptive => "Adaptive",
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Linearize)]
pub enum InputDeviceClickMethod {
None,
ButtonAreas,
Clickfinger,
}
impl StaticText for InputDeviceClickMethod {
fn text(&self) -> &'static str {
match self {
InputDeviceClickMethod::None => "none",
InputDeviceClickMethod::ButtonAreas => "button-areas",
InputDeviceClickMethod::Clickfinger => "clickfinger",
}
}
}
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Linearize)]
pub enum InputDeviceCapability {
Keyboard,
Pointer,
Touch,
TabletTool,
TabletPad,
Gesture,
Switch,
}
impl StaticText for InputDeviceCapability {
fn text(&self) -> &'static str {
match self {
InputDeviceCapability::Keyboard => "keyboard",
InputDeviceCapability::Pointer => "pointer",
InputDeviceCapability::Touch => "touch",
InputDeviceCapability::TabletTool => "tablet tool",
InputDeviceCapability::TabletPad => "tablet pad",
InputDeviceCapability::Gesture => "gesture",
InputDeviceCapability::Switch => "switch",
}
}
}
linear_ids!(InputDeviceGroupIds, InputDeviceGroupId, usize);
linear_ids!(InputDeviceIds, InputDeviceId);
pub type TransformMatrix = [[f64; 2]; 2];
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum KeyState {
Released,
Pressed,
Repeated,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ButtonState {
Released,
Pressed,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Linearize)]
pub enum ScrollAxis {
Vertical = 0,
Horizontal = 1,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum AxisSource {
Wheel,
Finger,
Continuous,
}
pub const AXIS_120: i32 = 120;
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)]
pub struct Leds(pub u32);
pub const LED_NUM_LOCK: Leds = Leds(1 << 0);
pub const LED_CAPS_LOCK: Leds = Leds(1 << 1);
pub const LED_SCROLL_LOCK: Leds = Leds(1 << 2);
pub const LED_COMPOSE: Leds = Leds(1 << 3);
pub const LED_KANA: Leds = Leds(1 << 4);
impl Leds {
pub const fn none() -> Self {
Self(0)
}
pub const fn raw(self) -> u32 {
self.0
}
}
impl BitOr for Leds {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
Self(self.0 | rhs.0)
}
}
impl BitOrAssign for Leds {
fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0;
}
}
linear_ids!(TabletIds, TabletId);
#[derive(Debug, Clone)]
pub struct TabletInit {
pub id: TabletId,
pub group: InputDeviceGroupId,
pub name: String,
pub pid: u32,
pub vid: u32,
pub bustype: Option<u32>,
pub path: String,
}
linear_ids!(TabletToolIds, TabletToolId, usize);
#[derive(Debug, Clone)]
pub struct TabletToolInit {
pub tablet_id: TabletId,
pub id: TabletToolId,
pub type_: TabletToolType,
pub hardware_serial: u64,
pub hardware_id_wacom: u64,
pub capabilities: Vec<TabletToolCapability>,
}
linear_ids!(TabletPadIds, TabletPadId);
#[derive(Debug, Clone)]
pub struct TabletPadInit {
pub id: TabletPadId,
pub group: InputDeviceGroupId,
pub path: String,
pub buttons: u32,
pub strips: u32,
pub rings: u32,
pub dials: u32,
pub groups: Vec<TabletPadGroupInit>,
}
#[derive(Debug, Clone)]
pub struct TabletPadGroupInit {
pub buttons: Vec<u32>,
pub rings: Vec<u32>,
pub strips: Vec<u32>,
pub dials: Vec<u32>,
pub modes: u32,
pub mode: u32,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum PadButtonState {
Released,
Pressed,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ToolButtonState {
Released,
Pressed,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum TabletToolType {
Pen,
Eraser,
Brush,
Pencil,
Airbrush,
Finger,
Mouse,
Lens,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum TabletToolCapability {
Tilt,
Pressure,
Distance,
Rotation,
Slider,
Wheel,
}
#[derive(Copy, Clone, Debug)]
pub enum TabletRingEventSource {
Finger,
}
#[derive(Copy, Clone, Debug)]
pub enum TabletStripEventSource {
Finger,
}
#[derive(Debug, Default)]
pub struct TabletToolChanges {
pub down: Option<bool>,
pub pos: Option<TabletTool2dChange<TabletToolPositionChange>>,
pub pressure: Option<f64>,
pub distance: Option<f64>,
pub tilt: Option<TabletTool2dChange<f64>>,
pub rotation: Option<f64>,
pub slider: Option<f64>,
pub wheel: Option<TabletToolWheelChange>,
}
#[derive(Copy, Clone, Debug)]
pub struct TabletTool2dChange<T> {
pub x: T,
pub y: T,
}
#[derive(Copy, Clone, Debug)]
pub struct TabletToolPositionChange {
pub x: f64,
pub dx: f64,
}
#[derive(Copy, Clone, Debug)]
pub struct TabletToolWheelChange {
pub degrees: f64,
pub clicks: i32,
}
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub enum SwitchEvent {
LidOpened,
LidClosed,
ConvertedToLaptop,
ConvertedToTablet,
}
#[derive(Debug)]
pub enum InputEvent {
Key {
time_usec: u64,
key: u32,
state: KeyState,
},
ConnectorPosition {
time_usec: u64,
connector: ConnectorId,
x: Fixed,
y: Fixed,
},
Motion {
time_usec: u64,
dx: Fixed,
dy: Fixed,
dx_unaccelerated: Fixed,
dy_unaccelerated: Fixed,
},
MotionAbsolute {
time_usec: u64,
x_normed: f32,
y_normed: f32,
},
Button {
time_usec: u64,
button: u32,
state: ButtonState,
},
AxisPx {
dist: Fixed,
axis: ScrollAxis,
inverted: bool,
},
AxisSource {
source: AxisSource,
},
AxisStop {
axis: ScrollAxis,
},
Axis120 {
dist: i32,
axis: ScrollAxis,
inverted: bool,
},
AxisFrame {
time_usec: u64,
},
SwipeBegin {
time_usec: u64,
finger_count: u32,
},
SwipeUpdate {
time_usec: u64,
dx: Fixed,
dy: Fixed,
dx_unaccelerated: Fixed,
dy_unaccelerated: Fixed,
},
SwipeEnd {
time_usec: u64,
cancelled: bool,
},
PinchBegin {
time_usec: u64,
finger_count: u32,
},
PinchUpdate {
time_usec: u64,
dx: Fixed,
dy: Fixed,
dx_unaccelerated: Fixed,
dy_unaccelerated: Fixed,
scale: Fixed,
rotation: Fixed,
},
PinchEnd {
time_usec: u64,
cancelled: bool,
},
HoldBegin {
time_usec: u64,
finger_count: u32,
},
HoldEnd {
time_usec: u64,
cancelled: bool,
},
SwitchEvent {
time_usec: u64,
event: SwitchEvent,
},
TabletToolAdded {
time_usec: u64,
init: Box<TabletToolInit>,
},
TabletToolChanged {
time_usec: u64,
id: TabletToolId,
changes: Box<TabletToolChanges>,
},
TabletToolButton {
time_usec: u64,
id: TabletToolId,
button: u32,
state: ToolButtonState,
},
TabletToolRemoved {
time_usec: u64,
id: TabletToolId,
},
TabletPadButton {
time_usec: u64,
id: TabletPadId,
button: u32,
state: PadButtonState,
},
TabletPadModeSwitch {
time_usec: u64,
pad: TabletPadId,
group: u32,
mode: u32,
},
TabletPadRing {
time_usec: u64,
pad: TabletPadId,
ring: u32,
source: Option<TabletRingEventSource>,
angle: Option<f64>,
},
TabletPadStrip {
time_usec: u64,
pad: TabletPadId,
strip: u32,
source: Option<TabletStripEventSource>,
position: Option<f64>,
},
TabletPadDial {
time_usec: u64,
pad: TabletPadId,
dial: u32,
value120: i32,
},
TouchDown {
time_usec: u64,
id: i32,
x_normed: Fixed,
y_normed: Fixed,
},
TouchUp {
time_usec: u64,
id: i32,
},
TouchMotion {
time_usec: u64,
id: i32,
x_normed: Fixed,
y_normed: Fixed,
},
TouchCancel {
time_usec: u64,
id: i32,
},
TouchFrame {
time_usec: u64,
},
}

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