226 Commits

Author SHA1 Message Date
Nova
0ae20b23c5 feat: initial openxr stuff
feat: openxr system


refactor: rename oxr_runtime to openxr_runtime


refactor: make openxr methods/signals snake case


feat(openxr): session

feat(openxr): action, action set

feat(openxr/action): suggested bindings
2023-07-31 23:44:48 -04:00
Nova
7282684104 feat: fd passing 2023-07-31 23:44:17 -04:00
Nova
dac3a3092a refactor(objects): overhaul input 2023-07-26 08:56:34 -04:00
Nova
4ffab6580d refactor (wayland): move seat to client 2023-07-25 14:46:03 -04:00
Nova
1047f5242b refactor: trait away panel item backends 2023-07-23 19:59:35 -04:00
Nova
eedf5446e8 feat(xwayland): xwayland feature 2023-07-23 09:04:22 -04:00
Nova
824b1bdd26 feat: todo fuure plans 2023-07-23 08:30:09 -04:00
Nova
814d631bb8 fix: deadlock on close stereokit 2023-07-23 08:30:09 -04:00
Nova
fe28d6670e refactor(wayland): less crashy 2023-07-23 08:30:09 -04:00
Nova
37413a3e74 feat(xwayland): x window capabilities 2023-07-23 08:30:09 -04:00
Nova
8ad5fc6584 feat(xwayland): first x window 2023-07-23 08:30:09 -04:00
Nova
06dfba48d7 feat(xwayland): serialize start 2023-07-23 08:30:09 -04:00
Nova
f7ad007d54 refactor(wayland): separate backend from panel item 2023-07-23 08:30:09 -04:00
Nova
7a556439ca feat(wayland): initial xwayland support 2023-07-23 08:30:09 -04:00
Nova
945514aa31 fix: data uses flexbuffer type instead of value for mask map 2023-07-23 08:29:53 -04:00
Nova
3b557cd851 fix: wayland stability 2023-07-23 08:29:04 -04:00
Nova
95ecef3426 feat: version bump 2023-07-22 18:31:09 -04:00
Nova
a85504a71a fix: states 2023-07-19 06:04:15 -07:00
Nova
eb2ace5d29 fix: surface not mapping 2023-07-19 06:04:08 -07:00
Nova
1923b71985 feat: formatting 2023-07-19 06:03:28 -07:00
Nova
a87823dcc8 feat: play space 2023-07-16 10:42:35 -07:00
technobaboo
1c0290aff5 feat: make readme more readable 2023-07-11 11:44:38 -07:00
technobaboo
e131b2b00f fix(ci): add semicolons 2023-07-11 11:16:15 -07:00
technobaboo
728573191f fix(ci): appimagetool 2023-07-11 11:11:07 -07:00
technobaboo
cee1137269 fix(ci): xcb glx 2023-07-11 10:59:26 -07:00
technobaboo
c6af3298a2 fix(ci): ninja-build instead of ninja 2023-07-11 10:55:08 -07:00
technobaboo
11074c9d05 feat: ci take 2 2023-07-11 10:53:31 -07:00
Nova
aa6ec1afd8 refactor: use dmabuf v4 instead of bind_display 2023-06-27 05:53:45 -04:00
Nova
a524f2020b refactor: disable shader injection 2023-06-26 20:49:21 -04:00
Nova
e2ce337036 feat: match stereokit to log level 2023-06-26 20:43:02 -04:00
Nova
34c57254a9 fix: unwrap in main fn 2023-06-26 20:33:04 -04:00
Nova
d7bf01c417 feat: hardware accelerated wayland apps 2023-06-26 20:31:38 -04:00
Nova
59d049e7bd feat: proper dmabuf import 2023-06-26 20:09:20 -04:00
Nova
c05921fd71 feat: shaders!! working!! 2023-06-26 19:49:10 -04:00
Nova
5cbd4d807f feat: it borken 2023-06-26 04:37:38 -04:00
Nova
23fe6ead25 feat: glsl simula text shaders 2023-06-25 10:05:18 -04:00
Nova
b3247f5ba8 fix: ctrl+c in tty 2023-06-21 01:47:10 -04:00
Nova
269e5afcc9 fix: mouse pointer keyboard ray direction 2023-06-14 23:00:17 -04:00
Nova
20070eb778 feat: version bump 2023-06-11 01:37:21 -04:00
Nova
c8206b14dd feat: spatial bounds 2023-06-11 00:38:05 -04:00
Nova
e9089e83a9 feat(input): custom pointers 2023-05-31 08:50:15 -04:00
Nova
34faa98dc8 fix(node): better send remote signal 2023-05-31 08:48:59 -04:00
Nova
187d6ee7d1 fix(pointer): proper direction 2023-05-31 08:48:24 -04:00
Nova
f7a9976760 fix(main): make eye pointer not work in flatscreen 2023-05-31 08:47:16 -04:00
Nova
f42c8963e7 fix(scenegraph): recurse through aliases 2023-05-31 08:47:02 -04:00
Nova
f17b4c8ee2 feat: readd dmabufs 2023-05-30 02:20:27 -04:00
Nova
47410fe63a fix: janky dmabuf hack 2023-05-27 09:48:34 -04:00
Nova
04d0a77093 feat: eye gaze support 2023-05-23 18:56:46 -04:00
matthewcroughan
97c2b82fa7 ci: print flatland revision for gnome-graphical-test in Discord message 2023-05-20 11:08:43 +01:00
matthewcroughan
343962fd6f github: remove workflows
Since Hercules CI is in use now, GitHub Actions are not necessarily required
2023-05-20 11:08:43 +01:00
matthewcroughan
2c07278c72 flake: add hercules-ci
This commit also adds a HCI Effect for posting to Discord the result of the gnome-graphical-test on every single commit
2023-05-20 11:08:43 +01:00
matthewcroughan
bdadc527d2 nix: add graphical-gnome-test
This VM integration test spawns Gnome, monado-service,
stardust-xr-server, flatland and weston-cliptest and tests that the
functionality works correctly. The result is a screenshot. If any
program in the test produces an exit code above 0 it will fail the test,
graphical rendering bugs should be visible in the resulting screenshot
2023-05-20 11:08:43 +01:00
matthewcroughan
7045904693 flake.lock: Update
Flake lock file updates:

• Updated input 'fenix':
    'github:nix-community/fenix/3a0b59a2ea946a533c62ac417596835779087f0e' (2023-04-20)
  → 'github:nix-community/fenix/5816c7bbcc385d2e65877631497df3f7d66b354a' (2023-05-11)
• Updated input 'fenix/rust-analyzer-src':
    'github:rust-lang/rust-analyzer/2400b36a2ed40f68a26473f69ac208ba10d98af9' (2023-04-19)
  → 'github:rust-lang/rust-analyzer/b7cdd93f3e1533e96d4cfa1ac8573e6210a2bedf' (2023-05-09)
• Added input 'flatland':
    'github:StardustXR/flatland/24613a496841bdf38e5f136608d5295860a75fce' (2023-05-11)
• Added input 'flatland/fenix':
    'github:nix-community/fenix/ee59e1c769657b1e27e608f8b981fa8f6b715583' (2023-03-14)
• Added input 'flatland/fenix/nixpkgs':
    follows 'flatland/nixpkgs'
• Added input 'flatland/fenix/rust-analyzer-src':
    'github:rust-lang/rust-analyzer/95497533524537b1cc7a2870ce94b0b14503be8b' (2023-03-13)
• Added input 'flatland/nixpkgs':
    'github:NixOS/nixpkgs/67f26c1cfc5d5783628231e776a81c1ade623e0b' (2023-03-13)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/da45bf6ec7bbcc5d1e14d3795c025199f28e0de0' (2023-04-30)
  → 'github:NixOS/nixpkgs/897876e4c484f1e8f92009fd11b7d988a121a4e7' (2023-05-06)
2023-05-20 11:08:43 +01:00
matthewcroughan
c45dc36310 flake: filter nix code and README out of src
This means that changing the Nix code doesn't cause the Rust code to need to be recompiled when using nix build
2023-05-20 11:08:43 +01:00
Nova
ed724dcb0f feat: appimage support!! 2023-05-19 18:12:22 -04:00
Nova
c4234becb9 feat: clean up main function 2023-05-19 18:11:52 -04:00
Nova
cac2be7a4f feat: flat wayland display env var 2023-05-18 06:00:53 -04:00
Nova
84ba4b47ec fix: better pointer compare distance algorithm 2023-05-10 23:44:36 -04:00
Nova
aea681b5c6 fix(model): copy on create to make unique 2023-05-10 23:44:23 -04:00
Nova
7c17dbddb2 feat: instant model loading 2023-05-10 20:10:31 -04:00
Nova
3f56d1e029 fix(wayland): stop crash 2023-05-10 19:14:57 -04:00
Nova
2a145de758 fix: remove dbg statement 2023-05-10 19:02:09 -04:00
Nova
54ad2667c3 fix: upgrade smithay 2023-05-10 16:51:39 -04:00
Nova
fa6ea5adc5 fix: model nodes 2023-05-10 16:49:56 -04:00
Nova
ee5a226c5e feat: model nodes 2023-05-10 08:38:21 -04:00
Matthew Croughan
3e0860073d fix: nix flake smithay lock issues
* flake: use allowBuiltInFetchGit to prevent narHash reproducibility issues

using the builtin fetcher allows fetching git dependencies with only the ref and without storing the narHash for the fixed-output-derivation

* gitignore: add nix result symlinks
2023-05-06 17:37:46 +00:00
Nova
ec7f9a1681 feat: order inputs 2023-05-02 21:58:53 -04:00
Astavie
779431be04 fix: nix overlay
* nix flake

* workflow

* remove flake-utils

* update flake

* fix

* remove cargo hash

* fix overlay
2023-05-01 23:29:34 +00:00
Astavie
683b0c20a1 fix: nix flake
* nix flake

* workflow

* remove flake-utils

* update flake

* fix

* remove cargo hash
2023-05-01 21:56:49 +00:00
Nova King
341da038ea feat: FUNDING.yml 2023-05-01 17:31:03 +00:00
Nova
367066381d better panel item startup settings order 2023-05-01 12:59:49 -04:00
Nova
2f5760c902 fix: launch env vars to launch as much stuff in wayland as possible 2023-05-01 00:05:00 -04:00
Nova
9072c34d50 fix(objects/hand): hand enabled when controller not 2023-04-30 18:28:40 -04:00
Nova
aefa6dc62f feat: new stereokit 2023-04-30 13:25:13 -04:00
Saphira Kai
35f42559ac remove broken Debug derivation for XdgSurfaceData 2023-04-24 13:12:44 -03:00
Nova
63ead46a2c feat(startup): get environment 2023-04-24 09:53:20 -04:00
Nova
5682718713 feat: cargo lock update 2023-04-24 08:31:28 -04:00
Nova
58b9ee0f01 feat: dependency updates 2023-04-24 08:31:07 -04:00
Nova
57039a3ccb feat(wayland): popups, more compatibility, more stability
get_parent


grab


popups

fix head thingy


popup list


feat: remove set_active

feat(wayland): commit_popup

feat(wayland): cleanup


moar changess


actually fix the problem with everything oh my god


proper popup state


fix: multi thread event loop


fix: match popup surface ID


make wayland input system go over surfaces instead of toplevels


feat: massive refactor of all wayland things
2023-04-24 06:30:39 -04:00
Nova
0fec416731 fix: mouse pointer 2023-04-23 09:34:43 -04:00
Nova
f2c9f91a6b feat: custom startup script 2023-04-23 09:34:43 -04:00
Astavie
aef78f12b5 fix: nix flake
* nix flake

* workflow

* remove flake-utils

* update flake
2023-04-20 10:35:50 +00:00
Nova
c19a5bcd91 feat: desktop file 2023-03-25 03:06:50 -04:00
Nova
de3ada1b5d feat: upgrade stereokit-rs 2023-03-23 14:12:48 -04:00
Astavie
d95b3f55e5 feat: nix support & github workflow
* nix flake

* workflow

* remove flake-utils
2023-03-14 19:35:40 +00:00
Nova
643c4959fb fix: unignore cargo.lock 2023-03-13 13:44:12 -07:00
Nova
1b055c9406 fix(input/hand): correct serialization transform matrix order 2023-03-08 01:45:03 -05:00
Nova
42782d0f87 feat(input): new system 2023-02-25 16:39:30 -05:00
Nova
b5fc321e36 refactor(input): make all inputs have nodes 2023-02-24 11:43:06 -05:00
Nova
931f857458 feat: update everything, clean dependencies 2023-02-23 08:41:40 -05:00
Nova
8ecdd3acc6 refactor(model): remove shader use 2023-02-23 07:17:36 -05:00
Nova
951ca7c4db fix(model): make default pbr shader clip 2023-02-20 18:03:02 -05:00
Nova
fee12854ca fix(items): proper drop 2023-02-20 10:24:59 -05:00
Nova
14b9129fca fix(mouse pointer): keyboard 2023-02-18 02:06:17 -05:00
Nova
55ac288391 fix(cargo.toml): upgrade stereokit 2023-02-17 13:10:23 -05:00
Nova
0c15f54bdc fix(mouse pointer): proper pointer transform 2023-02-17 13:10:08 -05:00
Nova
c42f69cb69 fix: order of operations on wayland material properties 2023-02-16 14:03:13 -05:00
Nova
7818ae4add fix(item): send acceptors to new item ui 2023-02-16 14:02:43 -05:00
Nova
c09d565dc5 fix: update stereokit 2023-02-16 14:02:21 -05:00
Nova
9076a0b897 feat: disabled/enabled 2023-02-16 00:30:25 -05:00
Nova
37f283b393 fix: node aspect drawable 2023-02-09 03:58:56 -05:00
Nova
40e7c6cd20 refactor: node aspect drawable 2023-02-08 21:06:24 -05:00
Nova
4620e0ca09 fix: cargo fmt 2023-02-07 18:04:20 -05:00
Nova
33d12787c9 fix(dev profile): optimization level 0 2023-02-07 16:15:37 -05:00
Nova
d7b895f0f4 feat(model): pbr clip shader 2023-02-07 16:15:20 -05:00
awtterpip
fb027de390 fixed bug where sound wasn't stoppable 2023-02-07 10:34:10 -06:00
awtterpip
19c0ec42ac gave audio interface proper name 2023-02-07 09:13:55 -06:00
piper
baea2c71a3 feat: audio! 2023-02-04 20:39:08 -05:00
Nova
0694848c9b fix(stereokit): version 2023-02-04 20:38:10 -05:00
Nova
fd95988a42 refactor(cargo.toml): profile optimizations 2023-02-02 16:32:43 -05:00
Nova
24d8fefcaa feat(spatial): fields_distance, normal, closest_point 2023-02-01 19:21:35 -05:00
Nova
a434c2aa55 fix(items): make acceptor fields non-optional 2023-01-28 11:12:51 -05:00
Nova
c46e01e280 refactor: change logic_step to frame event 2023-01-26 09:08:40 -05:00
Nova
529b86fa91 refactor: remove many unwrap calls 2023-01-25 11:50:53 -05:00
Nova
b5e87d5911 feat(material): auto copy on change parameter 2023-01-25 09:23:01 -05:00
Nova
063be773e0 feat: make event loop multithreaded 2023-01-25 09:17:52 -05:00
Nova
3dadbccfff fix(spatial): moving object relative to itself 2023-01-25 05:40:37 -05:00
Nova
cbf5af88b9 fix(spatial): parse_transform returned result 2023-01-25 05:40:19 -05:00
Nova
f46e870864 refactor(wayland): remove SeatData wrapper 2023-01-22 02:38:40 -05:00
Nova
2358ee899a fix(wayland): update pointer scroll 2023-01-22 02:30:12 -05:00
Nova
4f50772b92 fix(panel_item): allow surfaces with size of 0,0 2023-01-22 00:58:25 -05:00
Nova
c52472fe3c refactor(spatial): get/set parent methods 2023-01-22 00:42:04 -05:00
Nova
52011bd92f fix(spatial): reference space can be self 2023-01-22 00:24:38 -05:00
Nova
be97de8a2e feat: update/clean dependencies 2023-01-22 00:24:13 -05:00
Nova
e9e4d3599d feat: input multiplexing 2023-01-21 18:07:25 -05:00
Nova
6d7d7be3f1 feat: startup script 2023-01-17 18:51:46 -05:00
Nova
d5a951d3c6 feat(spatial): keep track of children 2023-01-16 11:17:12 -05:00
Nova
1b0ef4b7df refactor(main): make arrays tuples 2023-01-16 11:03:46 -05:00
Nova
66879148c6 feat: update stereokit to have fancy tracing 2023-01-15 04:23:26 -05:00
Nova
7108cfa365 fix(input): grab issues 2023-01-15 04:04:09 -05:00
Nova
05e60c43ce feat(stereokit): log filtering 2023-01-15 04:03:21 -05:00
Nova
75359427c9 feat: even more tracing 2023-01-15 01:13:22 -05:00
Nova
ba938dfaf9 feat: adaptive sleep delay 2023-01-14 23:48:49 -05:00
Nova
c3721872e0 feat: spatial tracing 2023-01-14 22:59:00 -05:00
Nova
73f3d99877 feat: span tracing!!! 2023-01-14 22:32:41 -05:00
Nova
1a5edee751 refactor(input): more compact registry contains 2023-01-14 20:29:33 -05:00
Nova
27d75d3098 fix(event loop, client): better async 2023-01-14 12:38:05 -05:00
Nova
198b342dec refactor(cargo.toml): remove unneeded deps and features 2023-01-14 11:17:52 -05:00
Nova
457ff55904 fix(input): reduce latency by several frames 2023-01-14 11:03:47 -05:00
Nova
795780130c feat(tokio): profiling 2023-01-14 10:38:39 -05:00
Nova
7a69d64e46 refactor(wayland): remove commented out code\ 2023-01-07 10:15:56 -05:00
Nova
5350adef98 feat(model): use resource ID for texture 2023-01-06 09:05:30 -05:00
Nova
1ae7e4afaa fix: remove stereokit patch 2023-01-05 21:49:03 -05:00
Nova
e0e5f3b3bc feat(model): set material parameter 2023-01-05 21:46:25 -05:00
Nova
d1522a05e8 fix(drawable/lines): properly make cyclic point 2023-01-05 08:28:29 -05:00
Nova
4f1d03196a fix: remove opt level 3 for dev 2023-01-05 07:59:20 -05:00
Nova
3cf65e5c7b feat: update stereokit 2023-01-04 23:51:48 -05:00
Nova
3f08e1e212 feat(wayland/surface): geometry resizing, unused 2023-01-04 21:36:55 -05:00
Nova
a0d97c8385 feat(delta): mark_changed 2023-01-04 21:36:25 -05:00
Nova
95629422a7 fix(wayland): SSD all the things 2023-01-04 08:26:10 -05:00
Nova
46b3d21fca fix(wayland): toplevel states bytemucked to u8 2023-01-04 08:25:54 -05:00
Nova
2ce9bf8cd4 feat(wayland): proper surface geometry 2023-01-04 07:25:33 -05:00
Nova
1f745fddde feat(wayland): serial counter 2023-01-04 07:25:22 -05:00
Nova
45998df389 feat(delta): const 2023-01-04 07:23:23 -05:00
Nova
fe22451be5 feat(wayland): set toplevel capabilities 2023-01-03 10:08:39 -05:00
Nova
57ea51fb40 feat(wayland): recommended_state 2023-01-02 18:53:58 -05:00
Nova
2d0865e73c refactor(wayland): remove xdg output manager 2023-01-02 03:49:29 -05:00
Nova
8556046b25 refactor(wayland): comment out xdg activation protocol 2023-01-02 03:43:50 -05:00
Nova
c0c0dddf23 fix(wayland): drop panel item correctly 2023-01-01 14:37:11 -05:00
Nova
c2665938d9 feat(wayland): switch pointer focus dynamically 2023-01-01 14:36:46 -05:00
Nova
6b7a074fea fix(wayland): set seat cursor 2023-01-01 14:34:18 -05:00
Nova
bdd5cbe8b0 fix(wayland): xdg surface size when not set 2023-01-01 14:33:07 -05:00
Nova
6068ad5089 feat(wayland/xdg_shell): set surface states None 2022-12-26 11:15:37 -05:00
Nova
2de49d981b feat(wayland): make state fields optional 2022-12-26 08:22:09 -05:00
Nova
246cf5ceea fix(wayland): panel item configure toplevel 2022-12-25 16:19:22 -05:00
Nova
17583471bf feat(wayland): configure and commit for toplevel] 2022-12-25 16:01:23 -05:00
Nova
868621a68c feat(input): allow scaling input handlers 2022-12-21 05:34:34 -05:00
Nova
769fa24c1d feat: update stardust-xr 2022-12-17 02:29:32 -05:00
Nova
89f4aae1ff refactor(data): identical values for mask 2022-12-17 02:28:06 -05:00
Nova
aa79cb2147 refactor(startup): auto acceptor on item add to 2022-12-10 10:26:26 -05:00
Nova
0cb039c1be refactor(startup): generate startup token 2022-12-10 09:46:47 -05:00
Nova
697320d1c0 refactor(startup): rename to STARTUP_SETTINGS 2022-12-10 09:27:37 -05:00
Nova
9f5ad2b9af feat(startup): automatic acceptors 2022-12-10 09:18:02 -05:00
Nova
2315aca8b5 feat(startup settings): use /proc/{pid}/environ 2022-12-10 09:17:37 -05:00
Nova
004454d75e fix(wayland): dmabuf formats 2022-12-10 00:30:53 -05:00
Nova
4286bc119a refactor(wayland): remove manual dmabuf importing 2022-12-09 07:04:50 -05:00
Nova
e7f7f1f062 fix: drain all dmabufs 2022-12-08 05:40:54 -05:00
Nova
226d674365 feat: optimization level 3 for debug 2022-12-07 14:47:25 -05:00
Nova
0dc9dc23e9 feat(wayland): dmabuf 2022-12-07 14:30:48 -05:00
Nova
48719c9862 feat(resources): list of extensions to check 2022-12-05 22:44:04 -05:00
Nova
125ab4f74d refactor(sk_hand): use snake case for datamap keys 2022-12-03 17:18:27 -05:00
Nova
42d2a41d57 feat: tracing 2022-12-02 20:46:28 -05:00
Nova
692ee95500 switch to color_eyre instead of anyhow 2022-12-02 13:58:54 -05:00
Nova
9ec6d31d67 fix(input): O(n log n) instead of O(n^2) 2022-12-02 11:09:23 -05:00
Nova
e30ad3957a fix(items): give aliases a proper lifetime 2022-11-30 22:34:16 -05:00
Nova
35e6e3f1f9 fix(alias): make output optional 2022-11-30 07:04:06 -05:00
Nova
597cadd9f2 fix(lines): use sRGB colors 2022-11-28 00:32:41 -05:00
Nova
358aaa917a fix(lines): convert f32 to u8 colors correctly 2022-11-26 15:22:23 -05:00
Nova
6ef154e9b3 fix(lines): accept f32 colors 2022-11-26 00:43:04 -05:00
Nova
5874eaef78 fix: text not working 2022-11-26 00:34:37 -05:00
Nova
5b15df42c3 fix(wayland): update smithay version 2022-11-25 23:58:22 -05:00
Nova
c10735acd1 feat(fields): torus field 2022-11-24 18:57:11 -05:00
Nova
2cb000c445 refactor(fields): use let else for getting field 2022-11-24 18:56:59 -05:00
Nova
77bc08a78b feat(drawable): lines 2022-11-21 16:39:28 -05:00
Nova
fe4930e919 refactor(items): move capture to item acceptor 2022-11-20 13:34:15 -05:00
Nova
d3653e17cb feat(items): acceptor 2022-11-19 11:25:10 -05:00
Nova
418e9fdc54 fix(wayland): cursor material queue higher 2022-11-14 11:53:08 -05:00
Nova
bd62e7d342 refactor(stereokit): upgrade version 2022-11-14 09:05:36 -05:00
Nova
9da111a3e0 refactor(wayland): make code cleaner 2022-11-12 11:53:11 -05:00
Nova
b278a43d6d fix: upgrade rust version 2022-11-11 13:25:57 -05:00
Nova
892a4165f8 fix(wayland): pointer_motion works when inactive 2022-11-11 13:25:48 -05:00
Nova
088e8e2c9c fix(wayland): remove unwraps 2022-11-11 12:52:51 -05:00
Nova
043a1a1b29 refactor(wayland): s/ObjectId/Weak<WlSurface>/ 2022-11-09 13:05:21 -05:00
Nova
41e76c9d0a feat: wayland feature 2022-11-09 11:13:07 -05:00
Nova
6b578fe044 fix(wayland): account for surface data map panic 2022-11-09 11:02:48 -05:00
Nova
e2b00f23ee fix(panel item): set cursor full of snake 2022-11-08 21:16:03 -05:00
Nova
215b91a4bd refactor(items): genericize item acceptors/ui 2022-11-08 20:25:43 -05:00
Nova
9e0ba504ac fix: make aliased signals snake case 2022-11-08 20:17:15 -05:00
Nova
3cdb6da09c refactor: use snake case for method names 2022-11-08 06:10:03 -05:00
Nova
3821e14fbd fix(wayland): upgrade smithay version 2022-11-06 16:49:30 -05:00
Nova
e3e7714f1a feat(objects/input): add keyboard to mouse_pointer 2022-11-05 17:56:52 -04:00
Nova
cdbfc27792 refactor(data): simplify 2022-11-05 17:56:34 -04:00
Nova
cbb09a9d97 refactor: remove item alias remote_methods 2022-11-05 17:56:09 -04:00
Nova
12c2443e5d feat(field): ray_march 2022-11-05 17:55:27 -04:00
Nova
8b38f61e35 feat(field): expose ray marching to clients 2022-11-01 07:59:49 -04:00
Nova
fad9b7d50c refactor(client): use new messenger 2022-10-30 00:14:24 -04:00
Nova
60f642364f refactor(node): return Result<&T> from get aspect 2022-10-29 07:32:51 -04:00
Nova
454318b0af feat: terrible hack for moses 2022-10-26 05:19:20 -04:00
Nova
4e858e4212 fix(wayland): remove wayland crate pinning 2022-10-25 16:21:22 -04:00
Nova
e4d0159572 refactor: use master smithay branch 2022-10-25 16:07:36 -04:00
Nova
8ea05e998d fix(wayland): set default output size 2022-10-25 12:56:59 -04:00
Nova
af3833b263 fix(data): send "data" to receiver 2022-10-25 07:32:25 -04:00
Nova
94a78314bb feat: pulse sender/receiver 2022-10-21 06:21:56 -04:00
Nova
a572b215b9 fix: clippy 2022-10-21 06:21:49 -04:00
Nova
b189a6a73c feat: zones 2022-10-20 11:32:33 -04:00
Nova King
44b91bf0e8 Merge pull request #2 from philpax/fix-build
fix: remove path dependency for stereokit
2022-10-19 05:55:53 +00:00
86 changed files with 5560 additions and 6720 deletions

1111
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,28 +1,24 @@
[package]
edition = "2021"
rust-version = "1.75"
name = "stardust-xr-server"
version = "0.44.1"
version = "0.42.1"
authors = ["Nova King <technobaboo@proton.me>"]
description = "Stardust XR reference display server"
license = "GPLv2"
repository = "https://github.com/StardustXR/stardust-xr-server/"
homepage = "https://stardustxr.org"
[workspace]
members = ["codegen"]
[[bin]]
name = "stardust-xr-server"
path = "src/main.rs"
[features]
default = ["wayland"]
default = ["wayland", "xwayland"]
openxr_runtime = []
wayland = ["dep:smithay", "dep:xkbcommon"]
xwayland_rootful = []
xwayland_rootless = ["smithay/xwayland"]
xwayland = ["smithay/xwayland"]
profile_tokio = ["dep:console-subscriber", "tokio/tracing"]
profile_app = ["dep:tracing-tracy"]
profile_app = ["dep:tracing-chrome"]
[package.metadata.appimage]
auto_link = true
@@ -44,6 +40,7 @@ lto = true
[dependencies]
color-eyre = { version = "0.6.2", default-features = false }
clap = { version = "4.2.4", features = ["derive"] }
dashmap = "5.4.0"
glam = { version = "0.23.0", features = ["mint"] }
lazy_static = "1.4.0"
mint = "0.5.9"
@@ -52,63 +49,46 @@ once_cell = "1.17.1"
parking_lot = "0.12.1"
portable-atomic = { version = "1.2.0", features = ["float", "std"] }
rustc-hash = "1.1.0"
tokio = { version = "1.27.0", features = ["rt-multi-thread", "signal", "time"] }
tokio = { version = "1.27.0", features = ["rt-multi-thread", "signal"] }
send_wrapper = "0.6.0"
prisma = "0.1.1"
xkbcommon = { version = "0.5.0", default-features = false, optional = true }
stardust-xr = "0.13.0"
directories = "5.0.0"
serde = { version = "1.0.160", features = ["derive"] }
serde_repr = "0.1.16"
tracing = "0.1.37"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
global_counter = "0.2.2"
rand = "0.8.5"
atty = "0.2.14"
xkbcommon = { version = "0.7.0", default-features = false, optional = true }
ctrlc = "3.4.1"
libc = "0.2.148"
input-event-codes = "5.16.8"
nix = "0.27.1"
wayland-scanner = "0.31.1"
wayland-backend = "0.3.3"
cluFlock = "1.2.7"
fxtypemap = "0.2.0"
toml = "0.8.10"
[dependencies.smithay]
# git = "https://github.com/technobaboo/smithay.git" # Until we get stereokit to understand OES samplers and external textures
git = "https://github.com/smithay/smithay.git" # Until we get stereokit to understand OES samplers and external textures
# path = "../smithay"
default-features = false
features = [
"desktop",
"backend_drm",
"backend_egl",
"renderer_gl",
"wayland_frontend",
]
version = "*"
optional = true
[dependencies.stereokit]
default-features = false
features = ["linux-egl"]
version = "0.16.9"
[dependencies.smithay]
# git = "https://github.com/technobaboo/smithay.git" # Until we get stereokit to understand OES samplers and external textures
git = "https://github.com/smithay/smithay.git" # Until we get stereokit to understand OES samplers and external textures
# path = "../smithay"
default-features = false
features = ["desktop", "backend_drm", "renderer_gl", "wayland_frontend"]
version = "*"
optional = true
[dependencies.console-subscriber]
version = "0.1.8"
optional = true
[dependencies.tracing-tracy]
version = "0.10.4"
[dependencies.tracing-chrome]
version = "0.7.1"
optional = true
[dependencies.stardust-xr]
git = "https://github.com/StardustXR/core.git"
[dependencies.stardust-xr-server-codegen]
path = "codegen"
# [patch.crates-io.stereokit]
# path = "../stereokit-rs"
# [patch.crates-io.stereokit-sys]
# path = "../stereokit-sys"
# [patch.crates-io.stardust-xr]
# path = "../core/core"
# [patch.crates-io.stardust-xr-schemas]
# path = "../core/schemas"

View File

@@ -1,17 +0,0 @@
[package]
edition = "2021"
name = "stardust-xr-server-codegen"
version = "0.1.0"
[lib]
proc-macro = true
[dependencies]
convert_case = "0.6.0"
quote = "1.0.33"
mint = "0.5.9"
proc-macro2 = "1.0.71"
split-iter = "0.1.0"
[dependencies.stardust-xr-schemas]
git = "https://github.com/StardustXR/core.git"

View File

@@ -1,563 +0,0 @@
use convert_case::{Case, Casing};
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens};
use split_iter::Splittable;
use stardust_xr_schemas::protocol::*;
fn fold_tokens(a: TokenStream, b: TokenStream) -> TokenStream {
quote!(#a #b)
}
// #[proc_macro]
// pub fn codegen_root_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
// codegen_protocol(ROOT_PROTOCOL)
// }
#[proc_macro]
pub fn codegen_node_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
codegen_protocol(NODE_PROTOCOL)
}
#[proc_macro]
pub fn codegen_spatial_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
codegen_protocol(SPATIAL_PROTOCOL)
}
#[proc_macro]
pub fn codegen_field_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
codegen_protocol(FIELD_PROTOCOL)
}
#[proc_macro]
pub fn codegen_data_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
codegen_protocol(DATA_PROTOCOL)
}
#[proc_macro]
pub fn codegen_audio_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
codegen_protocol(AUDIO_PROTOCOL)
}
#[proc_macro]
pub fn codegen_drawable_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
codegen_protocol(DRAWABLE_PROTOCOL)
}
// #[proc_macro]
// pub fn codegen_input_protocol(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
// codegen_protocol(INPUT_PROTOCOL)
// }
fn codegen_protocol(protocol: &'static str) -> proc_macro::TokenStream {
let protocol = Protocol::parse(protocol).unwrap();
let interface = protocol
.interface
.map(|p| {
let virtual_aspect_name = p.path[1..]
.split('/')
.map(ToString::to_string)
.reduce(|a, b| format!("{a}_{b}"))
.unwrap_or_default()
+ "_interface";
generate_aspect(&Aspect {
name: virtual_aspect_name,
description: protocol.description.clone(),
members: p.members,
})
})
.unwrap_or_default();
let custom_enums = protocol
.custom_enums
.iter()
.map(generate_custom_enum)
.reduce(fold_tokens)
.unwrap_or_default();
let custom_unions = protocol
.custom_unions
.iter()
.map(generate_custom_union)
.reduce(fold_tokens)
.unwrap_or_default();
let custom_structs = protocol
.custom_structs
.iter()
.map(generate_custom_struct)
.reduce(fold_tokens)
.unwrap_or_default();
let aspects = protocol
.aspects
.iter()
.map(generate_aspect)
.reduce(fold_tokens)
.unwrap_or_default();
// let nodes = protocol
// .nodes
// .iter()
// .map(generate_node)
// .reduce(fold_tokens)
// .unwrap_or_default();
quote!(#custom_enums #custom_unions #custom_structs #aspects #interface).into()
}
fn generate_custom_enum(custom_enum: &CustomEnum) -> TokenStream {
let name = Ident::new(&custom_enum.name.to_case(Case::Pascal), Span::call_site());
let description = &custom_enum.description;
let argument_decls = custom_enum
.variants
.iter()
.map(|a| Ident::new(&a.to_case(Case::Pascal), Span::call_site()).to_token_stream())
.reduce(|a, b| quote!(#a, #b))
.unwrap_or_default();
quote! {
#[doc = #description]
#[derive(Debug, Clone, Copy, serde_repr::Deserialize_repr, serde_repr::Serialize_repr)]
#[repr(u32)]
pub enum #name {#argument_decls}
}
}
fn generate_custom_union(custom_union: &CustomUnion) -> TokenStream {
let name = Ident::new(&custom_union.name.to_case(Case::Pascal), Span::call_site());
let description = &custom_union.description;
let option_decls = custom_union
.options
.iter()
.map(generate_union_option)
.reduce(|a, b| quote!(#a, #b))
.unwrap_or_default();
quote! {
#[doc = #description]
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
#[serde(untagged)]
pub enum #name {#option_decls}
}
}
fn generate_union_option(union_option: &UnionOption) -> TokenStream {
let name = union_option
.name
.as_ref()
.map(|n| n.to_case(Case::Pascal))
.unwrap_or_else(|| argument_type_option_name(&union_option._type));
let description = union_option
.description
.as_ref()
.map(|d| quote!(#[doc = #d]))
.unwrap_or_default();
let identifier = Ident::new(&name, Span::call_site());
let _type = generate_argument_type(&union_option._type, false, true);
quote! (#description #identifier(#_type))
}
fn generate_custom_struct(custom_struct: &CustomStruct) -> TokenStream {
let name = Ident::new(&custom_struct.name.to_case(Case::Pascal), Span::call_site());
let description = &custom_struct.description;
let argument_decls = custom_struct
.fields
.iter()
.map(|a| generate_argument_decl(a, true))
.map(|d| quote!(pub #d))
.reduce(|a, b| quote!(#a, #b))
.unwrap_or_default();
quote! {
#[doc = #description]
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct #name {#argument_decls}
}
}
// fn generate_node(node: &Node) -> TokenStream {
// let node_name = Ident::new(&node.name, Span::call_site());
// let description = &node.description;
// let aspects = node
// .aspects
// .iter()
// .map(|a| {
// let aspect_name = Ident::new(&format!("{a}Aspect"), Span::call_site());
// quote!(impl #aspect_name for #node_name {})
// })
// .reduce(fold_tokens)
// .unwrap_or_default();
// quote! {
// #[doc = #description]
// #[derive(Debug)]
// pub struct #node_name (crate::node::Node);
// impl crate::node::NodeType for #node_name {
// fn node(&self) -> &crate::node::Node {
// &self.0
// }
// fn alias(&self) -> Self {
// #node_name(self.0.alias())
// }
// fn from_path(client: &std::sync::Arc<crate::client::Client>, path: String, destroyable: bool) -> Self {
// #node_name(crate::node::Node::from_path(client, path, destroyable))
// }
// }
// impl serde::Serialize for #node_name {
// fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
// let node_path = self.0.get_path().map_err(|e| serde::ser::Error::custom(e))?;
// serializer.serialize_str(&node_path)
// }
// }
// #aspects
// }
// }
fn generate_aspect(aspect: &Aspect) -> TokenStream {
let description = &aspect.description;
let (client_members, server_members) = aspect.members.iter().split(|m| m.side == Side::Server);
let client_mod_name = Ident::new(
&format!("{}_client", &aspect.name.to_case(Case::Snake)),
Span::call_site(),
);
let client_side_members = client_members
.map(generate_member)
.reduce(fold_tokens)
.map(|t| {
quote! {
pub mod #client_mod_name {
#t
}
}
})
.unwrap_or_default();
let aspect_trait_name = Ident::new(
&format!("{}Aspect", &aspect.name.to_case(Case::Pascal)),
Span::call_site(),
);
let server_side_members = server_members
.map(generate_member)
.reduce(fold_tokens)
.unwrap_or_default();
let add_node_members = aspect
.members
.iter()
.filter(|m| m.side == Side::Server)
.map(generate_handler)
.reduce(fold_tokens)
.map(|members| {
quote! {
fn add_node_members(node: &crate::nodes::Node) {
#members
}
}
})
.unwrap_or_default();
let server_side_members = quote! {
#[doc = #description]
pub trait #aspect_trait_name {
#add_node_members
#server_side_members
}
};
quote!(#client_side_members #server_side_members)
}
fn generate_member(member: &Member) -> TokenStream {
let name_str = &member.name;
let name = Ident::new(&member.name.to_case(Case::Snake), Span::call_site());
let description = &member.description;
let side = member.side;
let _type = member._type;
let first_args = match member.side {
Side::Server => {
quote!(_node: std::sync::Arc<crate::nodes::Node>, _calling_client: std::sync::Arc<crate::core::client::Client>)
}
Side::Client => quote!(_node: &crate::nodes::Node),
};
let argument_decls = member
.arguments
.iter()
.map(|a| generate_argument_decl(a, member.side == Side::Server))
.fold(first_args, |a, b| quote!(#a, #b));
let argument_uses = member
.arguments
.iter()
.map(|a| generate_argument_serialize(&a.name, &a._type, a.optional))
.reduce(|a, b| quote!(#a, #b))
.unwrap_or_default();
let return_type = member
.return_type
.as_ref()
.map(|r| generate_argument_type(&r, false, true))
.unwrap_or_else(|| quote!(()));
match (side, _type) {
(Side::Client, MemberType::Method) => {
quote! {
#[doc = #description]
pub async fn #name(#argument_decls) -> color_eyre::eyre::Result<#return_type> {
_node.execute_remote_method(#name_str, &(#argument_uses)).await
}
}
}
(Side::Client, MemberType::Signal) => {
quote! {
#[doc = #description]
pub fn #name(#argument_decls) -> color_eyre::eyre::Result<()> {
let serialized = stardust_xr::schemas::flex::serialize((#argument_uses))?;
_node.send_remote_signal(#name_str, serialized)
}
}
}
(Side::Server, MemberType::Method) => {
quote! {
#[doc = #description]
fn #name(#argument_decls) -> impl std::future::Future<Output = color_eyre::eyre::Result<#return_type>> + Send + 'static;
}
}
(Side::Server, MemberType::Signal) => {
let prefix =
if let Some(ArgumentType::Node { _type, return_info }) = &member.return_type {
if let Some(return_info) = return_info {
let parent_name = Ident::new(
&(name_str.to_case(Case::ScreamingSnake) + "_PARENT_PATH"),
Span::call_site(),
);
let parent_path = &return_info.parent;
quote!(const #parent_name: &'static str = #parent_path;)
} else {
TokenStream::default()
}
} else {
TokenStream::default()
};
quote! {
#prefix
#[doc = #description]
fn #name(#argument_decls) -> color_eyre::eyre::Result<()>;
}
}
}
}
fn generate_handler(member: &Member) -> TokenStream {
let member_name = &member.name;
let member_name_ident = Ident::new(&member_name, Span::call_site());
let argument_names = member
.arguments
.iter()
.map(generate_argument_name)
.reduce(|a, b| quote!(#a, #b));
let argument_types = member
.arguments
.iter()
.map(|a| {
let _type = convert_deserializeable_argument_type(&a._type);
generate_argument_type(&_type, a.optional, true)
})
.reduce(|a, b| quote!(#a, #b));
// dbg!(&argument_types);
let deserialize = argument_names
.clone()
.zip(argument_types)
.map(|(argument_names, argument_types)| {
quote!(let (#argument_names): (#argument_types) = stardust_xr::schemas::flex::deserialize(_message.as_ref())?;)
})
.unwrap_or_default();
let argument_uses = member
.arguments
.iter()
.map(|a| generate_argument_deserialize(&a.name, &a._type, a.optional))
.reduce(|a, b| quote!(#a, #b))
.unwrap_or_default();
match member._type {
MemberType::Signal => quote! {
node.add_local_signal(#member_name, |_node, _calling_client, _message| {
#deserialize
Self::#member_name_ident(_node, _calling_client.clone(), #argument_uses)
});
},
MemberType::Method => quote! {
node.add_local_method(#member_name, |_node, _calling_client, _message, _method_response| {
_method_response.wrap_async(async move {
#deserialize
Ok((Self::#member_name_ident(_node, _calling_client.clone(), #argument_uses).await?, Vec::new()))
});
});
},
}
}
fn generate_argument_name(argument: &Argument) -> TokenStream {
Ident::new(&argument.name.to_case(Case::Snake), Span::call_site()).to_token_stream()
}
fn convert_deserializeable_argument_type(argument_type: &ArgumentType) -> ArgumentType {
match argument_type {
ArgumentType::Node { .. } => ArgumentType::String,
f => f.clone(),
}
}
fn generate_argument_deserialize(
argument_name: &str,
argument_type: &ArgumentType,
optional: bool,
) -> TokenStream {
let name = Ident::new(&argument_name.to_case(Case::Snake), Span::call_site());
match argument_type {
ArgumentType::Node { .. } => match optional {
true => quote!(#name.map(|n| _calling_client.get_node(#argument_name, &n)?)),
false => quote!(_calling_client.get_node(#argument_name, &#name)?),
},
ArgumentType::Color => quote!(color::rgba_linear!(#name[0], #name[1], #name[2], #name[3])),
ArgumentType::Vec(v) => {
let mapping = generate_argument_deserialize("a", v, false);
quote!(#name.into_iter().map(|a| Ok(#mapping)).collect::<color_eyre::eyre::Result<Vec<_>>>()?)
}
ArgumentType::Map(v) => {
let mapping = generate_argument_deserialize("a", v, false);
quote!(#name.into_iter().map(|(k, a)| Ok((k, #mapping))).collect::<color_eyre::eyre::Result<rustc_hash::FxHashMap<String, _>>>()?)
}
_ => quote!(#name),
}
}
fn generate_argument_serialize(
argument_name: &str,
argument_type: &ArgumentType,
optional: bool,
) -> TokenStream {
let name = Ident::new(&argument_name.to_case(Case::Snake), Span::call_site());
match argument_type {
ArgumentType::Node {
_type,
return_info: _,
} => match optional {
true => quote!(#name.map(|n| n.get_path())),
false => quote!(#name.get_path()),
},
ArgumentType::Color => quote!([#name.c.r, #name.c.g, #name.c.b, #name.a]),
ArgumentType::Vec(v) => {
let mapping = generate_argument_serialize("a", v, false);
quote!(#name.into_iter().map(|a| Ok(#mapping)).collect::<color_eyre::eyre::Result<Vec<_>>>()?)
}
ArgumentType::Map(v) => {
let mapping = generate_argument_serialize("a", v, false);
quote!(#name.into_iter().map(|(k, a)| Ok((k, #mapping))).collect::<color_eyre::eyre::Result<rustc_hash::FxHashMap<String, _>>>()?)
}
_ => quote!(#name),
}
}
fn generate_argument_decl(argument: &Argument, owned_values: bool) -> TokenStream {
let name = Ident::new(&argument.name.to_case(Case::Snake), Span::call_site());
let mut _type = generate_argument_type(&argument._type, argument.optional, owned_values);
quote!(#name: #_type)
}
fn argument_type_option_name(argument_type: &ArgumentType) -> String {
match argument_type {
ArgumentType::Bool => "Bool".to_string(),
ArgumentType::Int => "Int".to_string(),
ArgumentType::UInt => "UInt".to_string(),
ArgumentType::Float => "Float".to_string(),
ArgumentType::Vec2 => "Vec2".to_string(),
ArgumentType::Vec3 => "Vec3".to_string(),
ArgumentType::Quat => "Quat".to_string(),
ArgumentType::Color => "Color".to_string(),
ArgumentType::String => "String".to_string(),
ArgumentType::Bytes => "Bytes".to_string(),
ArgumentType::Vec(v) => format!("{}Vector", argument_type_option_name(&v)),
ArgumentType::Map(m) => format!("{}Map", argument_type_option_name(&m)),
ArgumentType::Datamap => "Datamap".to_string(),
ArgumentType::ResourceID => "ResourceID".to_string(),
ArgumentType::Enum(e) => e.clone(),
ArgumentType::Union(u) => u.clone(),
ArgumentType::Struct(s) => s.clone(),
ArgumentType::Node { _type, .. } => _type.clone(),
}
}
fn generate_argument_type(
argument_type: &ArgumentType,
optional: bool,
owned: bool,
) -> TokenStream {
let _type = match argument_type {
ArgumentType::Bool => quote!(bool),
ArgumentType::Int => quote!(i32),
ArgumentType::UInt => quote!(u32),
ArgumentType::Float => quote!(f32),
ArgumentType::Vec2 => quote!(mint::Vector2<f32>),
ArgumentType::Vec3 => quote!(mint::Vector3<f32>),
ArgumentType::Quat => quote!(mint::Quaternion<f32>),
ArgumentType::Color => quote!(stardust_xr::values::Color),
ArgumentType::Bytes => {
if !owned {
quote!(&[u8])
} else {
quote!(Vec<u8>)
}
}
ArgumentType::String => {
if !owned {
quote!(&str)
} else {
quote!(String)
}
}
ArgumentType::Vec(t) => {
let t = generate_argument_type(&t, false, true);
if !owned {
quote!(&[#t])
} else {
quote!(Vec<#t>)
}
}
ArgumentType::Map(t) => {
let t = generate_argument_type(&t, false, true);
if !owned {
quote!(&rustc_hash::FxHashMap<String, #t>)
} else {
quote!(rustc_hash::FxHashMap<String, #t>)
}
}
ArgumentType::Datamap => {
if !owned {
quote!(&stardust_xr::values::Datamap)
} else {
quote!(stardust_xr::values::Datamap)
}
}
ArgumentType::ResourceID => {
if !owned {
quote!(&stardust_xr::values::ResourceID)
} else {
quote!(stardust_xr::values::ResourceID)
}
}
ArgumentType::Enum(e) => {
let enum_name = Ident::new(&e.to_case(Case::Pascal), Span::call_site());
quote!(#enum_name)
}
ArgumentType::Union(u) => {
let union_name = Ident::new(&u.to_case(Case::Pascal), Span::call_site());
quote!(#union_name)
}
ArgumentType::Struct(s) => {
let struct_name = Ident::new(&s.to_case(Case::Pascal), Span::call_site());
if !owned {
quote!(&#struct_name)
} else {
quote!(#struct_name)
}
}
ArgumentType::Node {
_type,
return_info: _,
} => {
if !owned {
quote!(&std::sync::Arc<crate::nodes::Node>)
} else {
quote!(std::sync::Arc<crate::nodes::Node>)
}
}
};
if optional {
quote!(Option<#_type>)
} else {
_type
}
}

328
flake.lock generated
View File

@@ -3,17 +3,16 @@
"fenix": {
"inputs": {
"nixpkgs": [
"flatland",
"nixpkgs"
],
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1700115779,
"narHash": "sha256-oajhxEBg+16/KH74CaygAQ6b5KUHS7DwBoL9ecD9qeI=",
"lastModified": 1683786056,
"narHash": "sha256-Wrz/X9D0t8akhvEGj5a93xgpxI3vAcdPGcwn6tKHooc=",
"owner": "nix-community",
"repo": "fenix",
"rev": "4378e7e5f5bdef438eee5ce967f37593b9b5cd16",
"rev": "5816c7bbcc385d2e65877631497df3f7d66b354a",
"type": "github"
},
"original": {
@@ -22,37 +21,54 @@
"type": "github"
}
},
"fenix_2": {
"inputs": {
"nixpkgs": [
"flatland",
"nixpkgs"
],
"rust-analyzer-src": "rust-analyzer-src_2"
},
"locked": {
"lastModified": 1678775037,
"narHash": "sha256-chx0tWnXKpcayPkPY3Qh+2hNwptvX8XE3o+fYZ+GNzg=",
"owner": "nix-community",
"repo": "fenix",
"rev": "ee59e1c769657b1e27e608f8b981fa8f6b715583",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1673956053,
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1709336216,
"narHash": "sha256-Dt/wOWeW6Sqm11Yh+2+t0dfEWxoMxGBvv3JpIocFl9E=",
"lastModified": 1678379998,
"narHash": "sha256-TZdfNqftHhDuIFwBcN9MUThx5sQXCTeZk9je5byPKRw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "f7b3c975cf067e56e7cda6cb098ebe3fb4d74ca2",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-parts_2": {
"inputs": {
"nixpkgs-lib": [
"hercules-ci-effects",
"nixpkgs"
]
},
"locked": {
"lastModified": 1701473968,
"narHash": "sha256-YcVE5emp1qQ8ieHUnxt1wCZCC3ZfAS+SRRWZ2TMda7E=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "34fed993f1674c8d06d58b37ce1e0fe5eebcb9f5",
"rev": "c13d60b89adea3dc20704c045ec4d50dd964d447",
"type": "github"
},
"original": {
@@ -60,17 +76,54 @@
"type": "indirect"
}
},
"flake-parts_2": {
"inputs": {
"nixpkgs-lib": [
"hercules-ci-effects",
"hercules-ci-agent",
"nixpkgs"
]
},
"locked": {
"lastModified": 1678379998,
"narHash": "sha256-TZdfNqftHhDuIFwBcN9MUThx5sQXCTeZk9je5byPKRw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "c13d60b89adea3dc20704c045ec4d50dd964d447",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-utils": {
"locked": {
"lastModified": 1667395993,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flatland": {
"inputs": {
"fenix": "fenix",
"fenix": "fenix_2",
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1709774755,
"narHash": "sha256-6tSG4G+SB+l71101XNyEHN/J8pSuJvbNPhKgzqP5sVU=",
"lastModified": 1683766358,
"narHash": "sha256-wX1Lpj95kkHUZAloB1fGs+ixaRycaOJq4F77+HvaJCQ=",
"owner": "StardustXR",
"repo": "flatland",
"rev": "fef8c317928a1d1798d8cee9af94d219a7c09e8c",
"rev": "24613a496841bdf38e5f136608d5295860a75fce",
"type": "github"
},
"original": {
@@ -79,17 +132,78 @@
"type": "github"
}
},
"hercules-ci-effects": {
"gitignore": {
"inputs": {
"flake-parts": "flake-parts_2",
"nixpkgs": "nixpkgs_2"
"nixpkgs": [
"hercules-ci-effects",
"hercules-ci-agent",
"pre-commit-hooks-nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1708547820,
"narHash": "sha256-xU/KC1PWqq5zL9dQ9wYhcdgxAwdeF/dJCLPH3PNZEBg=",
"lastModified": 1660459072,
"narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "a20de23b925fd8264fd7fad6454652e142fd7f73",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"haskell-flake": {
"locked": {
"lastModified": 1678138103,
"narHash": "sha256-D0lao82bV3t2gEFjHiU6RN233t+1MnkQV+bq8MEu2ic=",
"owner": "hercules-ci",
"repo": "haskell-flake",
"rev": "1e1660e6dd00838ba73bc7952e6e73be67da18d1",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"ref": "0.1-extraLibraries",
"repo": "haskell-flake",
"type": "github"
}
},
"hercules-ci-agent": {
"inputs": {
"flake-parts": "flake-parts_2",
"haskell-flake": "haskell-flake",
"nix-darwin": "nix-darwin",
"nixpkgs": "nixpkgs_2",
"pre-commit-hooks-nix": "pre-commit-hooks-nix"
},
"locked": {
"lastModified": 1678446614,
"narHash": "sha256-Z6Gsba5ahn/N0QlF0vJfIEfnZgCs4qr1IZtXAqjbE7s=",
"owner": "hercules-ci",
"repo": "hercules-ci-agent",
"rev": "0b90d1a87c117a5861785cb85833dd1c9df0b6ef",
"type": "github"
},
"original": {
"id": "hercules-ci-agent",
"type": "indirect"
}
},
"hercules-ci-effects": {
"inputs": {
"flake-parts": "flake-parts",
"hercules-ci-agent": "hercules-ci-agent",
"nixpkgs": "nixpkgs_3"
},
"locked": {
"lastModified": 1681898675,
"narHash": "sha256-nIJ7CAdiHv4i1no/VgDoeTJLzbLYwu5+/Ycoyzn0S78=",
"owner": "hercules-ci",
"repo": "hercules-ci-effects",
"rev": "0ca27bd58e4d5be3135a4bef66b582e57abe8f4a",
"rev": "15ff4f63e5f28070391a5b09a82f6d5c6cc5c9d0",
"type": "github"
},
"original": {
@@ -98,18 +212,40 @@
"type": "github"
}
},
"nix-darwin": {
"inputs": {
"nixpkgs": [
"hercules-ci-effects",
"hercules-ci-agent",
"nixpkgs"
]
},
"locked": {
"lastModified": 1673295039,
"narHash": "sha256-AsdYgE8/GPwcelGgrntlijMg4t3hLFJFCRF3tL5WVjA=",
"owner": "LnL7",
"repo": "nix-darwin",
"rev": "87b9d090ad39b25b2400029c64825fc2a8868943",
"type": "github"
},
"original": {
"owner": "LnL7",
"repo": "nix-darwin",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1699781429,
"narHash": "sha256-UYefjidASiLORAjIvVsUHG6WBtRhM67kTjEY4XfZOFs=",
"lastModified": 1678703398,
"narHash": "sha256-Y1mW3dBsoWLHpYm+UIHb5VZ7rx024NNHaF16oZBx++o=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "e44462d6021bfe23dfb24b775cc7c390844f773d",
"rev": "67f26c1cfc5d5783628231e776a81c1ade623e0b",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"ref": "nixos-22.11",
"repo": "nixpkgs",
"type": "github"
}
@@ -117,11 +253,11 @@
"nixpkgs-lib": {
"locked": {
"dir": "lib",
"lastModified": 1709237383,
"narHash": "sha256-cy6ArO4k5qTx+l5o+0mL9f5fa86tYUX3ozE1S+Txlds=",
"lastModified": 1678375444,
"narHash": "sha256-XIgHfGvjFvZQ8hrkfocanCDxMefc/77rXeHvYdzBMc8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "1536926ef5621b09bba54035ae2bb6d806d72ac8",
"rev": "130fa0baaa2b93ec45523fdcde942f6844ee9f6e",
"type": "github"
},
"original": {
@@ -132,13 +268,29 @@
"type": "github"
}
},
"nixpkgs_2": {
"nixpkgs-stable": {
"locked": {
"lastModified": 1703637592,
"narHash": "sha256-8MXjxU0RfFfzl57Zy3OfXCITS0qWDNLzlBAdwxGZwfY=",
"lastModified": 1673800717,
"narHash": "sha256-SFHraUqLSu5cC6IxTprex/nTsI81ZQAtDvlBvGDWfnA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "cfc3698c31b1fb9cdcf10f36c9643460264d0ca8",
"rev": "2f9fd351ec37f5d479556cd48be4ca340da59b8f",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-22.11",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1678293141,
"narHash": "sha256-lLlQHaR0y+q6nd6kfpydPTGHhl1rS9nU9OQmztzKOYs=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c90c4025bb6e0c4eaf438128a3b2640314b1c58d",
"type": "github"
},
"original": {
@@ -150,36 +302,94 @@
},
"nixpkgs_3": {
"locked": {
"lastModified": 1709479366,
"narHash": "sha256-n6F0n8UV6lnTZbYPl1A9q1BS0p4hduAv1mGAP17CVd0=",
"owner": "nixos",
"lastModified": 1678891326,
"narHash": "sha256-cjgrjKx7y+hO9I8O2b6QvBaTt9w7Xhk/5hsnJYTUb2I=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b8697e57f10292a6165a20f03d2f42920dfaf973",
"rev": "1544ef240132d4357d9a39a40c8e6afd1678b052",
"type": "github"
},
"original": {
"owner": "nixos",
"owner": "NixOS",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1683408522,
"narHash": "sha256-9kcPh6Uxo17a3kK3XCHhcWiV1Yu1kYj22RHiymUhMkU=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "897876e4c484f1e8f92009fd11b7d988a121a4e7",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"pre-commit-hooks-nix": {
"inputs": {
"flake-compat": "flake-compat",
"flake-utils": "flake-utils",
"gitignore": "gitignore",
"nixpkgs": [
"hercules-ci-effects",
"hercules-ci-agent",
"nixpkgs"
],
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1678376203,
"narHash": "sha256-3tyYGyC8h7fBwncLZy5nCUjTJPrHbmNwp47LlNLOHSM=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "1a20b9708962096ec2481eeb2ddca29ed747770a",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"root": {
"inputs": {
"flake-parts": "flake-parts",
"fenix": "fenix",
"flatland": "flatland",
"hercules-ci-effects": "hercules-ci-effects",
"nixpkgs": "nixpkgs_3"
"nixpkgs": "nixpkgs_4"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1700077026,
"narHash": "sha256-Vf7ykubXsriSjBbeYAm8bzBIvSOYVUmRiCQ3iLL/E+U=",
"lastModified": 1683653808,
"narHash": "sha256-GiKwJySG/YCPIKwz9wSm9fJa5e4CU3GvTYAKZzjBhFo=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "58de0b130a763f3a2d373f508ac0c18a8e7d0acd",
"rev": "b7cdd93f3e1533e96d4cfa1ac8573e6210a2bedf",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
}
},
"rust-analyzer-src_2": {
"flake": false,
"locked": {
"lastModified": 1678695923,
"narHash": "sha256-rDhiiU8P6sf6mgj5IKgCuTRN9uYeqWr6xl4XLkKnMWg=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "95497533524537b1cc7a2870ce94b0b14503be8b",
"type": "github"
},
"original": {

183
flake.nix
View File

@@ -3,77 +3,130 @@
extra-substituters = [ "https://stardustxr.cachix.org" ];
extra-trusted-public-keys = [ "stardustxr.cachix.org-1:mWSn8Ap2RLsIWT/8gsj+VfbJB6xoOkPaZpbjO+r9HBo=" ];
};
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
hercules-ci-effects.url = "github:hercules-ci/hercules-ci-effects";
# Since we do not have a monorepo, we have to fetch Flatland in order to use
# it to create VM Tests
flatland.url = "github:StardustXR/flatland";
};
outputs = inputs@{ self, flake-parts, nixpkgs, hercules-ci-effects, flatland, ... }:
# 22.11 does not include PR #218472, hence we use the unstable version
inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-unstable;
# Since we do not have a monorepo, we have to fetch Flatland in order to use
# it to create VM Tests
inputs.flatland.url = "github:StardustXR/flatland";
inputs.fenix.url = github:nix-community/fenix;
inputs.fenix.inputs.nixpkgs.follows = "nixpkgs";
inputs.hercules-ci-effects.url = "github:hercules-ci/hercules-ci-effects";
outputs = { self, nixpkgs, fenix, hercules-ci-effects, flatland, ... }:
let
name = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.name;
src = builtins.path {
name = "${name}-source";
path = toString ./.;
filter = path: type:
nixpkgs.lib.all
(n: builtins.baseNameOf path != n)
[
"flake.nix"
"flake.lock"
"nix"
"README.md"
];
name = "server";
pkgs = system: import nixpkgs {
inherit system;
};
in
flake-parts.lib.mkFlake { inherit inputs; } {
imports = [
flake-parts.flakeModules.easyOverlay
];
systems = [ "aarch64-linux" "x86_64-linux" "riscv64-linux" ];
perSystem = { config, self', inputs', pkgs, system, ... }: {
_module.args.pkgs = import inputs.nixpkgs { inherit system; overlays = [ inputs.self.overlays.default ]; };
overlayAttrs = config.packages;
packages = {
default = self'.packages.${name};
gnome-graphical-test = self'.checks.gnome-graphical-test;
"${name}" = pkgs.callPackage ./nix/stardust-xr-server.nix { inherit name src; };
};
checks.gnome-graphical-test = pkgs.nixosTest (import ./nix/gnome-graphical-test.nix { inherit pkgs self; });
devShells.default = pkgs.mkShell {
inputsFrom = [ self'.packages.default ];
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
};
shell = pkgs: pkgs.mkShell {
inputsFrom = [ self.packages.${pkgs.system}.default ];
# ---- START package specific dev settings ----
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
# ---- END package specific dev settings ----
};
flake = {
herculesCI.ciSystems = [ "x86_64-linux" ];
effects = let
pkgs = nixpkgs.legacyPackages.x86_64-linux;
hci-effects = hercules-ci-effects.lib.withPkgs pkgs;
in { ref, rev, ... }: {
gnome-graphical-test = hci-effects.mkEffect {
secretsMap."stardustxrDiscord" = "stardustxrDiscord";
secretsMap."stardustxrIpfs" = "stardustxrIpfs";
effectScript = ''
readSecretString stardustxrDiscord .webhook > .webhook
readSecretString stardustxrIpfs .basicauth > .basicauth
set -x
export RESPONSE=$(curl -H @.basicauth -F file=@${self.packages."x86_64-linux".gnome-graphical-test}/screen.png https://ipfs-api.stardustxr.org/api/v0/add)
export CID=$(echo "$RESPONSE" | ${pkgs.jq}/bin/jq -r .Hash)
set +x
export ADDRESS="https://ipfs.stardustxr.org/ipfs/$CID"
${pkgs.discord-sh}/bin/discord.sh \
--description "\`stardustxr/server\` has been modified, here's how it renders \`weston-cliptest\` on \`flatland\` via \`monado-service\` inside of the \`gnome-graphical-test\`" \
--field "Ref;${ref}" \
--field "Commit ID;${rev}" \
--field "Flatland Revision;${flatland.rev}" \
--field "Reproducer;\`nix build github:stardustxr/server/${rev}#gnome-graphical-test\`" \
--image "$ADDRESS"
package = pkgs:
let
toolchain = fenix.packages.${pkgs.system}.minimal.toolchain;
in
(pkgs.makeRustPlatform {
cargo = toolchain;
rustc = toolchain;
}).buildRustPackage rec {
pname = "stardust-xr-${name}";
src = builtins.path {
name = "stardust-xr-source";
path = toString ./.;
filter = path: type:
nixpkgs.lib.all
(n: builtins.baseNameOf path != n)
[
"flake.nix"
"flake.lock"
"nix"
"README.md"
];
};
# ---- START package specific settings ----
version = "0.10.2";
cargoLock = {
lockFile = ./Cargo.lock;
allowBuiltinFetchGit = true;
};
postPatch = ''
sk=$(echo $cargoDepsCopy/stereokit-sys-*/StereoKit)
mkdir -p $sk/build/cpm
cp ${pkgs.fetchurl {
url = "https://github.com/cpm-cmake/CPM.cmake/releases/download/v0.32.2/CPM.cmake";
hash = "sha256-yDHlpqmpAE8CWiwJRoWyaqbuBAg0090G8WJIC2KLHp8=";
}} $sk/build/cpm/CPM_0.32.2.cmake
'';
CPM_SOURCE_CACHE = "./build";
nativeBuildInputs = with pkgs; [
cmake pkg-config llvmPackages.libcxxClang
];
buildInputs = with pkgs; [
openxr-loader libGL mesa xorg.libX11 fontconfig libxkbcommon
];
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
# ---- END package specific settings ----
};
in
{
overlays.default = final: prev: {
stardust-xr = (prev.stardust-xr or {}) // {
${name} = package final;
};
};
packages."x86_64-linux".default = package (pkgs "x86_64-linux");
packages."aarch64-linux".default = package (pkgs "aarch64-linux");
packages."x86_64-linux".gnome-graphical-test = self.checks.x86_64-linux.gnome-graphical-test;
packages."aarch64-linux".gnome-graphical-test = self.checks.aarch64-linux.gnome-graphical-test;
checks."x86_64-linux".gnome-graphical-test = (pkgs "x86_64-linux").nixosTest (import ./nix/gnome-graphical-test.nix { pkgs = (pkgs "x86_64-linux"); inherit self; });
checks."aarch64-linux".gnome-graphical-test = (pkgs "aarch64-linux").nixosTest (import ./nix/gnome-graphical-test.nix { pkgs = (pkgs "aarch64-linux"); inherit self; });
devShells."x86_64-linux".default = shell (pkgs "x86_64-linux");
devShells."aarch64-linux".default = shell (pkgs "aarch64-linux");
herculesCI.ciSystems = [ "x86_64-linux" ];
effects = let
pkgs = nixpkgs.legacyPackages.x86_64-linux;
hci-effects = hercules-ci-effects.lib.withPkgs pkgs;
in { branch, rev, ... }: {
gnome-graphical-test = hci-effects.mkEffect {
secretsMap."stardustxrDiscord" = "stardustxrDiscord";
secretsMap."stardustxrIpfs" = "stardustxrIpfs";
effectScript = ''
readSecretString stardustxrDiscord .webhook > .webhook
readSecretString stardustxrIpfs .basicauth > .basicauth
set -x
export RESPONSE=$(curl -H @.basicauth -F file=@${self.packages."x86_64-linux".gnome-graphical-test}/screen.png https://ipfs-api.stardustxr.org/api/v0/add)
export CID=$(echo "$RESPONSE" | ${pkgs.jq}/bin/jq -r .Hash)
set +x
export ADDRESS="https://ipfs.stardustxr.org/ipfs/$CID"
${pkgs.discord-sh}/bin/discord.sh \
--description "\`stardustxr/server\` has been modified, here's how it renders \`weston-cliptest\` on \`flatland\` via \`monado-service\` inside of the \`gnome-graphical-test\`" \
--field "Branch;${branch}" \
--field "Commit ID;${rev}" \
--field "Flatland Revision;${flatland.rev}" \
--field "Reproducer;\`nix build github:stardustxr/server/${rev}#gnome-graphical-test\`" \
--image "$ADDRESS"
'';
};
};
};

View File

@@ -1,40 +0,0 @@
{ rustPlatform
, src
, name
, openxr-loader
, libGL
, mesa
, xorg
, fontconfig
, libxkbcommon
, libclang
, cmake
, cpm-cmake
, pkg-config
, llvmPackages
}:
rustPlatform.buildRustPackage rec {
inherit src name;
cargoLock = {
lockFile = (src + "/Cargo.lock");
allowBuiltinFetchGit = true;
};
CPM_SOURCE_CACHE = "./build";
postPatch = ''
sk=$(echo $cargoDepsCopy/stereokit-sys-*/StereoKit)
mkdir -p $sk/build/cpm
# This is not ideal, the original approach was to fetch the exact cmake
# file version that was wanted from GitHub directly, but at least this way it comes from Nixpkgs.. so meh
cp ${cpm-cmake}/share/cpm/CPM.cmake $sk/build/cpm/CPM_0.32.2.cmake
'';
nativeBuildInputs = [
cmake pkg-config llvmPackages.libcxxClang
];
buildInputs = [
openxr-loader libGL mesa xorg.libX11 fontconfig libxkbcommon
];
LIBCLANG_PATH = "${libclang.lib}/lib";
}

View File

@@ -1,22 +1,23 @@
use super::{
client_state::{ClientState, CLIENT_STATES},
destroy_queue,
scenegraph::Scenegraph,
};
use super::scenegraph::Scenegraph;
#[cfg(feature = "oxr_runtime")]
use crate::openxr;
use crate::{
core::{registry::OwnedRegistry, task},
nodes::{audio, data, drawable, fields, hmd, input, items, root::Root, spatial, Node},
nodes::{
audio, data, drawable, fields, hmd, input, items,
root::Root,
spatial,
startup::{self, StartupSettings, STARTUP_SETTINGS},
Node,
},
};
use color_eyre::eyre::{eyre, Result};
use lazy_static::lazy_static;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use stardust_xr::{
messenger::{self, MessageSenderHandle},
schemas::flex::serialize,
};
use std::{fmt::Debug, fs, iter::FromIterator, path::PathBuf, sync::Arc};
use stardust_xr::messenger::{self, MessageSenderHandle};
use std::{fs, iter::FromIterator, path::PathBuf, sync::Arc};
use tokio::{net::UnixStream, task::JoinHandle};
use tracing::info;
@@ -35,7 +36,7 @@ lazy_static! {
scenegraph: Default::default(),
root: OnceCell::new(),
base_resource_prefixes: Default::default(),
state: Arc::new(ClientState::default()),
startup_settings: None,
});
}
@@ -47,13 +48,13 @@ pub fn get_env(pid: i32) -> Result<FxHashMap<String, String>, std::io::Error> {
.map(|(k, v)| (k.to_string(), v.to_string())),
))
}
pub fn state(env: &FxHashMap<String, String>) -> Option<Arc<ClientState>> {
pub fn startup_settings(env: &FxHashMap<String, String>) -> Option<StartupSettings> {
let token = env.get("STARDUST_STARTUP_TOKEN")?;
CLIENT_STATES.lock().get(token).cloned()
STARTUP_SETTINGS.lock().get(token).cloned()
}
pub struct Client {
pub pid: Option<i32>,
pid: Option<i32>,
// env: Option<FxHashMap<String, String>>,
exe: Option<PathBuf>,
dispatch_join_handle: OnceCell<JoinHandle<Result<()>>>,
@@ -64,7 +65,7 @@ pub struct Client {
pub scenegraph: Arc<Scenegraph>,
pub root: OnceCell<Arc<Root>>,
pub base_resource_prefixes: Mutex<Vec<PathBuf>>,
pub state: Arc<ClientState>,
pub startup_settings: Option<StartupSettings>,
}
impl Client {
pub fn from_connection(connection: UnixStream) -> Result<Arc<Self>> {
@@ -81,10 +82,7 @@ impl Client {
let (mut messenger_tx, mut messenger_rx) = messenger::create(connection);
let scenegraph = Arc::new(Scenegraph::default());
let state = env
.as_ref()
.and_then(state)
.unwrap_or_else(|| Arc::new(ClientState::default()));
let startup_settings = env.as_ref().and_then(startup_settings);
let client = CLIENTS.add(Client {
pid,
@@ -99,7 +97,7 @@ impl Client {
scenegraph: scenegraph.clone(),
root: OnceCell::new(),
base_resource_prefixes: Default::default(),
state,
startup_settings,
});
let _ = client.scenegraph.client.set(Arc::downgrade(&client));
let _ = client.root.set(Root::create(&client)?);
@@ -111,13 +109,9 @@ impl Client {
data::create_interface(&client)?;
items::create_interface(&client)?;
input::create_interface(&client)?;
client
.root
.get()
.unwrap()
.node
.send_remote_signal("restore_state", serialize(client.state.apply_to(&client))?)?;
startup::create_interface(&client)?;
#[cfg(feature = "openxr_runtime")]
openxr::create_interface(&client);
let pid_printable = pid
.map(|pid| pid.to_string())
@@ -174,27 +168,6 @@ impl Client {
Ok(client)
}
pub fn get_cmdline(&self) -> Option<Vec<String>> {
let pid = self.pid?;
let exe_proc_path = format!("/proc/{pid}/exe");
let cmdline_proc_path = format!("/proc/{pid}/cmdline");
let exe = std::fs::read_link(exe_proc_path).ok()?;
let cmdline = std::fs::read_to_string(cmdline_proc_path).ok()?;
let mut cmdline_split: Vec<_> = cmdline.split('\0').map(ToString::to_string).collect();
cmdline_split.pop();
*cmdline_split.get_mut(0).unwrap() = exe.to_str()?.to_string();
Some(cmdline_split)
}
pub fn get_cwd(&self) -> Option<PathBuf> {
let pid = self.pid?;
let cwd_proc_path = format!("/proc/{pid}/cwd");
std::fs::read_link(cwd_proc_path).ok()
}
pub async fn save_state(&self) -> Option<ClientState> {
let internal = self.root.get()?.save_state().await.ok()?;
Some(ClientState::from_deserialized(self, internal))
}
#[inline]
pub fn get_node(&self, name: &'static str, path: &str) -> Result<Arc<Node>> {
self.scenegraph
@@ -210,22 +183,7 @@ impl Client {
if let Some(flush_join_handle) = self.flush_join_handle.get() {
flush_join_handle.abort();
}
if let Some(client) = CLIENTS.remove(self) {
destroy_queue::add(client);
}
}
}
impl Debug for Client {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Client")
.field("pid", &self.pid)
.field("exe", &self.exe)
.field("dispatch_join_handle", &self.dispatch_join_handle)
.field("flush_join_handle", &self.flush_join_handle)
.field("disconnect_status", &self.disconnect_status)
.field("base_resource_prefixes", &self.base_resource_prefixes)
.field("state", &self.state)
.finish()
CLIENTS.remove(self);
}
}
impl Drop for Client {

View File

@@ -1,131 +0,0 @@
use super::client::{get_env, Client};
use crate::nodes::{spatial::Spatial, Node};
use glam::Mat4;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use std::{
path::{Path, PathBuf},
process::Command,
sync::Arc,
};
lazy_static::lazy_static! {
pub static ref CLIENT_STATES: Mutex<FxHashMap<String, Arc<ClientState>>> = Default::default();
}
#[derive(Debug, Serialize, Deserialize)]
pub struct LaunchInfo {
pub cmdline: Vec<String>,
pub cwd: PathBuf,
pub env: FxHashMap<String, String>,
}
impl LaunchInfo {
fn from_client(client: &Client) -> Option<Self> {
Some(LaunchInfo {
cmdline: client.get_cmdline()?,
cwd: client.get_cwd()?,
env: get_env(client.pid?).ok()?,
})
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ClientState {
pub launch_info: Option<LaunchInfo>,
pub data: Vec<u8>,
pub root: Mat4,
pub spatial_anchors: FxHashMap<String, Mat4>,
}
impl ClientState {
pub fn from_deserialized(client: &Client, state: ClientStateInternal) -> Self {
ClientState {
launch_info: LaunchInfo::from_client(client),
data: state.data.unwrap_or_default(),
root: Self::spatial_transform(client, &state.root.unwrap_or_default())
.unwrap_or_default(),
spatial_anchors: state
.spatial_anchors
.into_iter()
.filter_map(|(k, v)| Some((k, Self::spatial_transform(client, &v)?)))
.collect(),
}
}
fn spatial_transform(client: &Client, path: &str) -> Option<Mat4> {
let node = client.scenegraph.get_node(path)?;
let spatial = node.get_aspect::<Spatial>().ok()?;
Some(spatial.global_transform())
}
pub fn token(self) -> String {
let token = nanoid::nanoid!();
CLIENT_STATES.lock().insert(token.clone(), Arc::new(self));
token
}
pub fn from_file(file: &Path) -> Option<Self> {
let file_string = std::fs::read_to_string(file).ok()?;
toml::from_str(&file_string).ok()
}
pub fn to_file(self, directory: &Path) {
let app_name = self
.launch_info
.as_ref()
.map(|l| l.cmdline.get(0).unwrap().split('/').last().unwrap())
.unwrap_or("unknown");
let state_file_path = directory
.join(format!("{app_name}-{}", nanoid::nanoid!()))
.with_extension("toml");
std::fs::write(state_file_path, toml::to_string(&self).unwrap()).unwrap();
}
pub fn apply_to(&self, client: &Arc<Client>) -> ClientStateInternal {
if let Some(root) = client.root.get() {
root.set_transform(self.root)
}
ClientStateInternal {
data: Some(self.data.clone()),
root: Some("/".to_string()),
spatial_anchors: self
.spatial_anchors
.iter()
.map(|(k, v)| {
(k.clone(), {
let node = Node::create_parent_name(client, "/spatial/anchor", k, true)
.add_to_scenegraph()
.unwrap();
Spatial::add_to(&node, None, *v, false);
k.clone()
})
})
.collect(),
}
}
pub fn launch_command(self) -> Option<Command> {
let launch_info = self.launch_info.as_ref()?;
let mut cmdline = launch_info.cmdline.iter();
let mut command = Command::new(cmdline.next()?);
command.args(cmdline);
command.current_dir(&launch_info.cwd);
command.envs(launch_info.env.iter());
command.env("STARDUST_STARTUP_TOKEN", self.token());
Some(command)
}
}
impl Default for ClientState {
fn default() -> Self {
Self {
launch_info: None,
data: Default::default(),
root: Mat4::IDENTITY,
spatial_anchors: Default::default(),
}
}
}
#[derive(Default, Serialize, Deserialize)]
pub struct ClientStateInternal {
data: Option<Vec<u8>>,
root: Option<String>,
spatial_anchors: FxHashMap<String, String>,
}

View File

@@ -1,22 +1,12 @@
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use std::any::Any;
use tokio::sync::mpsc::{self, unbounded_channel};
static MAIN_DESTROY_QUEUE: Lazy<(
mpsc::UnboundedSender<Box<dyn Any + Send + Sync>>,
Mutex<mpsc::UnboundedReceiver<Box<dyn Any + Send + Sync>>>,
)> = Lazy::new(|| {
let (tx, rx) = unbounded_channel();
(tx, Mutex::new(rx))
});
static MAIN_DESTROY_QUEUE: Mutex<Vec<Box<dyn Any + Send + Sync>>> = Mutex::new(Vec::new());
pub fn add<T: Any + Sync + Send>(thing: T) {
MAIN_DESTROY_QUEUE.0.send(Box::new(thing)).unwrap();
MAIN_DESTROY_QUEUE.lock().push(Box::new(thing));
}
pub fn clear() {
while let Ok(thing) = MAIN_DESTROY_QUEUE.1.lock().try_recv() {
drop(thing)
}
MAIN_DESTROY_QUEUE.lock().clear();
}

View File

@@ -1,11 +0,0 @@
#[macro_export]
macro_rules! create_interface {
($iface:ident, $aspect:ident, $path:expr) => {
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
let node = Node::create_path(client, $path, false);
<$iface as $aspect>::add_node_members(&node);
node.add_to_scenegraph()?;
Ok(())
}
};
}

View File

@@ -1,11 +1,10 @@
pub mod client;
pub mod client_state;
pub mod delta;
pub mod destroy_queue;
pub mod eventloop;
pub mod idl_utils;
pub mod node_collections;
pub mod registry;
pub mod resource;
pub mod scenegraph;
pub mod task;
pub mod typed_datamap;

View File

@@ -7,32 +7,33 @@ use std::{
sync::{Arc, Weak},
};
// #[derive(Default)]
// pub struct LifeLinkedNodeList {
// nodes: Mutex<Vec<Weak<Node>>>,
// }
// impl LifeLinkedNodeList {
// pub fn add(&self, node: Weak<Node>) {
// self.nodes.lock().push(node);
// }
// pub fn clear(&self) {
// self.nodes
// .lock()
// .iter()
// .filter_map(|node| node.upgrade())
// .for_each(|node| {
// node.destroy();
// });
// self.nodes.lock().clear();
// }
// }
// impl Drop for LifeLinkedNodeList {
// fn drop(&mut self) {
// self.clear();
// }
// }
#[derive(Default)]
pub struct LifeLinkedNodeList {
nodes: Mutex<Vec<Weak<Node>>>,
}
impl LifeLinkedNodeList {
pub fn add(&self, node: Weak<Node>) {
self.nodes.lock().push(node);
}
#[derive(Default, Debug)]
pub fn clear(&self) {
self.nodes
.lock()
.iter()
.filter_map(|node| node.upgrade())
.for_each(|node| {
node.destroy();
});
self.nodes.lock().clear();
}
}
impl Drop for LifeLinkedNodeList {
fn drop(&mut self) {
self.clear();
}
}
#[derive(Default)]
pub struct LifeLinkedNodeMap<K: Hash + Eq> {
nodes: Mutex<FxHashMap<K, Weak<Node>>>,
}

View File

@@ -5,7 +5,6 @@ use rustc_hash::FxHashMap;
use std::ptr;
use std::sync::{Arc, Weak};
#[derive(Debug)]
pub struct Registry<T: Send + Sync + ?Sized>(Mutex<Option<FxHashMap<usize, Weak<T>>>>);
impl<T: Send + Sync + ?Sized> Registry<T> {
@@ -55,27 +54,12 @@ impl<T: Send + Sync + ?Sized> Registry<T> {
pub fn clear(&self) {
self.lock().clear();
}
pub fn is_empty(&self) -> bool {
let registry = self.0.lock();
let Some(registry) = &*registry else {
return true;
};
if registry.is_empty() {
return true;
}
registry.values().all(|v| v.strong_count() == 0)
}
}
impl<T: Send + Sync + ?Sized> Clone for Registry<T> {
fn clone(&self) -> Self {
Self(Mutex::new(self.0.lock().clone()))
}
}
impl<T: Send + Sync + ?Sized> Default for Registry<T> {
fn default() -> Self {
Self::new()
}
}
pub struct OwnedRegistry<T: Send + Sync + ?Sized>(Mutex<Option<FxHashMap<usize, Arc<T>>>>);
@@ -106,12 +90,9 @@ impl<T: Send + Sync + ?Sized> OwnedRegistry<T> {
self.lock()
.contains_key(&(ptr::addr_of!(*t) as *const () as usize))
}
pub fn remove(&self, t: &T) -> Option<Arc<T>>
where
T: Sized,
{
pub fn remove(&self, t: &T) {
self.lock()
.remove(&(ptr::addr_of!(*t) as *const () as usize))
.remove(&(ptr::addr_of!(*t) as *const () as usize));
}
pub fn clear(&self) {
self.lock().clear();

View File

@@ -1,46 +1,83 @@
use stardust_xr::values::ResourceID;
use color_eyre::eyre::eyre;
use serde::{de::Visitor, Deserialize};
use std::{ffi::OsStr, path::PathBuf};
use super::client::Client;
lazy_static::lazy_static! {
static ref THEMES: Vec<PathBuf> = std::env::var("STARDUST_THEMES").map(|s| s.split(":").map(PathBuf::from).collect()).unwrap_or_default();
#[derive(Debug)]
pub enum ResourceID {
File(PathBuf),
Namespaced { namespace: String, path: PathBuf },
}
fn has_extension(path: &PathBuf, extensions: &[&OsStr]) -> bool {
if let Some(path_extension) = path.extension() {
extensions.contains(&path_extension)
} else {
false
}
}
pub fn get_resource_file(
resource: &ResourceID,
client: &Client,
extensions: &[&OsStr],
) -> Option<PathBuf> {
match resource {
ResourceID::Direct(file) => {
(file.is_absolute() && file.exists() && has_extension(file, extensions))
.then_some(file.clone())
impl ResourceID {
pub fn get_file(&self, prefixes: &[PathBuf], extensions: &[&OsStr]) -> Option<PathBuf> {
match self {
ResourceID::File(file) => (file.is_absolute()
&& file.exists() && Self::has_extension(file, extensions))
.then_some(file.clone()),
ResourceID::Namespaced { namespace, path } => {
let file_name = path.file_name()?;
prefixes
.iter()
.filter_map(|prefix| {
let prefixed_path = prefix.clone().join(namespace).join(path);
let parent = prefixed_path.parent()?;
std::fs::read_dir(parent).ok()
})
.flatten()
.filter_map(|item| item.ok())
.map(|dir_entry| dir_entry.path())
.filter(|path| path.file_stem() == Some(file_name))
.find(|path| Self::has_extension(path, extensions))
}
}
ResourceID::Namespaced { namespace, path } => {
let file_name = path.file_name()?;
let base_prefixes = client.base_resource_prefixes.lock().clone();
THEMES
.iter()
.chain(base_prefixes.iter())
.filter_map(|prefix| {
let prefixed_path = prefix.clone().join(namespace).join(path);
let parent = prefixed_path.parent()?;
std::fs::read_dir(parent).ok()
})
.flatten()
.filter_map(|item| item.ok())
.map(|dir_entry| dir_entry.path())
.filter(|path| path.file_stem() == Some(file_name))
.find(|path| has_extension(path, extensions))
}
fn has_extension(path: &PathBuf, extensions: &[&OsStr]) -> bool {
if let Some(path_extension) = path.extension() {
extensions.contains(&path_extension)
} else {
false
}
}
}
impl<'de> Deserialize<'de> for ResourceID {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_any(ResourceVisitor)
}
}
struct ResourceVisitor;
impl<'de> Visitor<'de> for ResourceVisitor {
type Value = ResourceID;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("A string containing an absolute path to file or \"[namespace]:[path]\" for a namespaced resource")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(if v.starts_with('/') {
let path = PathBuf::from(v);
path.metadata().map_err(serde::de::Error::custom)?;
ResourceID::File(path)
} else if let Some((namespace, path)) = v.split_once(':') {
ResourceID::Namespaced {
namespace: namespace.to_string(),
path: PathBuf::from(path),
}
} else {
return Err(serde::de::Error::custom(eyre!("Invalid format for string")));
})
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_str(&v)
}
}

View File

@@ -1,25 +1,21 @@
use crate::nodes::alias::Alias;
use crate::nodes::Node;
use crate::{core::client::Client, nodes::Message};
use color_eyre::eyre::Result;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use portable_atomic::Ordering;
use rustc_hash::FxHashMap;
use serde::Serialize;
use stardust_xr::scenegraph;
use stardust_xr::scenegraph::ScenegraphError;
use stardust_xr::schemas::flex::serialize;
use std::future::Future;
use std::os::fd::OwnedFd;
use std::sync::{Arc, Weak};
use tokio::sync::oneshot;
use tracing::{debug, debug_span};
use tracing::{debug, debug_span, instrument};
use core::hash::BuildHasherDefault;
use dashmap::DashMap;
use rustc_hash::FxHasher;
#[derive(Default)]
pub struct Scenegraph {
pub(super) client: OnceCell<Weak<Client>>,
nodes: Mutex<FxHashMap<String, Arc<Node>>>,
nodes: DashMap<String, Arc<Node>, BuildHasherDefault<FxHasher>>,
}
impl Scenegraph {
@@ -35,62 +31,25 @@ impl Scenegraph {
pub fn add_node_raw(&self, node: Arc<Node>) {
debug!(node = ?&*node, "Add node");
let path = node.get_path().to_string();
self.nodes.lock().insert(path, node);
self.nodes.insert(path, node);
}
#[instrument(level = "debug", skip(self))]
pub fn get_node(&self, path: &str) -> Option<Arc<Node>> {
let mut node = self.nodes.lock().get(path)?.clone();
while let Ok(alias) = node.get_aspect::<Alias>() {
if alias.enabled.load(Ordering::Acquire) {
node = alias.original.upgrade()?;
} else {
return None;
}
let mut node = self.nodes.get(path)?.clone();
while let Some(alias) = node.alias.get() {
node = alias.original.upgrade()?;
}
Some(node)
}
pub fn remove_node(&self, path: &str) -> Option<Arc<Node>> {
debug!(path, "Remove node");
self.nodes.lock().remove(path)
let (_, node) = self.nodes.remove(path)?;
Some(node)
}
}
pub struct MethodResponseSender(oneshot::Sender<Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError>>);
impl MethodResponseSender {
pub fn send(self, t: Result<Message, ScenegraphError>) {
let _ = self.0.send(t.map(|m| (m.data, m.fds)));
}
// pub fn send_method_return<T: Serialize>(
// self,
// result: color_eyre::eyre::Result<(T, Vec<OwnedFd>)>,
// ) {
// let _ = self.0.send(map_method_return(result));
// }
pub fn wrap_sync<F: FnOnce() -> color_eyre::eyre::Result<Message>>(self, f: F) {
self.send(f().map_err(|e| ScenegraphError::MethodError {
error: e.to_string(),
}))
}
pub fn wrap_async<T: Serialize>(
self,
f: impl Future<Output = color_eyre::eyre::Result<(T, Vec<OwnedFd>)>> + Send + 'static,
) {
tokio::task::spawn(async move { self.0.send(map_method_return(f.await)) });
}
}
fn map_method_return<T: Serialize>(
result: color_eyre::eyre::Result<(T, Vec<OwnedFd>)>,
) -> Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError> {
let (value, fds) = result.map_err(|e| ScenegraphError::MethodError {
error: e.to_string(),
})?;
let serialized_value = serialize(value).map_err(|e| ScenegraphError::MethodError {
error: format!("Internal: Serialization failed: {e}"),
})?;
Ok((serialized_value, fds))
}
impl scenegraph::Scenegraph for Scenegraph {
fn send_signal(
&self,
@@ -99,9 +58,7 @@ impl scenegraph::Scenegraph for Scenegraph {
data: &[u8],
fds: Vec<OwnedFd>,
) -> Result<(), ScenegraphError> {
let Some(client) = self.get_client() else {
return Err(ScenegraphError::SignalNotFound);
};
let Some(client) = self.get_client() else {return Err(ScenegraphError::SignalNotFound)};
debug_span!("Handle signal", path, method).in_scope(|| {
self.get_node(path)
.ok_or(ScenegraphError::NodeNotFound)?
@@ -121,25 +78,21 @@ impl scenegraph::Scenegraph for Scenegraph {
method: &str,
data: &[u8],
fds: Vec<OwnedFd>,
response: oneshot::Sender<Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError>>,
) {
let Some(client) = self.get_client() else {
let _ = response.send(Err(ScenegraphError::MethodNotFound));
return;
};
debug!(path, method, "Handle method");
let Some(node) = self.get_node(path) else {
let _ = response.send(Err(ScenegraphError::NodeNotFound));
return;
};
node.execute_local_method(
client,
method,
Message {
data: data.to_vec(),
fds,
},
MethodResponseSender(response),
);
) -> Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError> {
let Some(client) = self.get_client() else {return Err(ScenegraphError::MethodNotFound)};
debug_span!("Handle method", path, method).in_scope(|| {
let message = self
.get_node(path)
.ok_or(ScenegraphError::NodeNotFound)?
.execute_local_method(
client,
method,
Message {
data: data.to_vec(),
fds,
},
)?;
Ok((message.data, message.fds))
})
}
}

View File

@@ -1,8 +1,10 @@
use color_eyre::eyre::Result;
use std::future::Future;
use tokio::task::JoinHandle;
use tracing::instrument;
#[allow(unused_variables)]
#[instrument(level = "debug", skip_all)]
pub fn new<
F: FnOnce() -> S,
S: AsRef<str>,

56
src/core/typed_datamap.rs Normal file
View File

@@ -0,0 +1,56 @@
#![allow(unused)]
use std::ops::{Deref, DerefMut};
use color_eyre::eyre::Result;
use once_cell::sync::Lazy;
use serde::{de::DeserializeOwned, Serialize};
use stardust_xr::schemas::{
flat::Datamap,
flex::flexbuffers::{FlexbufferSerializer, Reader, ReaderError},
};
use crate::nodes::Message;
pub struct TypedDatamap<T: DeserializeOwned + Serialize>(T);
impl<T: DeserializeOwned + Serialize> TypedDatamap<T> {
pub fn new(data: T) -> Self {
TypedDatamap(data)
}
pub fn from_flex(message: Message) -> Result<Self> {
let root = Reader::get_root(message.as_ref())?;
T::deserialize(root).map(Self::new).map_err(|e| e.into())
}
pub fn to_datamap(&mut self) -> Result<Datamap> {
let mut serializer = FlexbufferSerializer::default();
self.0.serialize(&mut serializer)?;
Datamap::new(serializer.take_buffer()).map_err(|e| e.into())
}
pub fn serialize(&mut self) -> Option<Vec<u8>> {
let mut serializer = FlexbufferSerializer::default();
self.0.serialize(&mut serializer).ok()?;
// check if this is actually a map
Reader::get_root(serializer.view()).ok()?.get_map().ok()?;
Some(serializer.take_buffer())
}
}
impl<T: DeserializeOwned + Serialize> Default for TypedDatamap<T>
where
T: Default,
{
fn default() -> Self {
Self(T::default())
}
}
impl<T: DeserializeOwned + Serialize> Deref for TypedDatamap<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: DeserializeOwned + Serialize> DerefMut for TypedDatamap<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

View File

@@ -1,29 +1,26 @@
mod core;
mod nodes;
mod objects;
#[cfg(feature = "openxr_runtime")]
mod openxr;
#[cfg(feature = "wayland")]
mod wayland;
use crate::core::client::CLIENTS;
use crate::core::destroy_queue;
use crate::nodes::items::camera;
use crate::nodes::{audio, drawable, hmd, input};
use crate::objects::input::eye_pointer::EyePointer;
use crate::objects::input::mouse_pointer::MousePointer;
use crate::objects::input::sk_controller::SkController;
use crate::objects::input::sk_hand::SkHand;
use crate::objects::play_space::PlaySpace;
use crate::wayland::X_DISPLAY;
use self::core::eventloop::EventLoop;
use clap::Parser;
use core::client_state::ClientState;
use directories::ProjectDirs;
use once_cell::sync::OnceCell;
use stardust_xr::server;
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Stdio};
use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::sync::Arc;
use std::time::Duration;
use stereokit::{
@@ -31,13 +28,13 @@ use stereokit::{
TextureFormat, TextureType,
};
use stereokit::{DisplayBlend, Sk};
use tokio::sync::Notify;
use tokio::task::LocalSet;
use tokio::{runtime::Handle, sync::oneshot};
use tracing::metadata::LevelFilter;
use tracing::{debug_span, error, info};
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
pub static SK_INFO: OnceCell<SystemInfo> = OnceCell::new();
#[derive(Parser)]
#[clap(author, version, about, long_about = None)]
struct CliArgs {
@@ -56,15 +53,10 @@ struct CliArgs {
/// Run a script when ready for clients to connect. If this is not set the script at $HOME/.config/stardust/startup will be ran if it exists.
#[clap(id = "PATH", short = 'e', long = "execute-startup-script", action)]
startup_script: Option<PathBuf>,
/// Restore the session with the given ID (or `latest`), ignoring the startup script. Sessions are stored in directories at `~/.local/state/stardust/`.
#[clap(id = "SESSION_ID", long = "restore", action)]
restore: Option<String>,
}
static STARDUST_INSTANCE: OnceCell<String> = OnceCell::new();
static SK_MULTITHREAD: OnceCell<Sk> = OnceCell::new();
static STOP_NOTIFIER: Notify = Notify::const_new();
struct EventLoopInfo {
tokio_handle: Handle,
@@ -72,17 +64,13 @@ struct EventLoopInfo {
}
fn main() {
ctrlc::set_handler(|| {
if atty::isnt(atty::Stream::Stdout) {
STOP_NOTIFIER.notify_waiters()
}
})
.unwrap();
let registry = tracing_subscriber::registry();
#[cfg(feature = "profile_app")]
let registry = registry.with(tracing_tracy::TracyLayer::new().with_filter(LevelFilter::DEBUG));
let (chrome_layer, _guard) = tracing_chrome::ChromeLayerBuilder::new()
.include_args(true)
.build();
#[cfg(feature = "profile_app")]
let registry = registry.with(chrome_layer);
#[cfg(feature = "profile_tokio")]
let (console_layer, _) = console_subscriber::ConsoleLayer::builder().build();
@@ -123,7 +111,6 @@ fn main() {
overlay_app: cli_args.overlay_priority.is_some(),
overlay_priority: cli_args.overlay_priority.unwrap_or(u32::MAX),
disable_desktop_input_window: true,
render_scaling: 2.0,
..Default::default()
}
.init()
@@ -131,7 +118,7 @@ fn main() {
let _ = SK_MULTITHREAD.set(sk.multithreaded());
info!("Init StereoKit");
sk.render_set_multisample(0);
SK_INFO.set(stereokit.system_info()).unwrap();
sk.material_set_shader(
sk.material_find("default/material_pbr").unwrap(),
@@ -182,11 +169,10 @@ fn main() {
left.zip(right)
})
.flatten();
let eye_pointer = (sk.active_display_mode() == DisplayMode::MixedReality
&& sk.device_has_eye_gaze())
.then(EyePointer::new)
.transpose()
.unwrap();
let eye_pointer = (!cli_args.flatscreen && sk.device_has_eye_gaze())
.then(EyePointer::new)
.transpose()
.unwrap();
if hands.is_none() {
sk.input_hand_visible(Handed::Left, false);
@@ -198,54 +184,78 @@ fn main() {
.then(|| PlaySpace::new().ok())
.flatten();
let (event_stop_tx, event_stop_rx) = oneshot::channel::<()>();
let (info_sender, info_receiver) = oneshot::channel::<EventLoopInfo>();
let event_thread = std::thread::Builder::new()
.name("event_loop".to_owned())
.spawn({
let project_dirs = project_dirs.clone();
move || event_loop(info_sender, project_dirs.clone())
})
.spawn(move || event_loop(info_sender, event_stop_rx))
.unwrap();
let event_loop_info = info_receiver.blocking_recv().unwrap();
let _tokio_handle = event_loop_info.tokio_handle.enter();
#[cfg(feature = "wayland")]
let mut wayland = wayland::Wayland::new().expect("Could not initialize wayland");
let mut wayland = Some(wayland::Wayland::new().expect("Could not initialize wayland"));
info!("Stardust ready!");
let mut startup_children = project_dirs
.as_ref()
.map(|project_dirs| {
launch_start(
&cli_args,
project_dirs,
&event_loop_info,
#[cfg(feature = "wayland")]
&wayland,
)
})
.unwrap_or_default();
let mut startup_child = if let Some(project_dirs) = project_dirs.as_ref() {
let startup_script_path = cli_args
.startup_script
.clone()
.and_then(|p| p.canonicalize().ok())
.unwrap_or_else(|| project_dirs.config_dir().join("startup"));
let mut startup_command = Command::new(startup_script_path);
startup_command.stdin(Stdio::null());
startup_command.env(
"FLAT_WAYLAND_DISPLAY",
std::env::var_os("WAYLAND_DISPLAY").unwrap_or_default(),
);
startup_command.env(
"STARDUST_INSTANCE",
event_loop_info
.socket_path
.file_name()
.expect("Stardust socket path not found"),
);
#[cfg(feature = "wayland")]
{
startup_command.env("WAYLAND_DISPLAY", &wayland.as_ref().unwrap().socket_name);
#[cfg(feature = "xwayland")]
startup_command.env(
"DISPLAY",
format!(":{}", wayland.as_ref().unwrap().xwayland_state.display),
);
startup_command.env("GDK_BACKEND", "wayland");
startup_command.env("QT_QPA_PLATFORM", "wayland");
startup_command.env("MOZ_ENABLE_WAYLAND", "1");
startup_command.env("CLUTTER_BACKEND", "wayland");
startup_command.env("SDL_VIDEODRIVER", "wayland");
}
startup_command.spawn().ok()
} else {
None
};
let mut last_frame_delta = Duration::ZERO;
let mut sleep_duration = Duration::ZERO;
debug_span!("StereoKit").in_scope(|| {
sk.run(
|sk| {
sk.run_stateful(
&mut wayland,
move |wayland, _, sk| {
let _span = debug_span!("StereoKit step");
let _span = _span.enter();
hmd::frame(sk);
camera::update(sk);
#[cfg(feature = "wayland")]
wayland.frame_event(sk);
wayland.as_mut().unwrap().frame_event(sk);
destroy_queue::clear();
if let Some(mouse_pointer) = &mut mouse_pointer {
mouse_pointer.update(sk);
}
if let Some((left_hand, right_hand)) = &mut hands {
left_hand.update(!cli_args.disable_controller, sk);
right_hand.update(!cli_args.disable_controller, sk);
left_hand.update(sk);
right_hand.update(sk);
}
if let Some((left_controller, right_controller)) = &mut controllers {
left_controller.update(sk);
@@ -267,29 +277,32 @@ fn main() {
);
#[cfg(feature = "wayland")]
wayland.update(sk);
wayland.as_mut().unwrap().update(sk);
drawable::draw(sk);
audio::update(sk);
#[cfg(feature = "wayland")]
wayland.make_context_current();
wayland.as_mut().unwrap().make_context_current();
},
|_sk| {
|wayland, _sk| {
info!("Cleanly shut down StereoKit");
if let Some(mut startup_child) = startup_child.take() {
let _ = startup_child.kill();
}
#[cfg(feature = "wayland")]
wayland.take();
},
)
});
#[cfg(feature = "wayland")]
drop(wayland);
STOP_NOTIFIER.notify_waiters();
let _ = event_stop_tx.send(());
event_thread
.join()
.expect("Failed to cleanly shut down event loop")
.unwrap();
for mut startup_child in startup_children.drain(..) {
let _ = startup_child.kill();
}
// #[cfg(feature = "wayland")]
// let _wayland = ManuallyDrop::new(wayland);
info!("Cleanly shut down Stardust");
}
@@ -317,11 +330,11 @@ fn adaptive_sleep(
});
}
// #[tokio::main]
#[tokio::main(flavor = "current_thread")]
#[tokio::main]
// #[tokio::main(flavor = "current_thread")]
async fn event_loop(
info_sender: oneshot::Sender<EventLoopInfo>,
project_dirs: Option<ProjectDirs>,
stop_rx: oneshot::Receiver<()>,
) -> color_eyre::eyre::Result<()> {
let socket_path =
server::get_free_socket_path().expect("Unable to find a free stardust socket path");
@@ -337,10 +350,14 @@ async fn event_loop(
socket_path,
});
STOP_NOTIFIER.notified().await;
println!("Stopping...");
if let Some(project_dirs) = project_dirs {
save_session(&project_dirs).await;
if atty::is(atty::Stream::Stdin) {
stop_rx.await?;
} else {
tokio::select! {
biased;
_ = tokio::signal::ctrl_c() => (),
_ = stop_rx => (),
};
}
info!("Cleanly shut down event loop");
@@ -351,122 +368,3 @@ async fn event_loop(
Ok(())
}
fn launch_start(
cli_args: &CliArgs,
project_dirs: &ProjectDirs,
event_loop_info: &EventLoopInfo,
#[cfg(feature = "wayland")] wayland: &wayland::Wayland,
) -> Vec<Child> {
if let Some(session_id) = &cli_args.restore {
let session_dir = project_dirs.state_dir().unwrap().join(session_id);
return restore_session(&session_dir, event_loop_info, wayland);
}
let startup_script_path = cli_args
.startup_script
.clone()
.and_then(|p| p.canonicalize().ok())
.unwrap_or_else(|| project_dirs.config_dir().join("startup"));
run_script(&startup_script_path, event_loop_info, wayland)
}
fn restore_session(
session_dir: &Path,
event_loop_info: &EventLoopInfo,
#[cfg(feature = "wayland")] wayland: &wayland::Wayland,
) -> Vec<Child> {
let Ok(clients) = session_dir.read_dir() else {
return Vec::new();
};
clients
.filter_map(Result::ok)
.filter_map(|c| ClientState::from_file(&c.path()))
.filter_map(ClientState::launch_command)
.filter_map(|startup_command| {
run_client(
startup_command,
event_loop_info,
#[cfg(feature = "wayland")]
wayland,
)
})
.collect()
}
fn run_script(
script_path: &Path,
event_loop_info: &EventLoopInfo,
#[cfg(feature = "wayland")] wayland: &wayland::Wayland,
) -> Vec<Child> {
let _ = std::fs::set_permissions(script_path, std::fs::Permissions::from_mode(0o755));
let startup_command = Command::new(script_path);
run_client(
startup_command,
event_loop_info,
#[cfg(feature = "wayland")]
wayland,
)
.map(|c| vec![c])
.unwrap_or_default()
}
fn run_client(
mut command: Command,
event_loop_info: &EventLoopInfo,
#[cfg(feature = "wayland")] wayland: &wayland::Wayland,
) -> Option<Child> {
command.stdin(Stdio::null());
command.stdout(Stdio::null());
command.stderr(Stdio::null());
command.env(
"FLAT_WAYLAND_DISPLAY",
std::env::var_os("WAYLAND_DISPLAY").unwrap_or_default(),
);
command.env(
"STARDUST_INSTANCE",
event_loop_info
.socket_path
.file_name()
.expect("Stardust socket path not found"),
);
#[cfg(feature = "wayland")]
{
if let Some(wayland_socket) = wayland.socket_name.as_ref() {
command.env("WAYLAND_DISPLAY", &wayland_socket);
}
command.env(
"DISPLAY",
format!(":{}", X_DISPLAY.get().cloned().unwrap_or_default()),
);
command.env("GDK_BACKEND", "wayland");
command.env("QT_QPA_PLATFORM", "wayland");
command.env("MOZ_ENABLE_WAYLAND", "1");
command.env("CLUTTER_BACKEND", "wayland");
command.env("SDL_VIDEODRIVER", "wayland");
}
let child = command.spawn().ok()?;
Some(child)
}
async fn save_session(project_dirs: &ProjectDirs) {
let session_id = nanoid::nanoid!();
let state_dir = project_dirs.state_dir().unwrap();
let session_dir = state_dir.join(&session_id);
std::fs::create_dir_all(&session_dir).unwrap();
let _ = std::fs::remove_dir_all(state_dir.join("latest"));
std::os::unix::fs::symlink(&session_dir, state_dir.join("latest")).unwrap();
let local_set = LocalSet::new();
for client in CLIENTS.get_vec() {
let session_dir = session_dir.clone();
local_set.spawn_local(async move {
tokio::select! {
biased;
s = client.save_state() => {s.map(|s| s.to_file(&session_dir));},
_ = tokio::time::sleep(Duration::from_millis(100)) => (),
}
});
}
local_set.await;
println!("Session ID for restore is {session_id}");
}

View File

@@ -1,7 +1,6 @@
use super::{Aspect, Node};
use super::Node;
use crate::core::client::Client;
use color_eyre::eyre::{ensure, Result};
use portable_atomic::AtomicBool;
use std::sync::{Arc, Weak};
#[derive(Debug, Default, Clone)]
@@ -13,7 +12,6 @@ pub struct AliasInfo {
#[allow(dead_code)]
pub struct Alias {
pub enabled: Arc<AtomicBool>,
pub(super) node: Weak<Node>,
pub original: Weak<Node>,
@@ -35,18 +33,14 @@ impl Alias {
"Node already exists"
);
let node = Node::create_parent_name(client, parent, name, true).add_to_scenegraph()?;
let node = Node::create(client, parent, name, true).add_to_scenegraph()?;
let alias = Alias {
enabled: Arc::new(AtomicBool::new(true)),
node: Arc::downgrade(&node),
original: Arc::downgrade(original),
info,
};
let alias = original.aliases.add(alias);
node.add_aspect_raw(alias);
let _ = node.alias.set(alias);
Ok(node)
}
}
impl Aspect for Alias {
const NAME: &'static str = "Alias";
}

View File

@@ -1,25 +1,23 @@
use super::{Aspect, Node};
use super::{Message, Node};
use crate::core::client::Client;
use crate::core::destroy_queue;
use crate::core::registry::Registry;
use crate::core::resource::get_resource_file;
use crate::create_interface;
use crate::nodes::spatial::{Spatial, Transform};
use color_eyre::eyre::{eyre, Result};
use crate::core::resource::ResourceID;
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
use color_eyre::eyre::{ensure, eyre, Result};
use glam::{vec3, Vec4Swizzles};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use send_wrapper::SendWrapper;
use stardust_xr::values::ResourceID;
use serde::Deserialize;
use stardust_xr::schemas::flex::deserialize;
use stardust_xr::values::Transform;
use std::ops::DerefMut;
use std::sync::Arc;
use std::{ffi::OsStr, path::PathBuf};
use std::{ffi::OsStr, path::PathBuf, sync::Arc};
use stereokit::{Sound as SkSound, SoundInstance, StereoKitDraw};
static SOUND_REGISTRY: Registry<Sound> = Registry::new();
stardust_xr_server_codegen::codegen_audio_protocol!();
pub struct Sound {
space: Arc<Spatial>,
@@ -30,16 +28,26 @@ pub struct Sound {
stop: Mutex<Option<()>>,
play: Mutex<Option<()>>,
}
impl Sound {
pub fn add_to(node: &Arc<Node>, resource_id: ResourceID) -> Result<Arc<Sound>> {
let pending_audio_path = get_resource_file(
&resource_id,
&*node.get_client().ok_or_else(|| eyre!("Client not found"))?,
&[OsStr::new("wav"), OsStr::new("mp3")],
)
.ok_or_else(|| eyre!("Resource not found"))?;
ensure!(
node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!"
);
let pending_audio_path = resource_id
.get_file(
&node
.get_client()
.ok_or_else(|| eyre!("Client not found"))?
.base_resource_prefixes
.lock()
.clone(),
&[OsStr::new("wav"), OsStr::new("mp3")],
)
.ok_or_else(|| eyre!("Resource not found"))?;
let sound = Sound {
space: node.get_aspect::<Spatial>().unwrap().clone(),
space: node.spatial.get().unwrap().clone(),
volume: 1.0,
pending_audio_path,
sk_sound: OnceCell::new(),
@@ -48,8 +56,9 @@ impl Sound {
play: Mutex::new(None),
};
let sound_arc = SOUND_REGISTRY.add(sound);
node.add_aspect_raw(sound_arc.clone());
<Sound as SoundAspect>::add_node_members(node);
node.add_local_signal("play", Sound::play_flex);
node.add_local_signal("stop", Sound::stop_flex);
let _ = node.sound.set(sound_arc.clone());
Ok(sound_arc)
}
@@ -62,7 +71,7 @@ impl Sound {
sk.sound_inst_stop(instance);
}
}
if self.instance.lock().is_none() && self.play.lock().take().is_some() {
if self.play.lock().is_some() && self.instance.lock().is_none() {
self.instance.lock().replace(sk.sound_play(
sound.as_ref(),
vec3(0.0, 0.0, 0.0),
@@ -73,30 +82,19 @@ impl Sound {
sk.sound_inst_set_pos(*instance, self.space.global_transform().w_axis.xyz());
}
}
}
impl Aspect for Sound {
const NAME: &'static str = "Sound";
}
impl SoundAspect for Sound {
fn play(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
let sound = node.get_aspect::<Sound>().unwrap();
fn play_flex(node: &Node, _calling_client: Arc<Client>, _message: Message) -> Result<()> {
let sound = node.sound.get().unwrap();
sound.play.lock().replace(());
Ok(())
}
fn stop(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
let sound = node.get_aspect::<Sound>().unwrap();
pub fn stop_flex(node: &Node, _calling_client: Arc<Client>, _message: Message) -> Result<()> {
let sound = node.sound.get().unwrap();
sound.stop.lock().replace(());
Ok(())
}
}
impl Drop for Sound {
fn drop(&mut self) {
if let Some(sk_sound) = self.sk_sound.take() {
destroy_queue::add(sk_sound);
}
SOUND_REGISTRY.remove(self);
}
}
pub fn update(sk: &impl StereoKitDraw) {
for sound in SOUND_REGISTRY.get_valid_contents() {
@@ -104,25 +102,35 @@ pub fn update(sk: &impl StereoKitDraw) {
}
}
create_interface!(AudioInterface, AudioInterfaceAspect, "/audio");
struct AudioInterface;
impl AudioInterfaceAspect for AudioInterface {
#[doc = "Create a sound node. WAV and MP3 are supported."]
fn create_sound(
_node: Arc<Node>,
calling_client: Arc<Client>,
name: String,
parent: Arc<Node>,
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
let node = Node::create(client, "", "audio", false);
node.add_local_signal("create_sound", create_flex);
node.add_to_scenegraph().map(|_| ())
}
pub fn create_flex(_node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
#[derive(Deserialize)]
struct CreateSoundInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
resource: ResourceID,
) -> Result<()> {
let node =
Node::create_parent_name(&calling_client, Self::CREATE_SOUND_PARENT_PATH, &name, true);
let parent = parent.get_aspect::<Spatial>()?;
let transform = transform.to_mat4(true, true, true);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, false);
Sound::add_to(&node, resource)?;
Ok(())
}
let info: CreateSoundInfo = deserialize(message.as_ref())?;
let node = Node::create(&calling_client, "/audio/sound", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, true);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
Sound::add_to(&node, info.resource)?;
Ok(())
}
impl Drop for Sound {
fn drop(&mut self) {
if let Some(instance) = self.instance.lock().take() {
destroy_queue::add(instance);
}
SOUND_REGISTRY.remove(self);
}
}

View File

@@ -1,50 +1,30 @@
use super::alias::AliasInfo;
use super::fields::Field;
use super::spatial::{parse_transform, Spatial};
use super::{Alias, Aspect, Node};
use super::{Alias, Message, Node};
use crate::core::client::Client;
use crate::core::node_collections::LifeLinkedNodeMap;
use crate::core::registry::Registry;
use crate::create_interface;
use crate::nodes::fields::FIELD_ALIAS_INFO;
use crate::nodes::spatial::Transform;
use color_eyre::eyre::{bail, ensure, eyre, Result};
use lazy_static::lazy_static;
use crate::nodes::fields::{find_field, FIELD_ALIAS_INFO};
use crate::nodes::spatial::find_spatial_parent;
use color_eyre::eyre::{ensure, eyre, Result};
use glam::vec3a;
use mint::{Quaternion, Vector3};
use nanoid::nanoid;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use stardust_xr::schemas::flex::flexbuffers;
use stardust_xr::values::Datamap;
use serde::{Deserialize, Serialize};
use stardust_xr::schemas::flex::{deserialize, flexbuffers, serialize};
use stardust_xr::values::Transform;
use std::sync::{Arc, Weak};
lazy_static! {
pub static ref KEYMAPS: Mutex<FxHashMap<String, String>> = Mutex::new(FxHashMap::default());
}
static PULSE_SENDER_REGISTRY: Registry<PulseSender> = Registry::new();
pub static PULSE_RECEIVER_REGISTRY: Registry<PulseReceiver> = Registry::new();
pub fn get_mask(datamap: &Datamap) -> Result<flexbuffers::MapReader<&[u8]>> {
flexbuffers::Reader::get_root(datamap.raw().as_slice())
.map_err(|_| eyre!("Mask is not a valid flexbuffer"))?
.get_map()
.map_err(|_| eyre!("Mask is not a valid map"))
}
pub fn mask_matches(mask_map_lesser: &Datamap, mask_map_greater: &Datamap) -> bool {
pub fn mask_matches(mask_map_lesser: &Mask, mask_map_greater: &Mask) -> bool {
(|| -> Result<_> {
for key in get_mask(mask_map_lesser)?.iter_keys() {
let lesser_key = get_mask(mask_map_lesser)?.index(key)?;
let greater_key = get_mask(mask_map_greater)?.index(key)?;
// otherwise zero-length vectors don't count the same as a single type vector
if lesser_key.flexbuffer_type().is_heterogenous_vector()
&& lesser_key.as_vector().len() == 0
&& greater_key.flexbuffer_type().is_vector()
{
continue;
}
if !lesser_key.flexbuffer_type().is_null()
&& lesser_key.flexbuffer_type() != greater_key.flexbuffer_type()
{
for key in mask_map_lesser.get_mask()?.iter_keys() {
let lesser_key = mask_map_lesser.get_mask()?.index(key)?;
let greater_key = mask_map_greater.get_mask()?.index(key)?;
if lesser_key.flexbuffer_type() != greater_key.flexbuffer_type() {
return Err(flexbuffers::ReaderError::InvalidPackedType {}.into());
}
}
@@ -53,24 +33,49 @@ pub fn mask_matches(mask_map_lesser: &Datamap, mask_map_greater: &Datamap) -> bo
.is_ok()
}
stardust_xr_server_codegen::codegen_data_protocol!();
pub struct Mask(pub Vec<u8>);
impl Mask {
pub fn from_struct<T: Default + Serialize>() -> Self {
let mut serializer = flexbuffers::FlexbufferSerializer::new();
T::default().serialize(&mut serializer).unwrap();
Mask(serializer.take_buffer())
}
pub fn get_mask(&self) -> Result<flexbuffers::MapReader<&[u8]>> {
flexbuffers::Reader::get_root(self.0.as_slice())
.map_err(|_| eyre!("Mask is not a valid flexbuffer"))?
.get_map()
.map_err(|_| eyre!("Mask is not a valid map"))
}
}
#[derive(Serialize, Deserialize)]
struct SendDataInfo<'a> {
uid: &'a str,
data: Vec<u8>,
}
pub struct PulseSender {
uid: String,
node: Weak<Node>,
pub mask: Datamap,
pub mask: Mask,
aliases: LifeLinkedNodeMap<String>,
}
impl PulseSender {
pub fn add_to(node: &Arc<Node>, mask: Datamap) -> Result<Arc<PulseSender>> {
pub fn add_to(node: &Arc<Node>, mask: Mask) -> Result<Arc<PulseSender>> {
ensure!(
node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!"
);
let sender = PulseSender {
uid: nanoid!(),
node: Arc::downgrade(node),
mask,
aliases: LifeLinkedNodeMap::default(),
};
// <PulseSender as PulseSenderAspect>::add_node_members(node);
let sender = PULSE_SENDER_REGISTRY.add(sender);
node.add_aspect_raw(sender.clone());
let _ = node.pulse_sender.set(sender.clone());
node.add_local_signal("send_data", PulseSender::send_data_flex);
for receiver in PULSE_RECEIVER_REGISTRY.get_valid_contents() {
sender.handle_new_receiver(&receiver);
}
@@ -80,61 +85,95 @@ impl PulseSender {
if !mask_matches(&self.mask, &receiver.mask) {
return;
}
let Some(tx_node) = self.node.upgrade() else {
return;
};
let Some(tx_client) = tx_node.get_client() else {
return;
};
let Some(rx_node) = receiver.node.upgrade() else {
return;
};
let Some(tx_node) = self.node.upgrade() else {return};
let Some(tx_client) = tx_node.get_client() else {return};
let Some(rx_node) = receiver.node.upgrade() else {return};
// Receiver itself
let Ok(rx_alias) = Alias::create(
let rx_alias = Alias::create(
&tx_client,
tx_node.get_path(),
receiver.uid.as_str(),
&rx_node,
AliasInfo {
server_methods: vec!["send_data"],
server_methods: vec!["sendData", "getTransform"],
..Default::default()
},
) else {
return;
};
self.aliases.add(receiver.uid.clone(), &rx_alias);
);
if let Ok(rx_alias) = rx_alias {
self.aliases.add(receiver.uid.clone(), &rx_alias);
// Receiver's field
let Ok(rx_field_alias) = Alias::create(
&tx_client,
rx_alias.get_path(),
"field",
&rx_node.get_aspect::<PulseReceiver>().unwrap().field_node,
FIELD_ALIAS_INFO.clone(),
) else {
return;
};
self.aliases
.add(receiver.uid.clone() + "-field", &rx_field_alias);
if let Some(rx_field_node) = receiver.field.spatial_ref().node.upgrade() {
// Receiver's field
let rx_field_alias = Alias::create(
&tx_client,
rx_alias.get_path(),
"field",
&rx_field_node,
FIELD_ALIAS_INFO.clone(),
);
if let Ok(rx_field_alias) = rx_field_alias {
self.aliases
.add(receiver.uid.clone() + "-field", &rx_field_alias);
}
}
}
let _ =
pulse_sender_client::new_receiver(&tx_node, &receiver.uid, &rx_alias, &rx_field_alias);
#[derive(Serialize)]
struct NewReceiverInfo<'a> {
uid: &'a str,
distance: f32,
position: Vector3<f32>,
rotation: Quaternion<f32>,
}
let (_, rotation, position) = Spatial::space_to_space_matrix(
rx_node.spatial.get().map(|s| s.as_ref()),
tx_node.spatial.get().map(|s| s.as_ref()),
)
.to_scale_rotation_translation();
let info = NewReceiverInfo {
uid: &receiver.uid,
distance: receiver
.field
.distance(tx_node.spatial.get().unwrap(), vec3a(0.0, 0.0, 0.0)),
position: position.into(),
rotation: rotation.into(),
};
let Ok(data) = serialize(info) else {return};
let _ = tx_node.send_remote_signal("new_receiver", data);
}
fn handle_drop_receiver(&self, receiver: &PulseReceiver) {
let uid = receiver.uid.as_str();
self.aliases.remove(uid);
self.aliases.remove(&(uid.to_string() + "-field"));
let Some(tx_node) = self.node.upgrade() else {
return;
};
let _ = pulse_sender_client::drop_receiver(&tx_node, uid);
let Some(tx_node) = self.node.upgrade() else {return};
let Ok(data) = serialize(&uid) else {return};
let _ = tx_node.send_remote_signal("drop_receiver", data);
}
fn send_data_flex(node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
let info: SendDataInfo = deserialize(message.as_ref())?;
let receiver_node = calling_client.get_node("Pulse receiver", info.uid)?;
let receiver =
receiver_node.get_aspect("Pulse Receiver", "pulse receiver", |n| &n.pulse_receiver)?;
let receiver_mask = &receiver_node
.get_aspect("Pulse receiver", "pulse receiver", |node| {
&node.pulse_receiver
})?
.mask;
let data_mask = Mask(info.data);
data_mask.get_mask()?;
ensure!(
mask_matches(receiver_mask, &data_mask),
"Message does not contain the same keys as the receiver's mask"
);
receiver.send_data(&node.pulse_sender.get().unwrap().uid, data_mask.0)
}
}
impl Aspect for PulseSender {
const NAME: &'static str = "PulseSender";
}
impl PulseSenderAspect for PulseSender {}
impl Drop for PulseSender {
fn drop(&mut self) {
PULSE_SENDER_REGISTRY.remove(self);
@@ -144,52 +183,39 @@ impl Drop for PulseSender {
pub struct PulseReceiver {
uid: String,
pub node: Weak<Node>,
pub field_node: Arc<Node>,
pub mask: Datamap,
pub field: Arc<Field>,
pub mask: Mask,
}
impl PulseReceiver {
pub fn add_to(
node: &Arc<Node>,
field_node: Arc<Node>,
mask: Datamap,
) -> Result<Arc<PulseReceiver>> {
pub fn add_to(node: &Arc<Node>, field: Arc<Field>, mask: Mask) -> Result<Arc<PulseReceiver>> {
ensure!(
node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!"
);
let receiver = PulseReceiver {
uid: nanoid!(),
node: Arc::downgrade(node),
field_node,
field,
mask,
};
let receiver = PULSE_RECEIVER_REGISTRY.add(receiver);
<PulseReceiver as PulseReceiverAspect>::add_node_members(node);
node.add_aspect_raw(receiver.clone());
for sender in PULSE_SENDER_REGISTRY.get_valid_contents() {
sender.handle_new_receiver(&receiver);
}
let _ = node.pulse_receiver.set(receiver.clone());
Ok(receiver)
}
}
impl Aspect for PulseReceiver {
const NAME: &'static str = "PulseReceiver";
}
impl PulseReceiverAspect for PulseReceiver {
fn send_data(
node: Arc<Node>,
_calling_client: Arc<Client>,
sender: Arc<Node>,
data: Datamap,
) -> Result<()> {
let this_receiver = node.get_aspect::<PulseReceiver>().unwrap();
ensure!(
mask_matches(&this_receiver.mask, &data),
"Message ({data:?}) does not contain the same keys as the receiver's mask ({:?})",
this_receiver.mask
);
pulse_receiver_client::data(&node, &sender.uid, &data)?;
pub fn send_data(&self, uid: &str, data: Vec<u8>) -> Result<()> {
if let Some(node) = self.node.upgrade() {
node.send_remote_signal("data", serialize(SendDataInfo { uid, data })?)?;
}
Ok(())
}
}
impl Drop for PulseReceiver {
fn drop(&mut self) {
PULSE_RECEIVER_REGISTRY.remove(self);
@@ -199,90 +225,62 @@ impl Drop for PulseReceiver {
}
}
create_interface!(DataInterface, DataInterfaceAspect, "/data");
struct DataInterface;
impl DataInterfaceAspect for DataInterface {
fn create_pulse_sender(
_node: Arc<Node>,
calling_client: Arc<Client>,
name: String,
parent: Arc<Node>,
transform: Transform,
mask: Datamap,
) -> Result<()> {
get_mask(&mask)?;
let node = Node::create_parent_name(
&calling_client,
Self::CREATE_PULSE_SENDER_PARENT_PATH,
&name,
true,
);
let parent = parent.get_aspect::<Spatial>()?;
let transform = transform.to_mat4(true, true, false);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, false);
PulseSender::add_to(&node, mask)?;
Ok(())
}
fn create_pulse_receiver(
_node: Arc<Node>,
calling_client: Arc<Client>,
name: String,
parent: Arc<Node>,
transform: Transform,
field: Arc<Node>,
mask: Datamap,
) -> Result<()> {
get_mask(&mask)?;
let node = Node::create_parent_name(
&calling_client,
Self::CREATE_PULSE_RECEIVER_PARENT_PATH,
&name,
true,
);
let parent = parent.get_aspect::<Spatial>()?;
let transform = parse_transform(transform, true, true, false);
let _ = field.get_aspect::<Field>()?;
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, false);
PulseReceiver::add_to(&node, field, mask)?;
Ok(())
}
async fn register_keymap(
_node: Arc<Node>,
_calling_client: Arc<Client>,
keymap: String,
) -> Result<String> {
let mut keymaps = KEYMAPS.lock();
if let Some(found_keymap_id) = keymaps
.iter()
.filter(|(_k, v)| *v == &keymap)
.map(|(k, _v)| k)
.last()
{
return Ok(found_keymap_id.clone());
}
let generated_id = nanoid!();
keymaps.insert(generated_id.clone(), keymap);
Ok(generated_id)
}
async fn get_keymap(
_node: Arc<Node>,
_calling_client: Arc<Client>,
keymap_id: String,
) -> Result<String> {
let keymaps = KEYMAPS.lock();
let Some(keymap) = keymaps.get(&keymap_id) else {
bail!("Could not find keymap. Try registering it")
};
Ok(keymap.clone())
}
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
let node = Node::create(client, "", "data", false);
node.add_local_signal("create_pulse_sender", create_pulse_sender_flex);
node.add_local_signal("create_pulse_receiver", create_pulse_receiver_flex);
node.add_to_scenegraph().map(|_| ())
}
pub fn create_pulse_sender_flex(
_node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
#[derive(Deserialize)]
struct CreatePulseSenderInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
mask: Vec<u8>,
}
let info: CreatePulseSenderInfo = deserialize(message.as_ref())?;
let node = Node::create(&calling_client, "/data/sender", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, false);
let mask = Mask(info.mask);
mask.get_mask()?;
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
PulseSender::add_to(&node, mask)?;
Ok(())
}
pub fn create_pulse_receiver_flex(
_node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
#[derive(Deserialize)]
struct CreatePulseReceiverInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
field_path: &'a str,
mask: Vec<u8>,
}
let info: CreatePulseReceiverInfo = deserialize(message.as_ref())?;
let node = Node::create(&calling_client, "/data/receiver", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, false);
let field = find_field(&calling_client, info.field_path)?;
let mask = Mask(info.mask);
mask.get_mask()?;
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
PulseReceiver::add_to(&node, field, mask)?;
Ok(())
}

View File

@@ -1,48 +1,68 @@
use super::{Line, LinesAspect};
use crate::{
core::{client::Client, registry::Registry},
nodes::{spatial::Spatial, Aspect, Node},
nodes::{
spatial::{find_spatial_parent, parse_transform, Spatial},
Message, Node,
},
};
use color_eyre::eyre::Result;
use color_eyre::eyre::{bail, ensure, Result};
use glam::Vec3A;
use mint::Vector3;
use parking_lot::Mutex;
use portable_atomic::{AtomicBool, Ordering};
use prisma::Lerp;
use prisma::{Flatten, Lerp, Rgba};
use serde::Deserialize;
use stardust_xr::{schemas::flex::deserialize, values::Transform};
use std::{collections::VecDeque, sync::Arc};
use stereokit::{bounds_grow_to_fit_pt, Bounds, Color128, LinePoint as SkLinePoint, StereoKitDraw};
use super::Drawable;
static LINES_REGISTRY: Registry<Lines> = Registry::new();
#[derive(Debug, Clone, Deserialize)]
struct LinePointRaw {
point: Vector3<f32>,
thickness: f32,
color: [f32; 4],
}
#[derive(Debug, Clone)]
struct LineData {
points: Vec<LinePointRaw>,
cyclic: bool,
}
pub struct Lines {
enabled: Arc<AtomicBool>,
space: Arc<Spatial>,
data: Mutex<Vec<Line>>,
data: Mutex<LineData>,
}
impl Lines {
pub fn add_to(node: &Arc<Node>, lines: Vec<Line>) -> Result<Arc<Lines>> {
let _ = node
.get_aspect::<Spatial>()
.unwrap()
.bounding_box_calc
.set(|node| {
let mut bounds = Bounds::default();
if let Ok(lines) = node.get_aspect::<Lines>() {
for line in &*lines.data.lock() {
for point in &line.points {
bounds = bounds_grow_to_fit_pt(bounds, point.point);
}
}
}
bounds
});
fn add_to(node: &Arc<Node>, points: Vec<LinePointRaw>, cyclic: bool) -> Result<Arc<Lines>> {
ensure!(
node.drawable.get().is_none(),
"Internal: Node already has a drawable attached!"
);
let _ = node.spatial.get().unwrap().bounding_box_calc.set(|node| {
let mut bounds = Bounds::default();
let Some(Drawable::Lines(lines)) = node.drawable.get() else {return bounds};
for point in &lines.data.lock().points {
bounds = bounds_grow_to_fit_pt(bounds, point.point);
}
bounds
});
let lines = LINES_REGISTRY.add(Lines {
enabled: node.enabled.clone(),
space: node.get_aspect::<Spatial>()?.clone(),
data: Mutex::new(lines),
space: node.get_aspect("Lines", "spatial", |n| &n.spatial)?.clone(),
data: Mutex::new(LineData { points, cyclic }),
});
<Lines as LinesAspect>::add_node_members(node);
node.add_aspect_raw(lines.clone());
node.add_local_signal("set_points", Lines::set_points_flex);
node.add_local_signal("set_cyclic", Lines::set_cyclic_flex);
let _ = node.drawable.set(Drawable::Lines(lines.clone()));
Ok(lines)
}
@@ -50,55 +70,57 @@ impl Lines {
fn draw(&self, draw_ctx: &impl StereoKitDraw) {
let transform_mat = self.space.global_transform();
let data = self.data.lock().clone();
for line in &data {
let mut points: VecDeque<SkLinePoint> = line
.points
.iter()
.map(|p| SkLinePoint {
pt: transform_mat.transform_point3a(Vec3A::from(p.point)).into(),
thickness: p.thickness,
color: stereokit::sys::color128::from([
p.color.c.r,
p.color.c.g,
p.color.c.b,
p.color.a,
])
let mut points: VecDeque<SkLinePoint> = data
.points
.iter()
.map(|p| SkLinePoint {
pt: transform_mat.transform_point3a(Vec3A::from(p.point)).into(),
thickness: p.thickness,
color: p.color.map(|c| (c * 255.0) as u8).into(),
})
.collect();
if data.cyclic && !points.is_empty() {
let first = data.points.first().unwrap();
let last = data.points.last().unwrap();
let color = Rgba::from_slice(&first.color).lerp(&Rgba::from_slice(&last.color), 0.5);
let connect_point = SkLinePoint {
pt: transform_mat
.transform_point3a(Vec3A::from(first.point).lerp(Vec3A::from(last.point), 0.5))
.into(),
})
.collect();
if line.cyclic && !points.is_empty() {
let first = line.points.first().unwrap();
let last = line.points.last().unwrap();
let color = Color128 {
r: first.color.c.r.lerp(&last.color.c.r, 0.5),
g: first.color.c.g.lerp(&last.color.c.g, 0.5),
b: first.color.c.b.lerp(&last.color.c.b, 0.5),
a: first.color.a.lerp(&last.color.a, 0.5),
};
let connect_point = SkLinePoint {
pt: transform_mat
.transform_point3a(
Vec3A::from(first.point).lerp(Vec3A::from(last.point), 0.5),
)
.into(),
thickness: (first.thickness + last.thickness) * 0.5,
color: color.into(),
};
points.push_front(connect_point.clone());
points.push_back(connect_point);
}
draw_ctx.line_add_listv(points.make_contiguous());
thickness: (first.thickness + last.thickness) * 0.5,
color: Color128::from([color.red(), color.green(), color.blue(), color.alpha()])
.into(),
};
points.push_front(connect_point.clone());
points.push_back(connect_point);
}
draw_ctx.line_add_listv(points.make_contiguous());
}
}
impl Aspect for Lines {
const NAME: &'static str = "Lines";
}
impl LinesAspect for Lines {
fn set_lines(node: Arc<Node>, _calling_client: Arc<Client>, lines: Vec<Line>) -> Result<()> {
let lines_aspect = node.get_aspect::<Lines>()?;
*lines_aspect.data.lock() = lines;
pub fn set_points_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(Drawable::Lines(lines)) = node.drawable.get() else {bail!("Not a drawable??")};
let mut points: Vec<LinePointRaw> = deserialize(message.as_ref())?;
for p in &mut points {
p.color[0] = p.color[0].powf(2.2);
p.color[1] = p.color[1].powf(2.2);
p.color[2] = p.color[2].powf(2.2);
}
lines.data.lock().points = points;
Ok(())
}
pub fn set_cyclic_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(Drawable::Lines(lines)) = node.drawable.get() else {bail!("Not a drawable??")};
lines.data.lock().cyclic = deserialize(message.as_ref())?;
Ok(())
}
}
@@ -115,3 +137,29 @@ pub fn draw_all(draw_ctx: &impl StereoKitDraw) {
}
}
}
pub fn create_flex(_node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
#[derive(Deserialize)]
struct CreateTextInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
points: Vec<LinePointRaw>,
cyclic: bool,
}
let mut info: CreateTextInfo = deserialize(message.as_ref())?;
let node = Node::create(&calling_client, "/drawable/lines", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, true);
for p in &mut info.points {
p.color[0] = p.color[0].powf(2.2);
p.color[1] = p.color[1].powf(2.2);
p.color[2] = p.color[2].powf(2.2);
}
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
Lines::add_to(&node, info.points, info.cyclic)?;
Ok(())
}

View File

@@ -3,22 +3,39 @@ pub mod model;
pub mod shaders;
pub mod text;
use self::{lines::Lines, model::Model, text::Text};
use super::{
spatial::{Spatial, Transform},
Node,
use self::{
lines::Lines,
model::{Model, ModelPart},
text::Text,
};
use crate::{
core::{client::Client, resource::get_resource_file},
create_interface,
};
use color_eyre::eyre::{self, Result};
use parking_lot::Mutex;
use stardust_xr::values::ResourceID;
use std::{ffi::OsStr, path::PathBuf, sync::Arc};
use stereokit::StereoKitDraw;
// #[instrument(level = "debug", skip(sk))]
use super::{Message, Node};
use crate::core::client::Client;
use color_eyre::eyre::Result;
use parking_lot::Mutex;
use serde::Deserialize;
use stardust_xr::schemas::flex::deserialize;
use std::{path::PathBuf, sync::Arc};
use stereokit::StereoKitDraw;
use tracing::instrument;
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
let node = Node::create(client, "", "drawable", false);
node.add_local_signal("create_lines", lines::create_flex);
node.add_local_signal("create_model", model::create_flex);
node.add_local_signal("create_text", text::create_flex);
node.add_local_signal("set_sky_file", set_sky_file_flex);
node.add_to_scenegraph().map(|_| ())
}
pub enum Drawable {
Lines(Arc<Lines>),
Model(Arc<Model>),
ModelPart(Arc<ModelPart>),
Text(Arc<Text>),
}
#[instrument(level = "debug", skip(sk))]
pub fn draw(sk: &impl StereoKitDraw) {
lines::draw_all(sk);
model::draw_all(sk);
@@ -39,83 +56,21 @@ pub fn draw(sk: &impl StereoKitDraw) {
static QUEUED_SKYLIGHT: Mutex<Option<PathBuf>> = Mutex::new(None);
static QUEUED_SKYTEX: Mutex<Option<PathBuf>> = Mutex::new(None);
stardust_xr_server_codegen::codegen_drawable_protocol!();
create_interface!(DrawableInterface, DrawableInterfaceAspect, "/drawable");
pub struct DrawableInterface;
impl DrawableInterfaceAspect for DrawableInterface {
fn set_sky_tex(_node: Arc<Node>, calling_client: Arc<Client>, tex: ResourceID) -> Result<()> {
let resource_path = get_resource_file(&tex, &calling_client, &[OsStr::new("hdr")])
.ok_or(eyre::eyre!("Could not find resource"))?;
QUEUED_SKYTEX.lock().replace(resource_path);
Ok(())
fn set_sky_file_flex(_node: &Node, _calling_client: Arc<Client>, message: Message) -> Result<()> {
#[derive(Deserialize)]
struct SkyFileInfo {
path: PathBuf,
skytex: Option<bool>,
skylight: Option<bool>,
}
let info: SkyFileInfo = deserialize(message.as_ref())?;
info.path.metadata()?;
if info.skytex.unwrap_or_default() {
QUEUED_SKYTEX.lock().replace(info.path.clone());
}
if info.skylight.unwrap_or_default() {
QUEUED_SKYLIGHT.lock().replace(info.path);
}
fn set_sky_light(
_node: Arc<Node>,
calling_client: Arc<Client>,
light: ResourceID,
) -> Result<()> {
let resource_path = get_resource_file(&light, &calling_client, &[OsStr::new("hdr")])
.ok_or(eyre::eyre!("Could not find resource"))?;
QUEUED_SKYLIGHT.lock().replace(resource_path);
Ok(())
}
fn create_lines(
_node: Arc<Node>,
calling_client: Arc<Client>,
name: String,
parent: Arc<Node>,
transform: Transform,
lines: Vec<Line>,
) -> Result<()> {
let node =
Node::create_parent_name(&calling_client, Self::CREATE_LINES_PARENT_PATH, &name, true);
let parent = parent.get_aspect::<Spatial>()?;
let transform = transform.to_mat4(true, true, true);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, false);
Lines::add_to(&node, lines)?;
Ok(())
}
fn load_model(
_node: Arc<Node>,
calling_client: Arc<Client>,
name: String,
parent: Arc<Node>,
transform: Transform,
model: ResourceID,
) -> Result<()> {
let node =
Node::create_parent_name(&calling_client, Self::LOAD_MODEL_PARENT_PATH, &name, true);
let parent = parent.get_aspect::<Spatial>()?;
let transform = transform.to_mat4(true, true, true);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, false);
Model::add_to(&node, model)?;
Ok(())
}
fn create_text(
_node: Arc<Node>,
calling_client: Arc<Client>,
name: String,
parent: Arc<Node>,
transform: Transform,
text: String,
style: TextStyle,
) -> Result<()> {
let node =
Node::create_parent_name(&calling_client, Self::CREATE_TEXT_PARENT_PATH, &name, true);
let parent = parent.get_aspect::<Spatial>()?;
let transform = transform.to_mat4(true, true, true);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, false);
Text::add_to(&node, text, style)?;
Ok(())
}
Ok(())
}

View File

@@ -1,31 +1,54 @@
use super::{MaterialParameter, ModelAspect, ModelPartAspect, Node};
use super::Node;
use crate::core::client::Client;
use crate::core::destroy_queue;
use crate::core::node_collections::LifeLinkedNodeMap;
use crate::core::registry::Registry;
use crate::core::resource::get_resource_file;
use crate::nodes::spatial::Spatial;
use crate::nodes::Aspect;
use crate::core::resource::ResourceID;
use crate::nodes::drawable::Drawable;
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
use crate::nodes::Message;
use crate::SK_MULTITHREAD;
use color_eyre::eyre::{eyre, Result};
use color_eyre::eyre::{bail, ensure, eyre, Result};
use glam::Mat4;
use mint::{ColumnMatrix4, Vector2, Vector3, Vector4};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use portable_atomic::{AtomicBool, Ordering};
use rustc_hash::FxHashMap;
use stardust_xr::values::ResourceID;
use send_wrapper::SendWrapper;
use serde::Deserialize;
use stardust_xr::schemas::flex::deserialize;
use stardust_xr::values::Transform;
use std::ffi::OsStr;
use std::path::PathBuf;
use std::sync::{Arc, Weak};
use stereokit::named_colors::WHITE;
use stereokit::{
Bounds, Color128, Material, Model as SKModel, RenderLayer, Shader, StereoKitDraw,
StereoKitMultiThread, Transparency,
StereoKitMultiThread,
};
static MODEL_REGISTRY: Registry<Model> = Registry::new();
static HOLDOUT_MATERIAL: OnceCell<Arc<Material>> = OnceCell::new();
#[derive(Deserialize, Debug)]
#[serde(tag = "t", content = "c")]
pub enum MaterialParameter {
Float(f32),
Vector2(Vector2<f32>),
Vector3(Vector3<f32>),
Vector4(Vector4<f32>),
Color([f32; 4]),
Int(i32),
Int2(Vector2<i32>),
Int3(Vector3<i32>),
Int4(Vector4<i32>),
Bool(bool),
UInt(u32),
UInt2(Vector2<u32>),
UInt3(Vector3<u32>),
UInt4(Vector4<u32>),
Matrix(ColumnMatrix4<f32>),
Texture(ResourceID),
}
impl MaterialParameter {
fn apply_to_material(
&self,
@@ -35,37 +58,56 @@ impl MaterialParameter {
parameter_name: &str,
) {
match self {
MaterialParameter::Bool(val) => {
sk.material_set_bool(material, parameter_name, *val);
MaterialParameter::Float(val) => {
sk.material_set_float(material, parameter_name, *val);
}
MaterialParameter::Vector2(val) => {
sk.material_set_vector2(material, parameter_name, *val);
}
MaterialParameter::Vector3(val) => {
sk.material_set_vector3(material, parameter_name, *val);
}
MaterialParameter::Vector4(val) => {
sk.material_set_vector4(material, parameter_name, *val);
}
MaterialParameter::Color(val) => {
sk.material_set_color(material, parameter_name, Color128::from(val.clone()));
}
MaterialParameter::Int(val) => {
sk.material_set_int(material, parameter_name, *val);
}
MaterialParameter::Int2(val) => {
sk.material_set_int2(material, parameter_name, val.x, val.y);
}
MaterialParameter::Int3(val) => {
sk.material_set_int3(material, parameter_name, val.x, val.y, val.z);
}
MaterialParameter::Int4(val) => {
sk.material_set_int4(material, parameter_name, val.w, val.x, val.y, val.z);
}
MaterialParameter::Bool(val) => {
sk.material_set_bool(material, parameter_name, *val);
}
MaterialParameter::UInt(val) => {
sk.material_set_uint(material, parameter_name, *val);
}
MaterialParameter::Float(val) => {
sk.material_set_float(material, parameter_name, *val);
MaterialParameter::UInt2(val) => {
sk.material_set_uint2(material, parameter_name, val.x, val.y);
}
MaterialParameter::Vec2(val) => {
sk.material_set_vector2(material, parameter_name, *val);
MaterialParameter::UInt3(val) => {
sk.material_set_uint3(material, parameter_name, val.x, val.y, val.z);
}
MaterialParameter::Vec3(val) => {
sk.material_set_vector3(material, parameter_name, *val);
MaterialParameter::UInt4(val) => {
sk.material_set_uint4(material, parameter_name, val.w, val.x, val.y, val.z);
}
MaterialParameter::Color(val) => {
sk.material_set_color(
material,
parameter_name,
Color128::new(val.c.r, val.c.g, val.c.b, val.a),
);
MaterialParameter::Matrix(val) => {
sk.material_set_matrix(material, parameter_name, Mat4::from(*val));
}
MaterialParameter::Texture(resource) => {
let Some(texture_path) =
get_resource_file(&resource, &client, &[OsStr::new("png"), OsStr::new("jpg")])
else {
return;
};
let Some(texture_path) = resource.get_file(
&client.base_resource_prefixes.lock().clone(),
&[OsStr::new("png"), OsStr::new("jpg")],
) else {return};
if let Ok(tex) = sk.tex_create_file(texture_path, true, 0) {
sk.material_set_texture(material, parameter_name, &tex);
}
@@ -80,26 +122,10 @@ pub struct ModelPart {
space: Arc<Spatial>,
model: Weak<Model>,
pending_material_parameters: Mutex<FxHashMap<String, MaterialParameter>>,
pending_material_replacement: Mutex<Option<Arc<Material>>>,
pending_material_replacement: Mutex<Option<Arc<SendWrapper<Material>>>>,
}
impl ModelPart {
fn create_for_model(sk: &impl StereoKitMultiThread, model: &Arc<Model>, sk_model: &SKModel) {
HOLDOUT_MATERIAL.get_or_init(|| {
let mat = sk.material_copy(Material::UNLIT);
sk.material_set_transparency(&mat, Transparency::None);
sk.material_set_color(
&mat,
"color",
stereokit::sys::color128 {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.0,
},
);
Arc::new(mat)
});
let first_root_part = sk.model_node_get_root(sk_model);
let mut current_option_part = Some(first_root_part);
@@ -133,50 +159,40 @@ impl ModelPart {
.and_then(|id| model.parts.get(&id));
let parent_part = parent_node
.as_ref()
.and_then(|node| node.get_aspect::<ModelPart>().ok());
.and_then(|node| match node.drawable.get() {
Some(Drawable::ModelPart(model_part)) => Some(model_part),
_ => None,
});
let stardust_model_part = model.space.node()?;
let client = stardust_model_part.get_client()?;
let mut part_path = parent_part.map(|n| n.path.clone()).unwrap_or_default();
part_path.push(sk.model_node_get_name(sk_model, id)?);
let node = client.scenegraph.add_node(Node::create_parent_name(
let node = client.scenegraph.add_node(Node::create(
&client,
stardust_model_part.get_path(),
part_path.to_str()?,
false,
));
let spatial_parent = parent_node
.and_then(|n| n.get_aspect::<Spatial>().ok())
.and_then(|n| n.spatial.get().cloned())
.unwrap_or_else(|| model.space.clone());
let space = Spatial::add_to(
&node,
Some(spatial_parent),
sk.model_node_get_transform_local(sk_model, id),
false,
);
)
.ok()?;
let _ = node
.get_aspect::<Spatial>()
.unwrap()
.bounding_box_calc
.set(|node| {
let Ok(model_part) = node.get_aspect::<ModelPart>() else {
return Bounds::default();
};
let Some(sk) = SK_MULTITHREAD.get() else {
return Bounds::default();
};
let Some(model) = model_part.model.upgrade() else {
return Bounds::default();
};
let Some(sk_model) = model.sk_model.get() else {
return Bounds::default();
};
let Some(sk_mesh) = sk.model_node_get_mesh(sk_model, model_part.id) else {
return Bounds::default();
};
sk.mesh_get_bounds(sk_mesh)
});
let _ = node.spatial.get().unwrap().bounding_box_calc.set(|node| {
let Some(Drawable::ModelPart(model_part)) = node.drawable.get() else {return Bounds::default()};
let Some(sk) = SK_MULTITHREAD.get() else {return Bounds::default()};
let Some(model) = model_part.model.upgrade() else {return Bounds::default()};
let Some(sk_model) = model.sk_model.get() else {return Bounds::default()};
let Some(sk_mesh) = sk.model_node_get_mesh(sk_model, model_part.id) else {return Bounds::default()};
sk.mesh_get_bounds(sk_mesh)
});
let model_part = Arc::new(ModelPart {
id,
@@ -186,40 +202,50 @@ impl ModelPart {
pending_material_parameters: Mutex::new(FxHashMap::default()),
pending_material_replacement: Mutex::new(None),
});
<ModelPart as ModelPartAspect>::add_node_members(&node);
node.add_aspect_raw(model_part.clone());
node.add_local_signal(
"set_material_parameter",
ModelPart::set_material_parameter_flex,
);
let _ = node.drawable.set(Drawable::ModelPart(model_part.clone()));
model.parts.add(id, &node);
Some(model_part)
}
pub fn replace_material(&self, replacement: Arc<Material>) {
fn set_material_parameter_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(Drawable::ModelPart(model_part)) = node.drawable.get() else {bail!("Not a drawable??")};
let (name, value): (String, MaterialParameter) = deserialize(message.as_ref())?;
model_part
.pending_material_parameters
.lock()
.insert(name, value);
Ok(())
}
pub fn replace_material(&self, replacement: Arc<SendWrapper<Material>>) {
self.pending_material_replacement
.lock()
.replace(replacement);
}
fn update(&self, sk: &impl StereoKitDraw) {
let Some(model) = self.model.upgrade() else {
return;
};
let Some(sk_model) = model.sk_model.get() else {
return;
};
let Some(node) = model.space.node() else {
return;
};
let Some(client) = node.get_client() else {
return;
};
let Some(model) = self.model.upgrade() else {return};
let Some(sk_model) = model.sk_model.get() else {return};
let Some(node) = model.space.node() else {return};
let Some(client) = node.get_client() else {return};
if let Some(material_replacement) = self.pending_material_replacement.lock().take() {
sk.model_node_set_material(sk_model, self.id, material_replacement.as_ref().as_ref());
}
let mut material_parameters = self.pending_material_parameters.lock();
for (parameter_name, parameter_value) in material_parameters.drain() {
let Some(material) = sk.model_node_get_material(sk_model, self.id) else {
continue;
};
let Some(material) = sk.model_node_get_material(sk_model, self.id) else {continue};
let new_material = sk.material_copy(material);
parameter_value.apply_to_material(&client, sk, &new_material, parameter_name.as_str());
sk.model_node_set_material(sk_model, self.id, &new_material);
@@ -232,33 +258,6 @@ impl ModelPart {
);
}
}
impl Aspect for ModelPart {
const NAME: &'static str = "ModelPart";
}
impl ModelPartAspect for ModelPart {
#[doc = "Set this model part's material to one that cuts a hole in the world. Often used for overlays/passthrough where you want to show the background through an object."]
fn apply_holdout_material(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
let model_part = node.get_aspect::<ModelPart>()?;
model_part.replace_material(HOLDOUT_MATERIAL.get().unwrap().clone());
Ok(())
}
#[doc = "Set the material parameter with `parameter_name` to `value`"]
fn set_material_parameter(
node: Arc<Node>,
_calling_client: Arc<Client>,
parameter_name: String,
value: MaterialParameter,
) -> Result<()> {
let model_part = node.get_aspect::<ModelPart>()?;
model_part
.pending_material_parameters
.lock()
.insert(parameter_name, value);
Ok(())
}
}
pub struct Model {
self_ref: Weak<Model>,
@@ -273,17 +272,31 @@ unsafe impl Sync for Model {}
impl Model {
pub fn add_to(node: &Arc<Node>, resource_id: ResourceID) -> Result<Arc<Model>> {
let pending_model_path = get_resource_file(
&resource_id,
&*node.get_client().ok_or_else(|| eyre!("Client not found"))?,
&[OsStr::new("glb"), OsStr::new("gltf")],
)
.ok_or_else(|| eyre!("Resource not found"))?;
ensure!(
node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!"
);
ensure!(
node.drawable.get().is_none(),
"Internal: Node already has a drawable attached!"
);
let pending_model_path = resource_id
.get_file(
&node
.get_client()
.ok_or_else(|| eyre!("Client not found"))?
.base_resource_prefixes
.lock()
.clone(),
&[OsStr::new("glb"), OsStr::new("gltf")],
)
.ok_or_else(|| eyre!("Resource not found"))?;
let model = Arc::new_cyclic(|self_ref| Model {
self_ref: self_ref.clone(),
enabled: node.enabled.clone(),
space: node.get_aspect::<Spatial>().unwrap().clone(),
space: node.spatial.get().unwrap().clone(),
_resource_id: resource_id,
sk_model: OnceCell::new(),
parts: LifeLinkedNodeMap::default(),
@@ -296,18 +309,15 @@ impl Model {
);
ModelPart::create_for_model(sk, &model.self_ref.upgrade().unwrap(), &sk_model);
let _ = model.sk_model.set(sk_model);
node.add_aspect_raw(model.clone());
let _ = node.drawable.set(Drawable::Model(model.clone()));
Ok(model)
}
fn draw(&self, sk: &impl StereoKitDraw) {
let Some(sk_model) = self.sk_model.get() else {
return;
};
let Some(sk_model) = self.sk_model.get() else {return};
for model_node_node in self.parts.nodes() {
if let Ok(model_node) = model_node_node.get_aspect::<ModelPart>() {
model_node.update(sk);
};
let Some(Drawable::ModelPart(model_node)) = model_node_node.drawable.get() else {continue};
model_node.update(sk);
}
sk.model_draw(
@@ -318,15 +328,9 @@ impl Model {
);
}
}
impl Aspect for Model {
const NAME: &'static str = "Model";
}
impl ModelAspect for Model {}
impl Drop for Model {
fn drop(&mut self) {
if let Some(sk_model) = self.sk_model.take() {
destroy_queue::add(sk_model);
}
MODEL_REGISTRY.remove(self);
}
}
@@ -338,3 +342,21 @@ pub fn draw_all(sk: &impl StereoKitDraw) {
}
}
}
pub fn create_flex(_node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
#[derive(Deserialize)]
struct CreateModelInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
resource: ResourceID,
}
let info: CreateModelInfo = deserialize(message.as_ref())?;
let node = Node::create(&calling_client, "/drawable/model", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, true);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
Model::add_to(&node, info.resource)?;
Ok(())
}

View File

@@ -1,4 +1,5 @@
#![allow(dead_code)]
use smithay::backend::renderer::gles::{
ffi::{self, Gles2, FRAGMENT_SHADER, VERTEX_SHADER},
GlesError,
@@ -7,12 +8,6 @@ use std::mem::transmute;
use stereokit::Shader;
use tracing::error;
// Simula shader with fancy lanzcos sampling
pub const UNLIT_SHADER_BYTES: &[u8] = include_bytes!("shader_unlit_gamma.sks");
// Simula shader with fancy lanzcos sampling
pub const PANEL_SHADER_BYTES: &[u8] = include_bytes!("shader_unlit_simula.sks");
struct FfiAssetHeader {
asset_type: i32,
asset_state: i32,

View File

@@ -1,79 +1,110 @@
use crate::{
core::{client::Client, destroy_queue, registry::Registry, resource::get_resource_file},
nodes::{spatial::Spatial, Aspect, Node},
core::{client::Client, destroy_queue, registry::Registry, resource::ResourceID},
nodes::{
drawable::Drawable,
spatial::{find_spatial_parent, parse_transform, Spatial},
Message, Node,
},
};
use color_eyre::eyre::{eyre, Result};
use color_eyre::eyre::{bail, ensure, eyre, Result};
use glam::{vec3, Mat4, Vec2};
use mint::Vector2;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use portable_atomic::{AtomicBool, Ordering};
use prisma::{Flatten, Rgba};
use send_wrapper::SendWrapper;
use serde::Deserialize;
use stardust_xr::{schemas::flex::deserialize, values::Transform};
use std::{ffi::OsStr, path::PathBuf, sync::Arc};
use stereokit::{
named_colors::WHITE, Color128, StereoKitDraw, TextAlign, TextFit, TextStyle as SkTextStyle,
};
use super::{TextAspect, TextStyle};
use stereokit::{named_colors::WHITE, Color128, StereoKitDraw, TextAlign, TextFit, TextStyle};
static TEXT_REGISTRY: Registry<Text> = Registry::new();
fn convert_align(x_align: super::XAlign, y_align: super::YAlign) -> TextAlign {
let x_align = match x_align {
super::XAlign::Left => TextAlign::XLeft,
super::XAlign::Center => TextAlign::XCenter,
super::XAlign::Right => TextAlign::XRight,
} as u32;
let y_align = match y_align {
super::YAlign::Top => TextAlign::YTop,
super::YAlign::Center => TextAlign::YCenter,
super::YAlign::Bottom => TextAlign::YBottom,
} as u32;
unsafe { std::mem::transmute(x_align | y_align) }
struct TextData {
text: String,
character_height: f32,
text_align: TextAlign,
bounds: Option<Vec2>,
fit: TextFit,
bounds_align: TextAlign,
color: Rgba<f32>,
}
pub struct Text {
enabled: Arc<AtomicBool>,
space: Arc<Spatial>,
font_path: Option<PathBuf>,
style: OnceCell<SkTextStyle>,
style: OnceCell<SendWrapper<TextStyle>>,
text: Mutex<String>,
data: Mutex<TextStyle>,
data: Mutex<TextData>,
}
impl Text {
pub fn add_to(node: &Arc<Node>, text: String, style: TextStyle) -> Result<Arc<Text>> {
#[allow(clippy::too_many_arguments)]
pub fn add_to(
node: &Arc<Node>,
font_resource_id: Option<ResourceID>,
text: String,
character_height: f32,
text_align: TextAlign,
bounds: Option<Vector2<f32>>,
fit: TextFit,
bounds_align: TextAlign,
color: Rgba<f32>,
) -> Result<Arc<Text>> {
ensure!(
node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!"
);
ensure!(
node.drawable.get().is_none(),
"Internal: Node already has a drawable attached!"
);
let client = node.get_client().ok_or_else(|| eyre!("Client not found"))?;
let text = TEXT_REGISTRY.add(Text {
enabled: node.enabled.clone(),
space: node.get_aspect::<Spatial>().unwrap().clone(),
font_path: style.font.as_ref().and_then(|res| {
get_resource_file(&res, &client, &[OsStr::new("ttf"), OsStr::new("otf")])
space: node.spatial.get().unwrap().clone(),
font_path: font_resource_id.and_then(|res| {
res.get_file(
&client.base_resource_prefixes.lock().clone(),
&[OsStr::new("ttf"), OsStr::new("otf")],
)
}),
style: OnceCell::new(),
text: Mutex::new(text),
data: Mutex::new(style),
data: Mutex::new(TextData {
text,
character_height,
text_align,
bounds: bounds.map(|b| b.into()),
fit,
bounds_align,
color,
}),
});
<Text as TextAspect>::add_node_members(node);
node.add_aspect_raw(text.clone());
node.add_local_signal("set_character_height", Text::set_character_height_flex);
node.add_local_signal("set_text", Text::set_text_flex);
let _ = node.drawable.set(Drawable::Text(text.clone()));
Ok(text)
}
fn draw(&self, sk: &impl StereoKitDraw) {
let style =
self.style
.get_or_try_init(|| -> Result<SkTextStyle, color_eyre::eyre::Error> {
let font = self
.font_path
.as_deref()
.and_then(|path| sk.font_create(path).ok())
.unwrap_or_else(|| sk.font_find("default/font").unwrap());
Ok(unsafe { sk.text_make_style(font, 1.0, WHITE) })
});
let style = self.style.get_or_try_init(
|| -> Result<SendWrapper<TextStyle>, color_eyre::eyre::Error> {
let font = self
.font_path
.as_deref()
.and_then(|path| sk.font_create(path).ok())
.unwrap_or_else(|| sk.font_find("default/font").unwrap());
Ok(SendWrapper::new(unsafe {
sk.text_make_style(font, 1.0, WHITE)
}))
},
);
if let Ok(style) = style {
let text = self.text.lock();
let data = self.data.lock();
let transform = self.space.global_transform()
* Mat4::from_scale(vec3(
@@ -81,55 +112,61 @@ impl Text {
data.character_height,
data.character_height,
));
if let Some(bounds) = &data.bounds {
if let Some(bounds) = data.bounds {
sk.text_add_in(
&*text,
&data.text,
transform,
Vec2::from(bounds.bounds) / data.character_height,
match bounds.fit {
super::TextFit::Wrap => TextFit::Wrap,
super::TextFit::Clip => TextFit::Clip,
super::TextFit::Squeeze => TextFit::Squeeze,
super::TextFit::Exact => TextFit::Exact,
super::TextFit::Overflow => TextFit::Overflow,
},
*style,
convert_align(bounds.anchor_align_x.clone(), bounds.anchor_align_y.clone()),
convert_align(data.text_align_x.clone(), data.text_align_y.clone()),
bounds / data.character_height,
data.fit,
**style,
data.bounds_align,
data.text_align,
vec3(0.0, 0.0, 0.0),
Color128::from([data.color.c.r, data.color.c.g, data.color.c.b, data.color.a]),
Color128::from([
data.color.red(),
data.color.green(),
data.color.blue(),
data.color.alpha(),
]),
);
} else {
sk.text_add_at(
&*text,
&data.text,
transform,
*style,
TextAlign::Center,
convert_align(data.text_align_x.clone(), data.text_align_y.clone()),
**style,
data.bounds_align,
data.text_align,
vec3(0.0, 0.0, 0.0),
Color128::from([data.color.c.r, data.color.c.g, data.color.c.b, data.color.a]),
Color128::from([
data.color.red(),
data.color.green(),
data.color.blue(),
data.color.alpha(),
]),
);
}
}
}
}
impl Aspect for Text {
const NAME: &'static str = "Text";
}
impl TextAspect for Text {
fn set_character_height(
node: Arc<Node>,
pub fn set_character_height_flex(
node: &Node,
_calling_client: Arc<Client>,
height: f32,
message: Message,
) -> Result<()> {
let this_text = node.get_aspect::<Text>()?;
this_text.data.lock().character_height = height;
let Some(Drawable::Text(text)) = node.drawable.get() else {bail!("Not a drawable??")};
text.data.lock().character_height = deserialize(message.as_ref())?;
Ok(())
}
fn set_text(node: Arc<Node>, _calling_client: Arc<Client>, text: String) -> Result<()> {
let this_text = node.get_aspect::<Text>()?;
*this_text.text.lock() = text;
pub fn set_text_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(Drawable::Text(text)) = node.drawable.get() else {bail!("Not a drawable??")};
text.data.lock().text = deserialize(message.as_ref())?;
Ok(())
}
}
@@ -149,3 +186,40 @@ pub fn draw_all(sk: &impl StereoKitDraw) {
}
}
}
pub fn create_flex(_node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
#[derive(Deserialize)]
struct CreateTextInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
text: String,
font_resource: Option<ResourceID>,
character_height: f32,
text_align: TextAlign,
bounds: Option<Vector2<f32>>,
fit: TextFit,
bounds_align: TextAlign,
color: [f32; 4],
}
let info: CreateTextInfo = deserialize(message.as_ref())?;
let node = Node::create(&calling_client, "/drawable/text", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, true);
let color = Rgba::from_slice(&info.color);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
Text::add_to(
&node,
info.font_resource,
info.text,
info.character_height,
info.text_align,
info.bounds,
info.fit,
info.bounds_align,
color,
)?;
Ok(())
}

View File

@@ -1,11 +1,14 @@
use super::{BoxFieldAspect, FieldTrait, Node};
use crate::nodes::fields::FieldAspect;
use crate::nodes::spatial::Spatial;
use crate::{core::client::Client, nodes::fields::Field};
use color_eyre::eyre::Result;
use super::{Field, FieldTrait, Node};
use crate::core::client::Client;
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
use crate::nodes::Message;
use color_eyre::eyre::{ensure, Result};
use glam::{vec3, vec3a, Vec3, Vec3A};
use mint::Vector3;
use parking_lot::Mutex;
use serde::Deserialize;
use stardust_xr::schemas::flex::deserialize;
use stardust_xr::values::Transform;
use std::sync::Arc;
pub struct BoxField {
@@ -14,19 +17,40 @@ pub struct BoxField {
}
impl BoxField {
pub fn add_to(node: &Arc<Node>, size: Vector3<f32>) {
pub fn add_to(node: &Arc<Node>, size: Vector3<f32>) -> Result<Arc<Field>> {
ensure!(
node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!"
);
ensure!(
node.field.get().is_none(),
"Internal: Node already has a field attached!"
);
let box_field = BoxField {
space: node.get_aspect::<Spatial>().unwrap().clone(),
space: node.spatial.get().unwrap().clone(),
size: Mutex::new(size.into()),
};
<BoxField as FieldAspect>::add_node_members(node);
<BoxField as BoxFieldAspect>::add_node_members(node);
node.add_aspect(Field::Box(box_field));
box_field.add_field_methods(node);
node.add_local_signal("set_size", BoxField::set_size_flex);
let field = Arc::new(Field::Box(box_field));
let _ = node.field.set(field.clone());
Ok(field)
}
pub fn set_size(&self, size: Vector3<f32>) {
*self.size.lock() = size.into();
}
pub fn set_size_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Field::Box(box_field) = node.field.get().unwrap().as_ref() else { return Ok(()) };
box_field.set_size(deserialize(message.as_ref())?);
Ok(())
}
}
impl FieldTrait for BoxField {
@@ -44,17 +68,25 @@ impl FieldTrait for BoxField {
self.space.as_ref()
}
}
impl BoxFieldAspect for BoxField {
fn set_size(
node: Arc<Node>,
_calling_client: Arc<Client>,
size: mint::Vector3<f32>,
) -> Result<()> {
let this_field = node.get_aspect::<Field>()?;
let Field::Box(this_field) = &*this_field else {
return Ok(());
};
this_field.set_size(size.into());
Ok(())
pub fn create_box_field_flex(
_node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
#[derive(Deserialize)]
struct CreateFieldInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
size: Vector3<f32>,
}
let info: CreateFieldInfo = deserialize(message.as_ref())?;
let node = Node::create(&calling_client, "/field", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, false);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
BoxField::add_to(&node, info.size)?;
Ok(())
}

View File

@@ -1,10 +1,13 @@
use super::{CylinderFieldAspect, Field, FieldTrait, Node};
use super::{Field, FieldTrait, Node};
use crate::core::client::Client;
use crate::nodes::fields::FieldAspect;
use crate::nodes::spatial::Spatial;
use color_eyre::eyre::Result;
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
use crate::nodes::Message;
use color_eyre::eyre::{ensure, Result};
use glam::{swizzles::*, vec2, Vec3A};
use portable_atomic::AtomicF32;
use serde::Deserialize;
use stardust_xr::schemas::flex::deserialize;
use stardust_xr::values::Transform;
use std::sync::atomic::Ordering;
use std::sync::Arc;
@@ -16,22 +19,43 @@ pub struct CylinderField {
}
impl CylinderField {
pub fn add_to(node: &Arc<Node>, length: f32, radius: f32) {
pub fn add_to(node: &Arc<Node>, length: f32, radius: f32) -> Result<()> {
ensure!(
node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!"
);
ensure!(
node.field.get().is_none(),
"Internal: Node already has a field attached!"
);
let cylinder_field = CylinderField {
space: node.get_aspect::<Spatial>().unwrap().clone(),
space: node.spatial.get().unwrap().clone(),
length: AtomicF32::new(length.abs()),
radius: AtomicF32::new(radius.abs()),
};
<CylinderField as FieldAspect>::add_node_members(node);
<CylinderField as CylinderFieldAspect>::add_node_members(node);
node.add_aspect(Field::Cylinder(cylinder_field));
cylinder_field.add_field_methods(node);
node.add_local_signal("set_size", CylinderField::set_size_flex);
let _ = node.field.set(Arc::new(Field::Cylinder(cylinder_field)));
Ok(())
}
pub fn set_size(&self, length: f32, radius: f32) {
self.length.store(length.abs(), Ordering::Relaxed);
self.radius.store(radius.abs(), Ordering::Relaxed);
}
pub fn set_size_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Field::Cylinder(cylinder_field) = node.field.get().unwrap().as_ref() else { return Ok(()) };
let (length, radius) = deserialize(message.as_ref())?;
cylinder_field.set_size(length, radius);
Ok(())
}
}
impl FieldTrait for CylinderField {
fn local_distance(&self, p: Vec3A) -> f32 {
let radius = self.radius.load(Ordering::Relaxed);
@@ -44,18 +68,26 @@ impl FieldTrait for CylinderField {
self.space.as_ref()
}
}
impl CylinderFieldAspect for CylinderField {
fn set_size(
node: Arc<Node>,
_calling_client: Arc<Client>,
pub fn create_cylinder_field_flex(
_node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
#[derive(Deserialize)]
struct CreateFieldInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
length: f32,
radius: f32,
) -> Result<()> {
let this_field = node.get_aspect::<Field>()?;
let Field::Cylinder(this_field) = &*this_field else {
return Ok(());
};
this_field.set_size(length, radius);
Ok(())
}
let info: CreateFieldInfo = deserialize(message.as_ref())?;
let node = Node::create(&calling_client, "/field", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, false);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
CylinderField::add_to(&node, info.length, info.radius)?;
Ok(())
}

View File

@@ -3,34 +3,38 @@ mod cylinder;
mod sphere;
mod torus;
use self::cylinder::CylinderField;
use self::r#box::BoxField;
use self::sphere::SphereField;
use self::torus::TorusField;
use self::cylinder::{create_cylinder_field_flex, CylinderField};
use self::r#box::{create_box_field_flex, BoxField};
use self::sphere::{create_sphere_field_flex, SphereField};
use self::torus::{create_torus_field_flex, TorusField};
use super::alias::AliasInfo;
use super::spatial::Spatial;
use super::{Aspect, Node};
use super::{Message, Node};
use crate::core::client::Client;
use crate::create_interface;
use crate::nodes::spatial::Transform;
use crate::nodes::spatial::find_reference_space;
use color_eyre::eyre::Result;
use glam::{vec2, vec3a, Mat4, Vec3, Vec3A};
use glam::{vec2, vec3a, Vec3, Vec3A};
use mint::Vector3;
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use stardust_xr::schemas::flex::{deserialize, serialize};
use std::ops::Deref;
use std::sync::Arc;
// TODO: get SDFs working properly with non-uniform scale and so on, output distance relative to the spatial it's compared against
pub static FIELD_ALIAS_INFO: Lazy<AliasInfo> = Lazy::new(|| AliasInfo {
server_methods: vec!["distance", "normal", "closest_point", "ray_march"],
..Default::default()
});
stardust_xr_server_codegen::codegen_field_protocol!();
pub trait FieldTrait: Send + Sync + 'static {
pub trait FieldTrait {
fn add_field_methods(&self, node: &Arc<Node>) {
node.add_local_method("distance", field_distance_flex);
node.add_local_method("normal", field_normal_flex);
node.add_local_method("closest_point", field_closest_point_flex);
node.add_local_method("ray_march", field_ray_march_flex);
}
fn spatial_ref(&self) -> &Spatial;
fn local_distance(&self, p: Vec3A) -> f32;
@@ -76,8 +80,6 @@ pub trait FieldTrait: Send + Sync + 'static {
fn ray_march(&self, ray: Ray) -> RayMarchResult {
let mut result = RayMarchResult {
ray_origin: ray.origin.into(),
ray_direction: ray.direction.into(),
min_distance: f32::MAX,
deepest_point_distance: 0_f32,
ray_length: 0_f32,
@@ -87,9 +89,7 @@ pub trait FieldTrait: Send + Sync + 'static {
let ray_to_field_matrix =
Spatial::space_to_space_matrix(Some(&ray.space), Some(self.spatial_ref()));
let mut ray_point = ray_to_field_matrix.transform_point3a(ray.origin.into());
let ray_direction = ray_to_field_matrix
.transform_vector3a(ray.direction.into())
.normalize();
let ray_direction = ray_to_field_matrix.transform_vector3a(ray.direction.into());
while result.ray_steps < MAX_RAY_STEPS && result.ray_length < MAX_RAY_LENGTH {
let distance = self.local_distance(ray_point);
@@ -109,60 +109,6 @@ pub trait FieldTrait: Send + Sync + 'static {
result
}
}
impl<Fi: FieldTrait + 'static> FieldAspect for Fi {
async fn distance(
node: Arc<Node>,
_calling_client: Arc<Client>,
space: Arc<Node>,
point: mint::Vector3<f32>,
) -> Result<f32> {
let reference_space = space.get_aspect::<Spatial>()?;
let this_field = node.get_aspect::<Field>()?;
Ok((*this_field).distance(reference_space.as_ref(), point.into()))
}
async fn normal(
node: Arc<Node>,
_calling_client: Arc<Client>,
space: Arc<Node>,
point: mint::Vector3<f32>,
) -> Result<Vector3<f32>> {
let reference_space = space.get_aspect::<Spatial>()?;
let this_field = node.get_aspect::<Field>()?;
Ok(this_field
.normal(reference_space.as_ref(), point.into(), 0.001)
.into())
}
async fn closest_point(
node: Arc<Node>,
_calling_client: Arc<Client>,
space: Arc<Node>,
point: mint::Vector3<f32>,
) -> Result<Vector3<f32>> {
let reference_space = space.get_aspect::<Spatial>()?;
let this_field = node.get_aspect::<Field>()?;
Ok(this_field
.closest_point(reference_space.as_ref(), point.into(), 0.001)
.into())
}
async fn ray_march(
node: Arc<Node>,
_calling_client: Arc<Client>,
space: Arc<Node>,
ray_origin: mint::Vector3<f32>,
ray_direction: mint::Vector3<f32>,
) -> Result<RayMarchResult> {
let reference_space = space.get_aspect::<Spatial>()?;
let this_field = node.get_aspect::<Field>()?;
Ok(this_field.ray_march(Ray {
origin: ray_origin.into(),
direction: ray_direction.into(),
space: reference_space.clone(),
}))
}
}
pub struct Ray {
pub origin: Vec3,
@@ -170,6 +116,14 @@ pub struct Ray {
pub space: Arc<Spatial>,
}
#[derive(Debug, Serialize)]
pub struct RayMarchResult {
pub min_distance: f32,
pub deepest_point_distance: f32,
pub ray_length: f32,
pub ray_steps: u32,
}
// const MIN_RAY_STEPS: u32 = 0;
const MAX_RAY_STEPS: u32 = 1000;
@@ -179,15 +133,97 @@ const MAX_RAY_MARCH: f32 = f32::MAX;
// const MIN_RAY_LENGTH: f32 = 0_f32;
const MAX_RAY_LENGTH: f32 = 1000_f32;
fn field_distance_flex(
node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<Message> {
#[derive(Deserialize)]
struct FieldInfoArgs<'a> {
reference_space_path: &'a str,
point: Vector3<f32>,
}
let args: FieldInfoArgs = deserialize(message.as_ref())?;
let reference_space = find_reference_space(&calling_client, args.reference_space_path)?;
let distance = node
.field
.get()
.unwrap()
.distance(reference_space.as_ref(), args.point.into());
Ok(serialize(distance)?.into())
}
fn field_normal_flex(
node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<Message> {
#[derive(Deserialize)]
struct FieldInfoArgs<'a> {
reference_space_path: &'a str,
point: Vector3<f32>,
radius: Option<f32>,
}
let args: FieldInfoArgs = deserialize(message.as_ref())?;
let reference_space = find_reference_space(&calling_client, args.reference_space_path)?;
let normal = node.field.get().as_ref().unwrap().normal(
reference_space.as_ref(),
args.point.into(),
args.radius.unwrap_or(0.001),
);
Ok(serialize(mint::Vector3::from(normal))?.into())
}
fn field_closest_point_flex(
node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<Message> {
#[derive(Deserialize)]
struct FieldInfoArgs<'a> {
reference_space_path: &'a str,
point: Vector3<f32>,
radius: Option<f32>,
}
let args: FieldInfoArgs = deserialize(message.as_ref())?;
let reference_space = find_reference_space(&calling_client, args.reference_space_path)?;
let closest_point = node.field.get().as_ref().unwrap().closest_point(
reference_space.as_ref(),
args.point.into(),
args.radius.unwrap_or(0.001),
);
Ok(serialize(mint::Vector3::from(closest_point))?.into())
}
fn field_ray_march_flex(
node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<Message> {
#[derive(Deserialize)]
struct FieldInfoArgs<'a> {
reference_space_path: &'a str,
ray_origin: Vector3<f32>,
ray_direction: Vector3<f32>,
}
let args: FieldInfoArgs = deserialize(message.as_ref())?;
let reference_space = find_reference_space(&calling_client, args.reference_space_path)?;
let ray_march_result = node.field.get().unwrap().ray_march(Ray {
origin: args.ray_origin.into(),
direction: args.ray_direction.into(),
space: reference_space,
});
Ok(serialize(ray_march_result)?.into())
}
pub enum Field {
Box(BoxField),
Cylinder(CylinderField),
Sphere(SphereField),
Torus(TorusField),
}
impl Aspect for Field {
const NAME: &'static str = "Field";
}
impl Deref for Field {
type Target = dyn FieldTrait;
fn deref(&self) -> &Self::Target {
@@ -199,171 +235,19 @@ impl Deref for Field {
}
}
}
// impl FieldTrait for Field {
// fn spatial_ref(&self) -> &Spatial {
// match self {
// Field::Box(field) => field.spatial_ref(),
// Field::Cylinder(field) => field.spatial_ref(),
// Field::Sphere(field) => field.spatial_ref(),
// Field::Torus(field) => field.spatial_ref(),
// }
// }
// fn local_distance(&self, p: Vec3A) -> f32 {
// match self {
// Field::Box(field) => field.local_distance(p),
// Field::Cylinder(field) => field.local_distance(p),
// Field::Sphere(field) => field.local_distance(p),
// Field::Torus(field) => field.local_distance(p),
// }
// }
// fn local_normal(&self, p: Vec3A, r: f32) -> Vec3A {
// match self {
// Field::Box(field) => field.local_normal(p, r),
// Field::Cylinder(field) => field.local_normal(p, r),
// Field::Sphere(field) => field.local_normal(p, r),
// Field::Torus(field) => field.local_normal(p, r),
// }
// }
// fn local_closest_point(&self, p: Vec3A, r: f32) -> Vec3A {
// match self {
// Field::Box(field) => field.local_closest_point(p, r),
// Field::Cylinder(field) => field.local_closest_point(p, r),
// Field::Sphere(field) => field.local_closest_point(p, r),
// Field::Torus(field) => field.local_closest_point(p, r),
// }
// }
// fn distance(&self, reference_space: &Spatial, p: Vec3A) -> f32 {
// match self {
// Field::Box(field) => field.distance(reference_space, p),
// Field::Cylinder(field) => field.distance(reference_space, p),
// Field::Sphere(field) => field.distance(reference_space, p),
// Field::Torus(field) => field.distance(reference_space, p),
// }
// }
// fn normal(&self, reference_space: &Spatial, p: Vec3A, r: f32) -> Vec3A {
// match self {
// Field::Box(field) => field.normal(reference_space, p, r),
// Field::Cylinder(field) => field.normal(reference_space, p, r),
// Field::Sphere(field) => field.normal(reference_space, p, r),
// Field::Torus(field) => field.normal(reference_space, p, r),
// }
// }
// fn closest_point(&self, reference_space: &Spatial, p: Vec3A, r: f32) -> Vec3A {
// match self {
// Field::Box(field) => field.closest_point(reference_space, p, r),
// Field::Cylinder(field) => field.closest_point(reference_space, p, r),
// Field::Sphere(field) => field.closest_point(reference_space, p, r),
// Field::Torus(field) => field.closest_point(reference_space, p, r),
// }
// }
// fn ray_march(&self, ray: Ray) -> RayMarchResult {
// match self {
// Field::Box(field) => field.ray_march(ray),
// Field::Cylinder(field) => field.ray_march(ray),
// Field::Sphere(field) => field.ray_march(ray),
// Field::Torus(field) => field.ray_march(ray),
// }
// }
// }
create_interface!(FieldInterface, FieldInterfaceAspect, "/field");
pub struct FieldInterface;
impl FieldInterfaceAspect for FieldInterface {
fn create_box_field(
_node: Arc<Node>,
calling_client: Arc<Client>,
name: String,
parent: Arc<Node>,
transform: Transform,
size: mint::Vector3<f32>,
) -> Result<()> {
let transform = transform.to_mat4(true, true, false);
let parent = parent.get_aspect::<Spatial>()?;
let node = Node::create_parent_name(
&calling_client,
Self::CREATE_BOX_FIELD_PARENT_PATH,
&name,
true,
)
.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, false);
BoxField::add_to(&node, size);
Ok(())
}
fn create_cylinder_field(
_node: Arc<Node>,
calling_client: Arc<Client>,
name: String,
parent: Arc<Node>,
transform: Transform,
length: f32,
radius: f32,
) -> Result<()> {
let transform = transform.to_mat4(true, true, false);
let parent = parent.get_aspect::<Spatial>()?;
let node = Node::create_parent_name(
&calling_client,
Self::CREATE_CYLINDER_FIELD_PARENT_PATH,
&name,
true,
)
.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, false);
CylinderField::add_to(&node, length, radius);
Ok(())
}
fn create_sphere_field(
_node: Arc<Node>,
calling_client: Arc<Client>,
name: String,
parent: Arc<Node>,
position: mint::Vector3<f32>,
radius: f32,
) -> Result<()> {
let parent = parent.get_aspect::<Spatial>()?;
let node = Node::create_parent_name(
&calling_client,
Self::CREATE_SPHERE_FIELD_PARENT_PATH,
&name,
true,
)
.add_to_scenegraph()?;
Spatial::add_to(
&node,
Some(parent.clone()),
Mat4::from_translation(position.into()),
false,
);
SphereField::add_to(&node, radius);
Ok(())
}
fn create_torus_field(
_node: Arc<Node>,
calling_client: Arc<Client>,
name: String,
parent: Arc<Node>,
transform: Transform,
radius_a: f32,
radius_b: f32,
) -> Result<()> {
let transform = transform.to_mat4(true, true, false);
let parent = parent.get_aspect::<Spatial>()?;
let node = Node::create_parent_name(
&calling_client,
Self::CREATE_TORUS_FIELD_PARENT_PATH,
&name,
true,
)
.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, false);
TorusField::add_to(&node, radius_a, radius_b);
Ok(())
}
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
let node = Node::create(client, "", "field", false);
node.add_local_signal("create_box_field", create_box_field_flex);
node.add_local_signal("create_cylinder_field", create_cylinder_field_flex);
node.add_local_signal("create_sphere_field", create_sphere_field_flex);
node.add_local_signal("create_torus_field", create_torus_field_flex);
node.add_to_scenegraph().map(|_| ())
}
pub fn find_field(client: &Client, path: &str) -> Result<Arc<Field>> {
client.get_node("Field", path)?.get_aspect::<Field>()
client
.get_node("Field", path)?
.get_aspect("Field", "info", |n| &n.field)
.cloned()
}

View File

@@ -1,10 +1,13 @@
use super::{Field, FieldTrait, Node, SphereFieldAspect};
use super::{Field, FieldTrait, Node};
use crate::core::client::Client;
use crate::nodes::fields::FieldAspect;
use crate::nodes::spatial::Spatial;
use color_eyre::eyre::Result;
use glam::Vec3A;
use crate::nodes::spatial::{find_spatial_parent, Spatial};
use crate::nodes::Message;
use color_eyre::eyre::{ensure, Result};
use glam::{Mat4, Vec3A};
use mint::Vector3;
use portable_atomic::AtomicF32;
use serde::Deserialize;
use stardust_xr::schemas::flex::deserialize;
use std::sync::atomic::Ordering;
use std::sync::Arc;
@@ -14,19 +17,38 @@ pub struct SphereField {
}
impl SphereField {
pub fn add_to(node: &Arc<Node>, radius: f32) {
pub fn add_to(node: &Arc<Node>, radius: f32) -> Result<()> {
ensure!(
node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!"
);
ensure!(
node.field.get().is_none(),
"Internal: Node already has a field attached!"
);
let sphere_field = SphereField {
space: node.get_aspect::<Spatial>().unwrap().clone(),
space: node.spatial.get().unwrap().clone(),
radius: AtomicF32::new(radius),
};
<SphereField as FieldAspect>::add_node_members(node);
<SphereField as SphereFieldAspect>::add_node_members(node);
node.add_aspect(Field::Sphere(sphere_field));
sphere_field.add_field_methods(node);
node.add_local_signal("set_radius", SphereField::set_radius_flex);
let _ = node.field.set(Arc::new(Field::Sphere(sphere_field)));
Ok(())
}
pub fn set_radius(&self, radius: f32) {
self.radius.store(radius, Ordering::Relaxed);
}
pub fn set_radius_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Field::Sphere(sphere_field) = node.field.get().unwrap().as_ref() else { return Ok(()) };
sphere_field.set_radius(deserialize(message.as_ref())?);
Ok(())
}
}
impl FieldTrait for SphereField {
@@ -43,13 +65,29 @@ impl FieldTrait for SphereField {
self.space.as_ref()
}
}
impl SphereFieldAspect for SphereField {
fn set_radius(node: Arc<Node>, _calling_client: Arc<Client>, radius: f32) -> Result<()> {
let this_field = node.get_aspect::<Field>()?;
let Field::Sphere(this_field) = &*this_field else {
return Ok(());
};
this_field.set_radius(radius);
Ok(())
pub fn create_sphere_field_flex(
_node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
#[derive(Deserialize)]
struct CreateFieldInfo<'a> {
name: &'a str,
parent_path: &'a str,
origin: Option<Vector3<f32>>,
radius: f32,
}
let info: CreateFieldInfo = deserialize(message.as_ref())?;
let node = Node::create(&calling_client, "/field", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = Mat4::from_translation(
info.origin
.unwrap_or_else(|| Vector3::from([0.0; 3]))
.into(),
);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
SphereField::add_to(&node, info.radius)?;
Ok(())
}

View File

@@ -1,10 +1,13 @@
use super::{Field, FieldTrait, Node, TorusFieldAspect};
use super::{Field, FieldTrait, Node};
use crate::core::client::Client;
use crate::nodes::fields::FieldAspect;
use crate::nodes::spatial::Spatial;
use color_eyre::eyre::Result;
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
use crate::nodes::Message;
use color_eyre::eyre::{ensure, Result};
use glam::{swizzles::*, vec2, Vec3A};
use portable_atomic::AtomicF32;
use serde::Deserialize;
use stardust_xr::schemas::flex::deserialize;
use stardust_xr::values::Transform;
use std::sync::atomic::Ordering;
use std::sync::Arc;
@@ -16,22 +19,44 @@ pub struct TorusField {
}
impl TorusField {
pub fn add_to(node: &Arc<Node>, radius_a: f32, radius_b: f32) {
pub fn add_to(node: &Arc<Node>, radius_a: f32, radius_b: f32) -> Result<()> {
ensure!(
node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!"
);
ensure!(
node.field.get().is_none(),
"Internal: Node already has a field attached!"
);
let torus_field = TorusField {
space: node.get_aspect::<Spatial>().unwrap().clone(),
space: node.spatial.get().unwrap().clone(),
radius_a: AtomicF32::new(radius_a.abs()),
radius_b: AtomicF32::new(radius_b.abs()),
};
<TorusField as FieldAspect>::add_node_members(node);
<TorusField as TorusFieldAspect>::add_node_members(node);
node.add_aspect(Field::Torus(torus_field));
torus_field.add_field_methods(node);
node.add_local_signal("set_size", TorusField::set_size_flex);
let _ = node.field.set(Arc::new(Field::Torus(torus_field)));
Ok(())
}
pub fn set_size(&self, radius_a: f32, radius_b: f32) {
self.radius_a.store(radius_a.abs(), Ordering::Relaxed);
self.radius_b.store(radius_b.abs(), Ordering::Relaxed);
}
pub fn set_size_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Field::Torus(torus_field) = node.field.get().unwrap().as_ref() else { return Ok(()) };
let (radius_a, radius_b) = deserialize(message.as_ref())?;
torus_field.set_size(radius_a, radius_b);
Ok(())
}
}
impl FieldTrait for TorusField {
fn local_distance(&self, p: Vec3A) -> f32 {
let radius_a = self.radius_a.load(Ordering::Relaxed);
@@ -43,18 +68,26 @@ impl FieldTrait for TorusField {
self.space.as_ref()
}
}
impl TorusFieldAspect for TorusField {
fn set_size(
node: Arc<Node>,
_calling_client: Arc<Client>,
pub fn create_torus_field_flex(
_node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
#[derive(Deserialize)]
struct CreateFieldInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
radius_a: f32,
radius_b: f32,
) -> Result<()> {
let this_field = node.get_aspect::<Field>()?;
let Field::Torus(this_field) = &*this_field else {
return Ok(());
};
this_field.set_size(radius_a, radius_b);
Ok(())
}
let info: CreateFieldInfo = deserialize(message.as_ref())?;
let node = Node::create(&calling_client, "/field", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, false);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
TorusField::add_to(&node, info.radius_a, info.radius_b)?;
Ok(())
}

View File

@@ -7,20 +7,25 @@ use color_eyre::eyre::Result;
use glam::{vec3, Mat4};
use std::sync::Arc;
use stereokit::StereoKitMultiThread;
use tracing::instrument;
lazy_static::lazy_static! {
static ref HMD: Arc<Node> = create();
}
fn create() -> Arc<Node> {
let node = Arc::new(Node::create_parent_name(&INTERNAL_CLIENT, "", "hmd", false));
Spatial::add_to(&node, None, Mat4::IDENTITY, false);
let node = Arc::new(Node::create(&INTERNAL_CLIENT, "", "hmd", false));
Spatial::add_to(&node, None, Mat4::IDENTITY, false).expect("Unable to make spatial for HMD");
node
}
#[instrument(level = "debug", name = "Update HMD Pose", skip(sk))]
pub fn frame(sk: &impl StereoKitMultiThread) {
let spatial = HMD.get_aspect::<Spatial>().unwrap();
let spatial = HMD
.spatial
.get()
.expect("Unable to get spatial to update HMD");
let hmd_pose = sk.input_head();
*spatial.transform.lock() = Mat4::from_scale_rotation_translation(
vec3(1.0, 1.0, 1.0),

View File

@@ -31,7 +31,7 @@ impl InputSpecialization for Hand {
}
fn serialize(
&self,
distance_link: &DistanceLink,
_distance_link: &DistanceLink,
local_to_handler_matrix: Mat4,
) -> InputDataType {
let mut hand = self.base;
@@ -68,10 +68,6 @@ impl InputSpecialization for Hand {
let (_, rotation, position) = joint_matrix.to_scale_rotation_translation();
joint.position = position.into();
joint.rotation = rotation.into();
joint.distance = distance_link
.handler
.field
.distance(&distance_link.handler.spatial, position.into());
}
InputDataType::Hand(Box::new(hand))

View File

@@ -9,22 +9,23 @@ use self::tip::Tip;
use super::{
alias::{Alias, AliasInfo},
fields::{find_field, Field, FIELD_ALIAS_INFO},
spatial::{parse_transform, Spatial},
Aspect, Message, Node,
spatial::{find_spatial_parent, parse_transform, Spatial},
Message, Node,
};
use crate::core::{client::Client, node_collections::LifeLinkedNodeMap};
use crate::{core::registry::Registry, nodes::spatial::Transform};
use color_eyre::eyre::Result;
use crate::core::{node_collections::LifeLinkedNodeList, registry::Registry};
use color_eyre::eyre::{ensure, Result};
use glam::Mat4;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use portable_atomic::AtomicBool;
use serde::Deserialize;
use stardust_xr::schemas::{flat::InputDataType, flex::serialize};
use stardust_xr::{
schemas::{flat::InputData, flex::deserialize},
values::Datamap,
use stardust_xr::schemas::{flat::InputData, flex::deserialize};
use stardust_xr::schemas::{
flat::{Datamap, InputDataType},
flex::serialize,
};
use stardust_xr::values::Transform;
use std::ops::Deref;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Weak};
@@ -76,6 +77,11 @@ impl InputMethod {
specialization: InputType,
datamap: Option<Datamap>,
) -> Result<Arc<InputMethod>> {
ensure!(
node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!"
);
node.add_local_signal("capture", InputMethod::capture_flex);
node.add_local_signal("set_datamap", InputMethod::set_datamap_flex);
node.add_local_signal("set_handlers", InputMethod::set_handlers_flex);
@@ -84,7 +90,7 @@ impl InputMethod {
node: Arc::downgrade(node),
uid: node.uid.clone(),
enabled: Mutex::new(true),
spatial: node.get_aspect::<Spatial>().unwrap().clone(),
spatial: node.spatial.get().unwrap().clone(),
specialization: Mutex::new(specialization),
captures: Registry::new(),
datamap: Mutex::new(datamap),
@@ -93,41 +99,30 @@ impl InputMethod {
};
for handler in INPUT_HANDLER_REGISTRY.get_valid_contents() {
method.handle_new_handler(&handler);
method.make_alias(&handler);
}
let method = INPUT_METHOD_REGISTRY.add(method);
node.add_aspect_raw(method.clone());
let _ = node.input_method.set(method.clone());
Ok(method)
}
fn get(node: &Node) -> Result<Arc<Self>> {
node.get_aspect::<Self>()
node.get_aspect("Input Method", "input method", |n| &n.input_method)
.cloned()
}
fn capture_flex(node: Arc<Node>, calling_client: Arc<Client>, message: Message) -> Result<()> {
let method = InputMethod::get(&node)?;
fn capture_flex(node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
let method = InputMethod::get(node)?;
let handler = InputHandler::find(&calling_client, deserialize(message.as_ref())?)?;
method.captures.add_raw(&handler);
node.send_remote_signal("capture", message)
}
fn set_datamap_flex(
node: Arc<Node>,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let method = InputMethod::get(&node)?;
method
.datamap
.lock()
.replace(Datamap::from_raw(message.data)?);
fn set_datamap_flex(node: &Node, _calling_client: Arc<Client>, message: Message) -> Result<()> {
let method = InputMethod::get(node)?;
method.datamap.lock().replace(Datamap::new(message.data)?);
Ok(())
}
fn set_handlers_flex(
node: Arc<Node>,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let method = InputMethod::get(&node)?;
fn set_handlers_flex(node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
let method = InputMethod::get(node)?;
let handler_paths: Vec<&str> = deserialize(message.as_ref())?;
let handlers: Vec<Weak<InputHandler>> = handler_paths
.into_iter()
@@ -142,59 +137,19 @@ impl InputMethod {
Ok(())
}
fn make_alias(&self, handler: &InputHandler) {
let Some(method_node) = self.node.upgrade() else {
return;
};
let Some(handler_node) = handler.node.upgrade() else {
return;
};
let Some(client) = handler_node.get_client() else {
return;
};
let Ok(method_alias) = Alias::create(
&client,
handler_node.get_path(),
&self.uid,
&method_node,
AliasInfo {
server_signals: vec!["capture"],
..Default::default()
},
) else {
return;
};
method_alias.enabled.store(false, Ordering::Relaxed);
handler
.method_aliases
.add(self as *const InputMethod as usize, &method_alias);
}
fn compare_distance(&self, to: &InputHandler) -> f32 {
let distance = self
.specialization
fn compare_distance(&self, to: &Field) -> f32 {
self.specialization
.lock()
.compare_distance(&self.spatial, &to.field);
if self.captures.contains(to) {
distance * 0.5
} else {
distance
}
.compare_distance(&self.spatial, to)
}
fn true_distance(&self, to: &Field) -> f32 {
self.specialization.lock().true_distance(&self.spatial, to)
}
fn handle_new_handler(&self, handler: &InputHandler) {
let Some(method_node) = self.node.upgrade() else {
return;
};
let Some(method_client) = method_node.get_client() else {
return;
};
let Some(handler_node) = handler.node.upgrade() else {
return;
};
let Some(method_node) = self.node.upgrade() else {return};
let Some(method_client) = method_node.get_client() else {return};
let Some(handler_node) = handler.node.upgrade() else {return};
// Receiver itself
let Ok(handler_alias) = Alias::create(
&method_client,
@@ -202,49 +157,38 @@ impl InputMethod {
handler.uid.as_str(),
&handler_node,
AliasInfo {
server_methods: vec!["get_transform"],
server_methods: vec!["getTransform"],
..Default::default()
},
) else {
return;
};
) else {return};
self.handler_aliases
.add(handler.uid.clone(), &handler_alias);
if let Some(handler_field_node) = handler.field.spatial_ref().node.upgrade() {
// Handler's field
let Ok(rx_field_alias) = Alias::create(
&method_client,
handler_alias.get_path(),
"field",
&handler_field_node,
FIELD_ALIAS_INFO.clone(),
) else {
return;
};
&method_client,
handler_alias.get_path(),
"field",
&handler_field_node,
FIELD_ALIAS_INFO.clone(),
) else {return};
self.handler_aliases
.add(handler.uid.clone() + "-field", &rx_field_alias);
}
let Ok(data) = serialize(&handler.uid) else {
return;
};
let Ok(data) = serialize(&handler.uid) else {return};
let _ = method_node.send_remote_signal("handler_created", data);
}
fn handle_drop_handler(&self, handler: &InputHandler) {
let uid = handler.uid.as_str();
self.handler_aliases.remove(uid);
self.handler_aliases.remove(&(uid.to_string() + "-field"));
let Some(tx_node) = self.node.upgrade() else {
return;
};
let Ok(data) = serialize(&uid) else { return };
let Some(tx_node) = self.node.upgrade() else {return};
let Ok(data) = serialize(&uid) else {return};
let _ = tx_node.send_remote_signal("handler_destroyed", data);
}
}
impl Aspect for InputMethod {
const NAME: &'static str = "InputMethod";
}
impl Drop for InputMethod {
fn drop(&mut self) {
INPUT_METHOD_REGISTRY.remove(self);
@@ -257,19 +201,32 @@ pub struct DistanceLink {
handler: Arc<InputHandler>,
}
impl DistanceLink {
fn from(method: Arc<InputMethod>, handler: Arc<InputHandler>) -> Self {
DistanceLink {
distance: method.compare_distance(&handler),
fn from(method: Arc<InputMethod>, handler: Arc<InputHandler>) -> Option<Self> {
let handler_node = handler.node.upgrade()?;
let method_alias = Alias::create(
&handler_node.get_client()?,
handler_node.get_path(),
&method.uid,
&method.node.upgrade()?,
AliasInfo {
server_signals: vec!["capture"],
..Default::default()
},
)
.ok()?;
handler.method_aliases.add(Arc::downgrade(&method_alias));
Some(DistanceLink {
distance: method.compare_distance(&handler.field),
method,
handler,
}
})
}
fn send_input(&self, order: u32, captured: bool, datamap: Datamap) {
self.handler.send_input(order, captured, self, datamap);
fn send_input(&self, order: u32, datamap: Datamap) {
self.handler.send_input(order, self, datamap);
}
#[instrument(level = "debug", skip(self))]
fn serialize(&self, order: u32, captured: bool, datamap: Datamap) -> Vec<u8> {
fn serialize(&self, order: u32, datamap: Datamap) -> Vec<u8> {
let input = self.method.specialization.lock().serialize(
self,
Spatial::space_to_space_matrix(Some(&self.method.spatial), Some(&self.handler.spatial)),
@@ -281,7 +238,6 @@ impl DistanceLink {
distance: self.method.true_distance(&self.handler.field),
datamap,
order,
captured,
};
root.serialize()
}
@@ -293,47 +249,44 @@ pub struct InputHandler {
node: Weak<Node>,
spatial: Arc<Spatial>,
field: Arc<Field>,
method_aliases: LifeLinkedNodeMap<usize>,
method_aliases: LifeLinkedNodeList,
}
impl InputHandler {
pub fn add_to(node: &Arc<Node>, field: &Arc<Field>) -> Result<()> {
ensure!(
node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!"
);
let handler = InputHandler {
enabled: node.enabled.clone(),
uid: node.uid.clone(),
node: Arc::downgrade(node),
spatial: node.get_aspect::<Spatial>().unwrap().clone(),
spatial: node.spatial.get().unwrap().clone(),
field: field.clone(),
method_aliases: LifeLinkedNodeMap::default(),
method_aliases: LifeLinkedNodeList::default(),
};
for method in INPUT_METHOD_REGISTRY.get_valid_contents() {
method.make_alias(&handler);
method.handle_new_handler(&handler);
}
let handler = INPUT_HANDLER_REGISTRY.add(handler);
node.add_aspect_raw(handler);
let _ = node.input_handler.set(handler);
Ok(())
}
fn find(client: &Client, path: &str) -> Result<Arc<Self>> {
client.get_node("Input Handler", path)?.get_aspect::<Self>()
InputHandler::get(&*client.get_node("Input Handler", path)?)
}
fn get(node: &Node) -> Result<Arc<Self>> {
node.get_aspect("Input Handler", "input handler", |n| &n.input_handler)
.cloned()
}
#[instrument(level = "debug", skip(self, distance_link))]
fn send_input(
&self,
order: u32,
captured: bool,
distance_link: &DistanceLink,
datamap: Datamap,
) {
let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal("input", distance_link.serialize(order, captured, datamap));
fn send_input(&self, order: u32, distance_link: &DistanceLink, datamap: Datamap) {
let Some(node) = self.node.upgrade() else {return};
let _ = node.send_remote_signal("input", distance_link.serialize(order, datamap));
}
}
impl Aspect for InputHandler {
const NAME: &'static str = "InputHandler";
}
impl PartialEq for InputHandler {
fn eq(&self, other: &Self) -> bool {
self.spatial == other.spatial
@@ -349,7 +302,7 @@ impl Drop for InputHandler {
}
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
let node = Node::create_path(client, "/input", false);
let node = Node::create(client, "", "input", false);
node.add_local_signal("create_input_handler", create_input_handler_flex);
node.add_local_signal("create_input_method_pointer", pointer::create_pointer_flex);
node.add_local_signal("create_input_method_tip", tip::create_tip_flex);
@@ -357,7 +310,7 @@ pub fn create_interface(client: &Arc<Client>) -> Result<()> {
}
pub fn create_input_handler_flex(
_node: Arc<Node>,
_node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
@@ -369,15 +322,13 @@ pub fn create_input_handler_flex(
field_path: &'a str,
}
let info: CreateInputHandlerInfo = deserialize(message.as_ref())?;
let parent = calling_client
.get_node("Spatial parent", info.parent_path)?
.get_aspect::<Spatial>()?;
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, true);
let field = find_field(&calling_client, info.field_path)?;
let node = Node::create_parent_name(&calling_client, "/input/handler", info.name, true)
.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, false);
let node =
Node::create(&calling_client, "/input/handler", info.name, true).add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, false)?;
InputHandler::add_to(&node, &field)?;
Ok(())
}
@@ -392,12 +343,10 @@ pub fn process_input() {
.filter(|method| method.datamap.lock().is_some())
});
let handlers = INPUT_HANDLER_REGISTRY.get_valid_contents();
const LIMIT: usize = 50;
for handler in &handlers {
handler.method_aliases.clear();
}
for method in methods {
for alias in method.node.upgrade().unwrap().aliases.get_valid_contents() {
alias.enabled.store(false, Ordering::Release);
}
debug_span!("Process input method").in_scope(|| {
// Get all valid input handlers and convert them to DistanceLink objects
let distance_links: Vec<DistanceLink> = debug_span!("Generate distance links")
@@ -408,16 +357,14 @@ pub fn process_input() {
.iter()
.filter_map(|h| h.upgrade())
.filter(|handler| handler.enabled.load(Ordering::Relaxed))
.map(|handler| DistanceLink::from(method.clone(), handler))
.filter_map(|handler| DistanceLink::from(method.clone(), handler))
.collect()
} else {
let mut distance_links: Vec<_> = handlers
.iter()
.filter(|handler| handler.enabled.load(Ordering::Relaxed))
.map(|handler| {
debug_span!("Create distance link").in_scope(|| {
DistanceLink::from(method.clone(), handler.clone())
})
.filter_map(|handler| {
DistanceLink::from(method.clone(), handler.clone())
})
.collect();
@@ -428,7 +375,6 @@ pub fn process_input() {
});
});
distance_links.truncate(LIMIT);
distance_links
}
});
@@ -436,24 +382,11 @@ pub fn process_input() {
let captures = method.captures.take_valid_contents();
// Iterate over the distance links and send input to them
for (i, distance_link) in distance_links.into_iter().enumerate() {
if let Some(method_alias) = distance_link
.handler
.method_aliases
.get(&(Arc::as_ptr(&distance_link.method) as usize))
.and_then(|a| a.get_aspect::<Alias>().ok())
{
method_alias.enabled.store(true, Ordering::Release);
}
let captured = captures.contains(&distance_link.handler);
distance_link.send_input(
i as u32,
captured,
method.datamap.lock().clone().unwrap(),
);
distance_link.send_input(i as u32, method.datamap.lock().clone().unwrap());
// If the current distance link is in the list of captured input handlers,
// break out of the loop to avoid sending input to the remaining distance links
if captured {
if captures.contains(&distance_link.handler) {
break;
}
}

View File

@@ -2,14 +2,13 @@ use super::{DistanceLink, InputSpecialization};
use crate::core::client::Client;
use crate::nodes::fields::{Field, Ray, RayMarchResult};
use crate::nodes::input::{InputMethod, InputType};
use crate::nodes::spatial::{parse_transform, Spatial, Transform};
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
use crate::nodes::{Message, Node};
use glam::{vec3, Mat4};
use serde::Deserialize;
use stardust_xr::schemas::flat::{InputDataType, Pointer as FlatPointer};
use stardust_xr::schemas::flat::{Datamap, InputDataType, Pointer as FlatPointer};
use stardust_xr::schemas::flex::deserialize;
use stardust_xr::values::Datamap;
use stardust_xr::values::Transform;
use std::sync::Arc;
#[derive(Default)]
@@ -35,13 +34,9 @@ impl Pointer {
impl InputSpecialization for Pointer {
fn compare_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
let ray_info = self.ray_march(space, field);
if ray_info.min_distance > 0.0 {
ray_info.deepest_point_distance + 1000.0
} else {
ray_info
.deepest_point_distance
.hypot(0.001 / ray_info.min_distance)
}
ray_info
.deepest_point_distance
.hypot(ray_info.min_distance.recip())
}
fn true_distance(&self, space: &Arc<Spatial>, field: &Field) -> f32 {
let ray_info = self.ray_march(space, field);
@@ -66,7 +61,7 @@ impl InputSpecialization for Pointer {
}
pub fn create_pointer_flex(
_node: Arc<Node>,
_node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> color_eyre::eyre::Result<()> {
@@ -78,19 +73,16 @@ pub fn create_pointer_flex(
datamap: Option<Vec<u8>>,
}
let info: CreatePointerInfo = deserialize(message.as_ref())?;
let node = Node::create_parent_name(&calling_client, "/input/method/pointer", info.name, true);
let parent = calling_client
.get_node("Spatial parent", info.parent_path)?
.get_aspect::<Spatial>()?;
let node = Node::create(&calling_client, "/input/method/pointer", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, false);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, false);
Spatial::add_to(&node, Some(parent), transform, false)?;
InputMethod::add_to(
&node,
InputType::Pointer(Pointer),
info.datamap
.and_then(|datamap| Datamap::from_raw(datamap).ok()),
info.datamap.and_then(|datamap| Datamap::new(datamap).ok()),
)?;
Ok(())
}

View File

@@ -2,15 +2,14 @@ use super::{DistanceLink, InputSpecialization};
use crate::core::client::Client;
use crate::nodes::fields::Field;
use crate::nodes::input::{InputMethod, InputType};
use crate::nodes::spatial::{parse_transform, Spatial, Transform};
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial};
use crate::nodes::{Message, Node};
use color_eyre::eyre::Result;
use glam::{vec3a, Mat4};
use serde::Deserialize;
use stardust_xr::schemas::flat::{InputDataType, Tip as FlatTip};
use stardust_xr::schemas::flat::{Datamap, InputDataType, Tip as FlatTip};
use stardust_xr::schemas::flex::deserialize;
use stardust_xr::values::Datamap;
use stardust_xr::values::Transform;
use std::sync::Arc;
#[derive(Default)]
@@ -18,9 +17,8 @@ pub struct Tip {
pub radius: f32,
}
impl Tip {
fn set_radius(node: Arc<Node>, _calling_client: Arc<Client>, message: Message) -> Result<()> {
let input_method = node.get_aspect::<InputMethod>()?;
if let InputType::Tip(tip) = &mut *input_method.specialization.lock() {
fn set_radius(node: &Node, _calling_client: Arc<Client>, message: Message) -> Result<()> {
if let InputType::Tip(tip) = &mut *node.input_method.get().unwrap().specialization.lock() {
tip.radius = deserialize(message.as_ref())?;
}
Ok(())
@@ -47,11 +45,7 @@ impl InputSpecialization for Tip {
}
}
pub fn create_tip_flex(
_node: Arc<Node>,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
pub fn create_tip_flex(_node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
#[derive(Deserialize)]
struct CreateTipInfo<'a> {
name: &'a str,
@@ -61,21 +55,18 @@ pub fn create_tip_flex(
datamap: Option<Vec<u8>>,
}
let info: CreateTipInfo = deserialize(message.as_ref())?;
let node = Node::create_parent_name(&calling_client, "/input/method/tip", info.name, true);
let parent = calling_client
.get_node("Spatial parent", info.parent_path)?
.get_aspect::<Spatial>()?;
let node = Node::create(&calling_client, "/input/method/tip", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, false);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, false);
Spatial::add_to(&node, Some(parent), transform, false)?;
InputMethod::add_to(
&node,
InputType::Tip(Tip {
radius: info.radius,
}),
info.datamap
.and_then(|datamap| Datamap::from_raw(datamap).ok()),
info.datamap.and_then(|datamap| Datamap::new(datamap).ok()),
)?;
node.add_local_signal("set_radius", Tip::set_radius);
Ok(())

View File

@@ -1,193 +0,0 @@
use super::{Item, ItemType};
use crate::{
core::{
client::{Client, INTERNAL_CLIENT},
registry::Registry,
scenegraph::MethodResponseSender,
},
nodes::{
drawable::{model::ModelPart, shaders::UNLIT_SHADER_BYTES},
items::TypeInfo,
spatial::{parse_transform, Spatial, Transform},
Message, Node,
},
};
use color_eyre::eyre::{bail, eyre, Result};
use glam::Mat4;
use lazy_static::lazy_static;
use mint::{RowMatrix4, Vector2};
use nanoid::nanoid;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use serde::Deserialize;
use stardust_xr::schemas::flex::{deserialize, serialize};
use std::sync::Arc;
use stereokit::{
Color128, Material, Rect, RenderLayer, StereoKitDraw, Tex, TextureType, Transparency,
};
lazy_static! {
pub(super) static ref ITEM_TYPE_INFO_CAMERA: TypeInfo = TypeInfo {
type_name: "camera",
aliased_local_signals: vec!["apply_preview_material", "frame"],
aliased_local_methods: vec![],
aliased_remote_signals: vec![],
ui: Default::default(),
items: Registry::new(),
acceptors: Registry::new(),
};
}
struct FrameInfo {
proj_matrix: Mat4,
px_size: Vector2<u32>,
}
pub struct CameraItem {
space: Arc<Spatial>,
frame_info: Mutex<FrameInfo>,
sk_tex: OnceCell<Tex>,
sk_mat: OnceCell<Arc<Material>>,
applied_to: Registry<ModelPart>,
apply_to: Registry<ModelPart>,
}
impl CameraItem {
pub fn add_to(node: &Arc<Node>, proj_matrix: Mat4, px_size: Vector2<u32>) {
Item::add_to(
node,
nanoid!(),
&ITEM_TYPE_INFO_CAMERA,
ItemType::Camera(CameraItem {
space: node.get_aspect::<Spatial>().unwrap().clone(),
frame_info: Mutex::new(FrameInfo {
proj_matrix,
px_size,
}),
sk_tex: OnceCell::new(),
sk_mat: OnceCell::new(),
applied_to: Registry::new(),
apply_to: Registry::new(),
}),
);
node.add_local_method("frame", CameraItem::frame_flex);
node.add_local_signal(
"apply_preview_material",
CameraItem::apply_preview_material_flex,
);
}
fn frame_flex(
node: Arc<Node>,
_calling_client: Arc<Client>,
_message: Message,
response: MethodResponseSender,
) {
response.wrap_sync(move || {
let ItemType::Camera(_camera) = &node.get_aspect::<Item>().unwrap().specialization
else {
return Err(eyre!("Wrong item type?"));
};
Ok(serialize(())?.into())
});
}
fn apply_preview_material_flex(
node: Arc<Node>,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let ItemType::Camera(camera) = &node.get_aspect::<Item>().unwrap().specialization else {
bail!("Wrong item type?")
};
let model_part_node =
calling_client.get_node("Model part", deserialize(&message.data).unwrap())?;
let model_part = model_part_node.get_aspect::<ModelPart>()?;
camera.applied_to.add_raw(&model_part);
camera.apply_to.add_raw(&model_part);
Ok(())
}
pub fn serialize_start_data(&self, id: &str) -> Result<Message> {
Ok(serialize(id)?.into())
}
pub fn update(&self, sk: &impl StereoKitDraw) {
let frame_info = self.frame_info.lock();
let sk_tex = self.sk_tex.get_or_init(|| {
sk.tex_gen_color(
Color128::default(),
frame_info.px_size.x as i32,
frame_info.px_size.y as i32,
TextureType::RENDER_TARGET,
stereokit::TextureFormat::RGBA32Linear,
)
});
let sk_mat = self.sk_mat.get_or_init(|| {
let shader = sk.shader_create_mem(&UNLIT_SHADER_BYTES).unwrap();
let mat = sk.material_create(&shader);
sk.material_set_texture(&mat, "diffuse", sk_tex.as_ref());
sk.material_set_transparency(&mat, Transparency::Blend);
Arc::new(mat)
});
for model_part in self.apply_to.take_valid_contents() {
model_part.replace_material(sk_mat.clone())
}
if !self.applied_to.is_empty() {
sk.render_to(
sk_tex,
frame_info.proj_matrix,
self.space.global_transform(),
RenderLayer::all(),
stereokit::RenderClear::All,
Rect {
x: 0.0,
y: 0.0,
w: 0.0,
h: 0.0,
},
);
}
}
}
pub fn update(sk: &impl StereoKitDraw) {
for camera in ITEM_TYPE_INFO_CAMERA.items.get_valid_contents() {
let ItemType::Camera(camera) = &camera.specialization else {
continue;
};
camera.update(sk);
}
}
pub(super) fn create_camera_item_flex(
_node: Arc<Node>,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
#[derive(Deserialize)]
struct CreateCameraItemInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
proj_matrix: RowMatrix4<f32>,
px_size: Vector2<u32>,
}
let info: CreateCameraItemInfo = deserialize(message.as_ref())?;
let parent_name = format!("/item/{}/item", ITEM_TYPE_INFO_CAMERA.type_name);
let space = calling_client
.get_node("Spatial parent", info.parent_path)?
.get_aspect::<Spatial>()?;
let transform = parse_transform(info.transform, true, true, false);
let node = Node::create_parent_name(&INTERNAL_CLIENT, &parent_name, info.name, false)
.add_to_scenegraph()?;
Spatial::add_to(&node, None, transform * space.global_transform(), false);
CameraItem::add_to(&node, info.proj_matrix.into(), info.px_size);
node.get_aspect::<Item>().unwrap().make_alias_named(
&calling_client,
&parent_name,
info.name,
)?;
Ok(())
}

View File

@@ -3,11 +3,10 @@ use crate::{
core::{
client::{Client, INTERNAL_CLIENT},
registry::Registry,
scenegraph::MethodResponseSender,
},
nodes::{
items::TypeInfo,
spatial::{parse_transform, Spatial, Transform},
spatial::{find_spatial_parent, parse_transform, Spatial},
Message, Node,
},
};
@@ -15,7 +14,10 @@ use color_eyre::eyre::{eyre, Result};
use lazy_static::lazy_static;
use nanoid::nanoid;
use serde::Deserialize;
use stardust_xr::schemas::flex::{deserialize, serialize};
use stardust_xr::{
schemas::flex::{deserialize, serialize},
values::Transform,
};
use std::sync::Arc;
lazy_static! {
@@ -45,19 +47,14 @@ impl EnvironmentItem {
}
fn get_path_flex(
node: Arc<Node>,
node: &Node,
_calling_client: Arc<Client>,
_message: Message,
response: MethodResponseSender,
) {
response.wrap_sync(move || {
let ItemType::Environment(environment_item) =
&node.get_aspect::<Item>().unwrap().specialization
else {
return Err(eyre!("Wrong item type?"));
};
Ok(serialize(environment_item.path.as_str())?.into())
});
) -> Result<Message> {
let ItemType::Environment(environment_item) = &node.item.get().unwrap().specialization else {
return Err(eyre!("Wrong item type?"))
};
Ok(serialize(environment_item.path.as_str())?.into())
}
pub fn serialize_start_data(&self, id: &str) -> Result<Message> {
@@ -66,7 +63,7 @@ impl EnvironmentItem {
}
pub(super) fn create_environment_item_flex(
_node: Arc<Node>,
_node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
@@ -79,19 +76,16 @@ pub(super) fn create_environment_item_flex(
}
let info: CreateEnvironmentItemInfo = deserialize(message.as_ref())?;
let parent_name = format!("/item/{}/item", ITEM_TYPE_INFO_ENVIRONMENT.type_name);
let space = calling_client
.get_node("Spatial parent", info.parent_path)?
.get_aspect::<Spatial>()?;
let space = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, false);
let node = Node::create_parent_name(&INTERNAL_CLIENT, &parent_name, info.name, false)
.add_to_scenegraph()?;
Spatial::add_to(&node, None, transform * space.global_transform(), false);
let node =
Node::create(&INTERNAL_CLIENT, &parent_name, info.name, false).add_to_scenegraph()?;
Spatial::add_to(&node, None, transform * space.global_transform(), false)?;
EnvironmentItem::add_to(&node, info.item_data);
node.get_aspect::<Item>().unwrap().make_alias_named(
&calling_client,
&parent_name,
info.name,
)?;
node.item
.get()
.unwrap()
.make_alias_named(&calling_client, &parent_name, info.name)?;
Ok(())
}

View File

@@ -1,19 +1,16 @@
pub mod camera;
mod environment;
pub mod panel;
use self::camera::CameraItem;
use self::environment::{EnvironmentItem, ITEM_TYPE_INFO_ENVIRONMENT};
use self::panel::{PanelItemTrait, ITEM_TYPE_INFO_PANEL};
use super::fields::Field;
use super::spatial::{parse_transform, Spatial};
use super::{Alias, Aspect, Message, Node};
use super::spatial::{find_spatial_parent, parse_transform, Spatial};
use super::{Alias, Message, Node};
use crate::core::client::Client;
use crate::core::node_collections::LifeLinkedNodeMap;
use crate::core::registry::Registry;
use crate::nodes::alias::AliasInfo;
use crate::nodes::fields::find_field;
use crate::nodes::spatial::Transform;
use color_eyre::eyre::{ensure, eyre, Result};
use lazy_static::lazy_static;
use nanoid::nanoid;
@@ -21,7 +18,7 @@ use parking_lot::Mutex;
use portable_atomic::Ordering;
use serde::Deserialize;
use stardust_xr::schemas::flex::{deserialize, serialize};
use stardust_xr::values::Transform;
use std::hash::Hash;
use std::sync::{Arc, Weak};
@@ -40,8 +37,8 @@ lazy_static! {
}
pub fn capture(item: &Arc<Item>, acceptor: &Arc<ItemAcceptor>) {
if item.captured_acceptor.lock().strong_count() > 0 {
release(item);
if let Some(acceptor) = item.captured_acceptor.lock().upgrade() {
release(item, Some(&acceptor));
}
*item.captured_acceptor.lock() = Arc::downgrade(acceptor);
acceptor.handle_capture(item);
@@ -49,9 +46,9 @@ pub fn capture(item: &Arc<Item>, acceptor: &Arc<ItemAcceptor>) {
ui.handle_capture_item(item, acceptor);
}
}
fn release(item: &Item) {
fn release(item: &Item, acceptor: Option<&ItemAcceptor>) {
let mut captured_acceptor = item.captured_acceptor.lock();
if let Some(acceptor) = captured_acceptor.upgrade().as_ref() {
if let Some(acceptor) = captured_acceptor.upgrade().as_deref().or(acceptor) {
*captured_acceptor = Weak::default();
acceptor.handle_release(item);
if let Some(ui) = item.type_info.ui.lock().upgrade() {
@@ -108,17 +105,17 @@ impl Item {
if let Some(ui) = type_info.ui.lock().upgrade() {
ui.handle_create_item(&item);
}
node.add_aspect_raw(item.clone());
let _ = node.item.set(item.clone());
// if let Some(auto_acceptor) = node.get_client().and_then(|client| {
// client
// .state
// .as_ref()
// .and_then(|settings| settings.acceptors.get(type_info))
// .and_then(|acceptor| acceptor.upgrade())
// }) {
// capture(&item, &auto_acceptor);
// }
if let Some(auto_acceptor) = node.get_client().and_then(|client| {
client
.startup_settings
.as_ref()
.and_then(|settings| settings.acceptors.get(type_info))
.and_then(|acceptor| acceptor.upgrade())
}) {
capture(&item, &auto_acceptor);
}
item
}
@@ -156,24 +153,17 @@ impl Item {
self.make_alias_named(client, parent, &self.uid)
}
fn release_flex(
node: Arc<Node>,
_calling_client: Arc<Client>,
_message: Message,
) -> Result<()> {
let item = node.get_aspect::<Item>()?;
release(&item);
fn release_flex(node: &Node, _calling_client: Arc<Client>, _message: Message) -> Result<()> {
let item = node.get_aspect("Item", "item", |n| &n.item)?;
release(item, None);
Ok(())
}
}
impl Aspect for Item {
const NAME: &'static str = "Item";
}
impl Drop for Item {
fn drop(&mut self) {
self.type_info.items.remove(self);
release(self);
release(self, None);
if let Some(ui) = self.type_info.ui.lock().upgrade() {
ui.handle_destroy_item(self);
}
@@ -181,14 +171,12 @@ impl Drop for Item {
}
pub enum ItemType {
Camera(CameraItem),
Environment(EnvironmentItem),
Panel(Arc<dyn PanelItemTrait>),
}
impl ItemType {
fn serialize_start_data(&self, id: &str) -> Result<Message> {
match self {
ItemType::Camera(c) => c.serialize_start_data(id),
ItemType::Environment(e) => e.serialize_start_data(id),
ItemType::Panel(p) => p.serialize_start_data(id),
}
@@ -227,7 +215,7 @@ impl ItemUI {
acceptor_field_aliases: Default::default(),
});
*type_info.ui.lock() = Arc::downgrade(&ui);
node.add_aspect_raw(ui.clone());
let _ = node.item_ui.set(ui.clone());
for item in type_info.items.get_valid_contents() {
ui.handle_create_item(&item);
@@ -238,9 +226,7 @@ impl ItemUI {
Ok(())
}
fn send_state(&self, state: &str, name: &str) {
let Ok(serialized_data) = serialize(name) else {
return;
};
let Ok(serialized_data) = serialize(name) else {return};
let _ = self
.node
.upgrade()
@@ -249,20 +235,14 @@ impl ItemUI {
}
fn handle_create_item(&self, item: &Item) {
let Some(node) = self.node.upgrade() else {
return;
};
let Some(client) = node.get_client() else {
return;
};
let Some(node) = self.node.upgrade() else {return};
let Some(client) = node.get_client() else {return};
if let Ok(alias_node) = item.make_alias(&client, &(node.get_path().to_string() + "/item")) {
self.item_aliases.add(item.uid.clone(), &alias_node);
}
let Ok(serialized_data) = item.specialization.serialize_start_data(&item.uid) else {
return;
};
let Ok(serialized_data) = item.specialization.serialize_start_data(&item.uid) else {return};
let _ = node.send_remote_signal("create_item", serialized_data);
}
fn handle_destroy_item(&self, item: &Item) {
@@ -270,45 +250,29 @@ impl ItemUI {
self.send_state("destroy_item", item.uid.as_str());
}
fn handle_capture_item(&self, item: &Item, acceptor: &ItemAcceptor) {
let Some(node) = self.node.upgrade() else {
return;
};
let Some(node) = self.node.upgrade() else {return};
let Ok(message) = serialize((item.uid.as_str(), acceptor.uid.as_str())) else {
return;
};
let Ok(message) = serialize((item.uid.as_str(), acceptor.uid.as_str())) else {return};
let _ = node.send_remote_signal("capture_item", message);
}
fn handle_release_item(&self, item: &Item, acceptor: &ItemAcceptor) {
let Some(node) = self.node.upgrade() else {
return;
};
let Some(node) = self.node.upgrade() else {return};
let Ok(message) = serialize((item.uid.as_str(), acceptor.uid.as_str())) else {
return;
};
let Ok(message) = serialize((item.uid.as_str(), acceptor.uid.as_str())) else {return};
let _ = node.send_remote_signal("release_item", message);
}
fn handle_create_acceptor(&self, acceptor: &ItemAcceptor) {
let Some(node) = self.node.upgrade() else {
return;
};
let Some(client) = node.get_client() else {
return;
};
let Some(node) = self.node.upgrade() else {return};
let Some(client) = node.get_client() else {return};
let Ok((alias, field_alias)) = acceptor.make_aliases(
&client,
&format!("/item/{}/acceptor", self.type_info.type_name),
) else {
return;
};
) else {return};
self.acceptor_aliases.add(acceptor.uid.clone(), &alias);
self.acceptor_field_aliases
.add(acceptor.uid.clone(), &field_alias);
let Ok(message) = serialize(&acceptor.uid) else {
return;
};
let Ok(message) = serialize(&acceptor.uid) else {return};
let _ = node.send_remote_signal("create_acceptor", message);
}
fn handle_destroy_acceptor(&self, acceptor: &ItemAcceptor) {
@@ -317,9 +281,6 @@ impl ItemUI {
self.acceptor_field_aliases.remove(&acceptor.uid);
}
}
impl Aspect for ItemUI {
const NAME: &'static str = "Item";
}
impl Drop for ItemUI {
fn drop(&mut self) {
*self.type_info.ui.lock() = Weak::new();
@@ -348,19 +309,19 @@ impl ItemAcceptor {
if let Some(ui) = type_info.ui.lock().upgrade() {
ui.handle_create_acceptor(&acceptor);
}
node.add_aspect_raw(acceptor);
let _ = node.item_acceptor.set(acceptor);
}
fn capture_flex(node: Arc<Node>, calling_client: Arc<Client>, message: Message) -> Result<()> {
fn capture_flex(node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
if !node.enabled.load(Ordering::Relaxed) {
return Ok(());
}
let acceptor = node.get_aspect::<ItemAcceptor>().unwrap();
let acceptor = node.item_acceptor.get().unwrap();
let item_path: &str = deserialize(message.as_ref())?;
let item_node = calling_client.get_node("Item", item_path)?;
let item = item_node.get_aspect::<Item>()?;
capture(&item, &acceptor);
let item = item_node.get_aspect("Item", "item", |n| &n.item)?;
capture(item, acceptor);
Ok(())
}
@@ -389,44 +350,31 @@ impl ItemAcceptor {
Ok((acceptor_alias, acceptor_field_alias))
}
fn handle_capture(&self, item: &Arc<Item>) {
let Some(node) = self.node.upgrade() else {
return;
};
let Some(client) = node.get_client() else {
return;
};
let Some(node) = self.node.upgrade() else {return};
let Some(client) = node.get_client() else {return};
self.accepted_registry.add_raw(item);
if let Ok(alias_node) = item.make_alias(&client, &node.path) {
self.accepted_aliases.add(item.uid.clone(), &alias_node);
}
let Ok(serialized_data) = item.specialization.serialize_start_data(&item.uid) else {
return;
};
let Ok(serialized_data) = item.specialization.serialize_start_data(&item.uid) else {return};
let _ = node.send_remote_signal("capture", serialized_data);
}
fn handle_release(&self, item: &Item) {
let Some(node) = self.node.upgrade() else {
return;
};
let Some(node) = self.node.upgrade() else {return};
self.accepted_registry.remove(item);
self.accepted_aliases.remove(&item.uid);
let Ok(message) = serialize(&item.uid) else {
return;
};
let Ok(message) = serialize(&item.uid) else {return};
let _ = node.send_remote_signal("release", message);
}
}
impl Aspect for ItemAcceptor {
const NAME: &'static str = "ItemAcceptor";
}
impl Drop for ItemAcceptor {
fn drop(&mut self) {
self.type_info.acceptors.remove(self);
for item in self.accepted_registry.get_valid_contents() {
release(&item);
release(&item, Some(self));
}
if let Some(ui) = self.type_info.ui.lock().upgrade() {
ui.handle_destroy_acceptor(self);
@@ -435,8 +383,7 @@ impl Drop for ItemAcceptor {
}
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
let node = Node::create_parent_name(client, "", "item", false);
node.add_local_signal("create_camera_item", camera::create_camera_item_flex);
let node = Node::create(client, "", "item", false);
node.add_local_signal(
"create_environment_item",
environment::create_environment_item_flex,
@@ -456,7 +403,7 @@ fn type_info(name: &str) -> Result<&'static TypeInfo> {
}
pub fn register_item_ui_flex(
_node: Arc<Node>,
_node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
@@ -466,14 +413,14 @@ pub fn register_item_ui_flex(
}
let info: RegisterItemUIInfo = deserialize(message.as_ref())?;
let type_info = type_info(info.item_type)?;
let ui = Node::create_parent_name(&calling_client, "/item", type_info.type_name, true)
.add_to_scenegraph()?;
let ui =
Node::create(&calling_client, "/item", type_info.type_name, true).add_to_scenegraph()?;
ItemUI::add_to(&ui, type_info)?;
Ok(())
}
fn create_item_acceptor_flex(
_node: Arc<Node>,
_node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
@@ -486,21 +433,19 @@ fn create_item_acceptor_flex(
item_type: &'a str,
}
let info: CreateItemAcceptorInfo = deserialize(message.as_ref())?;
let space = calling_client
.get_node("Reference space", info.parent_path)?
.get_aspect::<Spatial>()?;
let space = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, false);
let field = find_field(&calling_client, info.field_path)?;
let type_info = type_info(info.item_type)?;
let node = Node::create_parent_name(
let node = Node::create(
&calling_client,
&format!("/item/{}/acceptor", type_info.type_name),
info.name,
true,
)
.add_to_scenegraph()?;
Spatial::add_to(&node, Some(space.clone()), transform, false);
Spatial::add_to(&node, Some(space), transform, false)?;
ItemAcceptor::add_to(&node, type_info, field);
Ok(())
}

View File

@@ -1,21 +1,20 @@
use crate::{
core::{
client::{get_env, state, Client, INTERNAL_CLIENT},
client::{get_env, startup_settings, Client, INTERNAL_CLIENT},
registry::Registry,
},
nodes::{
drawable::model::ModelPart,
items::{Item, ItemType, TypeInfo},
drawable::{model::ModelPart, Drawable},
items::{self, Item, ItemType, TypeInfo},
spatial::Spatial,
Message, Node,
},
};
use color_eyre::eyre::{eyre, Result};
use color_eyre::eyre::{bail, eyre, Result};
use glam::Mat4;
use lazy_static::lazy_static;
use mint::Vector2;
use nanoid::nanoid;
use rustc_hash::FxHashMap;
use serde::{
de::{Deserializer, Error, SeqAccess, Visitor},
ser::Serializer,
@@ -23,40 +22,31 @@ use serde::{
};
use stardust_xr::schemas::flex::{deserialize, serialize};
use std::sync::{Arc, Weak};
use tracing::{debug, info};
use tracing::debug;
lazy_static! {
pub static ref ITEM_TYPE_INFO_PANEL: TypeInfo = TypeInfo {
type_name: "panel",
aliased_local_signals: vec![
"apply_surface_material",
"close_toplevel",
"auto_size_toplevel",
"set_toplevel_size",
"set_toplevel_focused_visuals",
"pointer_motion",
"pointer_button",
"configure_toplevel",
"set_toplevel_capabilities",
"pointer_scroll",
"keyboard_keymap",
"pointer_button",
"pointer_motion",
"keyboard_key",
"touch_down",
"touch_move",
"touch_up",
"reset_touches",
"keyboard_set_keymap_names",
"keyboard_set_keymap_string",
"close",
],
aliased_local_methods: vec![],
aliased_remote_signals: vec![
"toplevel_parent_changed",
"toplevel_title_changed",
"toplevel_app_id_changed",
"toplevel_fullscreen_active",
"toplevel_move_request",
"toplevel_resize_request",
"toplevel_size_changed",
"commit_toplevel",
"recommend_toplevel_state",
"set_cursor",
"new_child",
"reposition_child",
"drop_child",
"new_popup",
"reposition_popup",
"drop_popup",
],
ui: Default::default(),
items: Registry::new(),
@@ -70,7 +60,7 @@ lazy_static! {
pub enum SurfaceID {
Cursor,
Toplevel,
Child(String),
Popup(String),
}
impl Default for SurfaceID {
fn default() -> Self {
@@ -95,8 +85,8 @@ impl<'de> Visitor<'de> for SurfaceIDVisitor {
fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
let Some(discrim) = seq.next_element()? else {
return Err(A::Error::missing_field("discrim"));
};
return Err(A::Error::missing_field("discrim"));
};
// idk if you wanna check for extraneous elements
// I didn't bother
@@ -104,15 +94,15 @@ impl<'de> Visitor<'de> for SurfaceIDVisitor {
match discrim {
"Cursor" => Ok(SurfaceID::Cursor),
"Toplevel" => Ok(SurfaceID::Toplevel),
"Child" => {
"Popup" => {
let Some(text) = seq.next_element()? else {
return Err(A::Error::missing_field("child_text"));
};
Ok(SurfaceID::Child(text))
return Err(A::Error::missing_field("popup_text"));
};
Ok(SurfaceID::Popup(text))
}
_ => Err(A::Error::unknown_variant(
discrim,
&["Cursor", "Toplevel", "Child"],
&["Cursor", "Toplevel", "Popup"],
)),
}
}
@@ -123,68 +113,33 @@ impl serde::Serialize for SurfaceID {
match self {
Self::Cursor => ["Cursor"].serialize(serializer),
Self::Toplevel => ["Toplevel"].serialize(serializer),
Self::Child(text) => ["Child", text].serialize(serializer),
Self::Popup(text) => ["Popup", text].serialize(serializer),
}
}
}
/// The origin and size of the surface's "solid" part.
#[derive(Debug, Serialize, Clone, Copy)]
pub struct Geometry {
pub origin: Vector2<i32>,
pub size: Vector2<u32>,
}
/// The state of the panel item's toplevel.
#[derive(Debug, Clone, Serialize)]
pub struct ToplevelInfo {
/// The UID of the panel item of the parent of this toplevel, if it exists
pub parent: Option<String>,
/// Equivalent to the window title
pub title: Option<String>,
/// Application identifier, see <https://standards.freedesktop.org/desktop-entry-spec/>
pub app_id: Option<String>,
/// Current size in pixels
pub size: Vector2<u32>,
/// Recommended minimum size in pixels
pub min_size: Option<Vector2<u32>>,
/// Recommended maximum size in pixels
pub max_size: Option<Vector2<u32>>,
/// Surface geometry
pub logical_rectangle: Geometry,
}
/// Data on positioning a child
#[derive(Debug, Clone, Serialize)]
pub struct ChildInfo {
pub parent: SurfaceID,
pub geometry: Geometry,
}
/// The init data for the panel item.
#[derive(Debug, Clone, Serialize)]
pub struct PanelItemInitData {
/// The cursor, if applicable.
pub cursor: Option<Geometry>,
/// Size of the toplevel surface in pixels.
pub toplevel: ToplevelInfo,
/// Vector of childs that already exist
pub children: FxHashMap<String, ChildInfo>,
/// The surface, if any, that has exclusive input to the pointer.
pub pointer_grab: Option<SurfaceID>,
/// The surface, if any, that has exclusive input to the keyboard.
pub keyboard_grab: Option<SurfaceID>,
#[derive(Debug, Clone, Copy, Serialize)]
#[serde(tag = "type", content = "content")]
pub enum RecommendedState {
Maximize(bool),
Fullscreen(bool),
Minimize,
Move,
Resize(u32),
}
pub trait Backend: Send + Sync + 'static {
fn start_data(&self) -> Result<PanelItemInitData>;
fn serialize_start_data(&self, id: &str) -> Result<Message>;
fn serialize_toplevel(&self) -> Result<Message>;
fn set_toplevel_capabilities(&self, capabilities: Vec<u8>);
fn configure_toplevel(
&self,
size: Option<Vector2<u32>>,
states: Vec<u32>,
bounds: Option<Vector2<u32>>,
);
fn apply_surface_material(&self, surface: SurfaceID, model_part: &Arc<ModelPart>);
fn close_toplevel(&self);
fn auto_size_toplevel(&self);
fn set_toplevel_size(&self, size: Vector2<u32>);
fn set_toplevel_focused_visuals(&self, focused: bool);
fn pointer_motion(&self, surface: &SurfaceID, position: Vector2<f32>);
fn pointer_button(&self, surface: &SurfaceID, button: u32, pressed: bool);
fn pointer_scroll(
@@ -194,24 +149,18 @@ pub trait Backend: Send + Sync + 'static {
scroll_steps: Option<Vector2<f32>>,
);
fn keyboard_keys(&self, surface: &SurfaceID, keymap_id: &str, keys: Vec<i32>);
fn touch_down(&self, surface: &SurfaceID, id: u32, position: Vector2<f32>);
fn touch_move(&self, id: u32, position: Vector2<f32>);
fn touch_up(&self, id: u32);
fn reset_touches(&self);
fn keyboard_set_keymap(&self, keymap: &str) -> Result<()>;
fn keyboard_key(&self, surface: &SurfaceID, key: u32, state: bool);
}
pub fn panel_item_from_node(node: &Node) -> Option<Arc<dyn PanelItemTrait>> {
let ItemType::Panel(panel_item) = &node.get_aspect::<Item>().ok()?.specialization else {
return None;
};
let ItemType::Panel(panel_item) = &node.item.get()?.specialization else {return None};
Some(panel_item.clone())
}
pub trait PanelItemTrait: Backend + Send + Sync + 'static {
fn uid(&self) -> &str;
fn serialize_start_data(&self, id: &str) -> Result<Message>;
// fn node(&self) -> Option<Arc<Node>>;
}
pub struct PanelItem<B: Backend + ?Sized> {
@@ -225,18 +174,17 @@ impl<B: Backend + ?Sized> PanelItem<B> {
let startup_settings = pid
.and_then(|pid| get_env(pid).ok())
.and_then(|env| state(&env));
.and_then(|env| startup_settings(&env));
let uid = nanoid!();
let node = Arc::new(Node::create_parent_name(
&INTERNAL_CLIENT,
"/item/panel/item",
&uid,
true,
));
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false);
let node = Node::create(&INTERNAL_CLIENT, "/item/panel/item", &uid, true)
.add_to_scenegraph()
.unwrap();
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false).unwrap();
if let Some(startup_settings) = &startup_settings {
spatial.set_local_transform(startup_settings.root);
spatial.set_local_transform(
spatial.global_transform().inverse() * startup_settings.transform,
);
}
let panel_item = Arc::new(PanelItem {
@@ -246,149 +194,59 @@ impl<B: Backend + ?Sized> PanelItem<B> {
});
let generic_panel_item: Arc<dyn PanelItemTrait> = panel_item.clone();
Item::add_to(
let item = Item::add_to(
&node,
uid,
&ITEM_TYPE_INFO_PANEL,
ItemType::Panel(generic_panel_item),
);
// panel_item
// .seat_data
// .new_surface(&wl_surface, Arc::downgrade(&panel_item));
if let Some(startup_settings) = &startup_settings {
if let Some(acceptor) = startup_settings
.acceptors
.get(&*ITEM_TYPE_INFO_PANEL)
.and_then(|acc| acc.upgrade())
{
items::capture(&item, &acceptor);
}
}
node.add_local_signal("apply_surface_material", Self::apply_surface_material_flex);
node.add_local_signal("close_toplevel", Self::close_toplevel_flex);
node.add_local_signal("auto_size_toplevel", Self::auto_size_toplevel_flex);
node.add_local_signal("set_toplevel_size", Self::set_toplevel_size_flex);
node.add_local_signal("pointer_motion", Self::pointer_motion_flex);
node.add_local_signal("pointer_button", Self::pointer_button_flex);
node.add_local_signal("configure_toplevel", Self::configure_toplevel_flex);
node.add_local_signal(
"set_toplevel_capabilities",
Self::set_toplevel_capabilities_flex,
);
node.add_local_signal("pointer_scroll", Self::pointer_scroll_flex);
node.add_local_signal("pointer_button", Self::pointer_button_flex);
node.add_local_signal("pointer_motion", Self::pointer_motion_flex);
node.add_local_signal("keyboard_key", Self::keyboard_keys_flex);
node.add_local_signal("touch_down", Self::touch_down_flex);
node.add_local_signal("touch_move", Self::touch_move_flex);
node.add_local_signal("touch_up", Self::touch_up_flex);
node.add_local_signal("reset_touches", Self::reset_touches_flex);
node.add_local_signal(
"keyboard_set_keymap_string",
Self::keyboard_set_keymap_string_flex,
);
// node.add_local_signal(
// "keyboard_set_keymap_names",
// Self::keyboard_set_keymap_names_flex,
// );
node.add_local_signal("keyboard_key", Self::keyboard_key_flex);
(node, panel_item)
}
pub fn drop_toplevel(&self) {
let Some(node) = self.node.upgrade() else {
return;
};
node.destroy();
}
}
// Remote signals
impl<B: Backend + ?Sized> PanelItem<B> {
pub fn toplevel_parent_changed(&self, parent: &str) {
let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal("toplevel_parent_changed", serialize(parent).unwrap());
}
pub fn toplevel_title_changed(&self, title: &str) {
let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal("toplevel_title_changed", serialize(title).unwrap());
}
pub fn toplevel_app_id_changed(&self, app_id: &str) {
let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal("toplevel_app_id_changed", serialize(app_id).unwrap());
}
pub fn toplevel_fullscreen_active(&self, active: bool) {
let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal("toplevel_fullscreen_active", serialize(active).unwrap());
}
pub fn toplevel_move_request(&self) {
let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal("toplevel_move_request", Vec::<u8>::new());
}
pub fn toplevel_resize_request(&self, up: bool, down: bool, left: bool, right: bool) {
let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal(
"toplevel_resize_request",
serialize((up, down, left, right)).unwrap(),
);
}
pub fn toplevel_size_changed(&self, size: Vector2<u32>) {
let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal("toplevel_size_changed", serialize(size).unwrap());
pub fn node(&self) -> Option<Arc<Node>> {
self.node.upgrade()
}
pub fn set_cursor(&self, geometry: Option<Geometry>) {
let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal("set_cursor", serialize(geometry).unwrap());
}
pub fn new_child(&self, uid: &str, info: ChildInfo) {
let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal("new_child", serialize((uid, info)).unwrap());
}
pub fn reposition_child(&self, uid: &str, geometry: Geometry) {
let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal("reposition_child", serialize((uid, geometry)).unwrap());
}
pub fn drop_child(&self, uid: &str) {
let Some(node) = self.node.upgrade() else {
return;
};
let _ = node.send_remote_signal("drop_child", serialize(uid).unwrap());
}
}
// Local signals
macro_rules! flex_no_args {
($fn_name: ident, $trait_fn: ident) => {
fn $fn_name(
node: Arc<Node>,
_calling_client: Arc<Client>,
_message: Message,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
panel_item.$trait_fn();
Ok(())
}
};
}
macro_rules! flex_deserialize {
($fn_name: ident, $trait_fn: ident) => {
fn $fn_name(node: Arc<Node>, _calling_client: Arc<Client>, message: Message) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
panel_item.$trait_fn(deserialize(message.as_ref())?);
Ok(())
}
};
}
impl<B: Backend + ?Sized> PanelItem<B> {
fn apply_surface_material_flex(
node: Arc<Node>,
node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) };
#[derive(Debug, Deserialize)]
struct SurfaceMaterialInfo<'a> {
@@ -402,26 +260,20 @@ impl<B: Backend + ?Sized> PanelItem<B> {
.scenegraph
.get_node(info.model_node_path)
.ok_or_else(|| eyre!("Model node not found"))?;
let model_part = model_node.get_aspect::<ModelPart>()?;
let Some(Drawable::ModelPart(model_part)) = model_node.drawable.get() else {bail!("Node is not a model")};
debug!(?info, "Apply surface material");
panel_item.apply_surface_material(info.surface, &model_part);
panel_item.apply_surface_material(info.surface, model_part);
Ok(())
}
flex_no_args!(close_toplevel_flex, close_toplevel);
flex_no_args!(auto_size_toplevel_flex, auto_size_toplevel);
flex_deserialize!(set_toplevel_size_flex, set_toplevel_size);
fn pointer_motion_flex(
node: Arc<Node>,
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) };
let (surface_id, position): (SurfaceID, Vector2<f32>) = deserialize(message.as_ref())?;
debug!(?surface_id, ?position, "Pointer deactivate");
@@ -431,28 +283,24 @@ impl<B: Backend + ?Sized> PanelItem<B> {
Ok(())
}
fn pointer_button_flex(
node: Arc<Node>,
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) };
let (surface_id, button, state): (SurfaceID, u32, u32) = deserialize(message.as_ref())?;
debug!(?surface_id, button, state, "Pointer button");
panel_item.pointer_button(&surface_id, button, state != 0);
panel_item.pointer_button(&surface_id, button, state == 0);
Ok(())
}
fn pointer_scroll_flex(
node: Arc<Node>,
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) };
#[derive(Debug, Deserialize)]
struct PointerScrollInfo {
@@ -468,104 +316,172 @@ impl<B: Backend + ?Sized> PanelItem<B> {
Ok(())
}
fn keyboard_keys_flex(
node: Arc<Node>,
fn keyboard_set_keymap_string_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
let (surface_id, keymap_id, keys): (SurfaceID, &str, Vec<i32>) =
deserialize(message.as_ref())?;
debug!(?keys, "Set keyboard key state");
let keymap_string: &str = deserialize(message.as_ref())?;
panel_item.keyboard_keys(&surface_id, keymap_id, keys);
let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) };
debug!("Keyboard set keymap");
panel_item.keyboard_set_keymap(keymap_string)
// PanelItem::keyboard_set_keymap_flex(node, &keymap)
}
// fn keyboard_set_keymap_names_flex(
// node: &Node,
// _calling_client: Arc<Client>,
// message: Message,
// ) -> Result<()> {
// #[derive(Debug, Deserialize)]
// struct Names<'a> {
// rules: &'a str,
// model: &'a str,
// layout: &'a str,
// variant: &'a str,
// options: Option<String>,
// }
// let names: Names = deserialize(message.as_ref())?;
// let context = xkb::Context::new(0);
// let keymap = Keymap::new_from_names(
// &context,
// names.rules,
// names.model,
// names.layout,
// names.variant,
// names.options,
// XKB_KEYMAP_FORMAT_TEXT_V1,
// )
// .ok_or_else(|| eyre!("Keymap is not valid"))?;
// PanelItem::keyboard_set_keymap_flex(node, &keymap)
// }
// fn keyboard_set_keymap_flex(node: &Node, keymap: &str) -> Result<()> {
// let Some(panel_item): Option<Arc<PanelItem<dyn WaylandBackend>>> = panel_item_from_node(node) else { return Ok(()) };
// debug!("Keyboard set keymap");
// panel_item.seat_data.set_keymap(
// keymap,
// match &panel_item {
// Backend::Wayland(w) => w.input_surfaces(),
// #[cfg(feature = "xwayland")]
// Backend::X11(_) => panel_item
// .toplevel_wl_surface()
// .map(|s| vec![s])
// .unwrap_or_default(),
// },
// );
// Ok(())
// }
fn keyboard_key_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) };
let (surface_id, key, state): (SurfaceID, u32, u32) = deserialize(message.as_ref())?;
debug!(key, state, "Set keyboard key state");
panel_item.keyboard_key(&surface_id, key, state == 0);
Ok(())
}
pub fn grab_keyboard(&self, sid: Option<SurfaceID>) {
let Some(node) = self.node.upgrade() else {
return;
};
let Some(node) = self.node.upgrade() else {return};
let Ok(message) = serialize(sid) else { return };
let Ok(message) = serialize(sid) else {return};
let _ = node.send_remote_signal("grab_keyboard", message);
}
fn touch_down_flex(
node: Arc<Node>,
fn configure_toplevel_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) };
let (surface_id, id, position): (SurfaceID, u32, Vector2<f32>) =
deserialize(message.as_ref())?;
debug!(?surface_id, id, ?position, "Touch down");
panel_item.touch_down(&surface_id, id, position);
#[derive(Debug, Deserialize)]
struct ConfigureToplevelInfo {
size: Option<Vector2<u32>>,
states: Vec<u32>,
bounds: Option<Vector2<u32>>,
}
let info: ConfigureToplevelInfo = deserialize(message.as_ref())?;
panel_item.configure_toplevel(info.size, info.states, info.bounds);
Ok(())
}
fn touch_move_flex(
node: Arc<Node>,
fn set_toplevel_capabilities_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(panel_item) = panel_item_from_node(&node) else {
return Ok(());
};
let Some(panel_item) = panel_item_from_node(node) else { return Ok(()) };
let (id, position): (u32, Vector2<f32>) = deserialize(message.as_ref())?;
debug!(?position, "Touch move");
panel_item.touch_move(id, position);
let capabilities: Vec<u8> = deserialize(message.as_ref())?;
debug!("Set toplevel capabilities");
panel_item.set_toplevel_capabilities(capabilities);
Ok(())
}
flex_deserialize!(touch_up_flex, touch_up);
flex_no_args!(reset_touches_flex, reset_touches);
pub fn commit_toplevel(&self) {
debug!("Commit toplevel");
let Some(node) = self.node.upgrade() else {return};
let Ok(data) = self.backend.serialize_toplevel() else {return};
let _ = node.send_remote_signal("commit_toplevel", data);
}
pub fn recommend_toplevel_state(&self, state: RecommendedState) {
let Some(node) = self.node.upgrade() else {return};
let data = serialize(state).unwrap();
debug!(?state, "Recommend toplevel state");
let _ = node.send_remote_signal("recommend_toplevel_state", data);
}
}
impl<B: Backend + ?Sized> PanelItemTrait for PanelItem<B> {
fn uid(&self) -> &str {
&self.uid
}
fn serialize_start_data(&self, id: &str) -> Result<Message> {
Ok(serialize((id, self.start_data()?))?.into())
}
}
impl<B: Backend + ?Sized> Backend for PanelItem<B> {
fn start_data(&self) -> Result<PanelItemInitData> {
self.backend.start_data()
fn serialize_start_data(&self, id: &str) -> Result<Message> {
self.backend.serialize_start_data(id)
}
fn serialize_toplevel(&self) -> Result<Message> {
self.backend.serialize_toplevel()
}
fn set_toplevel_capabilities(&self, capabilities: Vec<u8>) {
self.backend.set_toplevel_capabilities(capabilities)
}
fn configure_toplevel(
&self,
size: Option<Vector2<u32>>,
states: Vec<u32>,
bounds: Option<Vector2<u32>>,
) {
self.backend.configure_toplevel(size, states, bounds)
}
fn apply_surface_material(&self, surface: SurfaceID, model_part: &Arc<ModelPart>) {
self.backend.apply_surface_material(surface, model_part)
}
fn close_toplevel(&self) {
self.backend.close_toplevel()
}
fn auto_size_toplevel(&self) {
self.backend.auto_size_toplevel()
}
fn set_toplevel_size(&self, size: Vector2<u32>) {
self.backend.set_toplevel_size(size)
}
fn set_toplevel_focused_visuals(&self, focused: bool) {
self.backend.set_toplevel_focused_visuals(focused)
}
fn pointer_motion(&self, surface: &SurfaceID, position: Vector2<f32>) {
self.backend.pointer_motion(surface, position)
}
fn pointer_button(&self, surface: &SurfaceID, button: u32, pressed: bool) {
self.backend.pointer_button(surface, button, pressed)
}
fn pointer_scroll(
&self,
surface: &SurfaceID,
@@ -576,26 +492,16 @@ impl<B: Backend + ?Sized> Backend for PanelItem<B> {
.pointer_scroll(surface, scroll_distance, scroll_steps)
}
fn keyboard_keys(&self, surface: &SurfaceID, keymap_id: &str, keys: Vec<i32>) {
self.backend.keyboard_keys(surface, keymap_id, keys)
fn keyboard_set_keymap(&self, keymap: &str) -> Result<()> {
self.backend.keyboard_set_keymap(keymap)
}
fn touch_down(&self, surface: &SurfaceID, id: u32, position: Vector2<f32>) {
self.backend.touch_down(surface, id, position)
}
fn touch_move(&self, id: u32, position: Vector2<f32>) {
self.backend.touch_move(id, position)
}
fn touch_up(&self, id: u32) {
self.backend.touch_up(id)
}
fn reset_touches(&self) {
self.backend.reset_touches()
fn keyboard_key(&self, surface: &SurfaceID, key: u32, state: bool) {
self.backend.keyboard_key(surface, key, state)
}
}
impl<B: Backend + ?Sized> Drop for PanelItem<B> {
fn drop(&mut self) {
// Dropped panel item, basically just a debug breakpoint place
info!("Dropped panel item {}", self.uid);
}
}

View File

@@ -8,29 +8,41 @@ pub mod input;
pub mod items;
pub mod root;
pub mod spatial;
pub mod startup;
use color_eyre::eyre::{eyre, Result};
use core::hash::BuildHasherDefault;
use dashmap::DashMap;
use nanoid::nanoid;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use portable_atomic::{AtomicBool, Ordering};
use rustc_hash::FxHashMap;
use serde::{de::DeserializeOwned, Serialize};
use rustc_hash::FxHasher;
use stardust_xr::messenger::MessageSenderHandle;
use stardust_xr::scenegraph::ScenegraphError;
use stardust_xr::schemas::flex::{deserialize, serialize};
use std::any::{Any, TypeId};
use stardust_xr::schemas::flex::deserialize;
use std::fmt::Debug;
use std::os::fd::OwnedFd;
use std::sync::{Arc, Weak};
use std::vec::Vec;
use tracing::{debug_span, instrument};
use crate::core::client::Client;
use crate::core::registry::Registry;
use crate::core::scenegraph::MethodResponseSender;
#[cfg(feature = "openxr_runtime")]
use crate::openxr;
use self::alias::Alias;
use self::audio::Sound;
use self::data::{PulseReceiver, PulseSender};
use self::drawable::Drawable;
use self::fields::Field;
use self::input::{InputHandler, InputMethod};
use self::items::{Item, ItemAcceptor, ItemUI};
use self::spatial::zone::Zone;
use self::spatial::Spatial;
use self::startup::StartupSettings;
#[derive(Default)]
pub struct Message {
pub data: Vec<u8>,
pub fds: Vec<OwnedFd>,
@@ -49,10 +61,8 @@ impl AsRef<[u8]> for Message {
}
}
pub type Signal = fn(Arc<Node>, Arc<Client>, Message) -> Result<()>;
pub type Method = fn(Arc<Node>, Arc<Client>, Message, MethodResponseSender);
stardust_xr_server_codegen::codegen_node_protocol!();
pub type Signal = fn(&Node, Arc<Client>, Message) -> Result<()>;
pub type Method = fn(&Node, Arc<Client>, Message) -> Result<Message>;
pub struct Node {
pub enabled: Arc<AtomicBool>,
@@ -61,12 +71,44 @@ pub struct Node {
client: Weak<Client>,
message_sender_handle: Option<MessageSenderHandle>,
// trailing_slash_pos: usize,
local_signals: Mutex<FxHashMap<String, Signal>>,
local_methods: Mutex<FxHashMap<String, Method>>,
aliases: Registry<Alias>,
aspects: Aspects,
local_signals: DashMap<String, Signal, BuildHasherDefault<FxHasher>>,
local_methods: DashMap<String, Method, BuildHasherDefault<FxHasher>>,
destroyable: bool,
pub alias: OnceCell<Arc<Alias>>,
aliases: Registry<Alias>,
pub spatial: OnceCell<Arc<Spatial>>,
pub field: OnceCell<Arc<Field>>,
pub zone: OnceCell<Arc<Zone>>,
// Data
pub pulse_sender: OnceCell<Arc<PulseSender>>,
pub pulse_receiver: OnceCell<Arc<PulseReceiver>>,
// Drawable
pub drawable: OnceCell<Drawable>,
// Input
pub input_method: OnceCell<Arc<InputMethod>>,
pub input_handler: OnceCell<Arc<InputHandler>>,
// Item
pub item: OnceCell<Arc<Item>>,
pub item_acceptor: OnceCell<Arc<ItemAcceptor>>,
pub item_ui: OnceCell<Arc<ItemUI>>,
// Sound
pub sound: OnceCell<Arc<Sound>>,
// Startup
pub startup_settings: OnceCell<Mutex<StartupSettings>>,
// OpenXR
#[cfg(feature = "openxr_runtime")]
pub openxr_object: OnceCell<openxr::Object>,
}
impl Node {
pub fn get_client(&self) -> Option<Arc<Client>> {
self.client.upgrade()
@@ -78,32 +120,42 @@ impl Node {
self.path.as_str()
}
pub fn create_parent_name(
client: &Arc<Client>,
parent: &str,
name: &str,
destroyable: bool,
) -> Self {
pub fn create(client: &Arc<Client>, parent: &str, name: &str, destroyable: bool) -> Self {
let mut path = parent.to_string();
path.push('/');
path.push_str(name);
Self::create_path(client, path, destroyable)
}
pub fn create_path(client: &Arc<Client>, path: impl ToString, destroyable: bool) -> Self {
let node = Node {
enabled: Arc::new(AtomicBool::new(true)),
uid: nanoid!(),
client: Arc::downgrade(client),
message_sender_handle: client.message_sender_handle.clone(),
path: path.to_string(),
path,
// trailing_slash_pos: parent.len(),
local_signals: Default::default(),
local_methods: Default::default(),
aliases: Default::default(),
aspects: Default::default(),
destroyable,
alias: OnceCell::new(),
aliases: Registry::new(),
spatial: OnceCell::new(),
field: OnceCell::new(),
zone: OnceCell::new(),
pulse_sender: OnceCell::new(),
pulse_receiver: OnceCell::new(),
drawable: OnceCell::new(),
input_method: OnceCell::new(),
input_handler: OnceCell::new(),
item: OnceCell::new(),
item_acceptor: OnceCell::new(),
item_ui: OnceCell::new(),
sound: OnceCell::new(),
#[cfg(feature = "openxr_runtime")]
openxr_object: OnceCell::new(),
startup_settings: OnceCell::new(),
};
<Node as NodeAspect>::add_node_members(&node);
node.add_local_signal("set_enabled", Node::set_enabled_flex);
node.add_local_signal("destroy", Node::destroy_flex);
node
}
pub fn add_to_scenegraph(self) -> Result<Arc<Node>> {
@@ -119,44 +171,54 @@ impl Node {
}
}
// very much up for debate if we should allow this, as you can match objects using this
// pub fn get_client_pid_flex(
// node: Arc<Node>,
// _calling_client: Arc<Client>,
// _message: Message,
// ) -> Result<Message> {
// let client = node
// .client
// .upgrade()
// .ok_or_else(|| eyre!("Could not get client for node?"))?;
// let pid = client.pid.ok_or_else(|| eyre!("Client PID is unknown"))?;
// Ok(serialize(pid)?.into())
// }
pub fn set_enabled_flex(
node: &Node,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
node.enabled
.store(deserialize(message.as_ref())?, Ordering::Relaxed);
Ok(())
}
pub fn destroy_flex(
node: &Node,
_calling_client: Arc<Client>,
_message: Message,
) -> Result<()> {
if node.destroyable {
node.destroy();
}
Ok(())
}
pub fn add_local_signal(&self, name: &str, signal: Signal) {
self.local_signals.lock().insert(name.to_string(), signal);
self.local_signals.insert(name.to_string(), signal);
}
pub fn add_local_method(&self, name: &str, method: Method) {
self.local_methods.lock().insert(name.to_string(), method);
self.local_methods.insert(name.to_string(), method);
}
pub fn add_aspect<A: Aspect>(&self, aspect: A) -> Arc<A> {
self.aspects.add(aspect)
}
pub fn add_aspect_raw<A: Aspect>(&self, aspect: Arc<A>) {
self.aspects.add_raw(aspect)
}
pub fn get_aspect<A: Aspect>(&self) -> Result<Arc<A>> {
self.aspects.get()
pub fn get_aspect<F, T>(
&self,
node_name: &'static str,
aspect_type: &'static str,
aspect_fn: F,
) -> Result<&T>
where
F: FnOnce(&Node) -> &OnceCell<T>,
{
aspect_fn(self)
.get()
.ok_or_else(|| eyre!("{} is not a {} node", node_name, aspect_type))
}
pub fn send_local_signal(
self: Arc<Self>,
&self,
calling_client: Arc<Client>,
method: &str,
message: Message,
) -> Result<(), ScenegraphError> {
if let Ok(alias) = self.get_aspect::<Alias>() {
if let Some(alias) = self.alias.get() {
if !alias.info.server_signals.iter().any(|e| e == &method) {
return Err(ScenegraphError::SignalNotFound);
}
@@ -168,9 +230,7 @@ impl Node {
} else {
let signal = self
.local_signals
.lock()
.get(method)
.cloned()
.ok_or(ScenegraphError::SignalNotFound)?;
signal(self, calling_client, message).map_err(|error| ScenegraphError::SignalError {
error: error.to_string(),
@@ -178,38 +238,43 @@ impl Node {
}
}
pub fn execute_local_method(
self: Arc<Self>,
&self,
calling_client: Arc<Client>,
method: &str,
message: Message,
response: MethodResponseSender,
) {
if let Ok(alias) = self.get_aspect::<Alias>() {
) -> Result<Message, ScenegraphError> {
if let Some(alias) = self.alias.get() {
if !alias.info.server_methods.iter().any(|e| e == &method) {
response.send(Err(ScenegraphError::MethodNotFound));
return;
return Err(ScenegraphError::MethodNotFound);
}
let Some(alias) = alias.original.upgrade() else {
response.send(Err(ScenegraphError::BrokenAlias));
return;
};
alias.execute_local_method(
calling_client,
method,
Message {
data: message.data.clone(),
fds: Vec::new(),
},
response,
)
alias
.original
.upgrade()
.ok_or(ScenegraphError::BrokenAlias)?
.execute_local_method(
calling_client,
method,
Message {
data: message.data.clone(),
fds: Vec::new(),
},
)
} else {
let Some(method) = self.local_methods.lock().get(method).cloned() else {
response.send(Err(ScenegraphError::MethodNotFound));
return;
};
method(self, calling_client, message, response);
let method = self
.local_methods
.get(method)
.ok_or(ScenegraphError::MethodNotFound)?;
debug_span!("Handle method").in_scope(|| {
method(self, calling_client, message).map_err(|error| {
ScenegraphError::MethodError {
error: error.to_string(),
}
})
})
}
}
#[instrument(level = "debug", skip_all)]
pub fn send_remote_signal(&self, method: &str, message: impl Into<Message>) -> Result<()> {
let message = message.into();
self.aliases
@@ -234,27 +299,21 @@ impl Node {
}
Ok(())
}
pub async fn execute_remote_method_typed<S: Serialize, D: DeserializeOwned>(
&self,
method: &str,
input: S,
fds: Vec<OwnedFd>,
) -> Result<(D, Vec<OwnedFd>)> {
let message_sender_handle = self
.message_sender_handle
.as_ref()
.ok_or(eyre!("Messenger does not exist for this node"))?;
// #[instrument(level = "debug", skip_all)]
// pub fn execute_remote_method(
// &self,
// method: &str,
// data: Vec<u8>,
// ) -> Result<impl Future<Output = Result<Message>>> {
// let message_sender_handle = self
// .message_sender_handle
// .as_ref()
// .ok_or(eyre!("Messenger does not exist for this node"))?;
let serialized = serialize(input)?;
let result = message_sender_handle
.method(self.path.as_str(), method, &serialized, fds)?
.await
.map_err(|e| eyre!(e))?;
// let future = message_sender_handle.method(self.path.as_str(), method, &data)?;
let (message, fds) = result.into_components();
let deserialized: D = deserialize(&message)?;
Ok((deserialized, fds))
}
// Ok(async { future.await.map_err(|e| eyre!(e)) })
// }
}
impl Debug for Node {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -264,54 +323,8 @@ impl Debug for Node {
.finish()
}
}
impl NodeAspect for Node {
fn set_enabled(node: Arc<Node>, _calling_client: Arc<Client>, enabled: bool) -> Result<()> {
node.enabled.store(enabled, Ordering::Relaxed);
Ok(())
}
fn destroy(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
if node.destroyable {
node.destroy();
}
Ok(())
}
}
impl Drop for Node {
fn drop(&mut self) {
// Debug breakpoint
}
}
pub trait Aspect: Any + Send + Sync + 'static {
const NAME: &'static str;
}
#[derive(Default)]
struct Aspects(Mutex<FxHashMap<TypeId, Arc<dyn Any + Send + Sync + 'static>>>);
impl Aspects {
fn add<A: Aspect>(&self, t: A) -> Arc<A> {
let aspect = Arc::new(t);
self.add_raw(aspect.clone());
aspect
}
fn add_raw<A: Aspect>(&self, aspect: Arc<A>) {
self.0.lock().insert(Self::type_key::<A>(), aspect);
}
fn get<A: Aspect + Any + Send + Sync + 'static>(&self) -> Result<Arc<A>> {
self.0
.lock()
.get(&Self::type_key::<A>())
.and_then(|a| Arc::downcast(a.clone()).ok())
.ok_or(eyre!("Couldn't get aspect {}", A::NAME.to_lowercase()))
}
fn type_key<A: 'static>() -> TypeId {
TypeId::of::<A>()
}
}
impl Drop for Aspects {
fn drop(&mut self) {
self.0.lock().clear()
}
}

View File

@@ -1,40 +1,37 @@
use super::spatial::Spatial;
use super::{Message, Node};
use crate::core::client::Client;
use crate::core::client_state::{ClientState, ClientStateInternal};
use crate::core::registry::Registry;
use crate::core::scenegraph::MethodResponseSender;
#[cfg(feature = "wayland")]
use crate::wayland::WAYLAND_DISPLAY;
#[cfg(feature = "xwayland")]
use crate::wayland::X_DISPLAY;
use crate::STARDUST_INSTANCE;
use color_eyre::eyre::Result;
use glam::Mat4;
use rustc_hash::FxHashMap;
use stardust_xr::schemas::flex::{deserialize, serialize};
use tracing::instrument;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tracing::instrument;
static ROOT_REGISTRY: Registry<Root> = Registry::new();
pub struct Root {
pub node: Arc<Node>,
node: Arc<Node>,
send_frame_event: AtomicBool,
}
impl Root {
pub fn create(client: &Arc<Client>) -> Result<Arc<Self>> {
let node = Node::create_parent_name(client, "", "", false);
let node = Node::create(client, "", "", false);
node.add_local_signal("subscribe_frame", Root::subscribe_frame_flex);
node.add_local_signal("set_base_prefixes", Root::set_base_prefixes_flex);
node.add_local_method("state_token", Root::state_token_flex);
node.add_local_method(
"get_connection_environment",
get_connection_environment_flex,
);
let node = node.add_to_scenegraph()?;
let _ = Spatial::add_to(&node, None, client.state.root, false);
let _ = Spatial::add_to(
&node,
None,
client
.startup_settings
.as_ref()
.map(|settings| settings.transform)
.unwrap_or(Mat4::IDENTITY),
false,
);
Ok(ROOT_REGISTRY.add(Root {
node,
@@ -43,7 +40,7 @@ impl Root {
}
fn subscribe_frame_flex(
_node: Arc<Node>,
_node: &Node,
calling_client: Arc<Client>,
_message: Message,
) -> Result<()> {
@@ -68,38 +65,13 @@ impl Root {
}
fn set_base_prefixes_flex(
_node: Arc<Node>,
_node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
*calling_client.base_resource_prefixes.lock() = deserialize(message.as_ref())?;
Ok(())
}
fn state_token_flex(
_node: Arc<Node>,
calling_client: Arc<Client>,
message: Message,
response: MethodResponseSender,
) {
response.wrap_sync(|| {
let state: ClientStateInternal = deserialize(message.as_ref())?;
let token = ClientState::from_deserialized(&calling_client, state).token();
Ok(serialize(token)?.into())
})
}
pub fn set_transform(&self, transform: Mat4) {
let spatial = self.node.get_aspect::<Spatial>().unwrap();
spatial.set_spatial_parent(None).unwrap();
spatial.set_local_transform(transform);
}
pub async fn save_state(&self) -> Result<ClientStateInternal> {
self.node
.execute_remote_method_typed("save_state", (), Vec::new())
.await
.map(|(m, _)| m)
}
}
impl Drop for Root {
@@ -107,36 +79,3 @@ impl Drop for Root {
ROOT_REGISTRY.remove(self);
}
}
macro_rules! var_env_insert {
($env:ident, $name:ident) => {
$env.insert(stringify!($name).to_string(), $name.get().unwrap().clone());
};
}
pub fn get_connection_environment_flex(
_node: Arc<Node>,
_calling_client: Arc<Client>,
_message: Message,
response: MethodResponseSender,
) {
response.wrap_sync(move || {
let mut env: FxHashMap<String, String> = FxHashMap::default();
var_env_insert!(env, STARDUST_INSTANCE);
#[cfg(feature = "wayland")]
{
var_env_insert!(env, WAYLAND_DISPLAY);
#[cfg(feature = "xwayland")]
env.insert(
"DISPLAY".to_string(),
format!(":{}", X_DISPLAY.get().unwrap()),
);
env.insert("GDK_BACKEND".to_string(), "wayland".to_string());
env.insert("QT_QPA_PLATFORM".to_string(), "wayland".to_string());
env.insert("MOZ_ENABLE_WAYLAND".to_string(), "1".to_string());
env.insert("CLUTTER_BACKEND".to_string(), "wayland".to_string());
env.insert("SDL_VIDEODRIVER".to_string(), "wayland".to_string());
}
Ok(serialize(env)?.into())
});
}

View File

@@ -1,66 +1,49 @@
pub mod zone;
use self::zone::Zone;
use super::fields::Field;
use super::{Aspect, Node};
use self::zone::{create_zone_flex, Zone};
use super::{Message, Node};
use crate::core::client::Client;
use crate::core::registry::Registry;
use crate::create_interface;
use color_eyre::eyre::{eyre, Result};
use color_eyre::eyre::{ensure, eyre, Result};
use glam::{vec3a, Mat4, Quat};
use mint::Vector3;
use nanoid::nanoid;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use serde::Deserialize;
use stardust_xr::schemas::flex::{deserialize, serialize};
use stardust_xr::values::Transform;
use std::fmt::Debug;
use std::ptr;
use std::sync::{Arc, Weak};
use std::sync::{Arc, OnceLock, Weak};
use stereokit::{bounds_grow_to_fit_box, Bounds};
stardust_xr_server_codegen::codegen_spatial_protocol!();
impl Transform {
pub fn to_mat4(self, position: bool, rotation: bool, scale: bool) -> Mat4 {
let position = position
.then_some(self.translation)
.flatten()
.unwrap_or_else(|| Vector3::from([0.0; 3]));
let rotation = rotation
.then_some(self.rotation)
.flatten()
.unwrap_or_else(|| Quat::IDENTITY.into());
let scale = scale
.then_some(self.scale)
.flatten()
.unwrap_or_else(|| Vector3::from([1.0; 3]));
Mat4::from_scale_rotation_translation(scale.into(), rotation.into(), position.into())
}
}
use tracing::instrument;
static ZONEABLE_REGISTRY: Registry<Spatial> = Registry::new();
pub struct Spatial {
uid: String,
pub(super) node: Weak<Node>,
self_ref: Weak<Spatial>,
parent: Mutex<Option<Arc<Spatial>>>,
old_parent: Mutex<Option<Arc<Spatial>>>,
pub(super) transform: Mutex<Mat4>,
zone: Mutex<Weak<Zone>>,
children: Registry<Spatial>,
pub(super) bounding_box_calc: OnceCell<fn(&Node) -> Bounds>,
pub(super) bounding_box_calc: OnceLock<fn(&Node) -> Bounds>,
}
impl Spatial {
pub fn new(node: Weak<Node>, parent: Option<Arc<Spatial>>, transform: Mat4) -> Arc<Self> {
Arc::new(Spatial {
Arc::new_cyclic(|self_ref| Spatial {
uid: nanoid!(),
node,
self_ref: self_ref.clone(),
parent: Mutex::new(parent),
old_parent: Mutex::new(None),
transform: Mutex::new(transform),
zone: Mutex::new(Weak::new()),
children: Registry::new(),
bounding_box_calc: OnceCell::default(),
bounding_box_calc: OnceLock::default(),
})
}
pub fn add_to(
@@ -68,24 +51,36 @@ impl Spatial {
parent: Option<Arc<Spatial>>,
transform: Mat4,
zoneable: bool,
) -> Arc<Spatial> {
let spatial = Spatial::new(Arc::downgrade(node), parent.clone(), transform);
<Spatial as SpatialAspect>::add_node_members(node);
) -> Result<Arc<Spatial>> {
ensure!(
node.spatial.get().is_none(),
"Internal: Node already has a Spatial aspect!"
);
let spatial = Spatial::new(Arc::downgrade(node), parent, transform);
node.add_local_method("get_bounding_box", Spatial::get_bounding_box_flex);
node.add_local_method("get_transform", Spatial::get_transform_flex);
node.add_local_signal("set_transform", Spatial::set_transform_flex);
node.add_local_signal("set_spatial_parent", Spatial::set_spatial_parent_flex);
node.add_local_signal(
"set_spatial_parent_in_place",
Spatial::set_spatial_parent_in_place_flex,
);
node.add_local_signal("set_zoneable", Spatial::set_zoneable_flex);
node.add_local_method("field_distance", Spatial::field_distance_flex);
node.add_local_method("field_normal", Spatial::field_normal_flex);
node.add_local_method("field_closest_point", Spatial::field_closest_point_flex);
if zoneable {
ZONEABLE_REGISTRY.add_raw(&spatial);
}
if let Some(parent) = parent {
parent.children.add_raw(&spatial);
}
<Spatial as SpatialAspect>::add_node_members(node);
node.add_aspect_raw(spatial.clone());
spatial
let _ = node.spatial.set(spatial.clone());
Ok(spatial)
}
pub fn node(&self) -> Option<Arc<Node>> {
self.node.upgrade()
}
#[instrument(level = "debug", skip_all)]
pub fn space_to_space_matrix(from: Option<&Spatial>, to: Option<&Spatial>) -> Mat4 {
let space_to_world_matrix = from.map_or(Mat4::IDENTITY, |from| from.global_transform());
let world_to_space_matrix = to.map_or(Mat4::IDENTITY, |to| to.global_transform().inverse());
@@ -93,10 +88,9 @@ impl Spatial {
}
// the output bounds are probably way bigger than they need to be
#[instrument(level = "debug")]
pub fn get_bounding_box(&self) -> Bounds {
let Some(node) = self.node() else {
return Bounds::default();
};
let Some(node) = self.node() else {return Bounds::default()};
let mut bounds = self
.bounding_box_calc
.get()
@@ -112,20 +106,21 @@ impl Spatial {
bounds
}
#[instrument(level = "debug", skip_all)]
pub fn local_transform(&self) -> Mat4 {
*self.transform.lock()
}
pub fn global_transform(&self) -> Mat4 {
let parent_transform = self
.get_parent()
.as_deref()
.map(Self::global_transform)
.unwrap_or_default();
parent_transform * self.local_transform()
match self.get_parent() {
Some(value) => value.global_transform() * *self.transform.lock(),
None => *self.transform.lock(),
}
}
#[instrument]
pub fn set_local_transform(&self, transform: Mat4) {
*self.transform.lock() = transform;
}
#[instrument(level = "debug", skip(self, reference_space))]
pub fn set_local_transform_components(
&self,
reference_space: Option<&Spatial>,
@@ -147,7 +142,7 @@ impl Spatial {
let (mut reference_space_scl, mut reference_space_rot, mut reference_space_pos) =
local_transform_in_reference_space.to_scale_rotation_translation();
if let Some(pos) = transform.translation {
if let Some(pos) = transform.position {
reference_space_pos = pos.into()
}
if let Some(rot) = transform.rotation {
@@ -169,6 +164,7 @@ impl Spatial {
);
}
#[instrument(level = "debug", skip_all)]
pub fn is_ancestor_of(&self, spatial: Arc<Spatial>) -> bool {
let mut current_ancestor = spatial;
loop {
@@ -187,21 +183,24 @@ impl Spatial {
fn get_parent(&self) -> Option<Arc<Spatial>> {
self.parent.lock().clone()
}
fn set_parent(self: &Arc<Self>, new_parent: Option<&Arc<Spatial>>) {
fn set_parent(&self, new_parent: Option<Arc<Spatial>>) {
if let Some(parent) = self.get_parent() {
parent.children.remove(&self);
parent.children.remove(self);
}
if let Some(new_parent) = &new_parent {
new_parent.children.add_raw(self);
new_parent
.children
.add_raw(&self.self_ref.upgrade().unwrap());
}
*self.parent.lock() = new_parent.cloned();
*self.parent.lock() = new_parent;
}
pub fn set_spatial_parent(self: &Arc<Self>, parent: Option<&Arc<Spatial>>) -> Result<()> {
#[instrument(level = "debug", skip_all)]
pub fn set_spatial_parent(&self, parent: Option<Arc<Spatial>>) -> Result<()> {
let is_ancestor = parent
.as_ref()
.map(|parent| self.is_ancestor_of((*parent).clone()))
.map(|parent| self.is_ancestor_of(parent.clone()))
.unwrap_or(false);
if is_ancestor {
return Err(eyre!("Setting spatial parent would cause a loop"));
@@ -210,87 +209,75 @@ impl Spatial {
Ok(())
}
pub fn set_spatial_parent_in_place(
self: &Arc<Self>,
parent: Option<&Arc<Spatial>>,
) -> Result<()> {
#[instrument(level = "debug", skip_all)]
pub fn set_spatial_parent_in_place(&self, parent: Option<Arc<Spatial>>) -> Result<()> {
let is_ancestor = parent
.as_ref()
.map(|parent| self.is_ancestor_of((*parent).clone()))
.map(|parent| self.is_ancestor_of(parent.clone()))
.unwrap_or(false);
if is_ancestor {
return Err(eyre!("Setting spatial parent would cause a loop"));
}
self.set_local_transform(Spatial::space_to_space_matrix(
Some(&self),
parent.map(AsRef::as_ref),
Some(self),
parent.as_deref(),
));
self.set_parent(parent);
Ok(())
}
pub(self) fn zone_distance(&self) -> f32 {
self.zone
.lock()
.upgrade()
.and_then(|zone| zone.field.upgrade())
.map(|field| field.distance(self, vec3a(0.0, 0.0, 0.0)))
.unwrap_or(f32::MAX)
}
}
impl Aspect for Spatial {
const NAME: &'static str = "Spatial";
}
impl SpatialAspect for Spatial {
async fn get_local_bounding_box(
node: Arc<Node>,
_calling_client: Arc<Client>,
) -> Result<BoundingBox> {
let this_spatial = node.get_aspect::<Spatial>()?;
let bounds = this_spatial.get_bounding_box();
Ok(BoundingBox {
center: mint::Vector3::from(bounds.center),
size: mint::Vector3::from(bounds.dimensions),
})
}
async fn get_relative_bounding_box(
node: Arc<Node>,
_calling_client: Arc<Client>,
relative_to: Arc<Node>,
) -> Result<BoundingBox> {
let this_spatial = node.get_aspect::<Spatial>()?;
let relative_spatial = relative_to.get_aspect::<Spatial>()?;
let center = Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial))
.transform_point3([0.0; 3].into());
let bounds = bounds_grow_to_fit_box(
Bounds {
pub fn get_bounding_box_flex(
node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<Message> {
let this_spatial = node
.spatial
.get()
.ok_or_else(|| eyre!("Node doesn't have a spatial?"))?;
let relative_spatial_path: Option<&str> = deserialize(message.as_ref())?;
let bounds = if let Some(relative_spatial_path) = relative_spatial_path {
let relative_spatial = find_reference_space(&calling_client, relative_spatial_path)?;
let center =
Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial))
.transform_point3([0.0; 3].into());
let bounds: Bounds = Bounds {
center,
dimensions: [0.0; 3].into(),
},
this_spatial.get_bounding_box(),
Some(Spatial::space_to_space_matrix(
Some(&this_spatial),
Some(&relative_spatial),
)),
);
};
bounds_grow_to_fit_box(
bounds,
this_spatial.get_bounding_box(),
Some(Spatial::space_to_space_matrix(
Some(&this_spatial),
Some(&relative_spatial),
)),
)
} else {
this_spatial.get_bounding_box()
};
Ok(BoundingBox {
center: mint::Vector3::from(bounds.center),
size: mint::Vector3::from(bounds.dimensions),
})
Ok(serialize((
mint::Vector3::from(bounds.center),
mint::Vector3::from(bounds.dimensions),
))?
.into())
}
async fn get_transform(
node: Arc<Node>,
_calling_client: Arc<Client>,
relative_to: Arc<Node>,
) -> Result<Transform> {
let this_spatial = node.get_aspect::<Spatial>()?;
let relative_spatial = relative_to.get_aspect::<Spatial>()?;
pub fn get_transform_flex(
node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<Message> {
let this_spatial = node
.spatial
.get()
.ok_or_else(|| eyre!("Node doesn't have a spatial?"))?;
let relative_spatial =
find_reference_space(&calling_client, deserialize(message.as_ref())?)?;
let (scale, rotation, position) = Spatial::space_to_space_matrix(
Some(this_spatial.as_ref()),
@@ -298,69 +285,150 @@ impl SpatialAspect for Spatial {
)
.to_scale_rotation_translation();
Ok(Transform {
translation: Some(position.into()),
rotation: Some(rotation.into()),
scale: Some(scale.into()),
})
Ok(serialize((
mint::Vector3::from(position),
mint::Quaternion::from(rotation),
mint::Vector3::from(scale),
))?
.into())
}
fn set_local_transform(
node: Arc<Node>,
_calling_client: Arc<Client>,
transform: Transform,
pub fn set_transform_flex(
node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let this_spatial = node.get_aspect::<Spatial>()?;
this_spatial.set_local_transform_components(None, transform);
#[derive(Deserialize)]
struct TransformArgs<'a> {
reference_space_path: Option<&'a str>,
transform: Transform,
}
let transform_args: TransformArgs = deserialize(message.as_ref())?;
let reference_space_transform = transform_args
.reference_space_path
.map(|path| find_reference_space(&calling_client, path))
.transpose()?;
node.spatial.get().unwrap().set_local_transform_components(
reference_space_transform.as_deref(),
transform_args.transform,
);
Ok(())
}
fn set_relative_transform(
node: Arc<Node>,
_calling_client: Arc<Client>,
relative_to: Arc<Node>,
transform: Transform,
pub fn set_spatial_parent_flex(
node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let this_spatial = node.get_aspect::<Spatial>()?;
let relative_spatial = relative_to.get_aspect::<Spatial>()?;
this_spatial.set_local_transform_components(Some(&relative_spatial), transform);
let parent = find_spatial_parent(&calling_client, deserialize(message.as_ref())?)?;
node.spatial.get().unwrap().set_spatial_parent(Some(parent))
}
pub fn set_spatial_parent_in_place_flex(
node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let parent = find_spatial_parent(&calling_client, deserialize(message.as_ref())?)?;
node.spatial
.get()
.unwrap()
.set_spatial_parent_in_place(Some(parent))?;
Ok(())
}
fn set_spatial_parent(
node: Arc<Node>,
pub fn set_zoneable_flex(
node: &Node,
_calling_client: Arc<Client>,
parent: Arc<Node>,
message: Message,
) -> Result<()> {
let this_spatial = node.get_aspect::<Spatial>()?;
let parent = parent.get_aspect::<Spatial>()?;
this_spatial.set_spatial_parent(Some(&parent))?;
Ok(())
}
fn set_spatial_parent_in_place(
node: Arc<Node>,
_calling_client: Arc<Client>,
parent: Arc<Node>,
) -> Result<()> {
let this_spatial = node.get_aspect::<Spatial>()?;
let parent = parent.get_aspect::<Spatial>()?;
this_spatial.set_spatial_parent_in_place(Some(&parent))?;
Ok(())
}
fn set_zoneable(node: Arc<Node>, _calling_client: Arc<Client>, zoneable: bool) -> Result<()> {
let spatial = node.get_aspect::<Spatial>()?;
let zoneable: bool = deserialize(message.as_ref())?;
let spatial = node.spatial.get().unwrap();
if zoneable {
ZONEABLE_REGISTRY.add_raw(&spatial);
ZONEABLE_REGISTRY.add_raw(spatial);
} else {
ZONEABLE_REGISTRY.remove(&spatial);
zone::release(&spatial);
ZONEABLE_REGISTRY.remove(spatial);
zone::release(spatial);
}
Ok(())
}
pub fn field_distance_flex(
node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<Message> {
let (point, fields): (Vector3<f32>, Vec<Option<&str>>) = deserialize(message.as_ref())?;
let spatial = node.spatial.get().unwrap();
let output = fields
.into_iter()
.map(|f| {
calling_client
.get_node("Field", f?)
.ok()?
.get_aspect("Field", "field", |n| &n.field)
.ok()
.cloned()
})
.map(|f| f.map(|f| f.distance(spatial, point.into())))
.collect::<Vec<Option<f32>>>();
Ok(serialize(output)?.into())
}
pub fn field_normal_flex(
node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<Message> {
let (point, fields): (Vector3<f32>, Vec<Option<&str>>) = deserialize(message.as_ref())?;
let spatial = node.spatial.get().unwrap();
let output = fields
.into_iter()
.map(|f| {
calling_client
.get_node("Field", f?)
.ok()?
.get_aspect("Field", "field", |n| &n.field)
.ok()
.cloned()
})
.map(|f| f.map(|f| Vector3::from(f.normal(spatial, point.into(), 0.001))))
.collect::<Vec<_>>();
Ok(serialize(output)?.into())
}
pub fn field_closest_point_flex(
node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<Message> {
let (point, fields): (Vector3<f32>, Vec<Option<&str>>) = deserialize(message.as_ref())?;
let spatial = node.spatial.get().unwrap();
let output = fields
.into_iter()
.map(|f| {
calling_client
.get_node("Field", f?)
.ok()?
.get_aspect("Field", "field", |n| &n.field)
.ok()
.cloned()
})
.map(|f| f.map(|f| Vector3::from(f.closest_point(spatial, point.into(), 0.001))))
.collect::<Vec<_>>();
Ok(serialize(output)?.into())
}
#[instrument]
pub(self) fn zone_distance(&self) -> f32 {
self.zone
.lock()
.upgrade()
.and_then(|zone| zone.field.upgrade())
.map(|field| field.distance(self, vec3a(0.0, 0.0, 0.0)))
.unwrap_or(f32::MAX)
}
}
impl PartialEq for Spatial {
fn eq(&self, other: &Self) -> bool {
@@ -379,14 +447,14 @@ impl Debug for Spatial {
}
impl Drop for Spatial {
fn drop(&mut self) {
zone::release_drop(self);
ZONEABLE_REGISTRY.remove(self);
zone::release(self);
}
}
pub fn parse_transform(transform: Transform, position: bool, rotation: bool, scale: bool) -> Mat4 {
let position = position
.then_some(transform.translation)
.then_some(transform.position)
.flatten()
.unwrap_or_else(|| Vector3::from([0.0; 3]));
let rotation = rotation
@@ -401,41 +469,53 @@ pub fn parse_transform(transform: Transform, position: bool, rotation: bool, sca
Mat4::from_scale_rotation_translation(scale.into(), rotation.into(), position.into())
}
pub struct SpatialInterface;
impl SpatialInterfaceAspect for SpatialInterface {
fn create_spatial(
_node: Arc<Node>,
calling_client: Arc<Client>,
name: String,
parent: Arc<Node>,
transform: Transform,
zoneable: bool,
) -> Result<()> {
let parent = parent.get_aspect::<Spatial>()?;
let transform = parse_transform(transform, true, true, true);
let node = Node::create_parent_name(&calling_client, "/spatial/spatial", &name, true)
.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, zoneable);
Ok(())
}
fn create_zone(
_node: Arc<Node>,
calling_client: Arc<Client>,
name: String,
parent: Arc<Node>,
transform: Transform,
field: Arc<Node>,
) -> Result<()> {
let parent = parent.get_aspect::<Spatial>()?;
let transform = parse_transform(transform, true, true, false);
let field = field.get_aspect::<Field>()?;
let node = Node::create_parent_name(&calling_client, "/spatial/zone", &name, true)
.add_to_scenegraph()?;
let space = Spatial::add_to(&node, Some(parent.clone()), transform, false);
Zone::add_to(&node, space, &field);
Ok(())
}
pub fn find_spatial(
calling_client: &Arc<Client>,
node_name: &'static str,
node_path: &str,
) -> color_eyre::eyre::Result<Arc<Spatial>> {
calling_client
.get_node(node_name, node_path)?
.get_aspect(node_name, "spatial", |n| &n.spatial)
.cloned()
}
pub fn find_spatial_parent(
calling_client: &Arc<Client>,
node_path: &str,
) -> color_eyre::eyre::Result<Arc<Spatial>> {
find_spatial(calling_client, "Spatial parent", node_path)
}
pub fn find_reference_space(
calling_client: &Arc<Client>,
node_path: &str,
) -> color_eyre::eyre::Result<Arc<Spatial>> {
find_spatial(calling_client, "Reference space", node_path)
}
create_interface!(SpatialInterface, SpatialInterfaceAspect, "/spatial");
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
let node = Node::create(client, "", "spatial", false);
node.add_local_signal("create_spatial", create_spatial_flex);
node.add_local_signal("create_zone", create_zone_flex);
node.add_to_scenegraph().map(|_| ())
}
pub fn create_spatial_flex(
_node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
#[derive(Deserialize)]
struct CreateSpatialInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
zoneable: bool,
}
let info: CreateSpatialInfo = deserialize(message.as_ref())?;
let node = Node::create(&calling_client, "/spatial/spatial", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, true);
let node = node.add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent), transform, info.zoneable)?;
Ok(())
}

View File

@@ -1,15 +1,22 @@
use super::{Spatial, ZoneAspect, ZONEABLE_REGISTRY};
use super::{find_spatial, Spatial, ZONEABLE_REGISTRY};
use crate::{
core::{client::Client, registry::Registry},
nodes::{
alias::{Alias, AliasInfo},
fields::Field,
Aspect, Node,
fields::{find_field, Field},
spatial::{find_spatial_parent, parse_transform},
Message, Node,
},
};
use color_eyre::eyre::Result;
use glam::vec3a;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use serde::Deserialize;
use stardust_xr::{
schemas::flex::{deserialize, serialize},
values::Transform,
};
use std::sync::{Arc, Weak};
pub fn capture(spatial: &Arc<Spatial>, zone: &Arc<Zone>) {
@@ -24,34 +31,22 @@ pub fn capture(spatial: &Arc<Spatial>, zone: &Arc<Zone>) {
*spatial.old_parent.lock() = spatial.get_parent();
*spatial.zone.lock() = Arc::downgrade(zone);
zone.captured.add_raw(spatial);
let Some(node) = zone.spatial.node.upgrade() else {
return;
};
let _ = super::zone_client::capture(&node, &spatial.uid);
let Some(node) = zone.spatial.node.upgrade() else {return};
let Ok(message) = serialize(&spatial.uid) else {return};
let _ = node.send_remote_signal("capture", message);
}
}
pub fn release(spatial: &Arc<Spatial>) {
let _ = spatial.set_spatial_parent_in_place(spatial.old_parent.lock().take().as_ref());
pub fn release(spatial: &Spatial) {
let _ = spatial.set_spatial_parent_in_place(spatial.old_parent.lock().take());
let mut spatial_zone = spatial.zone.lock();
if let Some(spatial_zone) = spatial_zone.upgrade() {
let Some(node) = spatial_zone.spatial.node.upgrade() else {
return;
};
let Some(node) = spatial_zone.spatial.node.upgrade() else {return};
spatial_zone.captured.remove(spatial);
let _ = super::zone_client::release(&node, &spatial.uid);
let Ok(message) = serialize(&spatial.uid) else {return};
let _ = node.send_remote_signal("release", message);
}
*spatial_zone = Weak::new();
}
pub(super) fn release_drop(spatial: &Spatial) {
let spatial_zone = spatial.zone.lock();
if let Some(spatial_zone) = spatial_zone.upgrade() {
let Some(node) = spatial_zone.spatial.node.upgrade() else {
return;
};
spatial_zone.captured.remove(spatial);
let _ = super::zone_client::release(&node, &spatial.uid);
}
}
pub struct Zone {
spatial: Arc<Spatial>,
@@ -67,28 +62,33 @@ impl Zone {
zoneables: Mutex::new(FxHashMap::default()),
captured: Registry::new(),
});
<Zone as ZoneAspect>::add_node_members(node);
node.add_aspect_raw(zone.clone());
node.add_local_signal("capture", Zone::capture_flex);
node.add_local_signal("release", Zone::release_flex);
node.add_local_signal("update", Zone::update);
let _ = node.zone.set(zone.clone());
zone
}
}
impl Aspect for Zone {
const NAME: &'static str = "Zone";
}
impl ZoneAspect for Zone {
fn update(node: Arc<Node>, _calling_client: Arc<Client>) -> color_eyre::eyre::Result<()> {
let zone = node.get_aspect::<Zone>()?;
let Some(field) = zone.field.upgrade() else {
return Err(color_eyre::eyre::eyre!("Zone's field has been destroyed"));
};
fn capture_flex(node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
let zone = node.zone.get().unwrap();
let capture_path: &str = deserialize(message.as_ref())?;
let spatial = find_spatial(&calling_client, "Spatial", capture_path)?;
capture(&spatial, zone);
Ok(())
}
fn release_flex(_node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
let capture_path: &str = deserialize(message.as_ref())?;
let spatial = find_spatial(&calling_client, "Spatial", capture_path)?;
release(&spatial);
Ok(())
}
fn update(node: &Node, _calling_client: Arc<Client>, _message: Message) -> Result<()> {
let zone = node.zone.get().unwrap();
let Some(field) = zone.field.upgrade() else { return Err(color_eyre::eyre::eyre!("Zone's field has been destroyed")) };
let Some((zone_client, zone_node)) = zone
.spatial
.node
.upgrade()
.and_then(|n| n.get_client().zip(Some(n)))
else {
return Err(color_eyre::eyre::eyre!("No client on node?"));
};
.and_then(|n| n.get_client().zip(Some(n))) else { return Err(color_eyre::eyre::eyre!("No client on node?")) };
let mut old_zoneables = zone.zoneables.lock();
for (_uid, zoneable) in old_zoneables.iter() {
zoneable.destroy();
@@ -130,41 +130,17 @@ impl ZoneAspect for Zone {
})
.collect::<FxHashMap<String, Arc<Node>>>();
for (uid, zoneable) in zoneables
.iter()
.filter(|(k, _)| !old_zoneables.contains_key(*k))
{
super::zone_client::enter(&node, uid, zoneable)?;
for entered_uid in zoneables.keys().filter(|k| !old_zoneables.contains_key(*k)) {
node.send_remote_signal("enter", serialize(entered_uid)?)?;
}
for left_uid in old_zoneables.keys().filter(|k| !zoneables.contains_key(*k)) {
super::zone_client::leave(&node, &left_uid)?;
node.send_remote_signal("leave", serialize(left_uid)?)?;
}
*old_zoneables = zoneables;
Ok(())
}
fn capture(
node: Arc<Node>,
_calling_client: Arc<Client>,
spatial: Arc<Node>,
) -> color_eyre::eyre::Result<()> {
let zone = node.get_aspect::<Zone>()?;
let spatial = spatial.get_aspect()?;
capture(&spatial, &zone);
Ok(())
}
fn release(
_node: Arc<Node>,
_calling_client: Arc<Client>,
spatial: Arc<Node>,
) -> color_eyre::eyre::Result<()> {
let spatial = spatial.get_aspect()?;
release(&spatial);
Ok(())
}
}
impl Drop for Zone {
fn drop(&mut self) {
@@ -173,3 +149,23 @@ impl Drop for Zone {
}
}
}
pub fn create_zone_flex(_node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
#[derive(Deserialize)]
struct CreateZoneInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
field_path: &'a str,
}
let info: CreateZoneInfo = deserialize(message.as_ref())?;
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, false);
let field = find_field(&calling_client, info.field_path)?;
let node =
Node::create(&calling_client, "/spatial/zone", info.name, true).add_to_scenegraph()?;
let space = Spatial::add_to(&node, Some(parent), transform, false)?;
Zone::add_to(&node, space, &field);
Ok(())
}

155
src/nodes/startup.rs Normal file
View File

@@ -0,0 +1,155 @@
#[cfg(feature = "xwayland")]
use crate::wayland::xwayland::DISPLAY;
use crate::{core::client::Client, wayland::WAYLAND_DISPLAY, STARDUST_INSTANCE};
use super::{
items::{ItemAcceptor, TypeInfo},
spatial::find_spatial,
Message, Node,
};
use color_eyre::eyre::Result;
use glam::Mat4;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use stardust_xr::schemas::flex::{deserialize, serialize};
use std::{
fmt::Debug,
sync::{Arc, Weak},
};
lazy_static::lazy_static! {
pub static ref STARTUP_SETTINGS: Mutex<FxHashMap<String, StartupSettings>> = Default::default();
}
#[derive(Default, Clone)]
pub struct StartupSettings {
pub transform: Mat4,
pub acceptors: FxHashMap<&'static TypeInfo, Weak<ItemAcceptor>>,
}
impl StartupSettings {
pub fn add_to(node: &Arc<Node>) {
let _ = node
.startup_settings
.set(Mutex::new(StartupSettings::default()));
}
fn set_root_flex(node: &Node, calling_client: Arc<Client>, message: Message) -> Result<()> {
let spatial = find_spatial(
&calling_client,
"Root spatial",
deserialize(message.as_ref())?,
)?;
node.startup_settings.get().unwrap().lock().transform = spatial.global_transform();
Ok(())
}
fn add_automatic_acceptor_flex(
node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let acceptor_node =
calling_client.get_node("Item acceptor", deserialize(message.as_ref())?)?;
let acceptor =
acceptor_node.get_aspect("Item acceptor", "item acceptor", |n| &n.item_acceptor)?;
let mut startup_settings = node.startup_settings.get().unwrap().lock();
startup_settings
.acceptors
.insert(acceptor.type_info, Arc::downgrade(acceptor));
Ok(())
}
fn generate_startup_token_flex(
node: &Node,
_calling_client: Arc<Client>,
_message: Message,
) -> Result<Message> {
let id = nanoid::nanoid!();
let data = serialize(&id)?;
STARTUP_SETTINGS
.lock()
.insert(id, node.startup_settings.get().unwrap().lock().clone());
Ok(data.into())
}
}
impl Debug for StartupSettings {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StartupSettings")
.field("transform", &self.transform)
.field(
"acceptors",
&self
.acceptors
.iter()
.map(|(k, _)| k.type_name)
.collect::<Vec<_>>(),
)
.finish()
}
}
pub fn create_interface(client: &Arc<Client>) -> Result<()> {
let node = Node::create(client, "", "startup", false);
node.add_local_signal("create_startup_settings", create_startup_settings_flex);
node.add_local_method(
"get_connection_environment",
get_connection_environment_flex,
);
node.add_to_scenegraph().map(|_| ())
}
pub fn create_startup_settings_flex(
_node: &Node,
calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let node = Node::create(
&calling_client,
"/startup/settings",
deserialize(message.as_ref())?,
true,
)
.add_to_scenegraph()?;
StartupSettings::add_to(&node);
node.add_local_signal("set_root", StartupSettings::set_root_flex);
node.add_local_signal(
"add_automatic_acceptor",
StartupSettings::add_automatic_acceptor_flex,
);
node.add_local_method(
"generate_startup_token",
StartupSettings::generate_startup_token_flex,
);
Ok(())
}
macro_rules! var_env_insert {
($env:ident, $name:ident) => {
$env.insert(stringify!($name).to_string(), $name.get().unwrap().clone());
};
}
pub fn get_connection_environment_flex(
_node: &Node,
_calling_client: Arc<Client>,
_message: Message,
) -> Result<Message> {
let mut env: FxHashMap<String, String> = FxHashMap::default();
var_env_insert!(env, STARDUST_INSTANCE);
#[cfg(feature = "wayland")]
{
var_env_insert!(env, WAYLAND_DISPLAY);
#[cfg(feature = "xwayland")]
var_env_insert!(env, DISPLAY);
env.insert("GDK_BACKEND".to_string(), "wayland".to_string());
env.insert("QT_QPA_PLATFORM".to_string(), "wayland".to_string());
env.insert("MOZ_ENABLE_WAYLAND".to_string(), "1".to_string());
env.insert("CLUTTER_BACKEND".to_string(), "wayland".to_string());
env.insert("SDL_VIDEODRIVER".to_string(), "wayland".to_string());
}
Ok(serialize(env)?.into())
}

Binary file not shown.

Binary file not shown.

View File

@@ -10,9 +10,10 @@ use color_eyre::eyre::Result;
use glam::Mat4;
use nanoid::nanoid;
use serde::Serialize;
use stardust_xr::{schemas::flex::flexbuffers, values::Datamap};
use stardust_xr::schemas::{flat::Datamap, flex::flexbuffers};
use std::sync::Arc;
use stereokit::StereoKitMultiThread;
use tracing::instrument;
#[derive(Debug, Clone, Serialize)]
pub struct KeyboardEvent {
@@ -28,14 +29,14 @@ pub struct EyePointer {
}
impl EyePointer {
pub fn new() -> Result<Self> {
let node = Node::create_parent_name(&INTERNAL_CLIENT, "", &nanoid!(), false)
.add_to_scenegraph()?;
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false);
let node = Node::create(&INTERNAL_CLIENT, "", &nanoid!(), false).add_to_scenegraph()?;
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false).unwrap();
let pointer =
InputMethod::add_to(&node, InputType::Pointer(Pointer::default()), None).unwrap();
Ok(EyePointer { spatial, pointer })
}
#[instrument(level = "debug", name = "Update Flatscreen Pointer Ray", skip_all)]
pub fn update(&self, sk: &impl StereoKitMultiThread) {
let ray = sk.input_eyes();
self.spatial
@@ -49,7 +50,7 @@ impl EyePointer {
let mut map = fbb.start_map();
map.push("eye", 2);
map.end_map();
*self.pointer.datamap.lock() = Datamap::from_raw(fbb.take_buffer()).ok();
*self.pointer.datamap.lock() = Datamap::new(fbb.take_buffer()).ok();
}
}
}

View File

@@ -1,10 +1,8 @@
use crate::{
core::client::INTERNAL_CLIENT,
core::{client::INTERNAL_CLIENT, typed_datamap::TypedDatamap},
nodes::{
data::{
mask_matches, pulse_receiver_client, PulseSender, KEYMAPS, PULSE_RECEIVER_REGISTRY,
},
fields::{Field, Ray},
data::{mask_matches, Mask, PulseSender, PULSE_RECEIVER_REGISTRY},
fields::Ray,
input::{pointer::Pointer, InputMethod, InputType},
spatial::Spatial,
Node,
@@ -14,78 +12,60 @@ use color_eyre::eyre::Result;
use glam::{vec2, vec3, Mat4, Vec2, Vec3};
use nanoid::nanoid;
use serde::{Deserialize, Serialize};
use stardust_xr::values::Datamap;
use stardust_xr::schemas::flex::flexbuffers;
use std::{convert::TryFrom, sync::Arc};
use stereokit::{ray_from_mouse, ButtonState, Key, StereoKitMultiThread};
use xkbcommon::xkb::{Context, Keymap, FORMAT_TEXT_V1};
use tracing::instrument;
const SK_KEYMAP: &str = include_str!("sk.kmp");
#[derive(Default, Deserialize, Serialize)]
struct MouseEvent {
struct MouseDatamap {
select: f32,
middle: f32,
context: f32,
grab: f32,
scroll_continuous: Vec2,
scroll_discrete: Vec2,
raw_input_events: Vec<u32>,
scroll: Vec2,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct KeyboardEvent {
pub keyboard: (),
pub xkbv1: (),
pub keymap_id: String,
pub keys: Vec<i32>,
}
impl Default for KeyboardEvent {
fn default() -> Self {
Self {
keyboard: (),
xkbv1: (),
keymap_id: "flatscreen".to_string(),
keys: Default::default(),
}
}
#[derive(Debug, Clone, Serialize)]
struct KeyboardEvent {
pub keyboard: String,
pub keymap: Option<String>,
pub keys_up: Option<Vec<u32>>,
pub keys_down: Option<Vec<u32>>,
}
pub struct MousePointer {
node: Arc<Node>,
spatial: Arc<Spatial>,
pointer: Arc<InputMethod>,
mouse_datamap: MouseEvent,
keyboard_datamap: KeyboardEvent,
datamap: TypedDatamap<MouseDatamap>,
keyboard_sender: Arc<PulseSender>,
}
impl MousePointer {
pub fn new() -> Result<Self> {
let node = Node::create_parent_name(&INTERNAL_CLIENT, "", &nanoid!(), false)
.add_to_scenegraph()?;
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false);
let node = Node::create(&INTERNAL_CLIENT, "", &nanoid!(), false).add_to_scenegraph()?;
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false).unwrap();
let pointer =
InputMethod::add_to(&node, InputType::Pointer(Pointer::default()), None).unwrap();
KEYMAPS.lock().insert(
"flatscreen".to_string(),
Keymap::new_from_names(&Context::new(0), "evdev", "", "", "", None, 0)
.unwrap()
.get_as_string(FORMAT_TEXT_V1),
);
let keyboard_sender = PulseSender::add_to(
&node,
Datamap::from_typed(KeyboardEvent::default()).unwrap(),
)
.unwrap();
let keyboard_mask = {
let mut fbb = flexbuffers::Builder::default();
let mut map = fbb.start_map();
map.push("keyboard", "xkbv1");
map.end_map();
Mask(fbb.take_buffer())
};
let keyboard_sender = PulseSender::add_to(&node, keyboard_mask).unwrap();
Ok(MousePointer {
node,
spatial,
pointer,
mouse_datamap: Default::default(),
keyboard_datamap: Default::default(),
datamap: Default::default(),
keyboard_sender,
})
}
#[instrument(level = "debug", name = "Update Flatscreen Pointer Ray", skip_all)]
pub fn update(&mut self, sk: &impl StereoKitMultiThread) {
let mouse = sk.input_mouse();
@@ -100,47 +80,29 @@ impl MousePointer {
);
{
// Set pointer input datamap
self.mouse_datamap.select =
if sk.input_key(Key::MouseLeft).contains(ButtonState::ACTIVE) {
1.0f32
} else {
0.0f32
};
self.mouse_datamap.middle =
if sk.input_key(Key::MouseCenter).contains(ButtonState::ACTIVE) {
1.0f32
} else {
0.0f32
};
self.mouse_datamap.context =
if sk.input_key(Key::MouseRight).contains(ButtonState::ACTIVE) {
1.0f32
} else {
0.0f32
};
self.mouse_datamap.grab = if sk.input_key(Key::MouseBack).contains(ButtonState::ACTIVE)
|| sk
.input_key(Key::MouseForward)
.contains(ButtonState::ACTIVE)
{
self.datamap.select = if sk.input_key(Key::MouseLeft).contains(ButtonState::ACTIVE) {
1.0f32
} else {
0.0f32
};
self.mouse_datamap.scroll_continuous = vec2(0.0, mouse.scroll_change / 120.0);
self.mouse_datamap.scroll_discrete = vec2(0.0, mouse.scroll_change / 120.0);
*self.pointer.datamap.lock() = Datamap::from_typed(&self.mouse_datamap).ok();
self.datamap.grab = if sk.input_key(Key::MouseRight).contains(ButtonState::ACTIVE) {
1.0f32
} else {
0.0f32
};
self.datamap.scroll = vec2(0.0, mouse.scroll_change / 120.0);
*self.pointer.datamap.lock() = self.datamap.to_datamap().ok();
}
self.send_keyboard_input(sk);
}
fn send_keyboard_input(&mut self, sk: &impl StereoKitMultiThread) {
fn send_keyboard_input(&self, sk: &impl StereoKitMultiThread) {
let rx = PULSE_RECEIVER_REGISTRY
.get_valid_contents()
.into_iter()
.filter(|rx| mask_matches(&rx.mask, &self.keyboard_sender.mask))
.map(|rx| {
let result = rx.field_node.get_aspect::<Field>().unwrap().ray_march(Ray {
let result = rx.field.ray_march(Ray {
origin: vec3(0.0, 0.0, 0.0),
direction: vec3(0.0, 0.0, -1.0),
space: self.spatial.clone(),
@@ -160,131 +122,29 @@ impl MousePointer {
.map(|(rx, _)| rx);
if let Some(rx) = rx {
let mut keys_up = vec![];
let mut keys_down = vec![];
let keys = (8_u32..254)
.filter_map(|i| Key::try_from(i).ok())
.filter_map(|k| Some((map_key(k)?, sk.input_key(k))))
.filter_map(|(i, k)| {
if k.contains(ButtonState::JUST_ACTIVE) {
Some(i as i32)
} else if k.contains(ButtonState::JUST_INACTIVE) {
Some(-(i as i32))
} else {
None
}
})
.collect();
self.keyboard_datamap.keys = keys;
if !self.keyboard_datamap.keys.is_empty() {
pulse_receiver_client::data(
&rx.node.upgrade().unwrap(),
&self.node.uid,
&Datamap::from_typed(&self.keyboard_datamap).unwrap(),
)
.unwrap();
.filter_map(|i| Some((i, Key::try_from(i).ok()?)))
.map(|(i, k)| (i - 8, sk.input_key(k)));
for (key, state) in keys {
if state.contains(ButtonState::JUST_ACTIVE) {
keys_down.push(key);
} else if state.contains(ButtonState::JUST_INACTIVE) {
keys_up.push(key);
}
}
let key_event = KeyboardEvent {
keyboard: "xkbv1".to_string(),
keymap: Some(SK_KEYMAP.to_string()),
keys_up: Some(keys_up),
keys_down: Some(keys_down),
};
let mut serializer = flexbuffers::FlexbufferSerializer::new();
let _ = key_event.serialize(&mut serializer);
rx.send_data(&self.node.uid, serializer.take_buffer())
.unwrap();
}
}
}
fn map_key(key: Key) -> Option<u32> {
match key {
Key::Backspace => Some(input_event_codes::KEY_BACKSPACE!()),
Key::Tab => Some(input_event_codes::KEY_TAB!()),
Key::Return => Some(input_event_codes::KEY_ENTER!()),
Key::Shift => Some(input_event_codes::KEY_LEFTSHIFT!()),
Key::Ctrl => Some(input_event_codes::KEY_LEFTCTRL!()),
Key::Alt => Some(input_event_codes::KEY_LEFTALT!()),
Key::CapsLock => Some(input_event_codes::KEY_CAPSLOCK!()),
Key::Esc => Some(input_event_codes::KEY_ESC!()),
Key::Space => Some(input_event_codes::KEY_SPACE!()),
Key::End => Some(input_event_codes::KEY_END!()),
Key::Home => Some(input_event_codes::KEY_HOME!()),
Key::Left => Some(input_event_codes::KEY_LEFT!()),
Key::Right => Some(input_event_codes::KEY_RIGHT!()),
Key::Up => Some(input_event_codes::KEY_UP!()),
Key::Down => Some(input_event_codes::KEY_DOWN!()),
Key::PageUp => Some(input_event_codes::KEY_PAGEUP!()),
Key::PageDown => Some(input_event_codes::KEY_PAGEDOWN!()),
Key::PrintScreen => Some(input_event_codes::KEY_PRINT!()),
Key::KeyInsert => Some(input_event_codes::KEY_INSERT!()),
Key::Del => Some(input_event_codes::KEY_DELETE!()),
Key::Key0 => Some(input_event_codes::KEY_0!()),
Key::Key1 => Some(input_event_codes::KEY_1!()),
Key::Key2 => Some(input_event_codes::KEY_2!()),
Key::Key3 => Some(input_event_codes::KEY_3!()),
Key::Key4 => Some(input_event_codes::KEY_4!()),
Key::Key5 => Some(input_event_codes::KEY_5!()),
Key::Key6 => Some(input_event_codes::KEY_6!()),
Key::Key7 => Some(input_event_codes::KEY_7!()),
Key::Key8 => Some(input_event_codes::KEY_8!()),
Key::Key9 => Some(input_event_codes::KEY_9!()),
Key::A => Some(input_event_codes::KEY_A!()),
Key::B => Some(input_event_codes::KEY_B!()),
Key::C => Some(input_event_codes::KEY_C!()),
Key::D => Some(input_event_codes::KEY_D!()),
Key::E => Some(input_event_codes::KEY_E!()),
Key::F => Some(input_event_codes::KEY_F!()),
Key::G => Some(input_event_codes::KEY_G!()),
Key::H => Some(input_event_codes::KEY_H!()),
Key::I => Some(input_event_codes::KEY_I!()),
Key::J => Some(input_event_codes::KEY_J!()),
Key::K => Some(input_event_codes::KEY_K!()),
Key::L => Some(input_event_codes::KEY_L!()),
Key::M => Some(input_event_codes::KEY_M!()),
Key::N => Some(input_event_codes::KEY_N!()),
Key::O => Some(input_event_codes::KEY_O!()),
Key::P => Some(input_event_codes::KEY_P!()),
Key::Q => Some(input_event_codes::KEY_Q!()),
Key::R => Some(input_event_codes::KEY_R!()),
Key::S => Some(input_event_codes::KEY_S!()),
Key::T => Some(input_event_codes::KEY_T!()),
Key::U => Some(input_event_codes::KEY_U!()),
Key::V => Some(input_event_codes::KEY_V!()),
Key::W => Some(input_event_codes::KEY_W!()),
Key::X => Some(input_event_codes::KEY_X!()),
Key::Y => Some(input_event_codes::KEY_Y!()),
Key::Z => Some(input_event_codes::KEY_Z!()),
Key::Numpad0 => Some(input_event_codes::KEY_NUMERIC_0!()),
Key::Numpad1 => Some(input_event_codes::KEY_NUMERIC_1!()),
Key::Numpad2 => Some(input_event_codes::KEY_NUMERIC_2!()),
Key::Numpad3 => Some(input_event_codes::KEY_NUMERIC_3!()),
Key::Numpad4 => Some(input_event_codes::KEY_NUMERIC_4!()),
Key::Numpad5 => Some(input_event_codes::KEY_NUMERIC_5!()),
Key::Numpad6 => Some(input_event_codes::KEY_NUMERIC_6!()),
Key::Numpad7 => Some(input_event_codes::KEY_NUMERIC_7!()),
Key::Numpad8 => Some(input_event_codes::KEY_NUMERIC_8!()),
Key::Numpad9 => Some(input_event_codes::KEY_NUMERIC_9!()),
Key::F1 => Some(input_event_codes::KEY_F1!()),
Key::F2 => Some(input_event_codes::KEY_F2!()),
Key::F3 => Some(input_event_codes::KEY_F3!()),
Key::F4 => Some(input_event_codes::KEY_F4!()),
Key::F5 => Some(input_event_codes::KEY_F5!()),
Key::F6 => Some(input_event_codes::KEY_F6!()),
Key::F7 => Some(input_event_codes::KEY_F7!()),
Key::F8 => Some(input_event_codes::KEY_F8!()),
Key::F9 => Some(input_event_codes::KEY_F9!()),
Key::F10 => Some(input_event_codes::KEY_F10!()),
Key::F11 => Some(input_event_codes::KEY_F11!()),
Key::F12 => Some(input_event_codes::KEY_F12!()),
Key::Comma => Some(input_event_codes::KEY_COMMA!()),
Key::Period => Some(input_event_codes::KEY_DOT!()),
Key::SlashFwd => Some(input_event_codes::KEY_SLASH!()),
Key::SlashBack => Some(input_event_codes::KEY_BACKSLASH!()),
Key::Semicolon => Some(input_event_codes::KEY_SEMICOLON!()),
Key::Apostrophe => Some(input_event_codes::KEY_APOSTROPHE!()),
Key::BracketOpen => Some(input_event_codes::KEY_LEFTBRACE!()),
Key::BracketClose => Some(input_event_codes::KEY_RIGHTBRACE!()),
Key::Minus => Some(input_event_codes::KEY_MINUS!()),
Key::Equals => Some(input_event_codes::KEY_EQUAL!()),
Key::Backtick => None,
Key::LCmd => Some(input_event_codes::KEY_LEFTMETA!()),
Key::RCmd => Some(input_event_codes::KEY_RIGHTMETA!()),
Key::Multiply => Some(input_event_codes::KEY_NUMERIC_STAR!()),
Key::Add => Some(input_event_codes::KEY_KPPLUS!()),
Key::Subtract => Some(input_event_codes::KEY_MINUS!()),
Key::Decimal => Some(input_event_codes::KEY_DOT!()),
Key::Divide => Some(input_event_codes::KEY_SLASH!()),
_ => None,
}
}

275
src/objects/input/sk.kmp Normal file
View File

@@ -0,0 +1,275 @@
xkb_keymap {
default xkb_keycodes "basic" {
minimum = 8;
maximum = 255;
<backspace> = 8;
<tab> = 9;
<return> = 13;
<shift> = 16;
<ctrl> = 17;
<alt> = 18;
<caps_lock> = 20;
<esc> = 27;
<space> = 32;
<end> = 35;
<home> = 36;
<left> = 37;
<right> = 39;
<up> = 38;
<down> = 40;
<page_up> = 33;
<page_down> = 34;
<printscreen> = 42;
<key_insert> = 45;
<del> = 46;
<0> = 48;
<1> = 49;
<2> = 50;
<3> = 51;
<4> = 52;
<5> = 53;
<6> = 54;
<7> = 55;
<8> = 56;
<9> = 57;
<a> = 65;
<b> = 66;
<c> = 67;
<d> = 68;
<e> = 69;
<f> = 70;
<g> = 71;
<h> = 72;
<i> = 73;
<j> = 74;
<k> = 75;
<l> = 76;
<m> = 77;
<n> = 78;
<o> = 79;
<p> = 80;
<q> = 81;
<r> = 82;
<s> = 83;
<t> = 84;
<u> = 85;
<v> = 86;
<w> = 87;
<x> = 88;
<y> = 89;
<z> = 90;
<num0> = 96;
<num1> = 97;
<num2> = 98;
<num3> = 99;
<num4> = 100;
<num5> = 101;
<num6> = 102;
<num7> = 103;
<num8> = 104;
<num9> = 105;
<f1> = 112;
<f2> = 113;
<f3> = 114;
<f4> = 115;
<f5> = 116;
<f6> = 117;
<f7> = 118;
<f8> = 119;
<f9> = 120;
<f10> = 121;
<f11> = 122;
<f12> = 123;
<comma> = 188;
<period> = 190;
<slash_fwd> = 191;
<slash_back> = 220;
<semicolon> = 186;
<apostrophe> = 222;
<bracket_open> = 219;
<bracket_close> = 221;
<minus> = 189;
<equals> = 187;
<backtick> = 192;
<lcmd> = 91;
<rcmd> = 92;
<multiply> = 106;
<add> = 107;
<subtract> = 109;
<decimal> = 110;
<divide> = 111;
};
partial default xkb_types "basic" {
virtual_modifiers Alt;
type "ONE_LEVEL" {
modifiers= none;
level_name[1]= "Any";
};
type "TWO_LEVEL" {
modifiers= Shift;
map[Shift]= 2;
level_name[1]= "Base";
level_name[2]= "Shift";
};
type "ALPHABETIC" {
modifiers= Shift+Lock;
map[Shift]= 2;
map[Lock]= 2;
level_name[1]= "Base";
level_name[2]= "Caps";
};
type "SHIFT+ALT" {
modifiers= Shift+Alt;
map[Shift+Alt]= 2;
level_name[1]= "Base";
level_name[2]= "Shift+Alt";
};
type "PC_CONTROL_LEVEL2" {
modifiers= Control;
map[Control]= 2;
level_name[1]= "Base";
level_name[2]= "Control";
};
};
partial default xkb_compatibility "basic" {
interpret.useModMapMods= AnyLevel;
interpret.repeat= False;
interpret ISO_Level2_Latch+Exactly(Shift) {
useModMapMods=level1;
action= LatchMods(modifiers=Shift,clearLocks,latchToLock);
};
interpret Caps_Lock+AnyOfOrNone(all) {
action= LockMods(modifiers=Lock);
};
indicator "Caps Lock" {
whichModState= locked;
modifiers= Lock;
};
};
default xkb_symbols "basic" {
name[Group1]="English (US)";
key <backspace> { [ BackSpace, BackSpace ] };
key <tab> { [ Tab, ISO_Left_Tab ] };
key <return> { [ Return ] };
key <shift> { [ Shift_L ] };
key <shift> { [ Shift_R ] };
key <ctrl> { [ Control_L ] };
key <ctrl> { [ Control_R ] };
key <alt> { [ Alt_L ] };
key <alt> { [ Alt_R ] };
key <caps_lock> { [ Caps_Lock ] };
key <esc> { [ Escape ] };
key <space> { [ space ] };
key <end> { [ End ] };
key <home> { [ Home ] };
key <left> { [ Left ] };
key <right> { [ Right ] };
key <up> { [ Up ] };
key <down> { [ Down ] };
key <page_up> { [ Page_Up ] };
key <page_down> { [ Page_Down ] };
key <printscreen> { [ Print ] };
key <key_insert> { [ Insert ] };
key <del> { [ Delete ] };
key <1> { [ 1, exclam ] };
key <2> { [ 2, at ] };
key <3> { [ 3, numbersign ] };
key <4> { [ 4, dollar ] };
key <5> { [ 5, percent ] };
key <6> { [ 6, asciicircum ] };
key <7> { [ 7, ampersand ] };
key <8> { [ 8, asterisk ] };
key <9> { [ 9, parenleft ] };
key <0> { [ 0, parenright ] };
key <a> { [ a, A ] };
key <b> { [ b, B ] };
key <c> { [ c, C ] };
key <d> { [ d, D ] };
key <e> { [ e, E ] };
key <f> { [ f, F ] };
key <g> { [ g, G ] };
key <h> { [ h, H ] };
key <i> { [ i, I ] };
key <j> { [ j, J ] };
key <k> { [ k, K ] };
key <l> { [ l, L ] };
key <m> { [ m, M ] };
key <n> { [ n, N ] };
key <o> { [ o, O ] };
key <p> { [ p, P ] };
key <q> { [ q, Q ] };
key <r> { [ r, R ] };
key <s> { [ s, S ] };
key <t> { [ t, T ] };
key <u> { [ u, U ] };
key <v> { [ v, V ] };
key <w> { [ w, W ] };
key <x> { [ x, X ] };
key <y> { [ y, Y ] };
key <z> { [ z, Z ] };
key <num0> { [ KP_0 ] };
key <num1> { [ KP_1 ] };
key <num2> { [ KP_2 ] };
key <num3> { [ KP_3 ] };
key <num4> { [ KP_4 ] };
key <num5> { [ KP_5 ] };
key <num6> { [ KP_6 ] };
key <num7> { [ KP_7 ] };
key <num8> { [ KP_8 ] };
key <num9> { [ KP_9 ] };
key <f1> { [ F1 ] };
key <f2> { [ F2 ] };
key <f3> { [ F3 ] };
key <f4> { [ F4 ] };
key <f5> { [ F5 ] };
key <f6> { [ F6 ] };
key <f7> { [ F7 ] };
key <f8> { [ F8 ] };
key <f9> { [ F9 ] };
key <f10> { [ F10 ] };
key <f11> { [ F11 ] };
key <f12> { [ F12 ] };
key <comma> { [ comma, less ] };
key <period> { [ period, greater ] };
key <slash_fwd> { [ slash, question ] };
key <slash_back> { [ backslash, bar ] };
key <semicolon> { [ semicolon, colon ] };
key <apostrophe> { [ apostrophe ] };
key <bracket_open> { [ bracketleft, braceleft ] };
key <bracket_close> { [ bracketright, braceright ] };
key <minus> { [ minus, underscore ] };
key <equals> { [ equal, plus ] };
key <backtick> { [ grave, asciitilde ] };
key <lcmd> { [ Super_L ] };
key <rcmd> { [ Super_R ] };
key <multiply> { [ KP_Multiply ] };
key <add> { [ KP_Add ] };
key <subtract> { [ KP_Subtract ] };
key <decimal> { [ KP_Decimal ] };
key <divide> { [ KP_Divide ] };
modifier_map Shift { <shift> };
modifier_map Lock { <caps_lock> };
modifier_map Control { <caps_lock> };
modifier_map Mod1 { <alt> };
modifier_map Mod4 { <lcmd>, <rcmd> };
};
};

View File

@@ -1,5 +1,5 @@
use crate::{
core::client::INTERNAL_CLIENT,
core::{client::INTERNAL_CLIENT, typed_datamap::TypedDatamap},
nodes::{
input::{tip::Tip, InputMethod, InputType},
spatial::Spatial,
@@ -7,14 +7,15 @@ use crate::{
},
};
use color_eyre::eyre::Result;
use glam::{Mat4, Vec2, Vec3};
use glam::{Mat4, Vec2};
use nanoid::nanoid;
use serde::{Deserialize, Serialize};
use stardust_xr::values::Datamap;
use stardust_xr::values::Transform;
use std::sync::Arc;
use stereokit::{
named_colors::WHITE, ButtonState, Handed, Model, RenderLayer, StereoKitDraw,
StereoKitMultiThread,
ButtonState, Color128, Handed, Model, RenderLayer, StereoKitDraw, StereoKitMultiThread,
};
use tracing::instrument;
#[derive(Default, Deserialize, Serialize)]
struct ControllerDatamap {
@@ -28,23 +29,13 @@ pub struct SkController {
input: Arc<InputMethod>,
model: Model,
handed: Handed,
datamap: ControllerDatamap,
datamap: TypedDatamap<ControllerDatamap>,
}
impl SkController {
pub fn new(sk: &impl StereoKitMultiThread, handed: Handed) -> Result<Self> {
let _node = Node::create_parent_name(
&INTERNAL_CLIENT,
"",
if handed == Handed::Left {
"controller_left"
} else {
"controller_right"
},
false,
)
.add_to_scenegraph()?;
Spatial::add_to(&_node, None, Mat4::IDENTITY, false);
let model = sk.model_create_mem("cursor.glb", include_bytes!("cursor.glb"), None)?;
let _node = Node::create(&INTERNAL_CLIENT, "", &nanoid!(), false).add_to_scenegraph()?;
Spatial::add_to(&_node, None, Mat4::IDENTITY, false)?;
let model = sk.model_create_mem("cursor", include_bytes!("cursor.glb"), None)?;
let tip = InputType::Tip(Tip::default());
let input = InputMethod::add_to(&_node, tip, None)?;
Ok(SkController {
@@ -55,25 +46,31 @@ impl SkController {
datamap: Default::default(),
})
}
#[instrument(level = "debug", name = "Update StereoKit Tip Input Method", skip_all)]
pub fn update(&mut self, sk: &impl StereoKitDraw) {
let controller = sk.input_controller(self.handed);
*self.input.enabled.lock() = controller.tracked.contains(ButtonState::ACTIVE);
if *self.input.enabled.lock() {
let world_transform = Mat4::from_rotation_translation(
controller.aim.orientation,
controller.aim.position,
);
sk.model_draw(
&self.model,
world_transform * Mat4::from_scale(Vec3::ONE * 0.02),
WHITE,
RenderLayer::LAYER0,
Mat4::from_rotation_translation(
controller.aim.orientation,
controller.aim.position,
),
Color128::default(),
RenderLayer::all(),
);
self.input.spatial.set_local_transform_components(
None,
Transform::from_position_rotation(
controller.aim.position,
controller.aim.orientation,
),
);
self.input.spatial.set_local_transform(world_transform);
}
self.datamap.select = controller.trigger;
self.datamap.grab = controller.grip;
self.datamap.scroll = controller.stick;
*self.input.datamap.lock() = Datamap::from_typed(&self.datamap).ok();
*self.input.datamap.lock() = self.datamap.to_datamap().ok();
}
}

View File

@@ -1,5 +1,5 @@
use crate::{
core::client::INTERNAL_CLIENT,
core::{client::INTERNAL_CLIENT, typed_datamap::TypedDatamap},
nodes::{
input::{hand::Hand, InputMethod, InputType},
spatial::Spatial,
@@ -10,19 +10,16 @@ use color_eyre::eyre::Result;
use glam::Mat4;
use nanoid::nanoid;
use serde::{Deserialize, Serialize};
use stardust_xr::{
schemas::flat::{Hand as FlatHand, Joint},
values::Datamap,
};
use stardust_xr::schemas::flat::{Hand as FlatHand, Joint};
use std::sync::Arc;
use stereokit::{ButtonState, HandJoint, Handed, StereoKitMultiThread};
use tracing::instrument;
fn convert_joint(joint: HandJoint) -> Joint {
Joint {
position: joint.position.into(),
rotation: joint.orientation.into(),
radius: joint.radius,
distance: 0.0,
}
}
@@ -36,13 +33,12 @@ pub struct SkHand {
_node: Arc<Node>,
input: Arc<InputMethod>,
handed: Handed,
datamap: HandDatamap,
datamap: TypedDatamap<HandDatamap>,
}
impl SkHand {
pub fn new(handed: Handed) -> Result<Self> {
let _node = Node::create_parent_name(&INTERNAL_CLIENT, "", &nanoid!(), false)
.add_to_scenegraph()?;
Spatial::add_to(&_node, None, Mat4::IDENTITY, false);
let _node = Node::create(&INTERNAL_CLIENT, "", &nanoid!(), false).add_to_scenegraph()?;
Spatial::add_to(&_node, None, Mat4::IDENTITY, false)?;
let hand = InputType::Hand(Box::new(Hand {
base: FlatHand {
right: handed == Handed::Right,
@@ -57,16 +53,13 @@ impl SkHand {
datamap: Default::default(),
})
}
pub fn update(&mut self, controller_enabled: bool, sk: &impl StereoKitMultiThread) {
#[instrument(level = "debug", name = "Update Hand Input Method", skip_all)]
pub fn update(&mut self, sk: &impl StereoKitMultiThread) {
let sk_hand = sk.input_hand(self.handed);
if let InputType::Hand(hand) = &mut *self.input.specialization.lock() {
let controller_active = controller_enabled
&& sk
.input_controller(self.handed)
.tracked
.contains(ButtonState::ACTIVE);
*self.input.enabled.lock() =
!controller_active && sk_hand.tracked_state.contains(ButtonState::ACTIVE);
let controller = sk.input_controller(self.handed);
*self.input.enabled.lock() = controller.tracked.contains(ButtonState::INACTIVE)
&& sk_hand.tracked_state.contains(ButtonState::ACTIVE);
sk.input_hand_visible(self.handed, *self.input.enabled.lock());
if *self.input.enabled.lock() {
hand.base.thumb.tip = convert_joint(sk_hand.fingers[0][4]);
@@ -102,6 +95,6 @@ impl SkHand {
}
self.datamap.pinch_strength = sk_hand.pinch_activation;
self.datamap.grab_strength = sk_hand.grip_activation;
*self.input.datamap.lock() = Datamap::from_typed(&self.datamap).ok();
*self.input.datamap.lock() = self.datamap.to_datamap().ok();
}
}

View File

@@ -5,13 +5,12 @@ use glam::Mat4;
use mint::Vector2;
use nanoid::nanoid;
use serde::{Deserialize, Serialize};
use stardust_xr::values::Datamap;
use stereokit::StereoKitMultiThread;
use crate::{
core::client::INTERNAL_CLIENT,
nodes::{
data::PulseReceiver,
data::{Mask, PulseReceiver},
fields::{r#box::BoxField, Field},
spatial::Spatial,
Node,
@@ -40,17 +39,12 @@ pub struct PlaySpace {
}
impl PlaySpace {
pub fn new() -> Result<Self> {
let node = Node::create_parent_name(&INTERNAL_CLIENT, "", &nanoid!(), false)
.add_to_scenegraph()?;
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false);
BoxField::add_to(&node, [0.0; 3].into());
let field = node.get_aspect::<Field>()?.clone();
let node = Node::create(&INTERNAL_CLIENT, "", &nanoid!(), false).add_to_scenegraph()?;
let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false)?;
let field = BoxField::add_to(&node, [0.0; 3].into())?;
let pulse_rx = PulseReceiver::add_to(
&node,
node.clone(),
Datamap::from_typed(PlaySpaceMap::default())?,
)?;
let pulse_rx =
PulseReceiver::add_to(&node, field.clone(), Mask::from_struct::<PlaySpaceMap>())?;
Ok(PlaySpace {
_node: node,
@@ -66,9 +60,7 @@ impl PlaySpace {
pose.orientation,
pose.position,
));
let Field::Box(box_field) = self.field.as_ref() else {
return;
};
let Field::Box(box_field) = self.field.as_ref() else {return};
box_field.set_size(
[
sk.world_get_bounds_size().x,

76
src/openxr/action.rs Normal file
View File

@@ -0,0 +1,76 @@
use super::Object;
use crate::{core::client::Client, nodes::Node};
use color_eyre::eyre::{bail, Result};
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use serde::Deserialize;
use stardust_xr::schemas::flex::deserialize;
use std::sync::Arc;
#[derive(Debug)]
pub struct Action {
// _info: InstanceInfo,
_localized_name: String,
suggested_bindings: Mutex<FxHashMap<String, String>>,
}
impl Action {
pub fn create_action_flex(
node: &Node,
_calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
let Object::ActionSet(action_set) = node.get_aspect("OpenXR interface", "Instance", |n| &n.openxr_object)? else {
bail!("Object not an instance")
};
#[derive(Debug, Deserialize)]
struct CreateActionInfo {
name: String,
localized_name: String,
}
let info: CreateActionInfo = dbg!(deserialize(data)?);
let node = Node::create(
&node.get_client().unwrap(),
node.get_path(),
&info.name,
true,
)
.add_to_scenegraph();
node.add_local_signal("suggest_binding", Self::suggest_binding_flex);
let action = Arc::new(Action {
_localized_name: info.localized_name,
suggested_bindings: Mutex::new(FxHashMap::default()),
});
action_set
.actions
.lock()
.insert(info.name, Arc::downgrade(&action));
node.openxr_object.set(Object::Action(action)).unwrap();
Ok(())
}
pub fn suggest_binding_flex(
node: &Node,
_calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
let Object::Action(action) = node.get_aspect("OpenXR interface", "Action", |n| &n.openxr_object)? else {
bail!("Object not an action")
};
#[derive(Debug, Deserialize)]
struct SuggestBindingArgs {
interaction_profile: String,
binding: String,
}
let args: SuggestBindingArgs = dbg!(deserialize(data)?);
action
.suggested_bindings
.lock()
.insert(args.interaction_profile, args.binding);
Ok(())
}
}

60
src/openxr/action_set.rs Normal file
View File

@@ -0,0 +1,60 @@
use super::{action::Action, Object};
use crate::{core::client::Client, nodes::Node};
use color_eyre::eyre::{bail, Result};
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use serde::Deserialize;
use stardust_xr::schemas::flex::deserialize;
use std::sync::{Arc, Weak};
#[derive(Debug)]
pub struct ActionSet {
// _info: InstanceInfo,
_localized_name: String,
_priority: u32,
pub actions: Mutex<FxHashMap<String, Weak<Action>>>,
}
impl ActionSet {
pub fn create_action_set_flex(
node: &Node,
_calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
let Object::Instance(instance) = node.get_aspect("OpenXR interface", "Instance", |n| &n.openxr_object)? else {
bail!("Object not an instance")
};
let Some(instance) = instance.get() else { bail!("Instance not initialized") };
#[derive(Deserialize)]
struct CreateActionSetInfo {
name: String,
localized_name: String,
priority: u32,
}
let info: CreateActionSetInfo = deserialize(data)?;
let node = Node::create(
&node.get_client().unwrap(),
"/openxr/action_set",
&info.name,
true,
)
.add_to_scenegraph();
node.add_local_signal("create_action", Action::create_action_flex);
let action_set = Arc::new(ActionSet {
_localized_name: info.localized_name,
_priority: info.priority,
actions: Mutex::new(FxHashMap::default()),
});
instance
.action_sets
.lock()
.insert(info.name, Arc::downgrade(&action_set));
node.openxr_object
.set(Object::ActionSet(action_set))
.unwrap();
Ok(())
}
}

47
src/openxr/instance.rs Normal file
View File

@@ -0,0 +1,47 @@
use super::{action_set::ActionSet, Object};
use crate::{core::client::Client, nodes::Node};
use color_eyre::eyre::{bail, eyre, Result};
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use serde::Deserialize;
use stardust_xr::schemas::flex::deserialize;
use std::sync::{Arc, Weak};
#[derive(Debug, Deserialize)]
struct InstanceInfo {
_app_info: ApplicationInfo,
_extension_names: Vec<String>,
}
#[derive(Debug, Deserialize)]
struct ApplicationInfo {
_app_name: String,
_app_version: u32,
_engine_name: String,
_engine_version: u32,
_api_version: u64,
}
#[derive(Debug)]
pub struct Instance {
_info: InstanceInfo,
pub action_sets: Mutex<FxHashMap<String, Weak<ActionSet>>>,
}
impl Instance {
pub fn setup_instance_flex(
node: &Node,
_calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
let Object::Instance(instance) = node.get_aspect("OpenXR interface", "Instance", |n| &n.openxr_object)? else {
bail!("Object not an instance")
};
let instance_info = Instance {
_info: deserialize(data)?,
action_sets: Mutex::new(FxHashMap::default()),
};
dbg!(&instance_info);
instance
.set(Arc::new(instance_info))
.map_err(|_| eyre!("Instance already set up"))
}
}

34
src/openxr/mod.rs Normal file
View File

@@ -0,0 +1,34 @@
mod action;
mod action_set;
mod instance;
mod session;
mod system;
use self::{
action::Action, action_set::ActionSet, instance::Instance, session::Session, system::System,
};
use crate::{core::client::Client, nodes::Node};
use once_cell::sync::OnceCell;
use std::sync::Arc;
#[derive(Debug)]
pub enum Object {
Instance(OnceCell<Arc<Instance>>),
System(System),
Session(Session),
ActionSet(Arc<ActionSet>),
Action(Arc<Action>),
}
pub fn create_interface(client: &Arc<Client>) {
let node = Node::create(client, "", "openxr", false);
node.add_local_signal("setup_instance", Instance::setup_instance_flex);
node.add_local_method("get_system", System::get_system_flex);
node.add_local_signal("create_action_set", ActionSet::create_action_set_flex);
node.openxr_object
.set(Object::Instance(OnceCell::new()))
.unwrap();
node.add_to_scenegraph();
}

34
src/openxr/session.rs Normal file
View File

@@ -0,0 +1,34 @@
use std::sync::Arc;
use color_eyre::eyre::{bail, Result};
use stardust_xr::schemas::flex::deserialize;
use super::Object;
use crate::{core::client::Client, nodes::Node};
#[derive(Debug)]
pub struct Session {
// _info: InstanceInfo,
}
impl Session {
pub fn create_session_flex(
node: &Node,
_calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
let Object::System(_system) = node.get_aspect("OpenXR interface", "Instance", |n| &n.openxr_object)? else {
bail!("Object not a system")
};
let node = Node::create(
&node.get_client().unwrap(),
node.get_path(),
deserialize(data)?,
true,
)
.add_to_scenegraph();
let session = Session {};
node.openxr_object.set(Object::Session(session)).unwrap();
Ok(())
}
}

76
src/openxr/system.rs Normal file
View File

@@ -0,0 +1,76 @@
use super::{session::Session, Object};
use crate::{core::client::Client, nodes::Node, SK_INFO};
use color_eyre::eyre::{bail, eyre, Result};
use serde::Serialize;
use stardust_xr::schemas::flex::{deserialize, serialize};
use std::sync::Arc;
#[derive(Debug)]
pub enum System {
Handheld,
HeadMounted,
}
impl System {
pub fn from_raw(raw: u32) -> Option<Self> {
match raw {
1 => Some(System::Handheld),
2 => Some(System::HeadMounted),
_ => None,
}
}
pub fn get_system_flex(
node: &Node,
_calling_client: Arc<Client>,
data: &[u8],
) -> Result<Vec<u8>> {
// let Object::Instance(instance) = node.get_aspect("OpenXR interface", "Instance", |n| &n.openxr_object)? else {
// bail!("Object not an instance")
// };
let system_type: u32 = deserialize(data)?;
let system = System::from_raw(system_type).ok_or_else(|| eyre!("No system exists!"))?;
let node = Node::create(
&node.get_client().unwrap(),
node.get_path(),
&format!("system{}", system_type),
true,
)
.add_to_scenegraph();
node.add_local_method("views", System::views_flex);
node.add_local_signal("create_session", Session::create_session_flex);
node.openxr_object.set(Object::System(system)).unwrap();
Ok(serialize(system_type)?)
}
fn views_flex(_node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<Vec<u8>> {
let view_configuration_type: u64 = deserialize(data)?;
let view_count: u32 = match view_configuration_type {
1 => 1,
2 => 2,
1000037000 => 4,
1000054000 => 1,
_ => bail!("Invalid view config type"),
};
#[derive(Debug, Serialize)]
struct View {
recommended_image_rect_width: u32,
max_image_rect_width: u32,
recommended_image_rect_height: u32,
max_image_rect_height: u32,
}
let sk_info = SK_INFO.get().unwrap();
Ok(serialize(
(0..view_count)
.map(|_| View {
recommended_image_rect_width: sk_info.display_width,
max_image_rect_width: sk_info.display_width,
recommended_image_rect_height: sk_info.display_height,
max_image_rect_height: sk_info.display_height,
})
.collect::<Vec<_>>(),
)?)
}
}

View File

@@ -25,9 +25,11 @@ impl CompositorHandler for WaylandState {
.data_map
.insert_if_missing_threadsafe(|| AtomicU32::new(0));
if !count_new {
if let Some(stored_count) = data.data_map.get::<AtomicU32>() {
count = stored_count.fetch_add(1, Ordering::Relaxed);
}
count = data
.data_map
.get::<AtomicU32>()
.unwrap()
.fetch_add(1, Ordering::Relaxed);
}
data.data_map.get::<Arc<CoreSurface>>().cloned()

View File

@@ -90,8 +90,7 @@ impl KdeDecorationHandler for WaylandState {
decoration: &OrgKdeKwinServerDecoration,
mode: WEnum<KdeMode>,
) {
let Ok(mode) = mode.into_result() else {return};
decoration.mode(mode);
decoration.mode(mode.into_result().unwrap());
}
}
delegate_kde_decoration!(WaylandState);

View File

@@ -1,144 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-only
// Re-export only the actual code, and then only use this re-export
// The `generated` module below is just some boilerplate to properly isolate stuff
// and avoid exposing internal details.
//
// You can use all the types from my_protocol as if they went from `wayland_client::protocol`.
pub use generated::wl_drm;
#[allow(non_upper_case_globals, non_camel_case_types)]
mod generated {
use smithay::reexports::wayland_server::{self, protocol::*};
pub mod __interfaces {
use smithay::reexports::wayland_server::protocol::__interfaces::*;
wayland_scanner::generate_interfaces!("src/wayland/wayland-drm.xml");
}
use self::__interfaces::*;
wayland_scanner::generate_server_code!("src/wayland/wayland-drm.xml");
}
use super::state::WaylandState;
use smithay::{
backend::allocator::{
dmabuf::{Dmabuf, DmabufFlags},
Fourcc, Modifier,
},
reexports::wayland_server::{
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
},
};
use std::convert::TryFrom;
impl GlobalDispatch<wl_drm::WlDrm, (), WaylandState> for WaylandState {
fn bind(
state: &mut WaylandState,
_dh: &DisplayHandle,
_client: &Client,
resource: New<wl_drm::WlDrm>,
_global_data: &(),
data_init: &mut DataInit<'_, WaylandState>,
) {
let drm_instance = data_init.init(resource, ());
drm_instance.device("/dev/dri/renderD128".to_string());
if drm_instance.version() >= 2 {
drm_instance.capabilities(wl_drm::Capability::Prime as u32);
}
for format in state.drm_formats.iter() {
if let Ok(converted) = wl_drm::Format::try_from(*format as u32) {
drm_instance.format(converted as u32);
}
}
}
fn can_view(_client: Client, _global_dataa: &()) -> bool {
true
}
}
impl Dispatch<wl_drm::WlDrm, (), WaylandState> for WaylandState {
fn request(
state: &mut WaylandState,
_client: &Client,
drm: &wl_drm::WlDrm,
request: wl_drm::Request,
_data: &(),
_dh: &DisplayHandle,
data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
wl_drm::Request::Authenticate { .. } => drm.authenticated(),
wl_drm::Request::CreateBuffer { .. } => drm.post_error(
wl_drm::Error::InvalidName,
String::from("Flink handles are unsupported, use PRIME"),
),
wl_drm::Request::CreatePlanarBuffer { .. } => drm.post_error(
wl_drm::Error::InvalidName,
String::from("Flink handles are unsupported, use PRIME"),
),
wl_drm::Request::CreatePrimeBuffer {
id,
name,
width,
height,
format,
offset0,
stride0,
..
} => {
let format = match Fourcc::try_from(format) {
Ok(format) => {
if !state.drm_formats.contains(&format) {
drm.post_error(
wl_drm::Error::InvalidFormat,
String::from("Format not advertised by wl_drm"),
);
return;
}
format
}
Err(_) => {
drm.post_error(
wl_drm::Error::InvalidFormat,
String::from("Format unknown / not advertised by wl_drm"),
);
return;
}
};
if width < 1 || height < 1 {
drm.post_error(
wl_drm::Error::InvalidFormat,
String::from("width or height not positive"),
);
return;
}
let mut dma = Dmabuf::builder(
(width, height),
format,
Modifier::Invalid,
DmabufFlags::empty(),
);
dma.add_plane(name, 0, offset0 as u32, stride0 as u32);
match dma.build() {
Some(dmabuf) => {
state.dmabuf_tx.send((dmabuf.clone(), None)).unwrap();
data_init.init(id, dmabuf);
}
None => {
// Buffer import failed. The protocol documentation heavily implies killing the
// client is the right thing to do here.
drm.post_error(
wl_drm::Error::InvalidName,
"dmabuf global was destroyed on server",
);
}
}
}
}
}
}

View File

@@ -2,22 +2,13 @@ mod compositor;
mod data_device;
mod decoration;
mod seat;
mod shaders;
mod state;
mod surface;
// mod xdg_activation;
mod drm;
mod utils;
mod xdg_shell;
#[cfg(feature = "xwayland_rootful")]
pub mod xwayland_rootful;
#[cfg(feature = "xwayland_rootful")]
use self::xwayland_rootful::X11Lock;
#[cfg(feature = "xwayland_rootful")]
use crate::wayland::xwayland_rootful::start_xwayland;
#[cfg(feature = "xwayland_rootless")]
pub mod xwayland_rootless;
#[cfg(feature = "xwayland_rootless")]
use self::xwayland_rootless::XWaylandState;
#[cfg(feature = "xwayland")]
pub mod xwayland;
use self::{state::WaylandState, surface::CORE_SURFACES};
use crate::wayland::seat::SeatData;
@@ -31,13 +22,7 @@ use smithay::backend::allocator::dmabuf::Dmabuf;
use smithay::backend::egl::EGLContext;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::backend::renderer::ImportDma;
use smithay::output::Output;
use smithay::reexports::wayland_server::backend::ClientId;
use smithay::reexports::wayland_server::DisplayHandle;
use smithay::reexports::wayland_server::{Display, ListeningSocket};
use smithay::wayland::dmabuf;
use std::ffi::OsStr;
use std::os::fd::OwnedFd;
use smithay::reexports::wayland_server::{backend::GlobalId, Display, ListeningSocket};
use std::os::unix::prelude::AsRawFd;
use std::{
ffi::c_void,
@@ -49,9 +34,8 @@ use tokio::sync::mpsc::UnboundedReceiver;
use tokio::{
io::unix::AsyncFd, net::UnixListener as AsyncUnixListener, sync::mpsc, task::JoinHandle,
};
use tracing::{debug_span, info, instrument};
use tracing::{debug, debug_span, info, instrument};
pub static X_DISPLAY: OnceCell<u32> = OnceCell::new();
pub static WAYLAND_DISPLAY: OnceCell<String> = OnceCell::new();
pub static SERIAL_COUNTER: CounterU32 = CounterU32::new(0);
@@ -76,35 +60,17 @@ fn get_sk_egl() -> Result<EGLRawHandles> {
})
}
pub struct DisplayWrapper(Mutex<Display<WaylandState>>, DisplayHandle);
impl DisplayWrapper {
pub fn handle(&self) -> DisplayHandle {
self.1.clone()
}
pub fn dispatch_clients(&self, state: &mut WaylandState) -> Result<usize, std::io::Error> {
self.0.lock().dispatch_clients(state)
}
pub fn flush_clients(&self, client: Option<ClientId>) {
if let Some(mut lock) = self.0.try_lock() {
let _ = lock.backend().flush(client);
}
}
pub fn poll_fd(&self) -> Result<OwnedFd, std::io::Error> {
self.0.lock().backend().poll_fd().try_clone_to_owned()
}
}
static GLOBAL_DESTROY_QUEUE: OnceCell<mpsc::Sender<GlobalId>> = OnceCell::new();
pub struct Wayland {
display: Arc<DisplayWrapper>,
pub socket_name: Option<String>,
display: Arc<Mutex<Display<WaylandState>>>,
pub socket_name: String,
join_handle: JoinHandle<Result<()>>,
renderer: GlesRenderer,
output: Output,
dmabuf_rx: UnboundedReceiver<(Dmabuf, Option<dmabuf::ImportNotifier>)>,
#[cfg(feature = "xwayland_rootful")]
pub x_lock: X11Lock,
#[cfg(feature = "xwayland_rootless")]
pub xwayland_state: XWaylandState,
dmabuf_rx: UnboundedReceiver<Dmabuf>,
wayland_state: Arc<Mutex<WaylandState>>,
#[cfg(feature = "xwayland")]
pub xwayland_state: xwayland::XWaylandState,
}
impl Wayland {
pub fn new() -> Result<Self> {
@@ -121,74 +87,80 @@ impl Wayland {
let display_handle = display.handle();
let (dmabuf_tx, dmabuf_rx) = mpsc::unbounded_channel();
let display = Arc::new(DisplayWrapper(Mutex::new(display), display_handle.clone()));
let display = Arc::new(Mutex::new(display));
#[cfg(feature = "xwayland")]
let xwayland_state = xwayland::XWaylandState::create(&display_handle).unwrap();
let wayland_state =
WaylandState::new(display.clone(), display_handle, &renderer, dmabuf_tx);
#[cfg(feature = "xwayland_rootless")]
let xwayland_state = XWaylandState::create(&display_handle)?;
let wayland_state = WaylandState::new(display_handle, &renderer, dmabuf_tx);
let output = wayland_state.lock().output.clone();
let (global_destroy_queue_in, global_destroy_queue) = mpsc::channel(8);
GLOBAL_DESTROY_QUEUE.set(global_destroy_queue_in).unwrap();
let socket = ListeningSocket::bind_auto("wayland", 0..33)?;
let socket_name = socket
.socket_name()
.and_then(OsStr::to_str)
.map(ToString::to_string);
if let Some(socket_name) = &socket_name {
let _ = WAYLAND_DISPLAY.set(socket_name.clone());
}
#[cfg(feature = "xwayland_rootful")]
let x_display = start_xwayland(socket.as_raw_fd())?;
let socket_name = socket.socket_name().unwrap().to_str().unwrap().to_string();
WAYLAND_DISPLAY
.set(socket_name.clone())
.expect("seriously message nova this time they screwed up big time");
info!(socket_name, "Wayland active");
let join_handle = Wayland::start_loop(display.clone(), socket, wayland_state)?;
let join_handle = Wayland::start_loop(
display.clone(),
socket,
wayland_state.clone(),
global_destroy_queue,
)?;
Ok(Wayland {
display,
socket_name,
join_handle,
renderer,
output,
dmabuf_rx,
#[cfg(feature = "xwayland_rootful")]
x_lock: x_display,
#[cfg(feature = "xwayland_rootless")]
wayland_state,
#[cfg(feature = "xwayland")]
xwayland_state,
})
}
fn start_loop(
display: Arc<DisplayWrapper>,
display: Arc<Mutex<Display<WaylandState>>>,
socket: ListeningSocket,
state: Arc<Mutex<WaylandState>>,
mut global_destroy_queue: mpsc::Receiver<GlobalId>,
) -> Result<JoinHandle<Result<()>>> {
let listen_async =
AsyncUnixListener::from_std(unsafe { UnixListener::from_raw_fd(socket.as_raw_fd()) })?;
let dispatch_poll_fd = display.poll_fd()?;
let dispatch_poll_fd = display.lock().backend().poll_fd().try_clone_to_owned()?;
let dispatch_poll_listener = AsyncFd::new(dispatch_poll_fd)?;
let dh1 = display.handle();
let dh1 = display.lock().handle();
let mut dh2 = dh1.clone();
Ok(task::new(|| "wayland loop", async move {
let _socket = socket; // Keep the socket alive
loop {
tokio::select! {
e = global_destroy_queue.recv() => { // New global to destroy
debug!(?e, "destroy global");
dh1.remove_global::<WaylandState>(e.unwrap());
}
acc = listen_async.accept() => { // New client connected
let (stream, _) = acc?;
let client_state = Arc::new(ClientState {
id: OnceCell::new(),
compositor_state: Default::default(),
display: Arc::downgrade(&display),
seat: SeatData::new(&dh1)
});
let client = dh2.insert_client(stream.into_std()?, client_state.clone())?;
let _ = client_state.seat.client.set(client.id());
client_state.seat.client.set(client.id()).unwrap();
}
e = dispatch_poll_listener.readable() => { // Dispatch
let mut guard = e?;
debug_span!("Dispatch wayland event").in_scope(|| -> Result<(), color_eyre::Report> {
display.dispatch_clients(&mut state.lock())?;
display.flush_clients(None);
let mut display = display.lock();
display.dispatch_clients(&mut *state.lock())?;
display.flush_clients()?;
Ok(())
})?;
guard.clear_ready();
@@ -200,29 +172,27 @@ impl Wayland {
#[instrument(level = "debug", name = "Wayland frame", skip(self, sk))]
pub fn update(&mut self, sk: &impl StereoKitDraw) {
while let Ok((dmabuf, notifier)) = self.dmabuf_rx.try_recv() {
if self.renderer.import_dmabuf(&dmabuf, None).is_err() {
if let Some(notifier) = notifier {
notifier.failed();
}
}
while let Ok(dmabuf) = self.dmabuf_rx.try_recv() {
let _ = self.renderer.import_dmabuf(&dmabuf, None);
}
for core_surface in CORE_SURFACES.get_valid_contents() {
core_surface.process(sk, &mut self.renderer);
}
self.display.flush_clients(None);
self.display.lock().flush_clients().unwrap();
}
pub fn frame_event(&self, sk: &impl StereoKitDraw) {
let state = self.wayland_state.lock();
for core_surface in CORE_SURFACES.get_valid_contents() {
core_surface.frame(sk, self.output.clone());
core_surface.frame(sk, state.output.clone());
}
}
pub fn make_context_current(&self) {
unsafe {
let _ = self.renderer.egl_context().make_current();
self.renderer.egl_context().make_current().unwrap();
}
}
}

View File

@@ -1,13 +1,10 @@
use super::{
state::{ClientState, WaylandState},
surface::CoreSurface,
SERIAL_COUNTER,
GLOBAL_DESTROY_QUEUE, SERIAL_COUNTER,
};
use crate::{
core::task,
nodes::items::panel::{Backend, Geometry, PanelItem},
};
use color_eyre::eyre::{bail, eyre, Result};
use crate::core::task;
use color_eyre::eyre::{eyre, Result};
use mint::Vector2;
use nanoid::nanoid;
use once_cell::sync::OnceCell;
@@ -36,46 +33,35 @@ use std::{
};
use tokio::sync::watch;
use tracing::{debug, warn};
use xkbcommon::xkb::{self, ffi::XKB_KEYMAP_FORMAT_TEXT_V1, Keycode, Keymap};
pub fn handle_cursor<B: Backend>(
panel_item: &Arc<PanelItem<B>>,
mut cursor: watch::Receiver<Option<CursorInfo>>,
) {
let panel_item_weak = Arc::downgrade(panel_item);
let _ = task::new(|| "cursor handler", async move {
while cursor.changed().await.is_ok() {
let Some(panel_item) = panel_item_weak.upgrade() else {continue};
let cursor_info = cursor.borrow();
panel_item.set_cursor(cursor_info.as_ref().and_then(CursorInfo::cursor_data));
}
});
}
use xkbcommon::xkb::{self, ffi::XKB_KEYMAP_FORMAT_TEXT_V1, Keymap};
pub struct KeyboardInfo {
keymap_string: String,
keymap: KeymapFile,
state: xkb::State,
mods: ModifiersState,
keys: FxHashSet<u32>,
}
impl KeyboardInfo {
pub fn new(keymap_string: String, keymap: &Keymap) -> Self {
pub fn new(keymap: &Keymap) -> Self {
KeyboardInfo {
keymap_string,
state: xkb::State::new(keymap),
keymap: KeymapFile::new(keymap),
mods: ModifiersState::default(),
keys: FxHashSet::default(),
}
}
pub fn process(&mut self, key: u32, pressed: bool, keyboard: &WlKeyboard) -> Result<usize> {
let xkb_key_state = if pressed {
xkb::KeyDirection::Down
} else {
xkb::KeyDirection::Up
pub fn process(&mut self, key: u32, state: u32, keyboard: &WlKeyboard) -> Result<usize> {
let wl_key_state = match state {
0 => KeyState::Released,
1 => KeyState::Pressed,
_ => color_eyre::eyre::bail!("Invalid key state!"),
};
let state_components = self.state.update_key(Keycode::new(key + 8), xkb_key_state);
let xkb_key_state = match state {
0 => xkb::KeyDirection::Up,
1 => xkb::KeyDirection::Down,
_ => color_eyre::eyre::bail!("Invalid key state!"),
};
let state_components = self.state.update_key(key + 8, xkb_key_state);
if state_components != 0 {
self.mods.update_with(&self.state);
keyboard.modifiers(
@@ -86,17 +72,6 @@ impl KeyboardInfo {
0,
);
}
// if pressed {
// println!("Key {key} is being pressed with {state_components} modifiers");
// } else {
// println!("Key {key} is being released with {state_components} modifiers");
// }
let wl_key_state = if pressed {
KeyState::Pressed
} else {
KeyState::Released
};
keyboard.key(SERIAL_COUNTER.inc(), 0, key, wl_key_state);
match wl_key_state {
KeyState::Pressed => {
@@ -127,10 +102,10 @@ pub enum PointerEvent {
#[derive(Debug, Clone)]
pub enum KeyboardEvent {
Keymap,
Key { key: u32, state: bool },
Key { key: u32, state: u32 },
}
const POINTER_EVENT_TIMEOUT: Duration = Duration::from_millis(50);
const POINTER_EVENT_TIMEOUT: Duration = Duration::from_secs(1);
struct SurfaceInfo {
wl_surface: WlWeak<WlSurface>,
cursor_sender: watch::Sender<Option<CursorInfo>>,
@@ -210,12 +185,12 @@ impl SurfaceInfo {
) => {
if let Some(axis_continuous) = axis_continuous {
pointer.axis(0, Axis::HorizontalScroll, axis_continuous.x as f64);
pointer.axis(0, Axis::VerticalScroll, -axis_continuous.y as f64);
pointer.axis(0, Axis::VerticalScroll, axis_continuous.y as f64);
}
if pointer.version() >= wl_pointer::EVT_AXIS_DISCRETE_SINCE {
if let Some(axis_discrete) = axis_discrete {
pointer.axis_discrete(Axis::HorizontalScroll, axis_discrete.x as i32);
pointer.axis_discrete(Axis::VerticalScroll, -axis_discrete.y as i32);
pointer.axis_discrete(Axis::VerticalScroll, axis_discrete.y as i32);
}
}
if pointer.version() >= wl_pointer::EVT_AXIS_STOP_SINCE
@@ -284,7 +259,6 @@ pub struct SeatData {
pointer: OnceCell<(WlPointer, Mutex<ObjectId>)>,
keyboard: OnceCell<(WlKeyboard, Mutex<ObjectId>)>,
touch: OnceCell<WlTouch>,
touches: Mutex<FxHashMap<ObjectId, u32>>,
}
impl SeatData {
pub fn new(dh: &DisplayHandle) -> Arc<Self> {
@@ -295,39 +269,37 @@ impl SeatData {
pointer: OnceCell::new(),
keyboard: OnceCell::new(),
touch: OnceCell::new(),
touches: Mutex::new(FxHashMap::default()),
});
let _ = seat_data
seat_data
.global_id
.set(dh.create_global::<WaylandState, _, _>(7, seat_data.clone()));
.set(dh.create_global::<WaylandState, _, _>(7, seat_data.clone()))
.unwrap();
seat_data
}
pub fn set_keymap(&self, keymap_str: String, surfaces: Vec<WlSurface>) -> Result<()> {
pub fn set_keymap_str(&self, keymap: &str, surfaces: Vec<WlSurface>) -> Result<()> {
let context = xkb::Context::new(0);
let keymap =
Keymap::new_from_string(&context, keymap_str.clone(), XKB_KEYMAP_FORMAT_TEXT_V1, 0)
Keymap::new_from_string(&context, keymap.to_string(), XKB_KEYMAP_FORMAT_TEXT_V1, 0)
.ok_or_else(|| eyre!("Keymap is not valid"))?;
self.set_keymap(&keymap, surfaces);
Ok(())
}
pub fn set_keymap(&self, keymap: &Keymap, surfaces: Vec<WlSurface>) {
let mut panels = self.surfaces.lock();
let Some((_, focus)) = self.keyboard.get() else {bail!("Could not get keyboard")};
let Some((_, focus)) = self.keyboard.get() else {return};
for surface in surfaces {
let Some(surface_info) = panels.get_mut(&surface.id()) else {continue};
if let Some(keyboard_info) = &mut surface_info.keyboard_info {
if &keyboard_info.keymap_string == &keymap_str {
continue;
}
}
surface_info
.keyboard_info
.replace(KeyboardInfo::new(keymap_str.clone(), &keymap));
.replace(KeyboardInfo::new(keymap));
if *focus.lock() == surface.id() {
surface_info.keyboard_queue.push_back(KeyboardEvent::Keymap);
}
}
Ok(())
}
pub fn pointer_event(&self, surface: &WlSurface, event: PointerEvent) {
@@ -425,36 +397,14 @@ impl SeatData {
*keyboard_focus = ObjectId::null();
}
}
self.touches.lock().remove(&surface.id());
}
pub fn touch_down(&self, surface: &WlSurface, id: u32, position: Vector2<f32>) {
let Some(touch) = self.touch.get() else {return};
touch.down(
SERIAL_COUNTER.inc(),
0,
surface,
id as i32,
position.x as f64,
position.y as f64,
);
self.touches.lock().insert(surface.id(), id);
}
pub fn touch_move(&self, id: u32, position: Vector2<f32>) {
let Some(touch) = self.touch.get() else {return};
touch.motion(0, id as i32, position.x as f64, position.y as f64);
}
pub fn touch_up(&self, id: u32) {
let Some(touch) = self.touch.get() else {return};
touch.up(SERIAL_COUNTER.inc(), 0, id as i32);
let mut touches = self.touches.lock();
touches.retain(|_, tid| *tid != id);
}
pub fn reset_touches(&self) {
let Some(touch) = self.touch.get() else {return};
for (_, touch_id) in self.touches.lock().drain() {
touch.up(SERIAL_COUNTER.inc(), 0, touch_id as i32);
}
}
impl Drop for SeatData {
fn drop(&mut self) {
let id = self.global_id.take().unwrap();
let _ = task::new(|| "global destroy queue garbage collection", async move {
GLOBAL_DESTROY_QUEUE.get().unwrap().send(id).await
});
}
}
@@ -464,12 +414,9 @@ pub struct CursorInfo {
pub hotspot_y: i32,
}
impl CursorInfo {
pub fn cursor_data(&self) -> Option<Geometry> {
pub fn cursor_data(&self) -> Option<(Vector2<u32>, Vector2<i32>)> {
let cursor_size = CoreSurface::from_wl_surface(&self.surface.upgrade().ok()?)?.size()?;
Some(Geometry {
origin: [self.hotspot_x, self.hotspot_y].into(),
size: cursor_size,
})
Some((cursor_size, [self.hotspot_x, self.hotspot_y].into()))
}
}
@@ -488,7 +435,7 @@ impl GlobalDispatch<WlSeat, Arc<SeatData>, WaylandState> for WaylandState {
resource.name(nanoid!());
}
resource.capabilities(Capability::Pointer | Capability::Keyboard | Capability::Touch);
resource.capabilities(Capability::Pointer | Capability::Keyboard);
}
fn can_view(client: Client, data: &Arc<SeatData>) -> bool {

View File

@@ -0,0 +1,16 @@
#version 320 es
precision mediump float;
precision highp int;
layout(binding = 0) uniform highp sampler2D diffuse;
layout(location = 0) in highp vec2 fs_uv;
layout(location = 0) out highp vec4 _entryPointOutput;
void main()
{
highp vec4 _101 = texture(diffuse, fs_uv);
highp vec3 _104 = pow(_101.xyz, vec3(2.2000000476837158203125));
_entryPointOutput = vec4(_104.x, _104.y, _104.z, _101.w);
}

View File

@@ -0,0 +1,13 @@
#![allow(dead_code)]
// Basic gamma correction shader
// pub const PANEL_SHADER_BYTES: &[u8] = include_bytes!("shader_unlit_gamma.sks");
// Simula shader with fancy lanzcos sampling
pub const PANEL_SHADER_BYTES: &[u8] = include_bytes!("shader_unlit_simula.sks");
// Simula text shader (fragment)
// pub const SIMULA_FRAG_STR: &str = include_str!("simula.frag");
// Simula text shader (vertex)
// pub const SIMULA_VERT_STR: &str = include_str!("simula.vert");

View File

@@ -0,0 +1,75 @@
#version 320 es
#extension GL_OES_EGL_image_external : require
precision mediump float;
precision highp int;
layout(binding = 0, std140) uniform _Global
{
highp vec4 diffuse_i;
highp vec2 uv_scale;
highp vec2 uv_offset;
highp float fcFactor;
highp float ripple;
highp float alpha_min;
highp float alpha_max;
} uniforms;
layout(binding = 0) uniform highp samplerExternalOES diffuse;
layout(location = 0) in highp vec2 fs_uv;
layout(location = 0) out highp vec4 fragColor;
void main()
{
highp vec2 dx_uv = dFdx(fs_uv);
highp vec2 dy_uv = dFdy(fs_uv);
highp vec2 width = fs_uv * uniforms.diffuse_i.xy;
ivec2 _475 = ivec2(width);
highp vec2 _477 = clamp(floor(abs(vec2(dx_uv.x, dy_uv.y)) * uniforms.diffuse_i.xy), vec2(1.0), vec2(2.0));
ivec2 _480 = ivec2(_477);
ivec2 _481 = _475 - _480;
ivec2 _485 = _475 + _480;
int _487 = _481.y;
highp vec4 _671;
highp float _672;
_672 = 0.0;
_671 = vec4(0.0);
highp vec4 _679;
highp float _681;
for (int _670 = _487; _670 <= _485.y; _672 = _681, _671 = _679, _670++)
{
int _496 = _481.x;
_681 = _672;
_679 = _671;
highp vec4 _553;
highp float _556;
for (int _673 = _496; _673 <= _485.x; _681 = _556, _679 = _553, _673++)
{
highp float _509 = float(_673);
highp float _514 = (uniforms.fcFactor * (width.x - _509)) / _477.x;
highp float _520 = float(_670);
highp float _525 = (uniforms.fcFactor * (width.y - _520)) / _477.y;
highp float _533 = sqrt((_514 * _514) + (_525 * _525));
highp float _675;
do
{
if (_533 > 1.0)
{
_675 = 0.0;
break;
}
highp float _592 = pow(uniforms.ripple * sqrt(1.0 - (_533 * _533)), 2.0);
_675 = 1.0 + (_592 * (0.25 + (_592 * (0.015625 + (_592 * (0.00043402801384218037128448486328125 + (_592 * (6.7816799855791032314300537109375e-06 + (_592 * (6.7816799287356843706220388412476e-08 + (_592 * (4.709500012189948847662890329957e-10 + (_592 * (2.4028099388645474121517509047408e-12 + (_592 * (9.3859703944590075486154034933861e-15 + (_592 * (2.8968999943407451927966655969016e-17 + (7.242260299760125752555485045131e-20 * _592)))))))))))))))))));
break;
} while(false);
_553 = _679 + (texture2D(diffuse, (vec2(_509, _520) + vec2(0.5)) / uniforms.diffuse_i.xy) * _675);
_556 = _681 + _675;
}
}
highp vec4 _568 = _671 / vec4(_672);
highp vec3 _417 = pow(_568.xyz, vec3(2.2000000476837158203125));
highp vec4 _669 = vec4(_417.x, _417.y, _417.z, _568.w);
_669.w = uniforms.alpha_min + (_568.w * (uniforms.alpha_max - uniforms.alpha_min));
fragColor = _669;
}

View File

@@ -0,0 +1,61 @@
#version 320 es
// #ifdef GL_AMD_vertex_shader_layer
// #extension GL_AMD_vertex_shader_layer : enable
// #elif defined(GL_NV_viewport_array2)
// #extension GL_NV_viewport_array2 : enable
// #else
// #define gl_Layer int _dummy_gl_layer_var
// #endif
struct Inst
{
mat4 world;
vec4 color;
};
layout(binding = 1, std140) uniform StereoKitBuffer
{
layout(row_major) mat4 sk_view[2];
layout(row_major) mat4 sk_proj[2];
layout(row_major) mat4 sk_proj_inv[2];
layout(row_major) mat4 sk_viewproj[2];
vec4 sk_lighting_sh[9];
vec4 sk_camera_pos[2];
vec4 sk_camera_dir[2];
vec4 sk_fingertip[2];
vec4 sk_cubemap_i;
float sk_time;
uint sk_view_count;
} _38;
layout(binding = 2, std140) uniform TransformBuffer
{
layout(row_major) Inst sk_inst[819];
} _56;
layout(binding = 0, std140) uniform _Global
{
vec4 diffuse_i;
vec2 uv_scale;
vec2 uv_offset;
float fcFactor;
float ripple;
float alpha_min;
float alpha_max;
} _91;
layout(location = 0) in vec4 input_pos;
layout(location = 1) in vec3 input_norm;
layout(location = 2) in vec2 input_uv;
layout(location = 0) out vec2 fs_uv;
mat4 spvWorkaroundRowMajor(mat4 wrap) { return wrap; }
void main()
{
uint _155 = uint(gl_InstanceID) % _38.sk_view_count;
gl_Position = spvWorkaroundRowMajor(_38.sk_viewproj[_155]) * vec4((spvWorkaroundRowMajor(_56.sk_inst[uint(gl_InstanceID) / _38.sk_view_count].world) * vec4(input_pos.xyz, 1.0)).xyz, 1.0);
fs_uv = (input_uv + _91.uv_offset) * _91.uv_scale;
// gl_Layer = int(_155);
}

View File

@@ -1,12 +1,10 @@
use super::DisplayWrapper;
use crate::wayland::{drm::wl_drm::WlDrm, seat::SeatData};
use once_cell::sync::OnceCell;
use crate::wayland::seat::SeatData;
use parking_lot::Mutex;
use smithay::{
backend::{
allocator::{dmabuf::Dmabuf, Fourcc},
allocator::dmabuf::Dmabuf,
egl::EGLDevice,
renderer::gles::GlesRenderer,
renderer::{gles::GlesRenderer, ImportDma},
},
delegate_dmabuf, delegate_output, delegate_shm,
output::{Mode, Output, Scale, Subpixel},
@@ -18,11 +16,8 @@ use smithay::{
wayland_protocols_misc::server_decoration::server::org_kde_kwin_server_decoration_manager::Mode as DecorationMode,
wayland_server::{
backend::{ClientData, ClientId, DisconnectReason},
protocol::{
wl_buffer::WlBuffer, wl_data_device_manager::WlDataDeviceManager,
wl_output::WlOutput,
},
DisplayHandle,
protocol::{wl_buffer::WlBuffer, wl_data_device_manager::WlDataDeviceManager},
Display, DisplayHandle,
},
},
utils::{Size, Transform},
@@ -31,8 +26,8 @@ use smithay::{
compositor::{CompositorClientState, CompositorState},
dmabuf::{
self, DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufHandler, DmabufState,
ImportError,
},
output::OutputHandler,
shell::kde::decoration::KdeDecorationState,
shm::{ShmHandler, ShmState},
},
@@ -42,23 +37,19 @@ use tokio::sync::mpsc::UnboundedSender;
use tracing::{info, warn};
pub struct ClientState {
pub id: OnceCell<ClientId>,
pub compositor_state: CompositorClientState,
pub display: Weak<DisplayWrapper>,
pub display: Weak<Mutex<Display<WaylandState>>>,
pub seat: Arc<SeatData>,
}
impl ClientState {
pub fn flush(&self) {
let Some(display) = self.display.upgrade() else {
return;
};
let _ = display.flush_clients(self.id.get().cloned());
let Some(display) = self.display.upgrade() else {return};
let _ = display.lock().flush_clients();
}
}
impl ClientData for ClientState {
fn initialized(&self, client_id: ClientId) {
info!("Wayland client {:?} connected", client_id);
let _ = self.id.set(client_id);
}
fn disconnected(&self, client_id: ClientId, reason: DisconnectReason) {
@@ -71,6 +62,7 @@ impl ClientData for ClientState {
pub struct WaylandState {
pub weak_ref: Weak<Mutex<WaylandState>>,
pub display: Arc<Mutex<Display<WaylandState>>>,
pub display_handle: DisplayHandle,
pub compositor_state: CompositorState,
@@ -78,16 +70,16 @@ pub struct WaylandState {
pub kde_decoration_state: KdeDecorationState,
pub shm_state: ShmState,
dmabuf_state: (DmabufState, DmabufGlobal, Option<DmabufFeedback>),
pub drm_formats: Vec<Fourcc>,
pub dmabuf_tx: UnboundedSender<(Dmabuf, Option<dmabuf::ImportNotifier>)>,
dmabuf_tx: UnboundedSender<Dmabuf>,
pub output: Output,
}
impl WaylandState {
pub fn new(
display: Arc<Mutex<Display<WaylandState>>>,
display_handle: DisplayHandle,
renderer: &GlesRenderer,
dmabuf_tx: UnboundedSender<(Dmabuf, Option<dmabuf::ImportNotifier>)>,
dmabuf_tx: UnboundedSender<Dmabuf>,
) -> Arc<Mutex<Self>> {
let compositor_state = CompositorState::new::<Self>(&display_handle);
// let xdg_activation_state = XdgActivationState::new::<Self, _>(&display_handle);
@@ -96,18 +88,16 @@ impl WaylandState {
let shm_state = ShmState::new::<Self>(&display_handle, vec![]);
let render_node = EGLDevice::device_for_display(renderer.egl_context().display())
.and_then(|device| device.try_get_render_node());
let dmabuf_formats = renderer
.egl_context()
.dmabuf_render_formats()
.iter()
.cloned()
.collect::<Vec<_>>();
let drm_formats = dmabuf_formats.iter().map(|f| f.code).collect();
let dmabuf_default_feedback = match render_node {
Ok(Some(node)) => DmabufFeedbackBuilder::new(node.dev_id(), dmabuf_formats.clone())
.build()
.ok(),
Ok(Some(node)) => {
let dmabuf_formats = renderer.dmabuf_formats().collect::<Vec<_>>();
let dmabuf_default_feedback =
DmabufFeedbackBuilder::new(node.dev_id(), dmabuf_formats)
.build()
.unwrap();
Some(dmabuf_default_feedback)
}
Ok(None) => {
warn!("failed to query render node, dmabuf will use v3");
None
@@ -127,9 +117,10 @@ impl WaylandState {
);
(dmabuf_state, dmabuf_global, Some(default_feedback))
} else {
let dmabuf_formats = renderer.dmabuf_formats().collect::<Vec<_>>();
let mut dmabuf_state = DmabufState::new();
let dmabuf_global =
dmabuf_state.create_global::<WaylandState>(&display_handle, dmabuf_formats.clone());
dmabuf_state.create_global::<WaylandState>(&display_handle, dmabuf_formats);
(dmabuf_state, dmabuf_global, None)
};
@@ -157,20 +148,19 @@ impl WaylandState {
display_handle.create_global::<Self, WlDataDeviceManager, _>(3, ());
display_handle.create_global::<Self, XdgWmBase, _>(5, ());
display_handle.create_global::<Self, ZxdgDecorationManagerV1, _>(1, ());
display_handle.create_global::<Self, WlDrm, _>(2, ());
info!("Init Wayland compositor");
Arc::new_cyclic(|weak| {
Mutex::new(WaylandState {
weak_ref: weak.clone(),
display,
display_handle,
compositor_state,
// xdg_activation_state,
kde_decoration_state,
shm_state,
drm_formats,
dmabuf_state,
dmabuf_tx,
output,
@@ -200,14 +190,10 @@ impl DmabufHandler for WaylandState {
&mut self,
_global: &DmabufGlobal,
dmabuf: Dmabuf,
notifier: dmabuf::ImportNotifier,
) {
self.dmabuf_tx.send((dmabuf, Some(notifier))).unwrap();
) -> Result<(), dmabuf::ImportError> {
self.dmabuf_tx.send(dmabuf).map_err(|_| ImportError::Failed)
}
}
impl OutputHandler for WaylandState {
fn output_bound(&mut self, _output: Output, _wl_output: WlOutput) {}
}
delegate_dmabuf!(WaylandState);
delegate_shm!(WaylandState);
delegate_output!(WaylandState);

View File

@@ -1,7 +1,7 @@
use super::{state::WaylandState, utils::get_data};
use super::{shaders::PANEL_SHADER_BYTES, state::WaylandState};
use crate::{
core::{delta::Delta, destroy_queue, registry::Registry},
nodes::drawable::{model::ModelPart, shaders::PANEL_SHADER_BYTES},
nodes::drawable::model::ModelPart,
};
use mint::Vector2;
use once_cell::sync::OnceCell;
@@ -18,10 +18,10 @@ use smithay::{
reexports::wayland_server::{self, protocol::wl_surface::WlSurface, DisplayHandle, Resource},
wayland::compositor::{self, SurfaceData},
};
use std::{cell::RefCell, ffi::c_void, sync::Arc, time::Duration};
use std::{ffi::c_void, sync::Arc, time::Duration};
use stereokit::{
Material, Shader, StereoKitDraw, Tex, TextureAddress, TextureFormat, TextureSample,
TextureType, Transparency,
Material, StereoKitDraw, Tex, TextureAddress, TextureFormat, TextureSample, TextureType,
Transparency,
};
pub static CORE_SURFACES: Registry<CoreSurface> = Registry::new();
@@ -40,8 +40,8 @@ pub struct CoreSurface {
pub dh: DisplayHandle,
pub weak_surface: wayland_server::Weak<WlSurface>,
mapped_data: Mutex<Option<CoreSurfaceData>>,
sk_tex: OnceCell<Tex>,
sk_mat: OnceCell<Arc<Material>>,
sk_tex: OnceCell<SendWrapper<Tex>>,
sk_mat: OnceCell<Arc<SendWrapper<Material>>>,
material_offset: Mutex<Delta<u32>>,
on_mapped: Box<dyn Fn() + Send + Sync>,
on_commit: Box<dyn Fn(u32) + Send + Sync>,
@@ -77,27 +77,27 @@ impl CoreSurface {
}
pub fn from_wl_surface(surf: &WlSurface) -> Option<Arc<CoreSurface>> {
get_data(surf)
compositor::with_states(surf, |data| {
data.data_map.get::<Arc<CoreSurface>>().cloned()
})
}
pub fn process(&self, sk: &impl StereoKitDraw, renderer: &mut GlesRenderer) {
let Some(wl_surface) = self.wl_surface() else {
return;
};
let Some(wl_surface) = self.wl_surface() else {return};
let sk_tex = self
.sk_tex
.get_or_init(|| sk.tex_create(TextureType::IMAGE_NO_MIPS, TextureFormat::RGBA32));
let sk_tex = self.sk_tex.get_or_init(|| {
SendWrapper::new(sk.tex_create(TextureType::IMAGE_NO_MIPS, TextureFormat::RGBA32))
});
self.sk_mat.get_or_init(|| {
let shader = sk.shader_create_mem(&PANEL_SHADER_BYTES);
let shader = sk.shader_create_mem(&PANEL_SHADER_BYTES).unwrap();
// let _ = renderer.with_context(|c| unsafe {
// shader_inject(c, &mut shader, SIMULA_VERT_STR, SIMULA_FRAG_STR)
// });
let mat = sk.material_create(shader.as_ref().unwrap_or(Shader::UI.as_ref()));
let mat = sk.material_create(&shader);
sk.material_set_texture(&mat, "diffuse", sk_tex.as_ref());
sk.material_set_transparency(&mat, Transparency::Blend);
Arc::new(mat)
Arc::new(SendWrapper::new(mat))
});
// Let smithay handle buffer management (has to be done here as RendererSurfaceStates is not thread safe)
@@ -121,26 +121,18 @@ impl CoreSurface {
let mut mapped_data = self.mapped_data.lock();
let just_mapped = mapped_data.is_none();
self.with_states(|data| {
let Some(renderer_surface_state) = data
let renderer_surface_state = data
.data_map
.get::<RendererSurfaceStateUserData>()
.map(RefCell::borrow)
else {
return;
};
let Some(smithay_tex) = renderer_surface_state
.unwrap()
.borrow();
let smithay_tex = renderer_surface_state
.texture::<GlesRenderer>(renderer.id())
.cloned()
else {
return;
};
.unwrap()
.clone();
let Some(sk_tex) = self.sk_tex.get() else {
return;
};
let Some(sk_mat) = self.sk_mat.get() else {
return;
};
let sk_tex = self.sk_tex.get().unwrap();
let sk_mat = self.sk_mat.get().unwrap();
unsafe {
sk.tex_set_surface(
sk_tex.as_ref(),
@@ -159,9 +151,7 @@ impl CoreSurface {
sk.material_set_queue_offset(sk_mat.as_ref().as_ref(), *material_offset as i32);
}
let Some(surface_size) = renderer_surface_state.surface_size() else {
return;
};
let surface_size = renderer_surface_state.surface_size().unwrap();
let new_mapped_data = CoreSurfaceData {
size: Vector2::from([surface_size.w as u32, surface_size.h as u32]),
wl_tex: Some(SendWrapper::new(smithay_tex)),
@@ -176,9 +166,7 @@ impl CoreSurface {
}
pub fn frame(&self, sk: &impl StereoKitDraw, output: Output) {
let Some(wl_surface) = self.wl_surface() else {
return;
};
let Some(wl_surface) = self.wl_surface() else {return};
send_frames_surface_tree(
&wl_surface,
@@ -198,19 +186,20 @@ impl CoreSurface {
}
fn apply_surface_materials(&self) {
if let Some(sk_mat) = self.sk_mat.get() {
for model_node in self.pending_material_applications.get_valid_contents() {
model_node.replace_material(sk_mat.clone());
}
self.pending_material_applications.clear();
for model_node in self.pending_material_applications.get_valid_contents() {
model_node.replace_material(self.sk_mat.clone().get().unwrap().clone());
}
self.pending_material_applications.clear();
}
pub fn wl_surface(&self) -> Option<WlSurface> {
self.weak_surface.upgrade().ok()
}
pub fn with_states<T, F: FnOnce(&SurfaceData) -> T>(&self, f: F) -> Option<T> {
pub fn with_states<F, T>(&self, f: F) -> Option<T>
where
F: FnOnce(&SurfaceData) -> T,
{
self.wl_surface()
.map(|wl_surface| compositor::with_states(&wl_surface, f))
}

View File

@@ -1,14 +0,0 @@
use smithay::{reexports::wayland_server::protocol::wl_surface::WlSurface, wayland::compositor};
use std::sync::Arc;
pub fn insert_data<T: Send + Sync + 'static>(wl_surface: &WlSurface, data: T) {
insert_data_raw(wl_surface, Arc::new(data))
}
pub fn insert_data_raw<T: Send + Sync + 'static>(wl_surface: &WlSurface, data: Arc<T>) {
compositor::with_states(wl_surface, |d| {
d.data_map.insert_if_missing_threadsafe(move || data)
});
}
pub fn get_data<T: Send + Sync + 'static>(wl_surface: &WlSurface) -> Option<Arc<T>> {
compositor::with_states(wl_surface, |d| d.data_map.get::<Arc<T>>().cloned())
}

View File

@@ -1,189 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="drm">
<copyright>
Copyright © 2008-2011 Kristian Høgsberg
Copyright © 2010-2011 Intel Corporation
Permission to use, copy, modify, distribute, and sell this
software and its documentation for any purpose is hereby granted
without fee, provided that\n the above copyright notice appear in
all copies and that both that copyright notice and this permission
notice appear in supporting documentation, and that the name of
the copyright holders not be used in advertising or publicity
pertaining to distribution of the software without specific,
written prior permission. The copyright holders make no
representations about the suitability of this software for any
purpose. It is provided "as is" without express or implied
warranty.
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
</copyright>
<!-- drm support. This object is created by the server and published
using the display's global event. -->
<interface name="wl_drm" version="2">
<enum name="error">
<entry name="authenticate_fail" value="0" />
<entry name="invalid_format" value="1" />
<entry name="invalid_name" value="2" />
</enum>
<enum name="format">
<!-- The drm format codes match the #defines in drm_fourcc.h.
The formats actually supported by the compositor will be
reported by the format event. New codes must not be added,
unless directly taken from drm_fourcc.h. -->
<entry name="c8" value="0x20203843" />
<entry name="rgb332" value="0x38424752" />
<entry name="bgr233" value="0x38524742" />
<entry name="xrgb4444" value="0x32315258" />
<entry name="xbgr4444" value="0x32314258" />
<entry name="rgbx4444" value="0x32315852" />
<entry name="bgrx4444" value="0x32315842" />
<entry name="argb4444" value="0x32315241" />
<entry name="abgr4444" value="0x32314241" />
<entry name="rgba4444" value="0x32314152" />
<entry name="bgra4444" value="0x32314142" />
<entry name="xrgb1555" value="0x35315258" />
<entry name="xbgr1555" value="0x35314258" />
<entry name="rgbx5551" value="0x35315852" />
<entry name="bgrx5551" value="0x35315842" />
<entry name="argb1555" value="0x35315241" />
<entry name="abgr1555" value="0x35314241" />
<entry name="rgba5551" value="0x35314152" />
<entry name="bgra5551" value="0x35314142" />
<entry name="rgb565" value="0x36314752" />
<entry name="bgr565" value="0x36314742" />
<entry name="rgb888" value="0x34324752" />
<entry name="bgr888" value="0x34324742" />
<entry name="xrgb8888" value="0x34325258" />
<entry name="xbgr8888" value="0x34324258" />
<entry name="rgbx8888" value="0x34325852" />
<entry name="bgrx8888" value="0x34325842" />
<entry name="argb8888" value="0x34325241" />
<entry name="abgr8888" value="0x34324241" />
<entry name="rgba8888" value="0x34324152" />
<entry name="bgra8888" value="0x34324142" />
<entry name="xrgb2101010" value="0x30335258" />
<entry name="xbgr2101010" value="0x30334258" />
<entry name="rgbx1010102" value="0x30335852" />
<entry name="bgrx1010102" value="0x30335842" />
<entry name="argb2101010" value="0x30335241" />
<entry name="abgr2101010" value="0x30334241" />
<entry name="rgba1010102" value="0x30334152" />
<entry name="bgra1010102" value="0x30334142" />
<entry name="yuyv" value="0x56595559" />
<entry name="yvyu" value="0x55595659" />
<entry name="uyvy" value="0x59565955" />
<entry name="vyuy" value="0x59555956" />
<entry name="ayuv" value="0x56555941" />
<entry name="xyuv8888" value="0x56555958" />
<entry name="nv12" value="0x3231564e" />
<entry name="nv21" value="0x3132564e" />
<entry name="nv16" value="0x3631564e" />
<entry name="nv61" value="0x3136564e" />
<entry name="yuv410" value="0x39565559" />
<entry name="yvu410" value="0x39555659" />
<entry name="yuv411" value="0x31315559" />
<entry name="yvu411" value="0x31315659" />
<entry name="yuv420" value="0x32315559" />
<entry name="yvu420" value="0x32315659" />
<entry name="yuv422" value="0x36315559" />
<entry name="yvu422" value="0x36315659" />
<entry name="yuv444" value="0x34325559" />
<entry name="yvu444" value="0x34325659" />
<entry name="abgr16f" value="0x48344241" />
<entry name="xbgr16f" value="0x48344258" />
</enum>
<!-- Call this request with the magic received from drmGetMagic().
It will be passed on to the drmAuthMagic() or
DRIAuthConnection() call. This authentication must be
completed before create_buffer could be used. -->
<request name="authenticate">
<arg name="id" type="uint" />
</request>
<!-- Create a wayland buffer for the named DRM buffer. The DRM
surface must have a name using the flink ioctl -->
<request name="create_buffer">
<arg name="id" type="new_id" interface="wl_buffer" />
<arg name="name" type="uint" />
<arg name="width" type="int" />
<arg name="height" type="int" />
<arg name="stride" type="uint" />
<arg name="format" type="uint" />
</request>
<!-- Create a wayland buffer for the named DRM buffer. The DRM
surface must have a name using the flink ioctl -->
<request name="create_planar_buffer">
<arg name="id" type="new_id" interface="wl_buffer" />
<arg name="name" type="uint" />
<arg name="width" type="int" />
<arg name="height" type="int" />
<arg name="format" type="uint" />
<arg name="offset0" type="int" />
<arg name="stride0" type="int" />
<arg name="offset1" type="int" />
<arg name="stride1" type="int" />
<arg name="offset2" type="int" />
<arg name="stride2" type="int" />
</request>
<!-- Notification of the path of the drm device which is used by
the server. The client should use this device for creating
local buffers. Only buffers created from this device should
be be passed to the server using this drm object's
create_buffer request. -->
<event name="device">
<arg name="name" type="string" />
</event>
<event name="format">
<arg name="format" type="uint" />
</event>
<!-- Raised if the authenticate request succeeded -->
<event name="authenticated" />
<enum name="capability" since="2">
<description summary="wl_drm capability bitmask">
Bitmask of capabilities.
</description>
<entry name="prime" value="1" summary="wl_drm prime available" />
</enum>
<event name="capabilities">
<arg name="value" type="uint" />
</event>
<!-- Version 2 additions -->
<!-- Create a wayland buffer for the prime fd. Use for regular and planar
buffers. Pass 0 for offset and stride for unused planes. -->
<request name="create_prime_buffer" since="2">
<arg name="id" type="new_id" interface="wl_buffer" />
<arg name="name" type="fd" />
<arg name="width" type="int" />
<arg name="height" type="int" />
<arg name="format" type="uint" />
<arg name="offset0" type="int" />
<arg name="stride0" type="int" />
<arg name="offset1" type="int" />
<arg name="stride1" type="int" />
<arg name="offset2" type="int" />
<arg name="stride2" type="int" />
</request>
</interface>
</protocol>

958
src/wayland/xdg_shell.rs Normal file
View File

@@ -0,0 +1,958 @@
use super::{
seat::{CursorInfo, KeyboardEvent, PointerEvent, SeatData},
state::{ClientState, WaylandState},
surface::CoreSurface,
SERIAL_COUNTER,
};
use crate::nodes::{
drawable::model::ModelPart,
items::panel::{Backend, PanelItem, RecommendedState, SurfaceID},
Message, Node,
};
use color_eyre::eyre::{eyre, Result};
use mint::Vector2;
use nanoid::nanoid;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use serde::{ser::SerializeSeq, Serialize, Serializer};
use smithay::reexports::{
wayland_protocols::xdg::shell::server::{
xdg_popup::{self, XdgPopup},
xdg_positioner::{self, Anchor, ConstraintAdjustment, Gravity, XdgPositioner},
xdg_surface::{self, XdgSurface},
xdg_toplevel::{self, XdgToplevel, EVT_CONFIGURE_BOUNDS_SINCE, EVT_WM_CAPABILITIES_SINCE},
xdg_wm_base::{self, XdgWmBase},
},
wayland_server::{
backend::{ClientId, ObjectId},
protocol::wl_surface::WlSurface,
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, WEnum,
Weak as WlWeak,
},
};
use stardust_xr::schemas::flex::serialize;
use std::{
fmt::Debug,
sync::{Arc, Weak},
};
use tokio::sync::watch;
use tracing::{debug, warn};
use xkbcommon::xkb::{self, ffi::XKB_KEYMAP_FORMAT_TEXT_V1, Keymap};
impl GlobalDispatch<XdgWmBase, (), WaylandState> for WaylandState {
fn bind(
_state: &mut WaylandState,
_handle: &DisplayHandle,
_client: &Client,
resource: New<XdgWmBase>,
_global_data: &(),
data_init: &mut DataInit<'_, WaylandState>,
) {
data_init.init(resource, ());
}
}
impl Dispatch<XdgWmBase, (), WaylandState> for WaylandState {
fn request(
_state: &mut WaylandState,
_client: &Client,
_resource: &XdgWmBase,
request: xdg_wm_base::Request,
_data: &(),
_dhandle: &DisplayHandle,
data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
xdg_wm_base::Request::CreatePositioner { id } => {
let positioner = data_init.init(id, Mutex::new(PositionerData::default()));
debug!(?positioner, "Create XDG positioner");
}
xdg_wm_base::Request::GetXdgSurface { id, surface } => {
let xdg_surface = data_init.init(id, Mutex::new(XdgSurfaceData::new(&surface)));
debug!(?xdg_surface, "Create XDG surface");
}
xdg_wm_base::Request::Pong { serial } => {
debug!(serial, "Client pong");
}
xdg_wm_base::Request::Destroy => {
debug!("Destroy XDG WM base");
}
_ => unreachable!(),
}
}
}
#[derive(Debug, Serialize, Clone, Copy)]
pub struct PositionerData {
size: Vector2<u32>,
anchor_rect_pos: Vector2<i32>,
anchor_rect_size: Vector2<u32>,
anchor: u32,
gravity: u32,
constraint_adjustment: u32,
offset: Vector2<i32>,
reactive: bool,
}
impl Default for PositionerData {
fn default() -> Self {
Self {
size: Vector2::from([0; 2]),
anchor_rect_pos: Vector2::from([0; 2]),
anchor_rect_size: Vector2::from([0; 2]),
anchor: Anchor::None as u32,
gravity: Gravity::None as u32,
constraint_adjustment: ConstraintAdjustment::None.bits(),
offset: Vector2::from([0; 2]),
reactive: false,
}
}
}
impl Dispatch<XdgPositioner, Mutex<PositionerData>, WaylandState> for WaylandState {
fn request(
_state: &mut WaylandState,
_client: &Client,
positioner: &XdgPositioner,
request: xdg_positioner::Request,
data: &Mutex<PositionerData>,
_dhandle: &DisplayHandle,
_data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
xdg_positioner::Request::SetSize { width, height } => {
debug!(?positioner, width, height, "Set positioner size");
data.lock().size = Vector2::from([width as u32, height as u32]);
}
xdg_positioner::Request::SetAnchorRect {
x,
y,
width,
height,
} => {
if width < 1 || height < 1 {
positioner.post_error(
xdg_positioner::Error::InvalidInput,
"Invalid size for positioner's anchor rectangle.",
);
warn!(
?positioner,
width, height, "Invalid size for positioner's anchor rectangle"
);
return;
}
debug!(
?positioner,
x, y, width, height, "Set positioner anchor rectangle"
);
let mut data = data.lock();
data.anchor_rect_pos = [x, y].into();
data.anchor_rect_size = [width as u32, height as u32].into();
}
xdg_positioner::Request::SetAnchor { anchor } => {
if let WEnum::Value(anchor) = anchor {
debug!(?positioner, ?anchor, "Set positioner anchor");
data.lock().anchor = anchor as u32;
}
}
xdg_positioner::Request::SetGravity { gravity } => {
if let WEnum::Value(gravity) = gravity {
debug!(?positioner, ?gravity, "Set positioner gravity");
data.lock().gravity = gravity as u32;
}
}
xdg_positioner::Request::SetConstraintAdjustment {
constraint_adjustment,
} => {
debug!(
?positioner,
constraint_adjustment, "Set positioner constraint adjustment"
);
data.lock().constraint_adjustment = constraint_adjustment;
}
xdg_positioner::Request::SetOffset { x, y } => {
debug!(?positioner, x, y, "Set positioner offset");
data.lock().offset = [x, y].into();
}
xdg_positioner::Request::SetReactive => {
debug!(?positioner, "Set positioner reactive");
data.lock().reactive = true;
}
xdg_positioner::Request::SetParentSize {
parent_width,
parent_height,
} => {
debug!(
?positioner,
parent_width, parent_height, "Set positioner parent size"
);
}
xdg_positioner::Request::SetParentConfigure { serial } => {
debug!(?positioner, serial, "Set positioner parent size");
}
xdg_positioner::Request::Destroy => (),
_ => unreachable!(),
}
}
}
#[derive(Debug, Serialize, Clone, Copy)]
pub struct Geometry {
pub origin: Vector2<i32>,
pub size: Vector2<u32>,
}
impl Default for Geometry {
fn default() -> Self {
Self {
origin: Vector2::from([0; 2]),
size: Vector2::from([0; 2]),
}
}
}
pub struct XdgSurfaceData {
wl_surface: WlWeak<WlSurface>,
surface_id: SurfaceID,
panel_item: Weak<PanelItem<XDGBackend>>,
geometry: Option<Geometry>,
}
impl XdgSurfaceData {
pub fn new(wl_surface: &WlSurface) -> Self {
XdgSurfaceData {
wl_surface: wl_surface.downgrade(),
surface_id: SurfaceID::Toplevel,
panel_item: Weak::new(),
geometry: None,
}
}
pub fn get(xdg_surface: &XdgSurface) -> Option<&Mutex<Self>> {
xdg_surface.data::<Mutex<Self>>()
}
pub fn wl_surface(&self) -> Option<WlSurface> {
self.wl_surface.upgrade().ok()
}
pub fn panel_item(&self) -> Option<Arc<PanelItem<XDGBackend>>> {
self.panel_item.upgrade()
}
}
// impl Clone for XdgSurfaceData {
// fn clone(&self) -> Self {
// Self {
// wl_surface: self.wl_surface.clone(),
// geometry: self.geometry.clone(),
// surface_type: Mutex::new(self.surface_type.lock().clone()),
// }
// }
// }
impl Dispatch<XdgSurface, Mutex<XdgSurfaceData>, WaylandState> for WaylandState {
fn request(
state: &mut WaylandState,
client: &Client,
xdg_surface: &XdgSurface,
request: xdg_surface::Request,
xdg_surface_data: &Mutex<XdgSurfaceData>,
_dhandle: &DisplayHandle,
data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
xdg_surface::Request::GetToplevel { id } => {
let toplevel_state = Mutex::new(ToplevelData::new(xdg_surface));
let toplevel = data_init.init(id, toplevel_state);
debug!(?toplevel, ?xdg_surface, "Create XDG toplevel");
if toplevel.version() >= EVT_WM_CAPABILITIES_SINCE {
toplevel.wm_capabilities(vec![2, 3, 4]);
}
toplevel.configure(
0,
0,
if toplevel.version() >= 2 {
vec![5, 6, 7, 8]
.into_iter()
.flat_map(u32::to_ne_bytes)
.collect()
} else {
vec![]
},
);
xdg_surface.configure(SERIAL_COUNTER.inc());
let client_credentials = client.get_credentials(&state.display_handle).ok();
let Some(seat_data) = client.get_data::<ClientState>().map(|s| s.seat.clone()) else {return};
let Some(wl_surface) = xdg_surface_data.lock().wl_surface() else {return};
CoreSurface::add_to(
state.display_handle.clone(),
&wl_surface,
{
let toplevel = toplevel.downgrade();
move || {
let toplevel = toplevel.upgrade().unwrap();
let toplevel_data = ToplevelData::get(&toplevel);
let Some(xdg_surface) = toplevel_data.lock().xdg_surface() else {return};
let Some(xdg_surface_data) = XdgSurfaceData::get(&xdg_surface) else {return};
xdg_surface_data.lock().surface_id = SurfaceID::Toplevel;
let Some(backend) = XDGBackend::create(toplevel.clone(), seat_data.clone()) else {return};
let (node, panel_item) = PanelItem::create(
Box::new(backend),
client_credentials.map(|c| c.pid),
);
toplevel_data.lock().panel_item_node.replace(node);
xdg_surface_data.lock().panel_item = Arc::downgrade(&panel_item);
}
},
{
let toplevel = toplevel.downgrade();
move |_| {
let toplevel = toplevel.upgrade().unwrap();
let toplevel_data = ToplevelData::get(&toplevel);
let Some(panel_item) = toplevel_data.lock().panel_item() else {
let Some(xdg_surface) = toplevel_data.lock().xdg_surface() else {return};
// if the wayland toplevel isn't mapped, hammer it again with a configure until it cooperates
toplevel.configure(
0,
0,
if toplevel.version() >= 2 {
vec![5, 6, 7, 8].into_iter().flat_map(u32::to_ne_bytes).collect()
} else {
vec![]
},
);
xdg_surface.configure(SERIAL_COUNTER.inc());
return
};
panel_item.commit_toplevel();
}
},
);
}
xdg_surface::Request::GetPopup {
id,
parent,
positioner,
} => {
let parent_clone = parent.clone().unwrap();
let parent_data = parent_clone.data::<Mutex<XdgSurfaceData>>().unwrap().lock();
// let positioner_data = positioner
// .data::<Mutex<PositionerData>>()
// .unwrap()
// .lock()
// .clone();
// let parent = match &*parent_data {
// XdgSurfaceType::Toplevel(_) => SurfaceID::Toplevel,
// XdgSurfaceType::Popup(p) => {
// SurfaceID::Popup(p.upgrade().unwrap().uid.clone())
// }
// XdgSurfaceType::Unknown => return,
// };
let uid = nanoid!();
let popup_data = Mutex::new(PopupData::new(
uid.clone(),
xdg_surface,
parent_data.surface_id.clone(),
positioner,
));
let xdg_popup = data_init.init(id, popup_data);
xdg_surface_data.lock().surface_id = SurfaceID::Popup(uid);
let panel_item = parent_data.panel_item().unwrap();
xdg_surface_data.lock().panel_item = Arc::downgrade(&panel_item);
debug!(?xdg_popup, ?xdg_surface, "Create XDG popup");
let xdg_surface = xdg_surface.downgrade();
let xdg_popup = xdg_popup.downgrade();
CoreSurface::add_to(
state.display_handle.clone(),
&xdg_surface_data.lock().wl_surface.upgrade().unwrap(),
move || {
let xdg_popup = xdg_popup.upgrade().unwrap();
let Some(popup_data) = PopupData::get(&xdg_popup) else {return};
let popup_data = popup_data.lock();
// panel_item.commit_popup(popup_data);
panel_item
.backend
.new_popup(&panel_item, &xdg_popup, &*popup_data);
},
move |commit_count| {
if commit_count == 0 {
xdg_surface
.upgrade()
.unwrap()
.configure(SERIAL_COUNTER.inc())
}
},
);
}
xdg_surface::Request::SetWindowGeometry {
x,
y,
width,
height,
} => {
debug!(
?xdg_surface,
x, y, width, height, "Set XDG surface geometry"
);
let geometry = Geometry {
origin: [x, y].into(),
size: [width as u32, height as u32].into(),
};
xdg_surface_data.lock().geometry.replace(geometry);
}
xdg_surface::Request::AckConfigure { serial } => {
debug!(?xdg_surface, serial, "Acknowledge XDG surface configure");
}
xdg_surface::Request::Destroy => {
debug!(?xdg_surface, "Destroy XDG surface");
}
_ => unreachable!(),
}
}
}
fn serde_error<S: Serializer>(msg: &str) -> Result<S::Ok, S::Error> {
Err(serde::ser::Error::custom(msg))
}
#[derive(Debug, Clone)]
pub struct ToplevelData {
panel_item_node: Option<Arc<Node>>,
xdg_surface: WlWeak<XdgSurface>,
parent: Option<WlWeak<XdgToplevel>>,
title: Option<String>,
app_id: Option<String>,
max_size: Option<Vector2<u32>>,
min_size: Option<Vector2<u32>>,
states: Vec<u32>,
}
impl ToplevelData {
fn new(xdg_surface: &XdgSurface) -> Self {
ToplevelData {
panel_item_node: None,
xdg_surface: xdg_surface.downgrade(),
parent: None,
title: None,
app_id: None,
max_size: None,
min_size: None,
states: Vec::new(),
}
}
pub fn get(toplevel: &XdgToplevel) -> &Mutex<ToplevelData> {
toplevel.data::<Mutex<ToplevelData>>().unwrap()
}
pub fn xdg_surface(&self) -> Option<XdgSurface> {
self.xdg_surface.upgrade().ok()
}
fn panel_item(&self) -> Option<Arc<PanelItem<XDGBackend>>> {
let xdg_surface = self.xdg_surface()?;
let xdg_surface_data = XdgSurfaceData::get(&xdg_surface)?.lock();
xdg_surface_data.panel_item()
}
}
impl Serialize for ToplevelData {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let Some(xdg_surface) = self.xdg_surface() else {return serializer.serialize_none()};
let Some(xdg_surface_data) = XdgSurfaceData::get(&xdg_surface) else {return serializer.serialize_none()};
let xdg_surface_data = xdg_surface_data.lock();
let geometry = xdg_surface_data.geometry.clone();
let Some(wl_surface) = xdg_surface_data.wl_surface() else {return serde_error::<S>("Wayland surface not found")};
let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface) else {return serde_error::<S>("Core surface not found")};
let Some(size) = core_surface.size() else {return serializer.serialize_none()};
let geometry = geometry.unwrap_or_else(|| Geometry {
origin: [0; 2].into(),
size,
});
let mut seq = serializer.serialize_seq(None)?;
// Parent UID
seq.serialize_element(&self.parent.as_ref().and_then(|p| {
Some(
ToplevelData::get(&p.upgrade().ok()?)
.lock()
.panel_item()?
.uid
.clone(),
)
}))?;
seq.serialize_element(&self.title)?;
seq.serialize_element(&self.app_id)?;
seq.serialize_element(&size)?;
seq.serialize_element(&self.min_size)?;
seq.serialize_element(&self.max_size)?;
seq.serialize_element(&geometry)?;
seq.serialize_element(&self.states)?;
seq.end()
}
}
impl Dispatch<XdgToplevel, Mutex<ToplevelData>, WaylandState> for WaylandState {
fn request(
_state: &mut WaylandState,
_client: &Client,
xdg_toplevel: &XdgToplevel,
request: xdg_toplevel::Request,
data: &Mutex<ToplevelData>,
_dhandle: &DisplayHandle,
_data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
xdg_toplevel::Request::SetParent { parent } => {
debug!(?xdg_toplevel, ?parent, "Set XDG Toplevel parent");
data.lock().parent = parent.map(|toplevel| toplevel.downgrade());
}
xdg_toplevel::Request::SetTitle { title } => {
debug!(?xdg_toplevel, ?title, "Set XDG Toplevel title");
data.lock().title = (!title.is_empty()).then_some(title);
}
xdg_toplevel::Request::SetAppId { app_id } => {
debug!(?xdg_toplevel, ?app_id, "Set XDG Toplevel app ID");
data.lock().app_id = (!app_id.is_empty()).then_some(app_id);
}
xdg_toplevel::Request::ShowWindowMenu { seat, serial, x, y } => {
debug!(
?xdg_toplevel,
?seat,
serial,
x,
y,
"Show XDG Toplevel window menu"
);
}
xdg_toplevel::Request::Move { seat, serial } => {
debug!(?xdg_toplevel, ?seat, serial, "XDG Toplevel move request");
let Some(panel_item) = data.lock().panel_item() else {return};
panel_item.recommend_toplevel_state(RecommendedState::Move);
}
xdg_toplevel::Request::Resize {
seat,
serial,
edges,
} => {
let WEnum::Value(edges) = edges else {return};
debug!(
?xdg_toplevel,
?seat,
serial,
?edges,
"XDG Toplevel resize request"
);
let Some(panel_item) = data.lock().panel_item() else {return};
panel_item.recommend_toplevel_state(RecommendedState::Resize(edges as u32));
}
xdg_toplevel::Request::SetMaxSize { width, height } => {
debug!(?xdg_toplevel, width, height, "Set XDG Toplevel max size");
data.lock().max_size = (width > 1 || height > 1)
.then_some(Vector2::from([width as u32, height as u32]));
}
xdg_toplevel::Request::SetMinSize { width, height } => {
debug!(?xdg_toplevel, width, height, "Set XDG Toplevel min size");
data.lock().min_size = (width > 1 || height > 1)
.then_some(Vector2::from([width as u32, height as u32]));
}
xdg_toplevel::Request::SetMaximized => {
let Some(panel_item) = data.lock().panel_item() else {return};
panel_item.recommend_toplevel_state(RecommendedState::Maximize(true));
}
xdg_toplevel::Request::UnsetMaximized => {
let Some(panel_item) = data.lock().panel_item() else {return};
panel_item.recommend_toplevel_state(RecommendedState::Maximize(false));
}
xdg_toplevel::Request::SetFullscreen { output: _ } => {
let Some(panel_item) = data.lock().panel_item() else {return};
panel_item.recommend_toplevel_state(RecommendedState::Fullscreen(true));
}
xdg_toplevel::Request::UnsetFullscreen => {
let Some(panel_item) = data.lock().panel_item() else {return};
panel_item.recommend_toplevel_state(RecommendedState::Fullscreen(true));
}
xdg_toplevel::Request::SetMinimized => {
let Some(panel_item) = data.lock().panel_item() else {return};
panel_item.recommend_toplevel_state(RecommendedState::Minimize);
}
xdg_toplevel::Request::Destroy => {
debug!(?xdg_toplevel, "Destroy XDG Toplevel");
let Some(panel_item) = data.lock().panel_item() else {return};
panel_item.backend.on_drop();
}
_ => unreachable!(),
}
}
}
#[derive(Clone)]
pub struct PopupData {
pub uid: String,
grabbed: bool,
parent_id: SurfaceID,
positioner: XdgPositioner,
xdg_surface: WlWeak<XdgSurface>,
}
impl PopupData {
fn new(
uid: impl ToString,
xdg_surface: &XdgSurface,
parent_id: SurfaceID,
positioner: XdgPositioner,
) -> Self {
PopupData {
uid: uid.to_string(),
grabbed: false,
parent_id,
positioner,
xdg_surface: xdg_surface.downgrade(),
}
}
pub fn get(popup: &XdgPopup) -> Option<&Mutex<Self>> {
popup.data::<Mutex<Self>>()
}
pub fn xdg_surface(&self) -> Option<XdgSurface> {
self.xdg_surface.upgrade().ok()
}
fn panel_item(&self) -> Option<Arc<PanelItem<XDGBackend>>> {
XdgSurfaceData::get(&self.xdg_surface()?)?
.lock()
.panel_item()
}
// fn get_parent(&self) -> Option<XdgSurface> {
// self.parent.as_ref()?.upgrade().ok()
// }
pub fn wl_surface(&self) -> Option<WlSurface> {
XdgSurfaceData::get(&self.xdg_surface()?)?
.lock()
.wl_surface()
}
pub fn positioner_data(&self) -> Option<PositionerData> {
Some(
self.positioner
.data::<Mutex<PositionerData>>()?
.lock()
.clone(),
)
}
}
impl Serialize for PopupData {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let Some(positioner_data) = self.positioner_data() else {return serde_error::<S>("Positioner not found")};
let mut seq = serializer.serialize_seq(None)?;
seq.serialize_element(&self.uid)?;
seq.serialize_element(&self.parent_id)?;
seq.serialize_element(&positioner_data)?;
seq.end()
}
}
impl Debug for PopupData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("XdgPopupData")
.field("uid", &self.uid)
.field("positioner", &self.positioner)
.field("xdg_surface", &self.xdg_surface)
.finish()
}
}
impl Dispatch<XdgPopup, Mutex<PopupData>, WaylandState> for WaylandState {
fn request(
_state: &mut WaylandState,
_client: &Client,
xdg_popup: &XdgPopup,
request: xdg_popup::Request,
data: &Mutex<PopupData>,
_dhandle: &DisplayHandle,
_data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
xdg_popup::Request::Grab { seat, serial } => {
let mut data = data.lock();
data.grabbed = true;
debug!(?xdg_popup, ?seat, serial, "XDG popup grab");
let Some(panel_item) = data.panel_item() else {return};
panel_item.grab_keyboard(Some(SurfaceID::Popup(data.uid.clone())));
}
xdg_popup::Request::Reposition { positioner, token } => {
let mut data = data.lock();
debug!(?xdg_popup, ?positioner, token, "XDG popup reposition");
data.positioner = positioner;
let Some(panel_item) = data.panel_item() else {return};
panel_item.backend.reposition_popup(&panel_item, &*data)
}
xdg_popup::Request::Destroy => {
let data = data.lock();
debug!(?xdg_popup, "Destroy XDG popup");
if data.grabbed {
let Some(panel_item) = data.panel_item() else {return};
panel_item.grab_keyboard(None);
}
}
_ => unreachable!(),
}
}
fn destroyed(
_state: &mut WaylandState,
_client: ClientId,
_resource: ObjectId,
data: &Mutex<PopupData>,
) {
let data = data.lock();
let Some(panel_item) = data.panel_item() else {return};
panel_item.backend.drop_popup(&panel_item, &data.uid);
}
}
pub struct XDGBackend {
toplevel: WlWeak<XdgToplevel>,
toplevel_wl_surface: WlWeak<WlSurface>,
popups: Mutex<FxHashMap<String, WlWeak<XdgPopup>>>,
cursor: watch::Receiver<Option<CursorInfo>>,
pub seat: Arc<SeatData>,
pointer_grab: Mutex<Option<SurfaceID>>,
keyboard_grab: Mutex<Option<SurfaceID>>,
}
impl XDGBackend {
pub fn create(toplevel: XdgToplevel, seat: Arc<SeatData>) -> Option<Self> {
let toplevel_wl_surface =
XdgSurfaceData::get(&ToplevelData::get(&toplevel).lock().xdg_surface()?)?
.lock()
.wl_surface()?
.downgrade();
let cursor = seat.new_surface(&toplevel_wl_surface.upgrade().ok()?);
Some(XDGBackend {
toplevel: toplevel.downgrade(),
toplevel_wl_surface,
popups: Mutex::new(FxHashMap::default()),
cursor,
seat,
pointer_grab: Mutex::new(None),
keyboard_grab: Mutex::new(None),
})
}
fn wl_surface_from_id(&self, id: &SurfaceID) -> Option<WlSurface> {
match id {
SurfaceID::Cursor => self.cursor.borrow().as_ref()?.surface.upgrade().ok(),
SurfaceID::Toplevel => self.toplevel_wl_surface(),
SurfaceID::Popup(popup) => {
let popups = self.popups.lock();
let popup = popups.get(popup)?.upgrade().ok()?;
let wl_surface = PopupData::get(&popup)?.lock().wl_surface();
wl_surface
}
}
}
fn toplevel(&self) -> Option<XdgToplevel> {
self.toplevel.upgrade().ok()
}
fn toplevel_xdg_surface(&self) -> Option<XdgSurface> {
let toplevel = self.toplevel()?;
let data = ToplevelData::get(&toplevel).lock();
data.xdg_surface()
}
fn toplevel_wl_surface(&self) -> Option<WlSurface> {
self.toplevel_wl_surface.upgrade().ok()
}
fn input_surfaces(&self) -> Vec<WlSurface> {
let mut surfaces = self
.toplevel_wl_surface()
.map(|s| vec![s])
.unwrap_or_default();
surfaces.extend(self.popups.lock().values().filter_map(|p| {
let popup = p.upgrade().ok()?;
let popup_data = PopupData::get(&popup)?.lock();
let xdg_surface = popup_data.xdg_surface()?;
let xdg_surface_data = XdgSurfaceData::get(&xdg_surface)?.lock();
xdg_surface_data.wl_surface()
}));
surfaces
}
pub fn new_popup(
&self,
panel_item: &PanelItem<XDGBackend>,
popup: &XdgPopup,
data: &PopupData,
) {
let uid = data.uid.clone();
self.popups.lock().insert(uid.clone(), popup.downgrade());
let Some(node) = panel_item.node() else {return};
let Ok(message) = serialize(&(&uid, data)) else {return};
let _ = node.send_remote_signal("new_popup", message);
}
pub fn reposition_popup(&self, panel_item: &PanelItem<XDGBackend>, popup_state: &PopupData) {
let Some(node) = panel_item.node() else {return};
let _ = node.send_remote_signal(
"reposition_popup",
serialize(popup_state.positioner_data().unwrap()).unwrap(),
);
}
pub fn drop_popup(&self, panel_item: &PanelItem<XDGBackend>, uid: &str) {
'seat_drop: {
let Some(popup) = self
.popups
.lock()
.remove(uid) else {break 'seat_drop};
let Some(popup) = popup.upgrade().ok() else {break 'seat_drop};
let Some(popup) = popup.data::<Arc<PopupData>>().cloned() else {break 'seat_drop};
let Some(wl_surface) = popup.wl_surface() else {break 'seat_drop};
self.seat.drop_surface(&wl_surface);
}
let Some(node) = panel_item.node() else {return};
let Ok(message) = serialize(uid) else {return};
let _ = node.send_remote_signal("drop_popup", message);
}
fn popups_data(&self) -> Vec<PopupData> {
self.popups
.lock()
.values()
.filter_map(|v| Some(v.upgrade().ok()?.data::<Mutex<PopupData>>()?.lock().clone()))
.collect::<Vec<_>>()
}
pub fn on_drop(&self) {
let Some(toplevel) = self.toplevel_wl_surface() else {return};
self.seat.drop_surface(&toplevel);
debug!("Dropped panel item gracefully");
}
fn flush_client(&self) {
let Some(client) = self.toplevel_wl_surface().and_then(|s| s.client()) else {return};
if let Some(client_state) = client.get_data::<ClientState>() {
client_state.flush();
}
}
}
impl Backend for XDGBackend {
fn serialize_start_data(&self, id: &str) -> Result<Message> {
let toplevel_state = self
.toplevel()
.map(|t| ToplevelData::get(&t).lock().clone());
let pointer_grab = self.pointer_grab.lock().clone();
let keyboard_grab = self.keyboard_grab.lock().clone();
Ok(serialize((
id,
(
self.cursor.borrow().as_ref().and_then(|c| c.cursor_data()),
toplevel_state,
self.popups_data(),
pointer_grab,
keyboard_grab,
),
))?
.into())
}
fn serialize_toplevel(&self) -> Result<Message> {
let toplevel = self
.toplevel()
.ok_or_else(|| eyre!("Toplevel does not exist"))?;
let state = ToplevelData::get(&toplevel);
let data = serialize(&state.lock().clone())?;
Ok(data.into())
}
fn set_toplevel_capabilities(&self, capabilities: Vec<u8>) {
let Ok(xdg_toplevel) = self.toplevel.upgrade() else {return};
let Some(xdg_surface) = self.toplevel_xdg_surface() else {return};
if xdg_toplevel.version() < EVT_WM_CAPABILITIES_SINCE {
return;
}
xdg_toplevel.wm_capabilities(capabilities);
xdg_surface.configure(SERIAL_COUNTER.inc());
self.flush_client();
}
fn configure_toplevel(
&self,
size: Option<Vector2<u32>>,
states: Vec<u32>,
bounds: Option<Vector2<u32>>,
) {
let Ok(xdg_toplevel) = self.toplevel.upgrade() else {return};
let Some(xdg_surface) = self.toplevel_xdg_surface() else {return};
debug!(?size, ?states, ?bounds, "Configure toplevel info");
if let Some(bounds) = bounds {
if xdg_toplevel.version() > EVT_CONFIGURE_BOUNDS_SINCE {
xdg_toplevel.configure_bounds(bounds.x as i32, bounds.y as i32);
}
}
let size = size.unwrap_or(Vector2::from([0; 2]));
xdg_toplevel.configure(
size.x as i32,
size.y as i32,
states
.into_iter()
.flat_map(|state| state.to_ne_bytes())
.collect(),
);
xdg_surface.configure(SERIAL_COUNTER.inc());
self.flush_client();
}
fn apply_surface_material(&self, surface: SurfaceID, model_part: &Arc<ModelPart>) {
let Some(wl_surface) = self.wl_surface_from_id(&surface) else {return};
let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface) else {return};
core_surface.apply_material(model_part);
}
fn pointer_motion(&self, surface: &SurfaceID, position: Vector2<f32>) {
let Some(surface) = self.wl_surface_from_id(surface) else {return};
self.seat
.pointer_event(&surface, PointerEvent::Motion(position));
}
fn pointer_button(&self, surface: &SurfaceID, button: u32, pressed: bool) {
let Some(surface) = self.wl_surface_from_id(surface) else {return};
self.seat.pointer_event(
&surface,
PointerEvent::Button {
button,
state: if pressed { 1 } else { 0 },
},
)
}
fn pointer_scroll(
&self,
surface: &SurfaceID,
scroll_distance: Option<Vector2<f32>>,
scroll_steps: Option<Vector2<f32>>,
) {
let Some(surface) = self.wl_surface_from_id(surface) else {return};
self.seat.pointer_event(
&surface,
PointerEvent::Scroll {
axis_continuous: scroll_distance,
axis_discrete: scroll_steps,
},
)
}
fn keyboard_set_keymap(&self, keymap: &str) -> Result<()> {
let context = xkb::Context::new(0);
let keymap =
Keymap::new_from_string(&context, keymap.to_string(), XKB_KEYMAP_FORMAT_TEXT_V1, 0)
.ok_or_else(|| eyre!("Keymap is not valid"))?;
self.seat.set_keymap(&keymap, self.input_surfaces());
Ok(())
}
fn keyboard_key(&self, surface: &SurfaceID, key: u32, state: bool) {
let Some(surface) = self.wl_surface_from_id(surface) else {return};
self.seat.keyboard_event(
&surface,
KeyboardEvent::Key {
key,
state: if state { 1 } else { 0 },
},
)
}
}

View File

@@ -1,310 +0,0 @@
use super::{popup::PopupData, surface::XdgSurfaceData, ToplevelData};
use crate::{
nodes::{
data::KEYMAPS,
drawable::model::ModelPart,
items::panel::{Backend, ChildInfo, PanelItem, PanelItemInitData, SurfaceID},
},
wayland::{
seat::{CursorInfo, KeyboardEvent, PointerEvent, SeatData},
state::ClientState,
surface::CoreSurface,
utils, SERIAL_COUNTER,
},
};
use color_eyre::eyre::{eyre, Result};
use mint::Vector2;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use smithay::reexports::{
wayland_protocols::xdg::shell::server::xdg_toplevel::XdgToplevel,
wayland_server::{protocol::wl_surface::WlSurface, Resource, Weak},
};
use std::sync::Arc;
use tokio::sync::watch;
use tracing::debug;
pub struct XdgToplevelState {
pub fullscreen: bool,
pub activated: bool,
}
pub struct XdgBackend {
toplevel: Weak<XdgToplevel>,
toplevel_wl_surface: Weak<WlSurface>,
pub toplevel_state: Mutex<XdgToplevelState>,
popups: Mutex<FxHashMap<String, Weak<WlSurface>>>,
pub cursor: watch::Receiver<Option<CursorInfo>>,
pub seat: Arc<SeatData>,
pointer_grab: Mutex<Option<SurfaceID>>,
keyboard_grab: Mutex<Option<SurfaceID>>,
}
impl XdgBackend {
pub fn create(
toplevel_wl_surface: WlSurface,
toplevel: XdgToplevel,
seat: Arc<SeatData>,
) -> Self {
let cursor = seat.new_surface(&toplevel_wl_surface);
XdgBackend {
toplevel: toplevel.downgrade(),
toplevel_wl_surface: toplevel_wl_surface.downgrade(),
toplevel_state: Mutex::new(XdgToplevelState {
fullscreen: false,
activated: false,
}),
popups: Mutex::new(FxHashMap::default()),
cursor,
seat,
pointer_grab: Mutex::new(None),
keyboard_grab: Mutex::new(None),
}
}
fn wl_surface_from_id(&self, id: &SurfaceID) -> Option<WlSurface> {
match id {
SurfaceID::Cursor => self.cursor.borrow().as_ref()?.surface.upgrade().ok(),
SurfaceID::Toplevel => self.toplevel_wl_surface(),
SurfaceID::Child(popup) => {
let popups = self.popups.lock();
popups.get(popup)?.upgrade().ok()
}
}
}
fn toplevel_wl_surface(&self) -> Option<WlSurface> {
self.toplevel_wl_surface.upgrade().ok()
}
pub fn configure(&self, size: Option<Vector2<u32>>) {
let Ok(xdg_toplevel) = self.toplevel.upgrade() else {
return;
};
let Some(wl_surface) = self.toplevel_wl_surface() else {
return;
};
let Some(xdg_surface_data) = wl_surface.data::<XdgSurfaceData>() else {
return;
};
let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface) else {
return;
};
let Some(surface_size) = core_surface.size() else {
return;
};
xdg_toplevel.configure(
size.unwrap_or(surface_size).x as i32,
size.unwrap_or(surface_size).y as i32,
self.states()
.into_iter()
.flat_map(|state| state.to_ne_bytes())
.collect(),
);
xdg_surface_data.xdg_surface.configure(SERIAL_COUNTER.inc());
self.flush_client();
}
fn states(&self) -> Vec<u32> {
let mut states = vec![1, 5, 6, 7, 8]; // maximized always and tiled
let toplevel_state = self.toplevel_state.lock();
if toplevel_state.fullscreen {
states.push(2);
}
if toplevel_state.activated {
states.push(4);
}
states
}
pub fn new_popup(
&self,
panel_item: &PanelItem<XdgBackend>,
popup_wl_surface: &WlSurface,
data: &PopupData,
) {
self.popups
.lock()
.insert(data.uid.clone(), popup_wl_surface.downgrade());
let Some(geometry) = data.geometry() else {
return;
};
panel_item.new_child(
&data.uid,
ChildInfo {
parent: utils::get_data::<SurfaceID>(&data.parent())
.unwrap()
.as_ref()
.clone(),
geometry,
},
)
}
pub fn reposition_popup(&self, panel_item: &PanelItem<XdgBackend>, popup_state: &PopupData) {
let Some(geometry) = popup_state.geometry() else {
return;
};
panel_item.reposition_child(&popup_state.uid, geometry)
}
pub fn drop_popup(&self, panel_item: &PanelItem<XdgBackend>, uid: &str) {
panel_item.drop_child(uid);
let Some(popup) = self.popups.lock().remove(uid) else {
return;
};
let Some(wl_surface) = popup.upgrade().ok() else {
return;
};
self.seat.drop_surface(&wl_surface);
}
fn child_data(&self) -> FxHashMap<String, ChildInfo> {
FxHashMap::from_iter(self.popups.lock().iter().filter_map(|(uid, v)| {
let wl_surface = v.upgrade().ok()?;
let popup_data = utils::get_data::<PopupData>(&wl_surface)?;
let parent = utils::get_data::<SurfaceID>(&popup_data.parent())?
.as_ref()
.clone();
let geometry = utils::get_data::<XdgSurfaceData>(&wl_surface)?
.geometry
.lock()
.clone()?;
Some((uid.clone(), ChildInfo { parent, geometry }))
}))
}
fn flush_client(&self) {
let Some(client) = self.toplevel_wl_surface().and_then(|s| s.client()) else {
return;
};
if let Some(client_state) = client.get_data::<ClientState>() {
client_state.flush();
}
}
}
impl Drop for XdgBackend {
fn drop(&mut self) {
debug!("Dropped panel item gracefully");
}
}
impl Backend for XdgBackend {
fn start_data(&self) -> Result<PanelItemInitData> {
let toplevel = self.toplevel_wl_surface();
let toplevel_data = toplevel.as_ref().and_then(utils::get_data::<ToplevelData>);
let toplevel_data = toplevel_data
.as_deref()
.clone()
.ok_or_else(|| eyre!("Could not get toplevel"))?;
let pointer_grab = self.pointer_grab.lock().clone();
let keyboard_grab = self.keyboard_grab.lock().clone();
Ok(PanelItemInitData {
cursor: self.cursor.borrow().as_ref().and_then(|c| c.cursor_data()),
toplevel: toplevel_data.into(),
children: self.child_data(),
pointer_grab,
keyboard_grab,
})
}
fn apply_surface_material(&self, surface: SurfaceID, model_part: &Arc<ModelPart>) {
let Some(wl_surface) = self.wl_surface_from_id(&surface) else {
return;
};
let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface) else {
return;
};
core_surface.apply_material(model_part);
}
fn close_toplevel(&self) {
let Ok(xdg_toplevel) = self.toplevel.upgrade() else {
return;
};
xdg_toplevel.close();
}
fn auto_size_toplevel(&self) {
self.configure(Some([0, 0].into()));
}
fn set_toplevel_size(&self, size: Vector2<u32>) {
self.configure(Some(size));
}
fn set_toplevel_focused_visuals(&self, focused: bool) {
self.toplevel_state.lock().activated = focused;
self.configure(None);
}
fn pointer_motion(&self, surface: &SurfaceID, position: Vector2<f32>) {
let Some(surface) = self.wl_surface_from_id(surface) else {
return;
};
self.seat
.pointer_event(&surface, PointerEvent::Motion(position));
}
fn pointer_button(&self, surface: &SurfaceID, button: u32, pressed: bool) {
let Some(surface) = self.wl_surface_from_id(surface) else {
return;
};
self.seat.pointer_event(
&surface,
PointerEvent::Button {
button,
state: if pressed { 1 } else { 0 },
},
)
}
fn pointer_scroll(
&self,
surface: &SurfaceID,
scroll_distance: Option<Vector2<f32>>,
scroll_steps: Option<Vector2<f32>>,
) {
let Some(surface) = self.wl_surface_from_id(surface) else {
return;
};
self.seat.pointer_event(
&surface,
PointerEvent::Scroll {
axis_continuous: scroll_distance,
axis_discrete: scroll_steps,
},
)
}
fn keyboard_keys(&self, surface: &SurfaceID, keymap_id: &str, keys: Vec<i32>) {
let Some(surface) = self.wl_surface_from_id(surface) else {
return;
};
let keymaps = KEYMAPS.lock();
let Some(keymap) = keymaps.get(keymap_id).cloned() else {
return;
};
if self.seat.set_keymap(keymap, vec![surface.clone()]).is_err() {
return;
}
for key in keys {
self.seat.keyboard_event(
&surface,
KeyboardEvent::Key {
key: key.abs() as u32,
state: key > 0,
},
);
}
}
fn touch_down(&self, surface: &SurfaceID, id: u32, position: Vector2<f32>) {
let Some(surface) = self.wl_surface_from_id(surface) else {
return;
};
self.seat.touch_down(&surface, id, position)
}
fn touch_move(&self, id: u32, position: Vector2<f32>) {
self.seat.touch_move(id, position)
}
fn touch_up(&self, id: u32) {
self.seat.touch_up(id)
}
fn reset_touches(&self) {
self.seat.reset_touches()
}
}

View File

@@ -1,69 +0,0 @@
use self::{backend::XdgBackend, toplevel::ToplevelData};
use super::state::WaylandState;
use crate::wayland::{
utils::insert_data,
xdg_shell::{positioner::PositionerData, surface::XdgSurfaceData},
};
use parking_lot::Mutex;
use smithay::reexports::{
wayland_protocols::xdg::shell::server::xdg_wm_base::{self, XdgWmBase},
wayland_server::{Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource},
};
use tracing::debug;
mod backend;
mod popup;
mod positioner;
mod surface;
mod toplevel;
impl GlobalDispatch<XdgWmBase, (), WaylandState> for WaylandState {
fn bind(
_state: &mut WaylandState,
_handle: &DisplayHandle,
_client: &Client,
resource: New<XdgWmBase>,
_global_data: &(),
data_init: &mut DataInit<'_, WaylandState>,
) {
data_init.init(resource, ());
}
}
impl Dispatch<XdgWmBase, (), WaylandState> for WaylandState {
fn request(
_state: &mut WaylandState,
_client: &Client,
_resource: &XdgWmBase,
request: xdg_wm_base::Request,
_data: &(),
_dhandle: &DisplayHandle,
data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
xdg_wm_base::Request::CreatePositioner { id } => {
let positioner = data_init.init(id, Mutex::new(PositionerData::default()));
debug!(?positioner, "Create XDG positioner");
}
xdg_wm_base::Request::GetXdgSurface { id, surface } => {
let xdg_surface = data_init.init(id, surface.downgrade());
debug!(?xdg_surface, "Create XDG surface");
insert_data(
&surface,
XdgSurfaceData {
wl_surface: surface.downgrade(),
xdg_surface,
geometry: Mutex::new(None),
},
);
}
xdg_wm_base::Request::Pong { serial } => {
debug!(serial, "Client pong");
}
xdg_wm_base::Request::Destroy => {
debug!("Destroy XDG WM base");
}
_ => unreachable!(),
}
}
}

View File

@@ -1,119 +0,0 @@
use super::{backend::XdgBackend, positioner::PositionerData};
use crate::{
nodes::items::panel::{Geometry, PanelItem, SurfaceID},
wayland::{state::WaylandState, utils::get_data},
};
use parking_lot::Mutex;
use smithay::reexports::{
wayland_protocols::xdg::shell::server::{
xdg_popup::{self, XdgPopup},
xdg_positioner::XdgPositioner,
},
wayland_server::{
protocol::wl_surface::WlSurface, Client, DataInit, Dispatch, DisplayHandle, Resource,
Weak as WlWeak,
},
};
use std::sync::{Arc, Weak};
use tracing::{debug, error};
use wayland_backend::server::ClientId;
#[derive(Debug)]
pub struct PopupData {
pub uid: String,
grabbed: Mutex<bool>,
parent: Mutex<WlWeak<WlSurface>>,
panel_item: Weak<PanelItem<XdgBackend>>,
positioner: Mutex<XdgPositioner>,
}
impl PopupData {
pub fn new(
uid: impl ToString,
parent: WlSurface,
panel_item: &Arc<PanelItem<XdgBackend>>,
positioner: XdgPositioner,
) -> Self {
PopupData {
uid: uid.to_string(),
grabbed: Mutex::new(false),
parent: Mutex::new(parent.downgrade()),
panel_item: Arc::downgrade(panel_item),
positioner: Mutex::new(positioner),
}
}
pub fn geometry(&self) -> Option<Geometry> {
let positioner = self.positioner.lock().clone();
let positioner_data = positioner.data::<Mutex<PositionerData>>()?.lock();
Some(positioner_data.clone().into())
}
pub fn parent(&self) -> WlSurface {
self.parent.lock().upgrade().unwrap()
}
}
impl Dispatch<XdgPopup, WlWeak<WlSurface>, WaylandState> for WaylandState {
fn request(
_state: &mut WaylandState,
_client: &Client,
xdg_popup: &XdgPopup,
request: xdg_popup::Request,
wl_surface_resource: &WlWeak<WlSurface>,
_dhandle: &DisplayHandle,
_data_init: &mut DataInit<'_, WaylandState>,
) {
let Ok(wl_surface) = wl_surface_resource.upgrade() else {
error!("Couldn't get the wayland surface of the xdg popup");
return;
};
let Some(popup_data) = get_data::<PopupData>(&wl_surface) else {
error!("Couldn't get the XdgPopup");
return;
};
let Some(panel_item) = popup_data.panel_item.upgrade() else {
error!("Couldn't get the panel item");
return;
};
match request {
xdg_popup::Request::Grab { seat, serial } => {
*popup_data.grabbed.lock() = true;
debug!(?xdg_popup, ?seat, serial, "XDG popup grab");
panel_item.grab_keyboard(Some(SurfaceID::Child(popup_data.uid.clone())));
}
xdg_popup::Request::Reposition { positioner, token } => {
debug!(?xdg_popup, ?positioner, token, "XDG popup reposition");
*popup_data.positioner.lock() = positioner;
panel_item
.backend
.reposition_popup(&panel_item, &popup_data);
}
xdg_popup::Request::Destroy => {
debug!(?xdg_popup, "Destroy XDG popup");
if *popup_data.grabbed.lock() {
panel_item.grab_keyboard(None);
}
}
_ => unreachable!(),
}
}
fn destroyed(
_state: &mut WaylandState,
_client: ClientId,
_popup: &XdgPopup,
data: &WlWeak<WlSurface>,
) {
let Ok(wl_surface) = data.upgrade() else {
error!("Couldn't get the wayland surface of the xdg popup");
return;
};
let Some(popup_data) = get_data::<PopupData>(&wl_surface) else {
error!("Couldn't get the XdgPopup");
return;
};
let Some(panel_item) = popup_data.panel_item.upgrade() else {
error!("Couldn't get the panel item");
return;
};
panel_item.backend.drop_popup(&panel_item, &popup_data.uid);
}
}

View File

@@ -1,226 +0,0 @@
use crate::{nodes::items::panel::Geometry, wayland::state::WaylandState};
use mint::Vector2;
use parking_lot::Mutex;
use smithay::reexports::{
wayland_protocols::xdg::shell::server::xdg_positioner::{
self, Anchor, ConstraintAdjustment, Gravity, XdgPositioner,
},
wayland_server::{Client, DataInit, Dispatch, DisplayHandle, Resource},
};
use tracing::{debug, warn};
use wayland_backend::protocol::WEnum;
#[derive(Debug, Clone, Copy)]
pub struct PositionerData {
size: Vector2<u32>,
anchor_rect_pos: Vector2<i32>,
anchor_rect_size: Vector2<u32>,
anchor: Anchor,
gravity: Gravity,
constraint_adjustment: ConstraintAdjustment,
offset: Vector2<i32>,
reactive: bool,
}
impl Default for PositionerData {
fn default() -> Self {
Self {
size: Vector2::from([0; 2]),
anchor_rect_pos: Vector2::from([0; 2]),
anchor_rect_size: Vector2::from([0; 2]),
anchor: Anchor::None,
gravity: Gravity::None,
constraint_adjustment: ConstraintAdjustment::None,
offset: Vector2::from([0; 2]),
reactive: false,
}
}
}
impl PositionerData {
fn anchor_has_edge(&self, edge: Anchor) -> bool {
match edge {
Anchor::Top => {
self.anchor == Anchor::Top
|| self.anchor == Anchor::TopLeft
|| self.anchor == Anchor::TopRight
}
Anchor::Bottom => {
self.anchor == Anchor::Bottom
|| self.anchor == Anchor::BottomLeft
|| self.anchor == Anchor::BottomRight
}
Anchor::Left => {
self.anchor == Anchor::Left
|| self.anchor == Anchor::TopLeft
|| self.anchor == Anchor::BottomLeft
}
Anchor::Right => {
self.anchor == Anchor::Right
|| self.anchor == Anchor::TopRight
|| self.anchor == Anchor::BottomRight
}
_ => unreachable!(),
}
}
fn gravity_has_edge(&self, edge: Gravity) -> bool {
match edge {
Gravity::Top => {
self.gravity == Gravity::Top
|| self.gravity == Gravity::TopLeft
|| self.gravity == Gravity::TopRight
}
Gravity::Bottom => {
self.gravity == Gravity::Bottom
|| self.gravity == Gravity::BottomLeft
|| self.gravity == Gravity::BottomRight
}
Gravity::Left => {
self.gravity == Gravity::Left
|| self.gravity == Gravity::TopLeft
|| self.gravity == Gravity::BottomLeft
}
Gravity::Right => {
self.gravity == Gravity::Right
|| self.gravity == Gravity::TopRight
|| self.gravity == Gravity::BottomRight
}
_ => unreachable!(),
}
}
pub fn get_pos(&self) -> Vector2<i32> {
let mut pos = self.offset;
if self.anchor_has_edge(Anchor::Top) {
pos.y += self.anchor_rect_pos.y;
} else if self.anchor_has_edge(Anchor::Bottom) {
pos.y += self.anchor_rect_pos.y + self.anchor_rect_size.y as i32;
} else {
pos.y += self.anchor_rect_pos.y + self.anchor_rect_size.y as i32 / 2;
}
if self.anchor_has_edge(Anchor::Left) {
pos.x += self.anchor_rect_pos.x;
} else if self.anchor_has_edge(Anchor::Right) {
pos.x += self.anchor_rect_pos.x + self.anchor_rect_size.x as i32;
} else {
pos.x += self.anchor_rect_pos.x + self.anchor_rect_size.x as i32 / 2;
}
if self.gravity_has_edge(Gravity::Top) {
pos.y -= self.size.y as i32;
} else if !self.gravity_has_edge(Gravity::Bottom) {
pos.y -= self.size.y as i32 / 2;
}
if self.gravity_has_edge(Gravity::Left) {
pos.x -= self.size.x as i32;
} else if !self.gravity_has_edge(Gravity::Right) {
pos.x -= self.size.x as i32 / 2;
}
pos
}
}
impl From<PositionerData> for Geometry {
fn from(value: PositionerData) -> Self {
Geometry {
origin: value.get_pos(),
size: value.size,
}
}
}
impl Dispatch<XdgPositioner, Mutex<PositionerData>, WaylandState> for WaylandState {
fn request(
_state: &mut WaylandState,
_client: &Client,
positioner: &XdgPositioner,
request: xdg_positioner::Request,
data: &Mutex<PositionerData>,
_dhandle: &DisplayHandle,
_data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
xdg_positioner::Request::SetSize { width, height } => {
debug!(?positioner, width, height, "Set positioner size");
data.lock().size = Vector2::from([width as u32, height as u32]);
}
xdg_positioner::Request::SetAnchorRect {
x,
y,
width,
height,
} => {
if width < 1 || height < 1 {
positioner.post_error(
xdg_positioner::Error::InvalidInput,
"Invalid size for positioner's anchor rectangle.",
);
warn!(
?positioner,
width, height, "Invalid size for positioner's anchor rectangle"
);
return;
}
debug!(
?positioner,
x, y, width, height, "Set positioner anchor rectangle"
);
let mut data = data.lock();
data.anchor_rect_pos = [x, y].into();
data.anchor_rect_size = [width as u32, height as u32].into();
}
xdg_positioner::Request::SetAnchor { anchor } => {
if let WEnum::Value(anchor) = anchor {
debug!(?positioner, ?anchor, "Set positioner anchor");
data.lock().anchor = anchor;
}
}
xdg_positioner::Request::SetGravity { gravity } => {
if let WEnum::Value(gravity) = gravity {
debug!(?positioner, ?gravity, "Set positioner gravity");
data.lock().gravity = gravity;
}
}
xdg_positioner::Request::SetConstraintAdjustment {
constraint_adjustment,
} => {
debug!(
?positioner,
constraint_adjustment, "Set positioner constraint adjustment"
);
let Some(constraint_adjustment) =
ConstraintAdjustment::from_bits(constraint_adjustment)
else {
return;
};
data.lock().constraint_adjustment = constraint_adjustment;
}
xdg_positioner::Request::SetOffset { x, y } => {
debug!(?positioner, x, y, "Set positioner offset");
data.lock().offset = [x, y].into();
}
xdg_positioner::Request::SetReactive => {
debug!(?positioner, "Set positioner reactive");
data.lock().reactive = true;
}
xdg_positioner::Request::SetParentSize {
parent_width,
parent_height,
} => {
debug!(
?positioner,
parent_width, parent_height, "Set positioner parent size"
);
}
xdg_positioner::Request::SetParentConfigure { serial } => {
debug!(?positioner, serial, "Set positioner parent size");
}
xdg_positioner::Request::Destroy => (),
_ => unreachable!(),
}
}
}

View File

@@ -1,236 +0,0 @@
use crate::{
nodes::items::panel::{Geometry, PanelItem, SurfaceID},
wayland::{
seat::handle_cursor,
state::{ClientState, WaylandState},
surface::CoreSurface,
utils,
xdg_shell::{popup::PopupData, toplevel::ToplevelData, XdgBackend},
SERIAL_COUNTER,
},
};
use nanoid::nanoid;
use parking_lot::Mutex;
use smithay::reexports::{
wayland_protocols::xdg::shell::server::{
xdg_surface::{self, XdgSurface},
xdg_toplevel::{XdgToplevel, EVT_WM_CAPABILITIES_SINCE},
},
wayland_server::{
protocol::wl_surface::WlSurface, Client, DataInit, Dispatch, DisplayHandle, Resource,
Weak as WlWeak,
},
};
use std::sync::{Arc, Weak};
use tracing::{debug, error};
#[derive(Debug)]
pub struct XdgSurfaceData {
pub wl_surface: WlWeak<WlSurface>,
pub xdg_surface: XdgSurface,
pub geometry: Mutex<Option<Geometry>>,
}
impl Dispatch<XdgSurface, WlWeak<WlSurface>, WaylandState> for WaylandState {
fn request(
state: &mut WaylandState,
client: &Client,
xdg_surface: &XdgSurface,
request: xdg_surface::Request,
wl_surface_resource: &WlWeak<WlSurface>,
_dhandle: &DisplayHandle,
data_init: &mut DataInit<'_, WaylandState>,
) {
let Ok(wl_surface) = wl_surface_resource.upgrade() else {
error!("Couldn't get the wayland surface of the xdg surface");
return;
};
let Some(xdg_surface_data) = utils::get_data::<XdgSurfaceData>(&wl_surface) else {
error!("Couldn't get the XdgSurface");
return;
};
match request {
xdg_surface::Request::GetToplevel { id } => {
let toplevel = data_init.init(id, wl_surface_resource.clone());
utils::insert_data(&wl_surface, SurfaceID::Toplevel);
utils::insert_data(&wl_surface, toplevel.clone());
utils::insert_data(&wl_surface, ToplevelData::new(&wl_surface));
debug!(?toplevel, ?xdg_surface, "Create XDG toplevel");
if toplevel.version() >= EVT_WM_CAPABILITIES_SINCE {
toplevel.wm_capabilities(vec![3]);
}
toplevel.configure(
0,
0,
if toplevel.version() >= 2 {
vec![1, 5, 6, 7, 8]
.into_iter()
.flat_map(u32::to_ne_bytes)
.collect()
} else {
vec![]
},
);
xdg_surface.configure(SERIAL_COUNTER.inc());
let client_credentials = client.get_credentials(&state.display_handle).ok();
let Some(seat_data) = client.get_data::<ClientState>().map(|s| s.seat.clone())
else {
return;
};
let xdg_surface = xdg_surface.clone();
CoreSurface::add_to(
state.display_handle.clone(),
&wl_surface,
{
let wl_surface_resource = wl_surface_resource.clone();
move || {
let wl_surface = wl_surface_resource.upgrade().unwrap();
let backend = XdgBackend::create(
wl_surface.clone(),
toplevel.clone(),
seat_data.clone(),
);
let (node, panel_item) = PanelItem::create(
Box::new(backend),
client_credentials.map(|c| c.pid),
);
utils::insert_data(&wl_surface, Arc::downgrade(&panel_item));
utils::insert_data_raw(&wl_surface, node);
handle_cursor(&panel_item, panel_item.backend.cursor.clone());
}
},
{
let wl_surface_resource = wl_surface_resource.clone();
move |_| {
let wl_surface = wl_surface_resource.upgrade().unwrap();
let Some(panel_item) =
utils::get_data::<PanelItem<XdgBackend>>(&wl_surface)
else {
let Some(toplevel) = utils::get_data::<XdgToplevel>(&wl_surface)
else {
return;
};
// if the wayland toplevel isn't mapped, hammer it again with a configure until it cooperates
toplevel.configure(
0,
0,
if toplevel.version() >= 2 {
vec![5, 6, 7, 8]
.into_iter()
.flat_map(u32::to_ne_bytes)
.collect()
} else {
vec![]
},
);
xdg_surface.configure(SERIAL_COUNTER.inc());
return;
};
let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface)
else {
return;
};
let Some(size) = core_surface.size() else {
return;
};
panel_item.toplevel_size_changed(size);
}
},
);
}
xdg_surface::Request::GetPopup {
id,
parent,
positioner,
} => {
let Some(parent) = parent else { return };
let Some(parent_wl_surface) = parent
.data::<WlWeak<WlSurface>>()
.map(WlWeak::upgrade)
.map(Result::ok)
.flatten()
else {
return;
};
let Some(panel_item) =
utils::get_data::<Weak<PanelItem<XdgBackend>>>(&parent_wl_surface)
.as_deref()
.and_then(Weak::upgrade)
else {
return;
};
let uid = nanoid!();
let popup_data = PopupData::new(
uid.clone(),
parent_wl_surface.clone(),
&panel_item,
positioner,
);
handle_cursor(
&panel_item,
panel_item.backend.seat.new_surface(&wl_surface),
);
let xdg_popup = data_init.init(id, wl_surface.downgrade());
utils::insert_data(&wl_surface, SurfaceID::Child(uid));
utils::insert_data(&wl_surface, Arc::downgrade(&panel_item));
utils::insert_data(&wl_surface, popup_data);
utils::insert_data(&wl_surface, xdg_popup.clone());
debug!(?xdg_popup, ?xdg_surface, "Create XDG popup");
let xdg_surface = xdg_surface.downgrade();
let popup_wl_surface = wl_surface.downgrade();
CoreSurface::add_to(
state.display_handle.clone(),
&wl_surface,
move || {
let Ok(wl_surface) = popup_wl_surface.upgrade() else {
return;
};
let Some(popup_data) = utils::get_data::<PopupData>(&wl_surface) else {
return;
};
panel_item
.backend
.new_popup(&panel_item, &wl_surface, &*popup_data);
},
move |commit_count| {
if commit_count == 0 {
if let Ok(xdg_surface) = xdg_surface.upgrade() {
xdg_surface.configure(SERIAL_COUNTER.inc())
}
}
},
);
}
xdg_surface::Request::SetWindowGeometry {
x,
y,
width,
height,
} => {
debug!(
?xdg_surface,
x, y, width, height, "Set XDG surface geometry"
);
let geometry = Geometry {
origin: [x, y].into(),
size: [width as u32, height as u32].into(),
};
xdg_surface_data.geometry.lock().replace(geometry);
}
xdg_surface::Request::AckConfigure { serial } => {
debug!(?xdg_surface, serial, "Acknowledge XDG surface configure");
}
xdg_surface::Request::Destroy => {
debug!(?xdg_surface, "Destroy XDG surface");
}
_ => unreachable!(),
}
}
}

View File

@@ -1,239 +0,0 @@
use super::{backend::XdgBackend, surface::XdgSurfaceData};
use crate::{
nodes::items::panel::{Geometry, PanelItem, ToplevelInfo},
wayland::{
state::WaylandState,
surface::CoreSurface,
utils::{self, get_data},
},
};
use mint::Vector2;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use smithay::reexports::{
wayland_protocols::xdg::shell::server::xdg_toplevel::{self, ResizeEdge, XdgToplevel},
wayland_server::{
protocol::wl_surface::WlSurface, Client, DataInit, Dispatch, DisplayHandle, Resource,
Weak as WlWeak,
},
};
use std::sync::Weak;
use tracing::{debug, error};
use wayland_backend::protocol::WEnum;
pub struct ToplevelData {
panel_item: OnceCell<Weak<PanelItem<XdgBackend>>>,
wl_surface: WlWeak<WlSurface>,
parent: Mutex<Option<WlWeak<WlSurface>>>,
title: Mutex<Option<String>>,
app_id: Mutex<Option<String>>,
max_size: Mutex<Option<Vector2<u32>>>,
min_size: Mutex<Option<Vector2<u32>>>,
}
impl ToplevelData {
pub fn new(wl_surface: &WlSurface) -> Self {
ToplevelData {
panel_item: OnceCell::new(),
wl_surface: wl_surface.downgrade(),
parent: Mutex::new(None),
title: Mutex::new(None),
app_id: Mutex::new(None),
max_size: Mutex::new(None),
min_size: Mutex::new(None),
}
}
pub fn parent(&self) -> Option<WlSurface> {
self.parent
.lock()
.as_ref()
.map(WlWeak::upgrade)
.map(Result::ok)
.flatten()
}
}
impl From<&ToplevelData> for ToplevelInfo {
fn from(value: &ToplevelData) -> Self {
let wl_surface = value.wl_surface.upgrade().ok();
let size = CoreSurface::from_wl_surface(wl_surface.as_ref().unwrap())
.unwrap()
.size()
.unwrap();
let logical_rectangle = wl_surface
.as_ref()
.and_then(utils::get_data::<XdgSurfaceData>)
.and_then(|d| d.geometry.lock().clone())
.unwrap_or_else(|| Geometry {
origin: [0, 0].into(),
size,
});
let parent = value
.parent()
.as_ref()
.and_then(utils::get_data::<Weak<PanelItem<XdgBackend>>>)
.as_deref()
.and_then(Weak::upgrade)
.map(|i| i.uid.clone());
ToplevelInfo {
parent,
title: value.title.lock().clone(),
app_id: value.app_id.lock().clone(),
size,
min_size: value.min_size.lock().clone(),
max_size: value.max_size.lock().clone(),
logical_rectangle,
}
}
}
impl Drop for ToplevelData {
fn drop(&mut self) {
// let Some(panel_item) = self.panel_item.get().and_then(Weak::upgrade) else {
// return;
// };
// panel_item.drop_toplevel();
}
}
impl Dispatch<XdgToplevel, WlWeak<WlSurface>, WaylandState> for WaylandState {
fn request(
_state: &mut WaylandState,
_client: &Client,
xdg_toplevel: &XdgToplevel,
request: xdg_toplevel::Request,
wl_surface_resource: &WlWeak<WlSurface>,
_dhandle: &DisplayHandle,
_data_init: &mut DataInit<'_, WaylandState>,
) {
let Ok(wl_surface) = wl_surface_resource.upgrade() else {
error!("Couldn't get the wayland surface of the xdg toplevel");
return;
};
let Some(toplevel_data) = utils::get_data::<ToplevelData>(&wl_surface) else {
error!("Couldn't get the XdgToplevel");
return;
};
match request {
xdg_toplevel::Request::SetParent { parent } => {
debug!(?xdg_toplevel, ?parent, "Set XDG Toplevel parent");
let Some(parent_xdg_toplevel) = parent else {
*toplevel_data.parent.lock() = None;
return;
};
let Some(parent_toplevel_data) = parent_xdg_toplevel.data::<ToplevelData>() else {
error!("Couldn't get XDG toplevel parent data");
return;
};
let Ok(parent_wl_surface) = parent_toplevel_data.wl_surface.upgrade() else {
error!("Couldn't get XDG toplevel parent wl surface");
return;
};
*toplevel_data.parent.lock() = Some(parent_wl_surface.downgrade());
let Some(parent_panel_item) = parent_toplevel_data
.panel_item
.get()
.and_then(Weak::upgrade)
else {
return;
};
let Some(panel_item) = get_data::<PanelItem<XdgBackend>>(&wl_surface) else {
error!("Couldn't get the panel item");
return;
};
panel_item.toplevel_parent_changed(&parent_panel_item.uid);
}
xdg_toplevel::Request::SetTitle { title } => {
debug!(?xdg_toplevel, ?title, "Set XDG Toplevel title");
*toplevel_data.title.lock() = (!title.is_empty()).then_some(title.clone());
let Some(panel_item) = get_data::<PanelItem<XdgBackend>>(&wl_surface) else {
error!("Couldn't get the panel item");
return;
};
panel_item.toplevel_title_changed(&title);
}
xdg_toplevel::Request::SetAppId { app_id } => {
debug!(?xdg_toplevel, ?app_id, "Set XDG Toplevel app ID");
*toplevel_data.app_id.lock() = (!app_id.is_empty()).then_some(app_id.clone());
let Some(panel_item) = get_data::<PanelItem<XdgBackend>>(&wl_surface) else {
error!("Couldn't get the panel item");
return;
};
panel_item.toplevel_app_id_changed(&app_id);
}
xdg_toplevel::Request::Move { seat, serial } => {
debug!(?xdg_toplevel, ?seat, serial, "XDG Toplevel move request");
let Some(panel_item) = get_data::<PanelItem<XdgBackend>>(&wl_surface) else {
error!("Couldn't get the panel item");
return;
};
panel_item.toplevel_move_request();
}
xdg_toplevel::Request::Resize {
seat,
serial,
edges,
} => {
let WEnum::Value(edges) = edges else { return };
debug!(
?xdg_toplevel,
?seat,
serial,
?edges,
"XDG Toplevel resize request"
);
let (up, down, left, right) = match edges {
ResizeEdge::Top => (true, false, false, false),
ResizeEdge::Bottom => (false, true, false, false),
ResizeEdge::Left => (false, false, true, false),
ResizeEdge::TopLeft => (true, false, true, false),
ResizeEdge::BottomLeft => (false, true, true, false),
ResizeEdge::Right => (false, false, false, true),
ResizeEdge::TopRight => (true, false, false, true),
ResizeEdge::BottomRight => (false, true, false, true),
_ => (false, false, false, false),
};
let Some(panel_item) = get_data::<PanelItem<XdgBackend>>(&wl_surface) else {
error!("Couldn't get the panel item");
return;
};
panel_item.toplevel_resize_request(up, down, left, right)
}
xdg_toplevel::Request::SetMaxSize { width, height } => {
debug!(?xdg_toplevel, width, height, "Set XDG Toplevel max size");
*toplevel_data.max_size.lock() = (width > 1 || height > 1)
.then_some(Vector2::from([width as u32, height as u32]));
}
xdg_toplevel::Request::SetMinSize { width, height } => {
debug!(?xdg_toplevel, width, height, "Set XDG Toplevel min size");
*toplevel_data.min_size.lock() = (width > 1 || height > 1)
.then_some(Vector2::from([width as u32, height as u32]));
}
xdg_toplevel::Request::SetFullscreen { output: _ } => {
let Some(panel_item) = get_data::<PanelItem<XdgBackend>>(&wl_surface) else {
error!("Couldn't get the panel item");
return;
};
panel_item.backend.toplevel_state.lock().fullscreen = true;
panel_item.backend.configure(None);
panel_item.toplevel_fullscreen_active(true);
}
xdg_toplevel::Request::UnsetFullscreen => {
let Some(panel_item) = get_data::<PanelItem<XdgBackend>>(&wl_surface) else {
error!("Couldn't get the panel item");
return;
};
panel_item.backend.toplevel_state.lock().fullscreen = false;
panel_item.backend.configure(None);
panel_item.toplevel_fullscreen_active(false);
}
xdg_toplevel::Request::Destroy => {
debug!(?xdg_toplevel, "Destroy XDG Toplevel");
let Some(panel_item) = get_data::<PanelItem<XdgBackend>>(&wl_surface) else {
error!("Couldn't get the panel item");
return;
};
panel_item.backend.seat.drop_surface(&wl_surface);
panel_item.drop_toplevel();
}
_ => {}
}
}
}

View File

@@ -1,12 +1,13 @@
use super::{
seat::{KeyboardEvent, PointerEvent, SeatData},
X_DISPLAY,
state::ClientState,
xdg_shell::PopupData,
};
use crate::{
nodes::{
data::KEYMAPS,
drawable::model::ModelPart,
items::panel::{Backend, Geometry, PanelItem, PanelItemInitData, SurfaceID, ToplevelInfo},
items::panel::{Backend, PanelItem, RecommendedState, SurfaceID},
Message,
},
wayland::surface::CoreSurface,
};
@@ -14,11 +15,11 @@ use color_eyre::eyre::Result;
use mint::Vector2;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use smithay::{
reexports::{
calloop::{EventLoop, LoopSignal},
wayland_server::{protocol::wl_surface::WlSurface, DisplayHandle, Resource},
wayland_protocols::xdg::shell::server::xdg_toplevel,
wayland_server::{protocol::wl_surface::WlSurface, DisplayHandle, Resource, WEnum},
x11rb::protocol::xproto::Window,
},
utils::{Logical, Rectangle},
@@ -28,10 +29,13 @@ use smithay::{
X11Surface, X11Wm, XWayland, XWaylandEvent, XwmHandler,
},
};
use stardust_xr::schemas::flex::serialize;
use std::{ffi::OsStr, iter::empty, sync::Arc, time::Duration};
use tokio::sync::oneshot;
use tracing::debug;
pub static DISPLAY: OnceCell<String> = OnceCell::new();
pub struct XWaylandState {
pub display: u32,
event_loop_signal: LoopSignal,
@@ -42,7 +46,7 @@ impl XWaylandState {
let (tx, rx) = oneshot::channel();
std::thread::spawn(move || {
tokio::task::spawn_blocking(move || {
let mut event_loop: EventLoop<XWaylandHandler> = EventLoop::try_new()?;
let (xwayland, connection) = XWayland::new(&dh);
let handle = event_loop.handle();
@@ -57,14 +61,10 @@ impl XWaylandState {
client_fd: _,
display: _,
} => {
handler.seat.client.set(client.id()).unwrap();
handler
.wm
.set(
X11Wm::start_wm(handle.clone(), dh.clone(), connection, client)
.unwrap(),
)
.unwrap();
handler.seat = client.get_data::<ClientState>().map(|s| s.seat.clone());
handler.wm =
X11Wm::start_wm(handle.clone(), dh.clone(), connection, client)
.ok();
}
XWaylandEvent::Exited => (),
}
@@ -83,15 +83,15 @@ impl XWaylandState {
event_loop_signal: event_loop.get_signal(),
});
let mut handler = XWaylandHandler {
wm: OnceCell::new(),
seat: SeatData::new(&dh),
wayland_display_handle: dh,
wm: None,
seat: None,
};
event_loop.run(Duration::from_millis(100), &mut handler, |_| ())
});
let state = rx.blocking_recv()?;
let _ = X_DISPLAY.set(state.display);
let _ = DISPLAY.set(format!(":{}", state.display));
Ok(state)
}
@@ -104,8 +104,8 @@ impl Drop for XWaylandState {
struct XWaylandHandler {
wayland_display_handle: DisplayHandle,
wm: OnceCell<X11Wm>,
seat: Arc<SeatData>,
wm: Option<X11Wm>,
seat: Option<Arc<SeatData>>,
}
impl XWaylandHandler {
fn panel_item(&self, window: &X11Surface) -> Option<Arc<PanelItem<X11Backend>>> {
@@ -117,7 +117,7 @@ impl XWaylandHandler {
impl XwmHandler for XWaylandHandler {
fn xwm_state(&mut self, _xwm: XwmId) -> &mut X11Wm {
self.wm.get_mut().unwrap()
self.wm.as_mut().unwrap()
}
fn new_window(&mut self, _xwm: XwmId, window: X11Surface) {
@@ -135,22 +135,18 @@ impl XwmHandler for XWaylandHandler {
fn map_window_notify(&mut self, _xwm: XwmId, window: X11Surface) {
debug!(?window, "X map window notify");
let _ = window.set_maximized(true);
let dh = self.wayland_display_handle.clone();
let seat = self.seat.clone();
let seat = self.seat.clone().unwrap();
CoreSurface::add_to(
self.wayland_display_handle.clone(),
&window.wl_surface().unwrap(),
{
let window = window.clone();
move || {
let Some(wl_surface) = window.wl_surface() else {
return;
};
let Some(wl_surface) = window.wl_surface() else {return};
let seat = seat.clone();
window.user_data().insert_if_missing_threadsafe(|| {
let panel_item = PanelItem::create(
let (_node, panel_item) = PanelItem::create(
Box::new(X11Backend {
toplevel_parent: None,
toplevel: window.clone(),
@@ -168,30 +164,20 @@ impl XwmHandler for XWaylandHandler {
}
},
move |_| {
let Some(panel_item) = window.user_data().get::<Arc<PanelItem<X11Backend>>>()
else {
return;
};
panel_item.toplevel_size_changed(
[
window.geometry().size.w as u32,
window.geometry().size.h as u32,
]
.into(),
);
let Some(panel_item) = window.user_data().get::<Arc<PanelItem<X11Backend>>>() else {return};
panel_item.commit_toplevel();
},
);
}
fn mapped_override_redirect_window(&mut self, _xwm: XwmId, window: X11Surface) {
debug!(?window, "X map override redirect window");
}
fn unmapped_window(&mut self, _xwm: XwmId, window: X11Surface) {
debug!(?window, "Unmap X window");
if let Some(panel_item) = window.user_data().get::<Arc<PanelItem<X11Backend>>>() {
panel_item.drop_toplevel();
}
}
fn destroyed_window(&mut self, _xwm: XwmId, window: X11Surface) {
debug!(?window, "Destroy X window");
}
@@ -220,11 +206,9 @@ impl XwmHandler for XWaylandHandler {
}
fn move_request(&mut self, _xwm: XwmId, window: X11Surface, button: u32) {
let Some(panel_item) = self.panel_item(&window) else {
return;
};
let Some(panel_item) = self.panel_item(&window) else {return};
debug!(?window, button, "X window requests move");
panel_item.toplevel_move_request();
panel_item.recommend_toplevel_state(RecommendedState::Move);
}
fn resize_request(
&mut self,
@@ -233,37 +217,42 @@ impl XwmHandler for XWaylandHandler {
button: u32,
resize_edge: ResizeEdge,
) {
let Some(panel_item) = self.panel_item(&window) else {
return;
};
let Some(panel_item) = self.panel_item(&window) else {return};
debug!(?window, button, ?resize_edge, "X window requests resize");
let (up, down, left, right) = match resize_edge {
ResizeEdge::Top => (true, false, false, false),
ResizeEdge::Bottom => (false, true, false, false),
ResizeEdge::Left => (false, false, true, false),
ResizeEdge::TopLeft => (true, false, true, false),
ResizeEdge::BottomLeft => (false, true, true, false),
ResizeEdge::Right => (false, false, false, true),
ResizeEdge::TopRight => (true, false, false, true),
ResizeEdge::BottomRight => (false, true, false, true),
// _ => (false, false, false, false),
};
panel_item.toplevel_resize_request(up, down, left, right)
panel_item.recommend_toplevel_state(RecommendedState::Resize(
WEnum::Value(match resize_edge {
ResizeEdge::Top => xdg_toplevel::ResizeEdge::Top,
ResizeEdge::Bottom => xdg_toplevel::ResizeEdge::Bottom,
ResizeEdge::Left => xdg_toplevel::ResizeEdge::Left,
ResizeEdge::TopLeft => xdg_toplevel::ResizeEdge::TopLeft,
ResizeEdge::BottomLeft => xdg_toplevel::ResizeEdge::BottomLeft,
ResizeEdge::Right => xdg_toplevel::ResizeEdge::Right,
ResizeEdge::TopRight => xdg_toplevel::ResizeEdge::TopRight,
ResizeEdge::BottomRight => xdg_toplevel::ResizeEdge::BottomRight,
})
.into(),
));
}
fn maximize_request(&mut self, _xwm: XwmId, window: X11Surface) {
let Some(panel_item) = self.panel_item(&window) else {return};
panel_item.recommend_toplevel_state(RecommendedState::Maximize(true));
}
fn unmaximize_request(&mut self, _xwm: XwmId, window: X11Surface) {
let Some(panel_item) = self.panel_item(&window) else {return};
panel_item.recommend_toplevel_state(RecommendedState::Maximize(false));
}
fn fullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) {
let _ = window.set_fullscreen(true);
let Some(panel_item) = self.panel_item(&window) else {
return;
};
panel_item.toplevel_fullscreen_active(true);
let Some(panel_item) = self.panel_item(&window) else {return};
panel_item.recommend_toplevel_state(RecommendedState::Fullscreen(true));
}
fn unfullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) {
let _ = window.set_fullscreen(false);
let Some(panel_item) = self.panel_item(&window) else {
return;
};
panel_item.toplevel_fullscreen_active(true);
let Some(panel_item) = self.panel_item(&window) else {return};
panel_item.recommend_toplevel_state(RecommendedState::Fullscreen(true));
}
fn minimize_request(&mut self, _xwm: XwmId, window: X11Surface) {
let Some(panel_item) = self.panel_item(&window) else {return};
panel_item.recommend_toplevel_state(RecommendedState::Minimize);
}
}
@@ -279,7 +268,7 @@ impl X11Backend {
match id {
SurfaceID::Cursor => None,
SurfaceID::Toplevel => self.toplevel.wl_surface(),
SurfaceID::Child(_) => None,
SurfaceID::Popup(_) => None,
}
}
@@ -291,79 +280,77 @@ impl X11Backend {
// }
}
impl Backend for X11Backend {
fn start_data(&self) -> Result<PanelItemInitData> {
Ok(PanelItemInitData {
cursor: None,
toplevel: ToplevelInfo {
parent: None,
title: Some(self.toplevel.title()),
app_id: Some(self.toplevel.instance()),
size: [
self.toplevel.geometry().size.w as u32,
self.toplevel.geometry().size.h as u32,
]
.into(),
min_size: self
.toplevel
.min_size()
.map(|s| [s.w as u32, s.h as u32].into()),
max_size: self
.toplevel
.max_size()
.map(|s| [s.w as u32, s.h as u32].into()),
logical_rectangle: Geometry {
origin: [0, 0].into(),
size: [
self.toplevel.geometry().size.w as u32,
self.toplevel.geometry().size.h as u32,
]
.into(),
},
},
children: FxHashMap::default(),
pointer_grab: self._pointer_grab.lock().clone(),
keyboard_grab: self._keyboard_grab.lock().clone(),
})
fn serialize_start_data(&self, id: &str) -> Result<Message> {
let size = (
self.toplevel.geometry().size.w as u32,
self.toplevel.geometry().size.h as u32,
);
let toplevel_state = (
None::<String>,
self.toplevel.title(),
None::<String>,
(
self.toplevel.geometry().size.w as u32,
self.toplevel.geometry().size.h as u32,
),
self.toplevel.min_size().map(|s| (s.w as u32, s.h as u32)),
self.toplevel.max_size().map(|s| (s.w as u32, s.w as u32)),
((0_i32, 0_i32), size),
vec![0_u32; 0],
);
let info = (
None::<(Vector2<u32>, Vector2<i32>)>,
toplevel_state,
Vec::<PopupData>::new(),
None::<SurfaceID>,
None::<SurfaceID>,
);
Ok(serialize((id, info))?.into())
}
fn close_toplevel(&self) {
let _ = self.toplevel.close();
fn serialize_toplevel(&self) -> Result<Message> {
let toplevel_state = (
None::<String>,
self.toplevel.title(),
None::<String>,
(
self.toplevel.geometry().size.w,
self.toplevel.geometry().size.h,
),
self.toplevel.min_size().map(|s| (s.w, s.h)),
self.toplevel.max_size().map(|s| (s.w, s.w)),
);
let data = serialize(&toplevel_state)?;
Ok(data.into())
}
fn auto_size_toplevel(&self) {
let _ = self.toplevel.configure(None);
}
fn set_toplevel_size(&self, size: Vector2<u32>) {
let _ = self.toplevel.configure(Some(Rectangle {
loc: self.toplevel.geometry().loc,
size: (size.x as i32, size.y as i32).into(),
}));
}
fn set_toplevel_focused_visuals(&self, focused: bool) {
let _ = self.toplevel.set_activated(focused);
fn set_toplevel_capabilities(&self, _capabilities: Vec<u8>) {}
fn configure_toplevel(
&self,
size: Option<Vector2<u32>>,
states: Vec<u32>,
_bounds: Option<Vector2<u32>>,
) {
let _ = self.toplevel.configure(
size.map(|s| Rectangle::from_loc_and_size((0, 0), (s.x as i32, s.y as i32))),
);
let _ = self.toplevel.set_maximized(states.contains(&1));
}
fn apply_surface_material(&self, surface: SurfaceID, model_part: &Arc<ModelPart>) {
let Some(wl_surface) = self.wl_surface_from_id(&surface) else {
return;
};
let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface) else {
return;
};
let Some(wl_surface) = self.wl_surface_from_id(&surface) else {return};
let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface) else {return};
core_surface.apply_material(model_part);
}
fn pointer_motion(&self, surface: &SurfaceID, position: Vector2<f32>) {
let Some(surface) = self.wl_surface_from_id(surface) else {
return;
};
let Some(surface) = self.wl_surface_from_id(surface) else {return};
self.seat
.pointer_event(&surface, PointerEvent::Motion(position));
}
fn pointer_button(&self, surface: &SurfaceID, button: u32, pressed: bool) {
let Some(surface) = self.wl_surface_from_id(surface) else {
return;
};
let Some(surface) = self.wl_surface_from_id(surface) else {return};
self.seat.pointer_event(
&surface,
PointerEvent::Button {
@@ -378,9 +365,7 @@ impl Backend for X11Backend {
scroll_distance: Option<Vector2<f32>>,
scroll_steps: Option<Vector2<f32>>,
) {
let Some(surface) = self.wl_surface_from_id(surface) else {
return;
};
let Some(surface) = self.wl_surface_from_id(surface) else {return};
self.seat.pointer_event(
&surface,
PointerEvent::Scroll {
@@ -390,41 +375,24 @@ impl Backend for X11Backend {
)
}
fn keyboard_keys(&self, surface: &SurfaceID, keymap_id: &str, keys: Vec<i32>) {
let Some(surface) = self.wl_surface_from_id(surface) else {
return;
};
let keymaps = KEYMAPS.lock();
let Some(keymap) = keymaps.get(keymap_id).cloned() else {
return;
};
if self.seat.set_keymap(keymap, vec![surface.clone()]).is_err() {
return;
}
for key in keys {
self.seat.keyboard_event(
&surface,
KeyboardEvent::Key {
key: key.abs() as u32,
state: key < 0,
},
);
}
fn keyboard_set_keymap(&self, keymap: &str) -> Result<()> {
self.seat.set_keymap_str(
&keymap,
if let Some(toplevel) = self.toplevel.wl_surface() {
vec![toplevel]
} else {
vec![]
},
)
}
fn touch_down(&self, surface: &SurfaceID, id: u32, position: Vector2<f32>) {
let Some(surface) = self.wl_surface_from_id(surface) else {
return;
};
self.seat.touch_down(&surface, id, position)
}
fn touch_move(&self, id: u32, position: Vector2<f32>) {
self.seat.touch_move(id, position)
}
fn touch_up(&self, id: u32) {
self.seat.touch_up(id)
}
fn reset_touches(&self) {
self.seat.reset_touches()
fn keyboard_key(&self, surface: &SurfaceID, key: u32, state: bool) {
let Some(surface) = self.wl_surface_from_id(surface) else {return};
self.seat.keyboard_event(
&surface,
KeyboardEvent::Key {
key,
state: if state { 1 } else { 0 },
},
)
}
}

View File

@@ -1,267 +0,0 @@
use crate::core::{client::get_env, task};
use crate::STOP_NOTIFIER;
use smithay::reexports::rustix;
use smithay::reexports::rustix::io::{fcntl_setfd, Errno, FdFlags};
use smithay::reexports::rustix::net::SocketAddrUnix;
use std::io::{Read, Write};
use std::{
io::ErrorKind,
os::{
fd::{AsRawFd, BorrowedFd, RawFd},
unix::process::CommandExt,
},
process::{ChildStdout, Command, Stdio},
};
use tokio::net::{UnixListener, UnixStream};
use tokio::task::AbortHandle;
use tracing::{debug, info, warn};
use super::X_DISPLAY;
pub fn start_xwayland(wayland_socket: RawFd) -> std::io::Result<X11Lock> {
let (mut lock, listener) = bind_socket()?;
let abort_handle = task::new(|| "X11 Client Acceptor", async move {
loop {
let Ok((stream, _)) = tokio::select! {
_ = STOP_NOTIFIER.notified() => break,
e = listener.accept() => e,
} else {
continue;
};
let Ok((x_wm_x11, _x_wm_me)) = UnixStream::pair() else {
continue;
};
let Ok(env) = stream
.peer_cred()
.and_then(|c| c.pid().ok_or(ErrorKind::Other.into()))
.and_then(get_env)
else {
continue;
};
let _ = spawn_xwayland(
lock.display,
wayland_socket,
x_wm_x11,
stream.as_raw_fd(),
env.get("STARDUST_STARTUP_TOKEN").cloned(),
);
}
})
.map_err(|_| ErrorKind::Other)?
.abort_handle();
lock.x_abort_handle.replace(abort_handle);
let _ = X_DISPLAY.set(lock.display);
Ok(lock)
}
/// Find a free X11 display slot and setup
pub(crate) fn bind_socket() -> Result<(X11Lock, UnixListener), std::io::Error> {
for d in 0..33 {
// if fails, try the next one
if let Ok(lock) = X11Lock::grab(d) {
// we got a lockfile, try and create the socket
match open_x11_socket_for_display(d) {
Ok(socket) => return Ok((lock, socket)),
Err(err) => warn!(display = d, "Failed to create sockets: {}", err),
}
}
}
// If we reach here, all values from 0 to 32 failed
// we need to stop trying at some point
Err(std::io::Error::new(
std::io::ErrorKind::AddrInUse,
"Could not find a free socket for the XServer.",
))
}
#[derive(Debug)]
pub(crate) struct X11Lock {
display: u32,
x_abort_handle: Option<AbortHandle>,
}
impl X11Lock {
/// Try to grab a lockfile for given X display number
fn grab(number: u32) -> Result<X11Lock, ()> {
debug!(display = number, "Attempting to aquire an X11 display lock");
let filename = format!("/tmp/.X{}-lock", number);
let lockfile = ::std::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&filename);
match lockfile {
Ok(mut file) => {
// we got it, write our PID in it and we're good
let ret = file.write_fmt(format_args!(
"{:>10}\n",
rustix::process::Pid::as_raw(Some(rustix::process::getpid()))
));
if ret.is_err() {
// write to the file failed ? we abandon
::std::mem::drop(file);
let _ = ::std::fs::remove_file(&filename);
Err(())
} else {
debug!(display = number, "X11 lock acquired");
// we got the lockfile and wrote our pid to it, all is good
Ok(X11Lock {
display: number,
x_abort_handle: None,
})
}
}
Err(_) => {
debug!(display = number, "Failed to acquire lock");
// we could not open the file, now we try to read it
// and if it contains the pid of a process that no longer
// exist (so if a previous x server claimed it and did not
// exit gracefully and remove it), we claim it
// if we can't open it, give up
let mut file = ::std::fs::File::open(&filename).map_err(|_| ())?;
let mut spid = [0u8; 11];
file.read_exact(&mut spid).map_err(|_| ())?;
::std::mem::drop(file);
let pid = rustix::process::Pid::from_raw(
::std::str::from_utf8(&spid)
.map_err(|_| ())?
.trim()
.parse::<i32>()
.map_err(|_| ())?,
)
.ok_or(())?;
if let Err(Errno::SRCH) = rustix::process::test_kill_process(pid) {
// no process whose pid equals the contents of the lockfile exists
// remove the lockfile and try grabbing it again
if let Ok(()) = ::std::fs::remove_file(filename) {
debug!(
display = number,
"Lock was blocked by a defunct X11 server, trying again"
);
return X11Lock::grab(number);
} else {
// we could not remove the lockfile, abort
return Err(());
}
}
// if we reach here, this lockfile exists and is probably in use, give up
Err(())
}
}
}
pub(crate) fn display(&self) -> u32 {
self.display
}
}
impl Drop for X11Lock {
fn drop(&mut self) {
info!("Cleaning up X11 lock.");
// Cleanup all the X11 files
if let Err(e) = ::std::fs::remove_file(format!("/tmp/.X11-unix/X{}", self.display)) {
warn!(error = ?e, "Failed to remove X11 socket");
}
if let Err(e) = ::std::fs::remove_file(format!("/tmp/.X{}-lock", self.display)) {
warn!(error = ?e, "Failed to remove X11 lockfile");
}
if let Some(join_handle) = self.x_abort_handle.take() {
join_handle.abort();
}
}
}
/// Open the two unix sockets an X server listens on
///
/// Should only be done after the associated lockfile is acquired!
fn open_x11_socket_for_display(display: u32) -> rustix::io::Result<UnixListener> {
let path = format!("/tmp/.X11-unix/X{}", display);
let _ = ::std::fs::remove_file(&path);
// We know this path is not too long, these unwrap cannot fail
let fs_addr = SocketAddrUnix::new(path.as_bytes()).unwrap();
open_socket(fs_addr)
}
/// Open an unix socket for listening and bind it to given path
fn open_socket(addr: SocketAddrUnix) -> rustix::io::Result<UnixListener> {
// create an unix stream socket
let fd = rustix::net::socket_with(
rustix::net::AddressFamily::UNIX,
rustix::net::SocketType::STREAM,
rustix::net::SocketFlags::CLOEXEC,
None,
)?;
// bind it to requested address
rustix::net::bind_unix(&fd, &addr)?;
rustix::net::listen(&fd, 1)?;
Ok(UnixListener::from_std(std::os::unix::net::UnixListener::from(fd)).unwrap())
}
fn spawn_xwayland(
display: u32,
wayland_socket: RawFd,
wm_socket: UnixStream,
listen_socket: RawFd,
stardust_startup_token: Option<String>,
) -> std::io::Result<ChildStdout> {
let mut command = Command::new("sh");
// We use output stream to communicate because FD is easier to handle than exit code.
command.stdout(Stdio::piped());
let mut xwayland_args = format!(":{} -geometry 1920x1080", display);
xwayland_args.push_str(&format!(" -listenfd {}", listen_socket));
// This command let sh to:
// * Set up signal handler for USR1
// * Launch Xwayland with USR1 ignored so Xwayland will signal us when it is ready (also redirect
// Xwayland's STDOUT to STDERR so its output, if any, won't distract us)
// * Print "S" and exit if USR1 is received
command.arg("-c").arg(format!(
"trap 'echo S' USR1; (trap '' USR1; exec Xwayland {}) 1>&2 & wait",
xwayland_args
));
// Setup the environment: clear everything except PATH and XDG_RUNTIME_DIR
command.env_clear();
for (key, value) in std::env::vars_os() {
if key.to_str() == Some("PATH") || key.to_str() == Some("XDG_RUNTIME_DIR") {
command.env(key, value);
continue;
}
}
command.env("WAYLAND_SOCKET", format!("{}", wayland_socket.as_raw_fd()));
command.env(
"STARDUST_STARTUP_TOKEN",
stardust_startup_token.unwrap_or_default(),
);
unsafe {
let wayland_socket_fd = wayland_socket.as_raw_fd();
let wm_socket_fd = wm_socket.as_raw_fd();
command.pre_exec(move || {
// unset the CLOEXEC flag from the sockets we need to pass
// to xwayland
unset_cloexec(wayland_socket_fd)?;
unset_cloexec(wm_socket_fd)?;
unset_cloexec(listen_socket)?;
Ok(())
});
}
let mut child = command.spawn()?;
Ok(child.stdout.take().expect("stdout should be piped"))
}
/// Remove the `O_CLOEXEC` flag from this `Fd`
///
/// This means that the `Fd` will *not* be automatically
/// closed when we `exec()` into XWayland
unsafe fn unset_cloexec(fd: RawFd) -> std::io::Result<()> {
let fd = BorrowedFd::borrow_raw(fd);
fcntl_setfd(fd, FdFlags::empty())?;
Ok(())
}