mirror of
https://github.com/wayvr-org/wayvr.git
synced 2026-10-29 04:03:04 +01:00
Compare commits
39
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
607da88151 | ||
|
|
03d534188e | ||
|
|
e1fc831934 | ||
|
|
e2fa349021 | ||
|
|
86689759c2 | ||
|
|
75175a2264 | ||
|
|
69b930141d | ||
|
|
5ef2fe7be3 | ||
|
|
73d41680ff | ||
|
|
c955afd466 | ||
|
|
fb971a7999 | ||
|
|
1d0a86ee65 | ||
|
|
f1ed8cd11c | ||
|
|
6e8643e566 | ||
|
|
8de6380ad9 | ||
|
|
a65b33fed2 | ||
|
|
786903efd5 | ||
|
|
466960d32a | ||
|
|
abafddefd8 | ||
|
|
9b1fc2774a | ||
|
|
c539ff6a90 | ||
|
|
f94c0c4ace | ||
|
|
5f4c97794c | ||
|
|
d9288a877c | ||
|
|
dff4b0067a | ||
|
|
adf852971f | ||
|
|
074d2de7aa | ||
|
|
e8d9a35714 | ||
|
|
36f9fab5c6 | ||
|
|
058792fc8e | ||
|
|
ef934e0b02 | ||
|
|
c42e992a62 | ||
|
|
41d7a0b3d2 | ||
|
|
471e4be946 | ||
|
|
26afa57fe3 | ||
|
|
d120617abe | ||
|
|
351b24533c | ||
|
|
09ed96542f | ||
|
|
9b89622ff9 |
No files matched your search
@@ -21,6 +21,9 @@ export LDAI_UPDATE_INFORMATION="gh-releases-zsync|wayvr-org|wayvr|latest|WayVR-*
|
||||
|
||||
echo "Packaging AppImage for ${APPIMAGE_ARCH}"
|
||||
|
||||
# linuxdeploy doesn't like multiple -d's being passed
|
||||
install -Dm 0644 wayvr-headless.desktop "${APPDIR}/usr/share/applications/wayvr-headless.desktop"
|
||||
|
||||
"./${LINUXDEPLOY}" \
|
||||
-dwayvr.desktop \
|
||||
-iwayvr.png \
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# Contributing
|
||||
|
||||
Contributions are warmly welcome. Before you start, please read the entire document carefully.
|
||||
|
||||
## AI Policy
|
||||
|
||||
Do not use LLMs to generate substantial pieces of code. You need to fully understand every line of code you change. No exceptions.
|
||||
|
||||
Commits, issues and pull requests must be made by humans. Co-authoring by AI is not allowed.
|
||||
Looking to create an issue on the project, such as a feature request or bug report? Focus on writing what you know for sure in simple terms. We'll figure it out from there.
|
||||
|
||||
Usage of Gen-AI for visuals and audio is prohibited.
|
||||
|
||||
The only exception is LLM-translated strings generated by the scripts/translator tool.
|
||||
|
||||
## Basics
|
||||
|
||||
Before committing, make sure to format your Rust code by running:
|
||||
|
||||
```
|
||||
cargo fmt
|
||||
```
|
||||
|
||||
You should also check for any compiler warnings too:
|
||||
|
||||
```
|
||||
cargo check
|
||||
```
|
||||
|
||||
or even better:
|
||||
|
||||
```
|
||||
cargo clippy
|
||||
```
|
||||
|
||||
## Large contributions
|
||||
|
||||
If you want to submit a relatively big pull requests, it's highly advisable to create a separate issue request for it. That way we can discuss the approach and sort out the details first.
|
||||
|
||||
## Adding translations
|
||||
|
||||
Look at `scripts/translator/run.sh` for more information.
|
||||
|
||||
## Licensing
|
||||
|
||||
WayVR is developed and published under [GPL-3 License](https://www.gnu.org/licenses/gpl-3.0.en.html).
|
||||
Generated
+233
-54
@@ -213,15 +213,6 @@ version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e149dc73cd30538307e7ffa2acd3d2221148eaeed4871f246657b1c3eaa1cbd2"
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f2a05fd1bd10b2527e20a2cd32d8873d115b8b39fe219ee25f42a8aca6ba278"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aquamarine"
|
||||
version = "0.1.12"
|
||||
@@ -430,12 +421,6 @@ version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||
|
||||
[[package]]
|
||||
name = "atomic_float"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "628d228f918ac3b82fe590352cc719d30664a0c13ca3a60266fe02c7132d480a"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
@@ -689,6 +674,12 @@ dependencies = [
|
||||
"wyz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
@@ -729,6 +720,17 @@ dependencies = [
|
||||
"piper",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "built"
|
||||
version = "0.8.1"
|
||||
@@ -795,14 +797,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "calloop"
|
||||
version = "0.14.4"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7"
|
||||
checksum = "43b32313e937c8721c9d17ee9bf60699a6402b4b7c2cb6ac1bee0ffcaa4b4e5e"
|
||||
dependencies = [
|
||||
"bitflags 2.13.0",
|
||||
"polling",
|
||||
"rustix 1.1.4",
|
||||
"slab",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
@@ -824,7 +825,7 @@ version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa"
|
||||
dependencies = [
|
||||
"calloop 0.14.4",
|
||||
"calloop 0.14.5",
|
||||
"rustix 1.1.4",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
@@ -879,16 +880,6 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "cgmath"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a98d30140e3296250832bbaaff83b27dcd6fa3cc70fb6f1f3e5c9c0023b5317"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.10.1"
|
||||
@@ -1393,7 +1384,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"glam",
|
||||
"glam 0.30.10",
|
||||
"keyvalues-parser",
|
||||
"log",
|
||||
"lz4_flex",
|
||||
@@ -1401,6 +1392,7 @@ dependencies = [
|
||||
"rust-embed",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shell-words",
|
||||
"smol",
|
||||
"strum",
|
||||
"ureq",
|
||||
@@ -1625,6 +1617,26 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enumflags2"
|
||||
version = "0.7.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef"
|
||||
dependencies = [
|
||||
"enumflags2_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enumflags2_derive"
|
||||
version = "0.7.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enumset"
|
||||
version = "1.1.13"
|
||||
@@ -1921,6 +1933,21 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
|
||||
|
||||
[[package]]
|
||||
name = "freedesktop-desktop-entry"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42a69ad6111a2b572cb41e0f3542d546386e841013cee7c45d705b61a15a87dd"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"gettext-rs",
|
||||
"log",
|
||||
"memchr",
|
||||
"thiserror 2.0.18",
|
||||
"unicase",
|
||||
"xdg 3.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs_extra"
|
||||
version = "1.3.0"
|
||||
@@ -2100,6 +2127,26 @@ dependencies = [
|
||||
"rand_core 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gettext-rs"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9df5e3fa8c077b15fdfa8ce130e2bc73d663b8aaa6d51b71421b8659980b1d80"
|
||||
dependencies = [
|
||||
"gettext-sys",
|
||||
"locale_config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gettext-sys"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4caa7e4460b196107cc1bb2629488e8b6dbb7f34f6683ea6753c4ac22f590785"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"temp-dir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gif"
|
||||
version = "0.14.2"
|
||||
@@ -2126,6 +2173,12 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glam"
|
||||
version = "0.33.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37bfe73dc8ec21f54d181e5a60554327ec3f1c600c455a75ccbfc65d2106a6c4"
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.3"
|
||||
@@ -2426,6 +2479,26 @@ version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306"
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.11.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cc00ea907cab49550b7da656f80ebb97be1b997d931fbcd28d39734e17ce592"
|
||||
dependencies = [
|
||||
"bitflags 2.13.0",
|
||||
"inotify-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify-sys"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c033f80b2c113cdf91ab7a33faa9cbc014726dcad99880c8609af2a370edf37d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "input"
|
||||
version = "0.9.1"
|
||||
@@ -2869,6 +2942,19 @@ version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||
|
||||
[[package]]
|
||||
name = "locale_config"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d2c35b16f4483f6c26f0e4e9550717a2f6575bcd6f12a53ff0c490a94a6934"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"objc",
|
||||
"objc-foundation",
|
||||
"regex",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.14"
|
||||
@@ -2930,6 +3016,15 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "malloc_buf"
|
||||
version = "0.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.2.0"
|
||||
@@ -3221,6 +3316,26 @@ dependencies = [
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
|
||||
dependencies = [
|
||||
"malloc_buf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc-foundation"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
|
||||
dependencies = [
|
||||
"block",
|
||||
"objc",
|
||||
"objc_id",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc-sys"
|
||||
version = "0.3.5"
|
||||
@@ -3528,6 +3643,15 @@ dependencies = [
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc_id"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
|
||||
dependencies = [
|
||||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.37.3"
|
||||
@@ -4021,7 +4145,7 @@ version = "0.14.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"heck 0.5.0",
|
||||
"itertools 0.14.0",
|
||||
"log",
|
||||
"multimap",
|
||||
@@ -4427,6 +4551,18 @@ version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||
|
||||
[[package]]
|
||||
name = "reis"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b967ec6489a42067a20724f11987bb96178ea796873cbe1afd25fb93ee6f85f"
|
||||
dependencies = [
|
||||
"calloop 0.14.5",
|
||||
"enumflags2",
|
||||
"log",
|
||||
"rustix 1.1.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "resvg"
|
||||
version = "0.47.0"
|
||||
@@ -4669,8 +4805,7 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
[[package]]
|
||||
name = "rxscreen"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afbab13f83503a8272b7150d487494992cf6c59c299dcaf5d8ffac75bad9fc96"
|
||||
source = "git+https://github.com/galister/rxscreen.git?rev=7197e1de6622b65859e6f2060b5750185e0dad2a#7197e1de6622b65859e6f2060b5750185e0dad2a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@@ -5005,27 +5140,27 @@ checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
|
||||
[[package]]
|
||||
name = "smithay"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "740cea6927892bc182d5bf70c8f79806c8bc9f68f2fb96e55a30be171b63af98"
|
||||
source = "git+https://github.com/Smithay/smithay.git?rev=0d66849533b475c40c0c461ec182d0f939424452#0d66849533b475c40c0c461ec182d0f939424452"
|
||||
dependencies = [
|
||||
"appendlist",
|
||||
"ash",
|
||||
"atomic_float",
|
||||
"bitflags 2.13.0",
|
||||
"calloop 0.14.4",
|
||||
"cgmath",
|
||||
"calloop 0.14.5",
|
||||
"cursor-icon",
|
||||
"downcast-rs",
|
||||
"drm-fourcc",
|
||||
"encoding_rs",
|
||||
"errno",
|
||||
"glam 0.33.12",
|
||||
"indexmap 2.14.0",
|
||||
"libc",
|
||||
"portable-atomic",
|
||||
"profiling",
|
||||
"rand 0.9.4",
|
||||
"rand 0.10.2",
|
||||
"reis",
|
||||
"rustix 1.1.4",
|
||||
"scopeguard",
|
||||
"sha2",
|
||||
"siphasher",
|
||||
"smallvec",
|
||||
"tempfile",
|
||||
"thiserror 2.0.18",
|
||||
@@ -5034,8 +5169,8 @@ dependencies = [
|
||||
"wayland-protocols-misc",
|
||||
"wayland-protocols-wlr",
|
||||
"wayland-server",
|
||||
"x11rb",
|
||||
"xkbcommon",
|
||||
"x11rb 0.14.0",
|
||||
"xkbcommon 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5071,7 +5206,7 @@ checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0"
|
||||
dependencies = [
|
||||
"bitflags 2.13.0",
|
||||
"bytemuck",
|
||||
"calloop 0.14.4",
|
||||
"calloop 0.14.5",
|
||||
"calloop-wayland-source 0.4.1",
|
||||
"cursor-icon",
|
||||
"libc",
|
||||
@@ -5089,7 +5224,7 @@ dependencies = [
|
||||
"wayland-protocols-misc",
|
||||
"wayland-protocols-wlr",
|
||||
"wayland-scanner",
|
||||
"xkbcommon",
|
||||
"xkbcommon 0.8.0",
|
||||
"xkeysym",
|
||||
]
|
||||
|
||||
@@ -5343,6 +5478,12 @@ version = "0.13.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
||||
|
||||
[[package]]
|
||||
name = "temp-dir"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83176759e9416cf81ee66cb6508dbfe9c96f20b8b56265a39917551c23c70964"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.27.0"
|
||||
@@ -5350,7 +5491,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.3.4",
|
||||
"getrandom 0.4.3",
|
||||
"once_cell",
|
||||
"rustix 1.1.4",
|
||||
"windows-sys 0.52.0",
|
||||
@@ -5683,7 +5824,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dash-frontend",
|
||||
"glam",
|
||||
"glam 0.30.10",
|
||||
"log",
|
||||
"rust-embed",
|
||||
"tracing-subscriber",
|
||||
@@ -5693,6 +5834,12 @@ dependencies = [
|
||||
"wlx-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.18"
|
||||
@@ -6095,7 +6242,7 @@ version = "26.8.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
"calloop 0.14.4",
|
||||
"calloop 0.14.5",
|
||||
"calloop-wayland-source 0.4.1",
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
@@ -6105,9 +6252,10 @@ dependencies = [
|
||||
"dbus",
|
||||
"evdev",
|
||||
"futures",
|
||||
"glam",
|
||||
"glam 0.30.10",
|
||||
"idmap",
|
||||
"idmap-derive",
|
||||
"image",
|
||||
"image_dds",
|
||||
"input",
|
||||
"input-linux",
|
||||
@@ -6147,6 +6295,7 @@ dependencies = [
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"wayland-protocols-misc",
|
||||
"wayland-scanner",
|
||||
"wayvr-ipc",
|
||||
"wgui",
|
||||
"whisper-rs",
|
||||
@@ -6155,7 +6304,7 @@ dependencies = [
|
||||
"wlx-common",
|
||||
"xcb",
|
||||
"xdg 3.0.0",
|
||||
"xkbcommon",
|
||||
"xkbcommon 0.8.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6236,7 +6385,7 @@ dependencies = [
|
||||
"dav1d-sys",
|
||||
"etagere",
|
||||
"flate2",
|
||||
"glam",
|
||||
"glam 0.30.10",
|
||||
"image",
|
||||
"log",
|
||||
"lru",
|
||||
@@ -6697,7 +6846,7 @@ dependencies = [
|
||||
"web-time",
|
||||
"windows-sys 0.52.0",
|
||||
"x11-dl",
|
||||
"x11rb",
|
||||
"x11rb 0.13.2",
|
||||
"xkbcommon-dl",
|
||||
]
|
||||
|
||||
@@ -6739,14 +6888,16 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"glam",
|
||||
"freedesktop-desktop-entry",
|
||||
"glam 0.30.10",
|
||||
"glob",
|
||||
"identicons-svg",
|
||||
"idmap",
|
||||
"idmap-derive",
|
||||
"inotify",
|
||||
"libc",
|
||||
"log",
|
||||
"rodio",
|
||||
"rust-ini",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_json5",
|
||||
@@ -6791,7 +6942,18 @@ dependencies = [
|
||||
"libloading 0.8.9",
|
||||
"once_cell",
|
||||
"rustix 1.1.4",
|
||||
"x11rb-protocol",
|
||||
"x11rb-protocol 0.13.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11rb"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a8885a854a8bfdf87a301e53e41b17c5f8f33639903131338b997b1eb614f44"
|
||||
dependencies = [
|
||||
"gethostname",
|
||||
"rustix 1.1.4",
|
||||
"x11rb-protocol 0.14.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6800,6 +6962,12 @@ version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
||||
|
||||
[[package]]
|
||||
name = "x11rb-protocol"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acf4d1bc32aa46eec18caa634ec3cf4c05bfa151f12b93b510b15190f69a1ca8"
|
||||
|
||||
[[package]]
|
||||
name = "xcb"
|
||||
version = "1.7.0"
|
||||
@@ -6842,6 +7010,17 @@ dependencies = [
|
||||
"xkeysym",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xkbcommon"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7a974f48060a14e95705c01f24ad9c3345022f4d97441b8a36beb7ed5c4a02d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"memmap2",
|
||||
"xkeysym",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xkbcommon-dl"
|
||||
version = "0.4.2"
|
||||
|
||||
+2
-1
@@ -23,6 +23,7 @@ dbus = { version = "0.9.12" }
|
||||
glam = { version = "0.30.9", features = ["mint", "serde"] }
|
||||
idmap = "0.2.2"
|
||||
idmap-derive = "0.2.22"
|
||||
image = { version = "0.25.10", default-features = false, features = ["png"] }
|
||||
interprocess = { version = "2.2.3" }
|
||||
libc = "0.2.178"
|
||||
log = "0.4.29"
|
||||
@@ -32,6 +33,7 @@ rust-embed = "8.9.0"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1.0.145"
|
||||
serde_json5 = "0.2.1"
|
||||
shell-words = "1.1.1"
|
||||
slotmap = "1.1.1"
|
||||
smallvec = "1.15.2"
|
||||
smol = "2.0.2"
|
||||
@@ -44,7 +46,6 @@ wayland-protocols-misc = { version = "0.3.12" }
|
||||
winit = "0.30.12"
|
||||
xdg = "3.0.0"
|
||||
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
debug = true
|
||||
|
||||
@@ -19,6 +19,7 @@ log.workspace = true
|
||||
rust-embed.workspace = true
|
||||
serde = { workspace = true, features = ["rc"] }
|
||||
serde_json.workspace = true
|
||||
shell-words.workspace = true
|
||||
smol = { workspace = true }
|
||||
strum.workspace = true
|
||||
uuid.workspace = true
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 24 24"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs id="defs1" />
|
||||
<path fill="currentColor" d="m 10,12 c 0,-1.104569 0.895431,-2 2,-2 1.104569,0 2,0.895431 2,2 0,1.104569 -0.895431,2 -2,2 -1.104569,0 -2,-0.895431 -2,-2" id="path1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 422 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="currentColor" d="M17 10q-.825 0-1.412-.575T15 8V4q0-.85.588-1.425T17 2q.85 0 1.425.575T19 4v4q0 .85-.575 1.425T17 10M5 22q-.825 0-1.412-.587T3 20V4q0-.825.588-1.412T5 2h8v2H5v16h11v-2h2v2q0 .825-.587 1.413T16 22zm2-4v-2h7v2zm0-3v-2h5v2zm11 1h-2v-2.6q-1.925-.35-3.213-1.862T11.5 8h2q0 1.45 1.025 2.475T17 11.5q1.475 0 2.488-1.025T20.5 8h2q0 2.025-1.275 3.538T18 13.4z"/></svg>
|
||||
|
After Width: | Height: | Size: 585 B |
@@ -6,6 +6,16 @@
|
||||
<var key="side_button_size" value="48" />
|
||||
</vars>
|
||||
|
||||
<blueprint name="context_menu_power_options">
|
||||
<context_menu>
|
||||
<cell translation="APP_SETTINGS.RESTART_WAYVR" action="restart" sprite_src_builtin="@/dashboard/refresh.svg"/>
|
||||
<cell translation="APP_SETTINGS.CLOSE_WAYVR" action="close" sprite_src_builtin="@/dashboard/close.svg"/>
|
||||
<separator/>
|
||||
<cell translation="POWER_OPTIONS.SUSPEND" action="suspend" sprite_src_builtin="@/dashboard/sleep.svg"/>
|
||||
<cell translation="POWER_OPTIONS.SHUTDOWN" action="shutdown" sprite_src_builtin="@/dashboard/power.svg"/>
|
||||
</context_menu>
|
||||
</blueprint>
|
||||
|
||||
<template name="SideButton">
|
||||
<Button
|
||||
id="${id}"
|
||||
@@ -136,6 +146,9 @@
|
||||
|
||||
<!-- Left bottom side -->
|
||||
<div margin_left="8">
|
||||
<Button id="btn_power" color="background(transparent)" border_color="background(transparent)" tooltip="POWER_OPTIONS.TITLE" tooltip_side="top">
|
||||
<sprite src_builtin="@/dashboard/power.svg" width="24" height="24" margin="8" />
|
||||
</Button>
|
||||
<Button id="btn_audio" color="background(transparent)" border_color="background(transparent)" tooltip="AUDIO.VOLUME" tooltip_side="top">
|
||||
<sprite src_builtin="@/dashboard/volume.svg" width="24" height="24" margin="8" />
|
||||
</Button>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<layout version="2">
|
||||
<template name="DialogBoxButton">
|
||||
<Button id="btn" translation="CLOSE_WINDOW" align_self="start" sprite_src_builtin="${icon}"/>
|
||||
<Button id="btn" translation="CLOSE_WINDOW" align_self="start" sprite_src_builtin="${icon}"
|
||||
text_size="16"
|
||||
padding_left="16" padding_right="16"
|
||||
padding_top="8" padding_bottom="8"/>
|
||||
</template>
|
||||
|
||||
<elements>
|
||||
|
||||
@@ -197,8 +197,6 @@
|
||||
"SAVE_PLAYSPACE_CENTER_HELP": "Nutze aktuellen Playspace für die Playspace-Reset-Belegung.",
|
||||
"RESET_PLAYSPACE_CENTER": "Playspace-Zentrum zurücksetzen",
|
||||
"RESET_PLAYSPACE_CENTER_HELP": "Vorheriges Playspace-Zentrum zurücksetzen.",
|
||||
"RESTART_SOFTWARE": "Software neu starten",
|
||||
"RESTART_SOFTWARE_HELP": "Einstellungen übernehmen, die einen Neustart erfordern",
|
||||
"ROUND_MULTIPLIER": "Abrundung der UI-Kanten",
|
||||
"SCREEN_RENDER_DOWN": "Bildschirm mit niedrigerer Auflösung rendern",
|
||||
"SCREEN_RENDER_DOWN_HELP": "Hilft gegen Treppeneffekte bei hochauflösenden Bildschirmen",
|
||||
@@ -263,7 +261,11 @@
|
||||
"TITLE": "Diagnose",
|
||||
"ENABLE_REBAR": "Aktiviere resizable bar in den BIOS-Einstellungen für optimale VR-Performance"
|
||||
},
|
||||
"TUTORIAL": "Tutorial"
|
||||
"TUTORIAL": "Tutorial",
|
||||
"RESTART_WAYVR": "WayVR neu starten",
|
||||
"CLOSE_WAYVR": "WayVR schließen",
|
||||
"SPEECH_TO_TEXT": "Sprache zu Text",
|
||||
"WHISPER_GPU_SELECT": "GPU auswählen"
|
||||
},
|
||||
"APPLICATION_LAUNCHER": "Programmstarter",
|
||||
"APPLICATION_STARTED": "Anwendung gestartet",
|
||||
@@ -368,5 +370,15 @@
|
||||
"REMOVE_FROM_FAVOURITES": "Aus angehefteten Anwendungen entfernen",
|
||||
"EMPTY": "Leer",
|
||||
"INSTALLED": "Installiert",
|
||||
"NOT_INSTALLED": "Nicht installiert"
|
||||
"NOT_INSTALLED": "Nicht installiert",
|
||||
"POWER_OPTIONS": {
|
||||
"TITLE": "Energieoptionen",
|
||||
"SHUTDOWN": "Herunterfahren",
|
||||
"SUSPEND": "Ruhezustand",
|
||||
"SHUTDOWN_ARE_YOU_SURE": "Bist du sicher, dass du den Computer ausschalten willst?",
|
||||
"NOT_SUPPORTED": "Energieoptionen erfordern systemd oder elogind.",
|
||||
"SUSPEND_ARE_YOU_SURE": "Bist du sicher, dass du den Computer in den Ruhezustand versetzen willst?"
|
||||
},
|
||||
"YES": "Ja",
|
||||
"NO": "Nein"
|
||||
}
|
||||
@@ -208,8 +208,8 @@ APP_SETTINGS.SAVE_PLAYSPACE_CENTER,Save playspace center,"Button in Settings > S
|
||||
APP_SETTINGS.SAVE_PLAYSPACE_CENTER_HELP,Use current playspace for reset playspace binding.,"Tooltip for the Save playspace center button. ""Playspace"" was previously translated as ""${DEFINITIONS.PLAYSPACE}""."
|
||||
APP_SETTINGS.RESET_PLAYSPACE_CENTER,Reset playspace center,"Button in Settings > Space Drag tab. Clears the previously saved playspace center. ""Playspace"" was previously translated as ""${DEFINITIONS.PLAYSPACE}""."
|
||||
APP_SETTINGS.RESET_PLAYSPACE_CENTER_HELP,Clear previously set playspace center.,"Tooltip for the Reset playspace center button. ""Playspace"" was previously translated as ""${DEFINITIONS.PLAYSPACE}""."
|
||||
APP_SETTINGS.RESTART_SOFTWARE,Restart software,Button in Settings > Troubleshooting tab. Restarts the WayVR software to apply settings that require a restart.
|
||||
APP_SETTINGS.RESTART_SOFTWARE_HELP,Apply settings that require a restart,Tooltip for the Restart software button.
|
||||
APP_SETTINGS.RESTART_WAYVR,Restart WayVR,Button in Settings which restarts WayVR
|
||||
APP_SETTINGS.CLOSE_WAYVR,Close WayVR,Button in Settings which closes WayVR
|
||||
APP_SETTINGS.ROUND_MULTIPLIER,UI Edge roundness,Slider in Settings > Look & Feel tab. Controls how rounded the corners of UI elements appear (range 0.1-5.0).
|
||||
APP_SETTINGS.SCREEN_RENDER_DOWN,Render screen at lower resolution,Checkbox in Settings > Miscellaneous tab. Renders the captured desktop screen at a lower resolution to improve performance or reduce aliasing.
|
||||
APP_SETTINGS.SCREEN_RENDER_DOWN_HELP,Helps with aliasing on high-res screens,Tooltip for the lower-resolution screen render setting.
|
||||
@@ -259,6 +259,8 @@ APP_SETTINGS.WHISPER_MODEL,Speech-to-text model,Dropdown label in Settings > Fea
|
||||
APP_SETTINGS.WHISPER_MODEL_HELP,"Whisper model to use.
|
||||
Larger models produce more accurate
|
||||
results, but consume more VRAM.",Tooltip for the speech-to-text model dropdown.
|
||||
APP_SETTINGS.SPEECH_TO_TEXT,"Speech to text","Category header in Settings"
|
||||
APP_SETTINGS.WHISPER_GPU_SELECT,"Select GPU","A list of GPUs the user can select from"
|
||||
APP_SETTINGS.TUTORIAL,Tutorial,Tooltip for tutorial button in the main screen.
|
||||
APP_SETTINGS.GRID_OPACITY,Floor grid opacity,Slider in Settings > Skybox tab. Controls the opacity of the floor grid when skybox is enabled.
|
||||
APP_SETTINGS.GRID_OPACITY_HELP,Opacity of the floor grid when the skymap is enabled,"Tooltip for the floor grid opacity slider. ""Skymap"" was previously translated as ""${DEFINITIONS.SKYMAP}""."
|
||||
@@ -336,6 +338,12 @@ MODIFICATION_DATE,Modification date,Column label in the remote skymap downloader
|
||||
MONADO_RUNTIME,Monado runtime,Tab label in the dashboard sidebar for the Monado runtime tab.
|
||||
LOADING,Loading...,Label in the loading spinner template used across the UI.
|
||||
POPUP_ADD_DISPLAY.RESOLUTION,Resolution,Label for the resolution field in the add display popup.
|
||||
POWER_OPTIONS.TITLE,"Power options","A button which allows the user to perform a specific power action"
|
||||
POWER_OPTIONS.SHUTDOWN,"Shut down","One of the power options: shuts down the computer"
|
||||
POWER_OPTIONS.SUSPEND,"Suspend","One of the power options: suspends the computer"
|
||||
POWER_OPTIONS.NOT_SUPPORTED,"Power options require systemd or elogind.","Notification to the user that power options (shutdown and suspend) are not available. 'systemd' and 'elogind' are package names and should not be translated."
|
||||
POWER_OPTIONS.SHUTDOWN_ARE_YOU_SURE,"Are you sure you want to turn off your computer?","Question dialog box before shutting down the computer"
|
||||
POWER_OPTIONS.SUSPEND_ARE_YOU_SURE,"Are you sure you want to put your computer to sleep?","Question dialog box before putting the computer in sleep mode"
|
||||
PROCESS.FORCE_KILL,Force-kill,Tooltip on the kill button in the running games list.
|
||||
PROCESS.STOP,Stop,Tooltip on the stop button in the running games list.
|
||||
PROCESS_LIST,Process list,Tab name and group box title in the Monado runtime process list section.
|
||||
@@ -357,3 +365,5 @@ REMOVE_FROM_FAVOURITES,Remove from pinned apps,"Label on the unpin section in th
|
||||
EMPTY,Empty,Label shown in the pinned apps section when there are no pinned apps.
|
||||
INSTALLED,"Installed","Installation flag (installed/not installed)"
|
||||
NOT_INSTALLED,"Not installed","Installation flag (installed/not installed)"
|
||||
YES,"Yes","Just a simple 'Yes' message, button in the dialog box"
|
||||
NO,"No","Just a simple 'No' message, button in the dialog box"
|
||||
|
@@ -211,8 +211,8 @@
|
||||
"SAVE_PLAYSPACE_CENTER_HELP": "Use current playspace for reset playspace binding.",
|
||||
"RESET_PLAYSPACE_CENTER": "Reset playspace center",
|
||||
"RESET_PLAYSPACE_CENTER_HELP": "Clear previously set playspace center.",
|
||||
"RESTART_SOFTWARE": "Restart software",
|
||||
"RESTART_SOFTWARE_HELP": "Apply settings that require a restart",
|
||||
"RESTART_WAYVR": "Restart WayVR",
|
||||
"CLOSE_WAYVR": "Close WayVR",
|
||||
"ROUND_MULTIPLIER": "UI Edge roundness",
|
||||
"SCREEN_RENDER_DOWN": "Render screen at lower resolution",
|
||||
"SCREEN_RENDER_DOWN_HELP": "Helps with aliasing on high-res screens",
|
||||
@@ -258,6 +258,8 @@
|
||||
},
|
||||
"WHISPER_MODEL": "Speech-to-text model",
|
||||
"WHISPER_MODEL_HELP": "Whisper model to use.\nLarger models produce more accurate\nresults, but consume more VRAM.",
|
||||
"SPEECH_TO_TEXT": "Speech to text",
|
||||
"WHISPER_GPU_SELECT": "Select GPU",
|
||||
"TUTORIAL": "Tutorial",
|
||||
"GRID_OPACITY": "Floor grid opacity",
|
||||
"GRID_OPACITY_HELP": "Opacity of the floor grid when the skymap is enabled",
|
||||
@@ -346,6 +348,14 @@
|
||||
"POPUP_ADD_DISPLAY": {
|
||||
"RESOLUTION": "Resolution"
|
||||
},
|
||||
"POWER_OPTIONS": {
|
||||
"TITLE": "Power options",
|
||||
"SHUTDOWN": "Shut down",
|
||||
"SUSPEND": "Suspend",
|
||||
"NOT_SUPPORTED": "Power options require systemd or elogind.",
|
||||
"SHUTDOWN_ARE_YOU_SURE": "Are you sure you want to turn off your computer?",
|
||||
"SUSPEND_ARE_YOU_SURE": "Are you sure you want to put your computer to sleep?"
|
||||
},
|
||||
"PROCESS": {
|
||||
"FORCE_KILL": "Force-kill",
|
||||
"STOP": "Stop"
|
||||
@@ -368,5 +378,7 @@
|
||||
"REMOVE_FROM_FAVOURITES": "Remove from pinned apps",
|
||||
"EMPTY": "Empty",
|
||||
"INSTALLED": "Installed",
|
||||
"NOT_INSTALLED": "Not installed"
|
||||
"NOT_INSTALLED": "Not installed",
|
||||
"YES": "Yes",
|
||||
"NO": "No"
|
||||
}
|
||||
@@ -197,8 +197,6 @@
|
||||
"SAVE_PLAYSPACE_CENTER_HELP": "Usa el espacio de juego actual para el comando de reiniciar espacio de juego.",
|
||||
"RESET_PLAYSPACE_CENTER": "Reiniciar centro del espacio de juego",
|
||||
"RESET_PLAYSPACE_CENTER_HELP": "Restablece el centro del espacio de juego definido previamente.",
|
||||
"RESTART_SOFTWARE": "Reiniciar software",
|
||||
"RESTART_SOFTWARE_HELP": "Aplicar ajustes que requieren reiniciar",
|
||||
"ROUND_MULTIPLIER": "Redondeo de bordes de la UI",
|
||||
"SCREEN_RENDER_DOWN": "Renderizar pantalla a resolución menor",
|
||||
"SCREEN_RENDER_DOWN_HELP": "Ayuda con el aliasing en pantallas de alta resolución",
|
||||
@@ -263,7 +261,11 @@
|
||||
"TITLE": "Diagnósticos",
|
||||
"ENABLE_REBAR": "Activa [resizable bar] en la configuración de la BIOS para un rendimiento óptimo de VR"
|
||||
},
|
||||
"TUTORIAL": "Tutorial"
|
||||
"TUTORIAL": "Tutorial",
|
||||
"RESTART_WAYVR": "Reiniciar WayVR",
|
||||
"CLOSE_WAYVR": "Cerrar WayVR",
|
||||
"SPEECH_TO_TEXT": "Dictado por voz",
|
||||
"WHISPER_GPU_SELECT": "Seleccionar GPU"
|
||||
},
|
||||
"APPLICATION_LAUNCHER": "Lanzador de aplicaciones",
|
||||
"APPLICATION_STARTED": "Aplicación iniciada",
|
||||
@@ -368,5 +370,15 @@
|
||||
"REMOVE_FROM_FAVOURITES": "Quitar de aplicaciones fijadas",
|
||||
"EMPTY": "Vacío",
|
||||
"INSTALLED": "Instalado",
|
||||
"NOT_INSTALLED": "No instalado"
|
||||
"NOT_INSTALLED": "No instalado",
|
||||
"POWER_OPTIONS": {
|
||||
"TITLE": "Opciones de energía",
|
||||
"SHUTDOWN": "Apagar",
|
||||
"SUSPEND": "Suspender",
|
||||
"SHUTDOWN_ARE_YOU_SURE": "¿Seguro que quieres apagar el ordenador?",
|
||||
"NOT_SUPPORTED": "Las opciones de energía requieren systemd o elogind.",
|
||||
"SUSPEND_ARE_YOU_SURE": "¿Estás seguro de que quieres suspender el equipo?"
|
||||
},
|
||||
"YES": "Sí",
|
||||
"NO": "No"
|
||||
}
|
||||
@@ -197,8 +197,6 @@
|
||||
"SAVE_PLAYSPACE_CENTER_HELP": "Usa il playspace corrente per il comando di reset del playspace.",
|
||||
"RESET_PLAYSPACE_CENTER": "Resetta centro playspace",
|
||||
"RESET_PLAYSPACE_CENTER_HELP": "Rimuovi il centro del playspace impostato precedentemente.",
|
||||
"RESTART_SOFTWARE": "Riavvia software",
|
||||
"RESTART_SOFTWARE_HELP": "Applica le impostazioni che richiedono il riavvio",
|
||||
"ROUND_MULTIPLIER": "Arrotondamento bordi UI",
|
||||
"SCREEN_RENDER_DOWN": "Rendering schermo a risoluzione inferiore",
|
||||
"SCREEN_RENDER_DOWN_HELP": "Riduce l'effetto aliasing sugli schermi ad alta risoluzione",
|
||||
@@ -263,7 +261,11 @@
|
||||
"TITLE": "Diagnostica",
|
||||
"ENABLE_REBAR": "Abilita Resizable BAR nelle impostazioni del BIOS per prestazioni VR ottimali"
|
||||
},
|
||||
"TUTORIAL": "Tutorial"
|
||||
"TUTORIAL": "Tutorial",
|
||||
"RESTART_WAYVR": "Riavvia WayVR",
|
||||
"CLOSE_WAYVR": "Chiudi WayVR",
|
||||
"SPEECH_TO_TEXT": "Speech-to-text",
|
||||
"WHISPER_GPU_SELECT": "Seleziona GPU"
|
||||
},
|
||||
"APPLICATION_LAUNCHER": "Avvio applicazioni",
|
||||
"APPLICATION_STARTED": "Applicazione avviata",
|
||||
@@ -368,5 +370,15 @@
|
||||
"REMOVE_FROM_FAVOURITES": "Rimuovi dalle app fissate",
|
||||
"EMPTY": "Vuoto",
|
||||
"INSTALLED": "Installato",
|
||||
"NOT_INSTALLED": "Non installato"
|
||||
"NOT_INSTALLED": "Non installato",
|
||||
"POWER_OPTIONS": {
|
||||
"TITLE": "Opzioni di alimentazione",
|
||||
"SHUTDOWN": "Spegni",
|
||||
"SUSPEND": "Sospendi",
|
||||
"SHUTDOWN_ARE_YOU_SURE": "Sei sicuro di voler spegnere il computer?",
|
||||
"NOT_SUPPORTED": "Le opzioni di alimentazione richiedono systemd o elogind.",
|
||||
"SUSPEND_ARE_YOU_SURE": "Sei sicuro di voler mettere il computer in sospensione?"
|
||||
},
|
||||
"YES": "Sì",
|
||||
"NO": "No"
|
||||
}
|
||||
@@ -197,8 +197,6 @@
|
||||
"SAVE_PLAYSPACE_CENTER_HELP": "現在のプレイスペースを、プレイスペースリセットのバインディングに使用する。",
|
||||
"RESET_PLAYSPACE_CENTER": "プレイスペースの中心をリセット",
|
||||
"RESET_PLAYSPACE_CENTER_HELP": "設定済みのプレイスペースの中心点をリセットする。",
|
||||
"RESTART_SOFTWARE": "ソフトウェアを再起動",
|
||||
"RESTART_SOFTWARE_HELP": "再起動が必要な設定を適用する",
|
||||
"ROUND_MULTIPLIER": "UIの角の丸み",
|
||||
"SCREEN_RENDER_DOWN": "低解像度で画面をレンダリング",
|
||||
"SCREEN_RENDER_DOWN_HELP": "高解像度ディスプレイでのジャギーを軽減する",
|
||||
@@ -263,7 +261,11 @@
|
||||
"TITLE": "診断",
|
||||
"ENABLE_REBAR": "VRパフォーマンスを最適化するため、BIOS設定でresizable BARを有効にして下さい"
|
||||
},
|
||||
"TUTORIAL": "チュートリアル"
|
||||
"TUTORIAL": "チュートリアル",
|
||||
"RESTART_WAYVR": "WayVRを再起動",
|
||||
"CLOSE_WAYVR": "WayVRを閉じる",
|
||||
"SPEECH_TO_TEXT": "音声入力",
|
||||
"WHISPER_GPU_SELECT": "GPUを選択"
|
||||
},
|
||||
"APPLICATION_LAUNCHER": "アプリランチャー",
|
||||
"APPLICATION_STARTED": "アプリが起動しました",
|
||||
@@ -368,5 +370,15 @@
|
||||
"REMOVE_FROM_FAVOURITES": "固定済みアプリから外す",
|
||||
"EMPTY": "なし",
|
||||
"INSTALLED": "インストール済み",
|
||||
"NOT_INSTALLED": "未インストール"
|
||||
"NOT_INSTALLED": "未インストール",
|
||||
"POWER_OPTIONS": {
|
||||
"TITLE": "電源オプション",
|
||||
"SHUTDOWN": "シャットダウン",
|
||||
"SUSPEND": "サスペンド",
|
||||
"SHUTDOWN_ARE_YOU_SURE": "コンピューターをシャットダウンしてもよろしいですか?",
|
||||
"NOT_SUPPORTED": "電源オプションには systemd または elogind が必要です。",
|
||||
"SUSPEND_ARE_YOU_SURE": "コンピューターをスリープ状態にしますか?"
|
||||
},
|
||||
"YES": "はい",
|
||||
"NO": "いいえ"
|
||||
}
|
||||
@@ -197,8 +197,6 @@
|
||||
"SAVE_PLAYSPACE_CENTER_HELP": "Użyj aktualnej przestrzeni gry dla przypisania resetowania przestrzeni.",
|
||||
"RESET_PLAYSPACE_CENTER": "Zresetuj centrum przestrzeni gry",
|
||||
"RESET_PLAYSPACE_CENTER_HELP": "Resetuj aktualny środek przestrzeni gry.",
|
||||
"RESTART_SOFTWARE": "Uruchom ponownie program",
|
||||
"RESTART_SOFTWARE_HELP": "Zastosuj ustawienia wymagające restartu",
|
||||
"ROUND_MULTIPLIER": "Zaokrąglenie krawędzi UI",
|
||||
"SCREEN_RENDER_DOWN": "Renderuj ekran w niższej rozdzielczości",
|
||||
"SCREEN_RENDER_DOWN_HELP": "Pomaga zredukować aliasing na ekranach o wysokiej rozdzielczości",
|
||||
@@ -263,7 +261,11 @@
|
||||
"TITLE": "Diagnostyka",
|
||||
"ENABLE_REBAR": "Włącz resizable BAR w ustawieniach BIOS, aby uzyskać optymalną wydajność VR"
|
||||
},
|
||||
"TUTORIAL": "Samouczek"
|
||||
"TUTORIAL": "Samouczek",
|
||||
"RESTART_WAYVR": "Zrestartuj WayVR",
|
||||
"CLOSE_WAYVR": "Zamknij WayVR",
|
||||
"SPEECH_TO_TEXT": "Zamiana mowy na tekst",
|
||||
"WHISPER_GPU_SELECT": "Wybierz GPU"
|
||||
},
|
||||
"APPLICATION_LAUNCHER": "Launcher aplikacji",
|
||||
"APPLICATION_STARTED": "Uruchomiono aplikację",
|
||||
@@ -368,5 +370,15 @@
|
||||
"REMOVE_FROM_FAVOURITES": "Usuń z przypiętych aplikacji",
|
||||
"EMPTY": "Puste",
|
||||
"INSTALLED": "Zainstalowano",
|
||||
"NOT_INSTALLED": "Nie zainstalowano"
|
||||
"NOT_INSTALLED": "Nie zainstalowano",
|
||||
"POWER_OPTIONS": {
|
||||
"TITLE": "Opcje zasilania",
|
||||
"SHUTDOWN": "Wyłącz",
|
||||
"SUSPEND": "Uśpij",
|
||||
"SHUTDOWN_ARE_YOU_SURE": "Czy na pewno chcesz wyłączyć komputer?",
|
||||
"NOT_SUPPORTED": "Opcje zasilania wymagają systemd lub elogind.",
|
||||
"SUSPEND_ARE_YOU_SURE": "Czy na pewno chcesz uśpić komputer?"
|
||||
},
|
||||
"YES": "Tak",
|
||||
"NO": "Nie"
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
{
|
||||
"DEFINITIONS": {
|
||||
"ANCHORED": "Förankrad",
|
||||
"FLOATING": "Flytande",
|
||||
"STATIC": "Stationär",
|
||||
"SET": "Set",
|
||||
"WATCH": "Klocka",
|
||||
"DASHBOARD": "Kontrollpanel",
|
||||
"EDIT_MODE": "Redigeringsläge",
|
||||
"SETTINGS": "Inställningar",
|
||||
"KEYBOARD": "Tangentbord",
|
||||
"TASKBAR": "Uppgiftsfält",
|
||||
"FEATURES": "Funktioner",
|
||||
"OVERLAY": "Overlay",
|
||||
"SKYMAP": "Skymap",
|
||||
"PLAYSPACE": "Playspace",
|
||||
"D_PAD": "D-pad"
|
||||
},
|
||||
"ACTIONS": {
|
||||
"RECENTER_PLAYSPACE": "Centrera om playspace"
|
||||
},
|
||||
"APP_LAUNCHER": {
|
||||
"ASPECT": {
|
||||
"SEMI_TALL": "Halvhög",
|
||||
"SEMI_WIDE": "Halvbredd",
|
||||
"SQUARE": "Kvadratisk",
|
||||
"TALL": "Hög",
|
||||
"WIDE": "Bredbild"
|
||||
},
|
||||
"ASPECT_TITLE": "Bildförhållande",
|
||||
"AUTOSTART": "Kör automatiskt vid start",
|
||||
"LAUNCH": "Starta",
|
||||
"MODE": {
|
||||
"CAGE": "Kompatibilitetsläge (Cage)",
|
||||
"NATIVE": "Nativt läge"
|
||||
},
|
||||
"RES_TITLE": "Upplösning"
|
||||
},
|
||||
"APP_SETTINGS": {
|
||||
"ALLOW_SLIDING": "Stick-interaktion vid grepp",
|
||||
"ANIMATION_SPEED": "Hastighet för UI-animationer",
|
||||
"AUTOSTART_APPS": "Appar som ska köras vid start",
|
||||
"BLOCK_GAME_INPUT": "Blockera spelindata",
|
||||
"BLOCK_GAME_INPUT_HELP": "Blockerar all input när en overlay hovras över",
|
||||
"BLOCK_GAME_INPUT_IGNORE_WATCH": "Ignorera klockan vid blockering av input",
|
||||
"BLOCK_GAME_INPUT_IGNORE_WATCH_HELP": "Blockera inte inmatning när klockan är markerad",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION": "Blockera poser vid interaktion med tangentbord",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Blockerar spelets mottagning av poser när tangentbordet hovras över och 'Blockera spel-input' är aktiverat",
|
||||
"BINDINGS": {
|
||||
"COMP": {
|
||||
"CLICK": "Klick",
|
||||
"FORCE": "Tryckkraft",
|
||||
"TOUCH": "Beröring",
|
||||
"VALUE": "Värde",
|
||||
"PROXIMITY": "Närhet",
|
||||
"X_AXIS": "X-axel",
|
||||
"Y_AXIS": "Y-axel"
|
||||
},
|
||||
"TYPE": {
|
||||
"BUMPER": "Bumper",
|
||||
"DPAD_UP": "D-pad upp",
|
||||
"DPAD_DOWN": "D-pad ned",
|
||||
"DPAD_LEFT": "D-pad vänster",
|
||||
"DPAD_RIGHT": "D-pad höger",
|
||||
"JOYSTICK": "Joystick",
|
||||
"MENU": "Meny",
|
||||
"SHOULDER": "Axel",
|
||||
"SQUEEZE": "Grepp",
|
||||
"SYSTEM": "System",
|
||||
"THUMBREST": "Tumstöd",
|
||||
"THUMBSTICK": "Thumbstick",
|
||||
"TRACKPAD": "Trackpad",
|
||||
"TRIGGER": "Trigger",
|
||||
"VIEW": "Visa"
|
||||
},
|
||||
"CLICK": {
|
||||
"ANY": "Vilken som helst",
|
||||
"DOUBLE": "Dubbelklicka",
|
||||
"TRIPLE": "Trippelklick",
|
||||
"TYPE": "Antal tryck"
|
||||
},
|
||||
"ACTION": {
|
||||
"CLICK": "Klicka*",
|
||||
"GRAB": "Greppa*",
|
||||
"ALT_CLICK": "Anpassat shell-kommando (ställ in alt_click i config)",
|
||||
"SHOW_HIDE": "Visa, dölj, centrera om*",
|
||||
"TOGGLE_DASHBOARD": "Växla instrumentpanel",
|
||||
"SPACE_DRAG": "Dra playspace",
|
||||
"SPACE_ROTATE": "Rotera playspace",
|
||||
"SPACE_RESET": "Återställ Playspace",
|
||||
"CLICK_MODIFIER_RIGHT": "Högerklick-modifierare",
|
||||
"CLICK_MODIFIER_MIDDLE": "Modifierare för mittklick",
|
||||
"MOVE_MOUSE": "Flytta mus (om avstängd som standard)",
|
||||
"SCROLL": "Scrolla*"
|
||||
},
|
||||
"LEFT": "Vänster",
|
||||
"RIGHT": "Höger",
|
||||
"CHORD": "Kombination",
|
||||
"CHORD_ADD_INPUT": "Lägg till inmatning för kombination",
|
||||
"CHORD_INPUT_HAND": "Inmatningshand",
|
||||
"COMPONENT": "Aktiveringstyp",
|
||||
"SUBPATH": "Aktiverande kontroll",
|
||||
"THRESHOLD": "Aktivera vid övre värdet\nAvaktivera vid nedre värdet"
|
||||
},
|
||||
"BROWSE_ONLINE_CATALOG": "Bläddra i onlinekatalogen...",
|
||||
"BROWSE_SKYMAPS": "Bläddra bland skymaps",
|
||||
"CAPTURE_METHOD": "Metod för skärmavbildning",
|
||||
"CAPTURE_METHOD_HELP": "Prova att ändra detta om du\nupplever svarta eller glitchiga skärmar",
|
||||
"COLOR_KEYING": "Färgnyckelning",
|
||||
"COLOR_PALETTE": "Färgpalett",
|
||||
"COLOR_PALETTE_HELP": "Klicka för att bläddra bland färgpaletter.\nSe webbplatsen för att skapa egna!",
|
||||
"COLOR_PALETTES": "Färgpaletter",
|
||||
"COLOR_PALETTE_CURRENT": "Nuvarande aktivt tema.",
|
||||
"COLOR_PALETTE_CUSTOM_URL": "Lägg till ett eget anpassat tema!",
|
||||
"COLOR_PALETTE_CUSTOM_URL2": "Klicka för att öppna webbmanualen.",
|
||||
"COLOR_PALETTE_ACTIVATE": "Klicka för att använda detta tema.",
|
||||
"CLEAR_PIPEWIRE_TOKENS": "Rensa PipeWire-tokens",
|
||||
"CLEAR_PIPEWIRE_TOKENS_HELP": "Fråga om skärmval vid nästa start",
|
||||
"CLEAR_SAVED_STATE": "Återställ sparade inställningar",
|
||||
"CLEAR_SAVED_STATE_HELP": "Återställ sets och overlay-positioner",
|
||||
"CLICK_FREEZE_TIME_MS": "Klickfrysningstid (ms)",
|
||||
"CLICK_FREEZE_TIME_MS_HELP": "Hjälper till med precision vid dubbelklick",
|
||||
"CLOCK_12H": "12-timmarsklocka",
|
||||
"CONTROLS": "Kontroller",
|
||||
"DELETE_ALL_CONFIGS": "Rensa konfiguration",
|
||||
"DELETE_ALL_CONFIGS_HELP": "Ta bort alla konfigurationsfiler från conf.d",
|
||||
"DOUBLE_CURSOR_FIX": "Fix för dubbla markörer",
|
||||
"DOUBLE_CURSOR_FIX_HELP": "Aktivera om du ser två markörer",
|
||||
"DEFAULT_OVERLAY_SCALE": "Standardstorlek för overlay",
|
||||
"DEFAULT_OVERLAY_SCALE_HELP": "Skapa nya overlays i den här skalan.",
|
||||
"DEFAULT_CURVATURE": "Standardkurvatur för overlay",
|
||||
"DEFAULT_OPACITY": "Standardopacitet för overlay",
|
||||
"DEFAULT_POSITIONING": "Standardposition för overlay",
|
||||
"OVERLAY_DEFAULTS_HELP": "Gäller endast nya overlays",
|
||||
"ENABLED": "Aktiverad",
|
||||
"FEATURES": "Funktioner",
|
||||
"FOCUS_FOLLOWS_MOUSE_MODE": "Musrörelse vid trigger-tryck",
|
||||
"HANDSFREE_POINTER": "Handsfree-läge",
|
||||
"HANDSFREE_POINTER_HELP": "Inmatning som används när rörelsekontroller saknas.\nVänster nyp är greppa, höger är klicka.",
|
||||
"HANDSFREE_ALT_TAB": "Sikte för app-växlare",
|
||||
"HANDSFREE_ALT_TAB_HELP": "Håll in Super+X, sikta på önskat fönster och släpp sedan för att byta fokus.",
|
||||
"DIAGNOSTICS": {
|
||||
"CPU_ACCESSIBLE_VRAM": "CPU-tillgängligt VRAM: {NUM} MiB",
|
||||
"TITLE": "Diagnostik",
|
||||
"ENABLE_REBAR": "Aktivera [resizable bar] i BIOS-inställningarna för optimal VR-prestanda"
|
||||
},
|
||||
"DIAGNOSTICS_REASON": {
|
||||
"PACTL": "Krävs för kontroll av högtalare/mikrofon och byte av ljudkort",
|
||||
"CAGE": "Rekommenderas vid inkompatibilitet med vissa X11-applikationer (kompatibilitetsläge)",
|
||||
"XWAYLAND_SATELLITE": "Rekommenderas om du vill köra X11 (Xwayland)-applikationer i nativt läge",
|
||||
"XDG_OPEN": "Krävs för att öppna URL:er eller Steam-spel"
|
||||
},
|
||||
"HIDE_GRAB_HELP": "Dölj grepphjälp",
|
||||
"HIDE_USERNAME": "Dölj användarnamn",
|
||||
"INPUT_EMULATION_METHOD": "Metod för inmatningssimulering",
|
||||
"INPUT_EMULATION_METHOD_HELP": "Prova att ändra detta om mus- eller\ntangentbordindata inte registreras.",
|
||||
"INPUT_CAPTURE_METHOD": "Metod för inmatningsfångst",
|
||||
"INPUT_CAPTURE_METHOD_HELP": "Möjliggör användning av fysisk kringutrustning i WayVR.\n Super+Z: Växla input mellan WayVR och skrivbordet\n Super+X: Växla mellan WayVR-appar",
|
||||
"INPUT_PROFILES": "Ändra styrinställningar",
|
||||
"INPUT_PROFILES_HELP": "OpenXR-kontrollbindningar",
|
||||
"INVERT_SCROLL_DIRECTION_X": "Invertera horisontell scrollriktning",
|
||||
"INVERT_SCROLL_DIRECTION_Y": "Invertera vertikal rullningsriktning",
|
||||
"LENS_SEPARATION_MM": "Linsavstånd (mm)",
|
||||
"KEYBOARD_MIDDLE_CLICK": "Tangentbordets mittklick",
|
||||
"KEYBOARD_MIDDLE_CLICK_HELP": "Modifier som används vid skrivning\nmed lila laser",
|
||||
"KEYBOARD_SOUND_ENABLED": "Tangentbordsljud",
|
||||
"LANGUAGE": "Språk",
|
||||
"LEFT_HANDED_MOUSE": "Vänstermus",
|
||||
"LEFT_HANDED_MOUSE_HELP": "Används om musknapparna är ombytta",
|
||||
"LONG_PRESS_DURATION": "Långtryckstid",
|
||||
"LOOK_AND_FEEL": "Utseende och känsla",
|
||||
"MISC": "Övrigt",
|
||||
"MIDDLE_GRABS_SINGLE": "Modifierare för mittklick greppar enskilda objekt",
|
||||
"MIDDLE_GRABS_SINGLE_HELP": "Att greppa med modifieraren för mittklick (lila laser)\ngreppar enskilda overlays istället för hela setet",
|
||||
"NOTIFICATIONS_ENABLED": "Aktivera aviseringar",
|
||||
"NOTIFICATIONS_SOUND_ENABLED": "Ljud för aviseringar",
|
||||
"NOT_SUPPORTED": "Ej stöds",
|
||||
"NO_SKYMAPS_FOUND": "Inga skymaps hittades",
|
||||
"OPAQUE_BACKGROUND": "Opak bakgrund",
|
||||
"OPTION": {
|
||||
"ANCHORED_HELP": "Följer centermarkören. Rekommenderas.",
|
||||
"FLOATING_HELP": "Flyttas oberoende, centreras om.\nLätt att tappa bort; rekommenderas ej.",
|
||||
"STATIC_HELP": "Håller sig på plats i förhållande till världen.",
|
||||
"AUTO": "Automatisk",
|
||||
"AUTO_HELP": "ScreenCopy GPU om det stöds,\nannars PipeWire GPU.",
|
||||
"EYE_ONLY": "Ögonlaser",
|
||||
"EYE_PINCH": "Öga + pinch",
|
||||
"HMD_ONLY": "HMD-laser",
|
||||
"HMD_PINCH": "HMD + pinch",
|
||||
"NONE": "Ingen",
|
||||
"NONE_INPUT_HELP": "Emulera inte mus och tangentbord.\nInteraktion med skärmar blir inte möjlig.",
|
||||
"NONE_INPUT_CAPTURE_HELP": "Fånga inte mus och tangentbord.\nDu kommer inte att kunna använda fysisk mus\neller tangentbord i appar som startas i WayVR.",
|
||||
"PIPEWIRE_HELP": "Snabb GPU-avbildning,\nstandard för alla skrivbord.",
|
||||
"PW_FALLBACK_HELP": "Långsam metod med hög CPU-belastning.\nProva om PipeWire GPU inte fungerar",
|
||||
"SCREENCOPY_GPU_HELP": "Snabb, inga popup-fönster för skärmdelning.\nFungerar på: Hyprland, Niri, River, Sway",
|
||||
"SCREENCOPY_HELP": "Långsam, inga popup-fönster för skärmdelning.\nFungerar på: Hyprland, Niri, River, Sway",
|
||||
"UINPUT": "Enhetsemulering",
|
||||
"UINPUT_HELP": "Använder modulen uinput för att emulera en enhet.\nFungerar generellt bra på de flesta skrivbord.",
|
||||
"EVDEV_HELP": "Ta kontroll över inmatningsenheter för exklusiv användning.\nAnvändaren måste tillhöra gruppen 'input'.",
|
||||
"WL_VIRTUAL": "Wayland virtuell inmatning",
|
||||
"WL_VIRTUAL_HELP": "Skickar händelser till Wayland-kompositorn via\nzwlr_virtual_pointer och zwp_virtual_keyboard.\nFungerar endast på vissa skrivbord.\nFungerar INTE på KDE, GNOME eller COSMIC."
|
||||
},
|
||||
"POINTER_ACCELERATION": "Musacceleration",
|
||||
"POINTER_ACCELERATION_HELP": "På: Adaptiv musacceleration\nAv: Konstant hastighet",
|
||||
"POINTER_LERP_FACTOR": "Utjämning av pekare",
|
||||
"POINTER_SPEED": "Mushastighet",
|
||||
"REQUIRES_RESTART": "Kräver omstart",
|
||||
"RESET_PLAYSPACE": "Återställ playspace",
|
||||
"RESET_PLAYSPACE_HELP": "Nollställ offset för playspace.",
|
||||
"SAVE_PLAYSPACE_CENTER": "Spara playspace-center",
|
||||
"SAVE_PLAYSPACE_CENTER_HELP": "Använd nuvarande playspace för bindningen \"återställ playspace\".",
|
||||
"RESET_PLAYSPACE_CENTER": "Återställ playspace-center",
|
||||
"RESET_PLAYSPACE_CENTER_HELP": "Rensa tidigare inställd mittpunkt för playspace.",
|
||||
"RESTART_WAYVR": "Starta om WayVR",
|
||||
"CLOSE_WAYVR": "Stäng WayVR",
|
||||
"ROUND_MULTIPLIER": "UI-kanternas rundning",
|
||||
"SCREEN_RENDER_DOWN": "Rendera skärm i lägre upplösning",
|
||||
"SCREEN_RENDER_DOWN_HELP": "Hjälper mot aliasing på skärmar med hög upplösning",
|
||||
"SCROLL_SPEED": "Scrollningshastighet",
|
||||
"SELECT_VARIANT": "Välj variant",
|
||||
"SNAP_ANGLE_DEG": "Snapvinkel för fönster (pitch & roll)",
|
||||
"SNAP_ANGLE_DEG_HELP": "Greppade fönster snäpps till denna vinkel på pitch- och rollaxeln.",
|
||||
"ENABLE_WATCH": "Aktivera klockan",
|
||||
"SETS_ON_WATCH": "Set på klockan",
|
||||
"SKYBOX": "Skymap",
|
||||
"SKYMAP_ALREADY_DOWNLOADED": "Denna skymap är redan nedladdad. Välj åtgärd.",
|
||||
"SPACE_DRAG": "Playspace",
|
||||
"SPACE_DRAG_MULTIPLIER": "Multiplikator för space-drag",
|
||||
"SPACE_DRAG_AFFECTS_WORLD": "Lämna overlays kvar vid flytt av playspace",
|
||||
"SPACE_DRAG_UNLOCKED": "Tillåt space-drag på alla axlar",
|
||||
"SPACE_GRAVITY_DAMPING": "Dämpning",
|
||||
"SPACE_GRAVITY_DAMPING_HELP": "Artificiell dämpning för att sakta ner rörelser. 0,1 – hög dämpning, 1,0 – ingen dämpning",
|
||||
"SPACE_GRAVITY_FLING_STRENGTH": "Kaststyrka",
|
||||
"SPACE_GRAVITY_FLING_STRENGTH_HELP": "Intensitetsmultiplikator för gravitationskraften efter space-drag.\n0.0 - ingen rörelse alls, 2.0 - dubbel intensitet",
|
||||
"SPACE_GRAVITY_GRAVITY": "Gravitation",
|
||||
"SPACE_GRAVITY_GRAVITY_HELP": "Mängd nedåtgående kraft. 0.0 - ingen gravitation",
|
||||
"SPACE_GRAVITY_GROUND_FRICTION": "Markfriktion",
|
||||
"SPACE_GRAVITY_GROUND_FRICTION_HELP": "Mängden friktion som saktar ner dig när du rör vid marken.\n0.0 - ingen friktion (som på is), 1.0 - sträv yta",
|
||||
"SPACE_GRAVITY_FLOOR_HEIGHT": "Golvhöjd",
|
||||
"SPACE_GRAVITY_FLOOR_HEIGHT_HELP": "Y-positionen för golvet. Gravitationen upphör när denna höjd nås.",
|
||||
"SPACE_ROTATE_UNLOCKED": "Tillåt rotation av playspace på alla axlar",
|
||||
"ROTATIONS": "HMD-rotationer",
|
||||
"SESSION_TIME": "Sessionstid",
|
||||
"STATISTICS": "Statistik",
|
||||
"TROUBLESHOOTING": "Felsökning",
|
||||
"UI_GRADIENT_INTENSITY": "Intensitet för UI-gradient",
|
||||
"UPRIGHT_SCREEN_FIX": "Fix för upprätt skärm",
|
||||
"UPRIGHT_SCREEN_FIX_HELP": "Fixar upprätt ställda skärmar på vissa skrivbord",
|
||||
"USE_PASSTHROUGH": "Aktivera passthrough",
|
||||
"USE_PASSTHROUGH_HELP": "Tillåt passthrough om XR-runtime stöder det",
|
||||
"USE_SKYBOX": "Visa skymap",
|
||||
"USE_SKYBOX_HELP": "Visa en skymap om ingen scenapp eller passthrough används",
|
||||
"WATCH_VIEW_ANGLE": "Klockans synvinklar",
|
||||
"WATCH_VIEW_ANGLE_HELP": "Styr hur klockan tonas ut",
|
||||
"WHISPER": {
|
||||
"NEED_TO_DOWNLOAD_MODEL": "Den valda modellen måste laddas ner.\nVill du ladda ner den nu?",
|
||||
"REMOVE_UNUSED_MODELS": "Du stänger av tal-till-text-funktionen.\nVill du ta bort nedladdade Whisper-modeller för att frigöra utrymme?"
|
||||
},
|
||||
"WHISPER_MODEL": "Tal-till-text-modell",
|
||||
"WHISPER_MODEL_HELP": "Whisper-modell som ska användas.\nStörre modeller ger mer precisa\nresultat, men förbrukar mer VRAM.",
|
||||
"SPEECH_TO_TEXT": "Tal till text",
|
||||
"WHISPER_GPU_SELECT": "Välj GPU",
|
||||
"TUTORIAL": "Handbok",
|
||||
"GRID_OPACITY": "Golvgridens opacitet",
|
||||
"GRID_OPACITY_HELP": "Opacitet för golvgridet när skymap är aktiverat",
|
||||
"XWAYLAND_BY_DEFAULT": "Kör appar i kompatibilitetsläge som standard",
|
||||
"SAVE": "Spara",
|
||||
"CANCEL": "Avbryt"
|
||||
},
|
||||
"APPLICATION_LAUNCHER": "App-startare",
|
||||
"APPLICATION_STARTED": "Applikationen startades",
|
||||
"APPLICATIONS": "Applikationer",
|
||||
"AUDIO": {
|
||||
"SWITCH_TO_THIS_DEVICE": "Byt till den här enheten",
|
||||
"AUTO_SWITCH_TO_VR_AUDIO": "Byt automatiskt till VR-ljud",
|
||||
"CARDS": "Kort",
|
||||
"FAILED_TO_SWITCH_MICROPHONE": "Kunde inte byta mikrofon",
|
||||
"MICROPHONE_SET_SUCCESSFULLY": "Mikrofonen har ändrats",
|
||||
"MICROPHONES": "Mikrofoner",
|
||||
"NO_VR_MICROPHONE_SWITCH_MANUALLY": "Ingen VR-mikrofon hittades. Byt manuellt.",
|
||||
"NO_VR_SPEAKERS_FOUND_SWITCH_MANUALLY": "Inga VR-högtalare hittades. Byt manuellt.",
|
||||
"SELECT_AUDIO_CARD_PROFILE": "Välj ljudkortsprofil",
|
||||
"SETTINGS": "Ljudinställningar",
|
||||
"SPEAKERS": "Högtalare",
|
||||
"SPEAKERS_SET_SUCCESSFULLY": "Högtalarna har ändrats",
|
||||
"VOLUME": "Volym"
|
||||
},
|
||||
"CLOSE_WINDOW": "Stäng fönster",
|
||||
"CREATION_DATE": "Skapad datum",
|
||||
"DEBUG_INFO": "Debuginfo",
|
||||
"DISPLAY_BRIGHTNESS": "Skärmljusstyrka",
|
||||
"DONATE": {
|
||||
"SUPPORT_US": "Stöd oss",
|
||||
"DESCRIPTION": "Vi är ett litet team på två utvecklare som bygger WayVR på vår fritid. Om du gillar att använda programvaran, överväg att stötta oss – varje donation hjälper oss att fortsätta.",
|
||||
"THANK_YOU_WAYVR": "Tack från WayVR-teamet!",
|
||||
"BUTTON_TOOLTIP": "Donera via Open Collective",
|
||||
"CURRENT_SUPPORTERS": "Nuvarande supportrar",
|
||||
"BROUGHT_TO_YOU_VARIANTS_OFTEN": "Stöds av {USER}|Särskilt tack till {USER}|Sponsras av {USER}|Backas av {USER}|Drivs av community-medlemmar som {USER}|Möjliggjorts av {USER}|Upprätthålls av {USER}|Bidrag från {USER}|Finansierat av {USER}",
|
||||
"BROUGHT_TO_YOU_VARIANTS_RARE": "Kaffe sponsrat av {USER}|Drivs av {USER}|Hälsningar till {USER}!|Håller oss igång: {USER}|Hjälper oss att växa: {USER}|Håller servrarna igång: {USER}|Med tacksamhet till {USER}|Drivs av {USER}|Stöds av vänner som {USER}",
|
||||
"BROUGHT_TO_YOU_VARIANTS_VERY_RARE": "Stöttas av legender som {USER}|Koffein sponsrat av {USER}|En sann legend: {USER}|Dagens hjälte: {USER}|En total superstjärna: {USER}|Betalar elräkningen: {USER}|Räddaren av vårt förstånd: {USER}|Visar vägen: {USER}|Koffein-till-kod-omvandlare tillhandahållen av {USER}|Håller produktionen stabil: {USER}|Den ende och ende: {USER}|Skickar dmabuf-buffertar: {USER}"
|
||||
},
|
||||
"DOWNLOAD": "Ladda ner",
|
||||
"DOWNLOADER": "Nedladdare",
|
||||
"WELCOME": {
|
||||
"ALL_SET": "Nu är du redo!",
|
||||
"BASICS": "Dessa grunder gör att du enkelt kan börja använda WayVR.",
|
||||
"CENTER_MARKER": "Centreringsmarkören visas framför dig vid omcentrering. Placera overlays runt den!",
|
||||
"DOUBLE_PRESS_TO_TOGGLE": "Dubbeltryck B (på Index) eller Y (på Meta) för att visa, centrera om eller dölja ditt set. Prova nu.",
|
||||
"FEATURES_DOCUMENTATION": "Det finns många andra funktioner värda att anpassa. För mer information, se dokumentationen på",
|
||||
"FINISH_TUTORIAL": "Avsluta handledningen",
|
||||
"GRAB_BOTH_HANDS": "Greppa med båda händerna om du vill flytta ett specifikt overlay i ditt set.",
|
||||
"GRAB_ONE_HAND": "Greppa med en hand för att flytta ditt set.",
|
||||
"GREETING": "Hej!",
|
||||
"GUIDE_INTRO": "Låt oss guida dig genom dina första steg.",
|
||||
"LASER_COLORS": "Laserfärger",
|
||||
"LASER_FUNCTIONALITY": "Mycket av funktionaliteten i WayVR beror på vilken laserfärg som används för att interagera med ett UI-element. Du behöver inte trycka på knapparna, det räcker att röra vid dem!",
|
||||
"LAST_PAGE_INFO": "Denna handledning slutar visas när du når den sista sidan.",
|
||||
"LOOK_AT_LEFT_WRIST": "Titta på din vänstra handled. Det är din klocka.",
|
||||
"MIDDLE_CLICK_MODE_PURPLE_LASER": "Mittenklick-läge: Lila laser",
|
||||
"MOVING_YOUR_SET": "Flytta ditt set",
|
||||
"NEXT_BUTTON": "Tryck på nästa-knappen nedan för att komma igång.",
|
||||
"REGULAR_MODE_BLUE_LASER": "Vanligt läge: Blå laser",
|
||||
"REPOSITION_THIS_WINDOW_BY_GRABBING": "Flytta det här fönstret med kontrollens grip-funktion.",
|
||||
"RIGHT_CLICK_MODE_ORANGE_LASER": "Högerklicksläge: Orange laser",
|
||||
"THANKS_FOR_INSTALLING": "Tack för att du installerade WayVR!",
|
||||
"WATCH": "Klocka",
|
||||
"WORKING_SET": "Aktivt Set"
|
||||
},
|
||||
"DOWNLOAD_AGAIN": "Ladda ner igen",
|
||||
"DOWNLOADING_FILE": "Laddar ner fil...",
|
||||
"FAILED_TO_LAUNCH_APPLICATION": "Kunde inte starta applikationen:",
|
||||
"GAME_LAUNCHED": "Spelet har startats",
|
||||
"GAME_LIST": {
|
||||
"NO_GAMES_FOUND": "Inga spel hittades",
|
||||
"RUNNING_GAMES_LIST": "Lista över körande spel"
|
||||
},
|
||||
"GAMES": "Spel",
|
||||
"GENERAL_SETTINGS": "Allmänna inställningar",
|
||||
"GETTING_STARTED": "Kom igång",
|
||||
"HEIGHT": "Höjd",
|
||||
"HELLO": "Hej!",
|
||||
"HELLO_USER": "Hej, {USER}!",
|
||||
"HIDE": "Dölj",
|
||||
"HOME_SCREEN": "Hem",
|
||||
"MODIFICATION_DATE": "Ändringsdatum",
|
||||
"MONADO_RUNTIME": "Monado runtime",
|
||||
"LOADING": "Laddar...",
|
||||
"POPUP_ADD_DISPLAY": {
|
||||
"RESOLUTION": "Upplösning"
|
||||
},
|
||||
"POWER_OPTIONS": {
|
||||
"TITLE": "Strömalternativ",
|
||||
"SHUTDOWN": "Stäng av",
|
||||
"SUSPEND": "Viloläge",
|
||||
"NOT_SUPPORTED": "Strömalternativ kräver systemd eller elogind.",
|
||||
"SHUTDOWN_ARE_YOU_SURE": "Är du säker på att du vill stänga av datorn?",
|
||||
"SUSPEND_ARE_YOU_SURE": "Är du säker på att du vill försätta datorn i viloläge?"
|
||||
},
|
||||
"PROCESS": {
|
||||
"FORCE_KILL": "Tvinga avslut",
|
||||
"STOP": "Stoppa"
|
||||
},
|
||||
"PROCESS_LIST": "Processlista",
|
||||
"REFRESH": "Uppdatera",
|
||||
"REMOVE": "Ta bort",
|
||||
"APPLY": "Verkställ",
|
||||
"RELOAD_FROM_DISK": "Ladda om från disk",
|
||||
"SETTINGS": "Inställningar",
|
||||
"SHOW": "Visa",
|
||||
"TARGET_PATH": "Mål sökväg",
|
||||
"TERMINATE_PROCESS": "Avsluta process",
|
||||
"VERSION": "Version",
|
||||
"WIDTH": "Bredd",
|
||||
"PINNED_APPS": "Fästa applikationer",
|
||||
"SAVED_TO_FAVOURITES": "Sparad i fästa applikationer",
|
||||
"ADD_TO_FAVOURITES": "Lägg till i fästa applikationer",
|
||||
"ADD_TO_FAVOURITES_HINT": "Spara förinställning och fäst den i app-menyn",
|
||||
"REMOVE_FROM_FAVOURITES": "Ta bort från fästa applikationer",
|
||||
"EMPTY": "Tomt",
|
||||
"INSTALLED": "Installerad",
|
||||
"NOT_INSTALLED": "Ej installerad",
|
||||
"YES": "Ja",
|
||||
"NO": "Nej"
|
||||
}
|
||||
@@ -197,8 +197,6 @@
|
||||
"SAVE_PLAYSPACE_CENTER_HELP": "将当前的活动空间设置为重置活动空间的绑定点。",
|
||||
"RESET_PLAYSPACE_CENTER": "重置活动空间中心",
|
||||
"RESET_PLAYSPACE_CENTER_HELP": "重置之前设置的活动空间中心。",
|
||||
"RESTART_SOFTWARE": "重启软件",
|
||||
"RESTART_SOFTWARE_HELP": "应用需要重启才能生效的设置",
|
||||
"ROUND_MULTIPLIER": "UI 边缘圆角",
|
||||
"SCREEN_RENDER_DOWN": "低分辨率渲染屏幕",
|
||||
"SCREEN_RENDER_DOWN_HELP": "有助于减轻高分辨率屏幕上的锯齿现象",
|
||||
@@ -263,7 +261,11 @@
|
||||
"TITLE": "诊断",
|
||||
"ENABLE_REBAR": "请在 BIOS 设置中启用 Resizable BAR 以获得最佳 VR 性能"
|
||||
},
|
||||
"TUTORIAL": "教程"
|
||||
"TUTORIAL": "教程",
|
||||
"RESTART_WAYVR": "重启 WayVR",
|
||||
"CLOSE_WAYVR": "关闭 WayVR",
|
||||
"SPEECH_TO_TEXT": "语音转文本",
|
||||
"WHISPER_GPU_SELECT": "选择 GPU"
|
||||
},
|
||||
"APPLICATION_LAUNCHER": "应用启动器",
|
||||
"APPLICATION_STARTED": "应用程序已启动",
|
||||
@@ -368,5 +370,15 @@
|
||||
"REMOVE_FROM_FAVOURITES": "从固定应用中移除",
|
||||
"EMPTY": "暂无",
|
||||
"INSTALLED": "已安装",
|
||||
"NOT_INSTALLED": "未安装"
|
||||
"NOT_INSTALLED": "未安装",
|
||||
"POWER_OPTIONS": {
|
||||
"TITLE": "电源选项",
|
||||
"SHUTDOWN": "关机",
|
||||
"SUSPEND": "暂停",
|
||||
"SHUTDOWN_ARE_YOU_SURE": "确定要关机吗?",
|
||||
"NOT_SUPPORTED": "电源选项需要 systemd 或 elogind。",
|
||||
"SUSPEND_ARE_YOU_SURE": "确定要让计算机进入睡眠状态吗?"
|
||||
},
|
||||
"YES": "确定",
|
||||
"NO": "否"
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
use std::{path::PathBuf, rc::Rc};
|
||||
use std::{path::PathBuf, process::Command, rc::Rc};
|
||||
|
||||
use chrono::Timelike;
|
||||
use glam::Vec2;
|
||||
use strum::EnumCount;
|
||||
use wgui::{
|
||||
assets::{AssetPathRef, AssetProvider},
|
||||
components::button::ComponentButton,
|
||||
components::{ComponentTrait, button::ComponentButton},
|
||||
event::{CallbackDataCommon, StyleSetRequest},
|
||||
font_config::WguiFontConfig,
|
||||
globals::WguiGlobals,
|
||||
@@ -17,12 +17,15 @@ use wgui::{
|
||||
task::Tasks,
|
||||
theme::WguiTheme,
|
||||
widget::{label::WidgetLabel, rectangle::WidgetRectangle, sprite::WidgetSprite},
|
||||
windowing::window::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra, WguiWindowPlacement},
|
||||
windowing::{
|
||||
context_menu::{self, ContextMenu},
|
||||
window::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra, WguiWindowPlacement},
|
||||
},
|
||||
};
|
||||
use wlx_common::{
|
||||
async_executor::AsyncExecutor,
|
||||
audio,
|
||||
dash_interface::{BoxDashInterface, ConfigChangeKind, DashPlayspaceTask},
|
||||
dash_interface::{BoxDashInterface, ConfigChangeKind, DashBackendTask, DashPlayspaceTask},
|
||||
locale::WayVRLangProvider,
|
||||
palette::load_palette,
|
||||
timestep::{self, Timestep},
|
||||
@@ -35,10 +38,11 @@ use crate::{
|
||||
settings::TabSettings, welcome::TabWelcome,
|
||||
},
|
||||
util::{
|
||||
popup_manager::{MountPopupOnceParams, PopupManager, PopupManagerParams},
|
||||
is_exec_installed,
|
||||
popup_manager::{MountPopupOnceParams, PopupHolder, PopupManager, PopupManagerParams},
|
||||
toast_manager::ToastManager,
|
||||
},
|
||||
views,
|
||||
views::{self, ViewUpdateParams},
|
||||
};
|
||||
|
||||
pub struct FrontendWidgets {
|
||||
@@ -74,9 +78,11 @@ pub struct Frontend<T> {
|
||||
toast_manager: ToastManager,
|
||||
timestep: Timestep,
|
||||
sounds_to_play: Vec<SoundType>,
|
||||
context_menu_power: ContextMenu,
|
||||
|
||||
window_audio_settings: WguiWindow,
|
||||
view_audio_settings: Option<views::audio_settings::View>,
|
||||
popup_dialog_box: PopupHolder<views::dialog_box::View>,
|
||||
}
|
||||
|
||||
pub struct FrontendUpdateParams<'a, T> {
|
||||
@@ -116,13 +122,16 @@ pub enum FrontendTask {
|
||||
MountPopupOnce(MountPopupOnceParams),
|
||||
RefreshPopupManager,
|
||||
ShowAudioSettings,
|
||||
ShowPowerSettings(WidgetID /* source widget */),
|
||||
UpdateAudioSettingsView,
|
||||
RecenterPlayspace,
|
||||
PushToast(Translation),
|
||||
PlaySound(SoundType),
|
||||
OpenURL(Rc<str>),
|
||||
RunCommand(String /* program */, Vec<String> /* args */),
|
||||
HideDashboard,
|
||||
MarkTutorialGraduated,
|
||||
BackendTask(DashBackendTask),
|
||||
}
|
||||
|
||||
impl<T: 'static> Frontend<T> {
|
||||
@@ -202,9 +211,11 @@ impl<T: 'static> Frontend<T> {
|
||||
popup_manager,
|
||||
toast_manager,
|
||||
window_audio_settings: WguiWindow::default(),
|
||||
context_menu_power: Default::default(),
|
||||
view_audio_settings: None,
|
||||
executor: params.executor,
|
||||
sounds_to_play: Vec::new(),
|
||||
popup_dialog_box: Default::default(),
|
||||
};
|
||||
|
||||
// init some things first
|
||||
@@ -275,12 +286,68 @@ impl<T: 'static> Frontend<T> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn power_action_confirm(&mut self, power_action: PowerAction) {
|
||||
let translation_key = match power_action {
|
||||
PowerAction::Suspend => "POWER_OPTIONS.SUSPEND_ARE_YOU_SURE",
|
||||
PowerAction::Poweroff => "POWER_OPTIONS.SHUTDOWN_ARE_YOU_SURE",
|
||||
};
|
||||
|
||||
let tasks = self.tasks.clone();
|
||||
views::dialog_box::mount_popup(
|
||||
self.popup_dialog_box.clone(),
|
||||
self.tasks.clone(),
|
||||
views::dialog_box::Params {
|
||||
globals: self.globals.clone(),
|
||||
message: Translation::from_translation_key(translation_key),
|
||||
entries: views::dialog_box::get_entries_yes_no(),
|
||||
on_action_click: Box::new(move |action| {
|
||||
if action == "yes" {
|
||||
tasks.push(task_for_power_action(power_action));
|
||||
}
|
||||
}),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn process_power_action(&mut self, action: &str, data: &mut T) {
|
||||
match action {
|
||||
"restart" => {
|
||||
self.interface.restart(data);
|
||||
}
|
||||
"close" => {
|
||||
self.interface.close(data);
|
||||
}
|
||||
"suspend" => {
|
||||
self.power_action_confirm(PowerAction::Suspend);
|
||||
}
|
||||
"shutdown" => {
|
||||
self.power_action_confirm(PowerAction::Poweroff);
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn tick(&mut self, params: FrontendUpdateParams<T>) -> anyhow::Result<FrontendUpdateResult> {
|
||||
// fixme: timer events instead of this thing
|
||||
if self.ticks.is_multiple_of(1000) {
|
||||
self.update_time(params.data)?;
|
||||
}
|
||||
|
||||
// TODO: refactor: simplify update function (no configs or T are required in most cases)
|
||||
let mut config_change_kind = None;
|
||||
self.popup_dialog_box.update(&mut ViewUpdateParams {
|
||||
layout: &mut self.layout,
|
||||
executor: &self.executor,
|
||||
general_config: self.interface.general_config(params.data),
|
||||
config_change_kind: &mut config_change_kind,
|
||||
})?;
|
||||
|
||||
if let context_menu::TickResult::Action(action) = self.context_menu_power.tick(&mut self.layout, &mut self.state)? {
|
||||
self.process_power_action(&action, params.data);
|
||||
}
|
||||
|
||||
{
|
||||
// always 30 times per second
|
||||
while self.timestep.on_tick() {
|
||||
@@ -371,13 +438,16 @@ impl<T: 'static> Frontend<T> {
|
||||
FrontendTask::MountPopupOnce(popup_params) => self.mount_popup_once(popup_params, params.data)?,
|
||||
FrontendTask::RefreshPopupManager => self.refresh_popup_manager()?,
|
||||
FrontendTask::ShowAudioSettings => self.action_show_audio_settings()?,
|
||||
FrontendTask::ShowPowerSettings(pos) => self.action_show_power_settings(pos)?,
|
||||
FrontendTask::UpdateAudioSettingsView => self.action_update_audio_settings()?,
|
||||
FrontendTask::RecenterPlayspace => self.action_recenter_playspace(params.data)?,
|
||||
FrontendTask::PushToast(content) => self.toast_manager.push(content),
|
||||
FrontendTask::PlaySound(sound_type) => self.queue_play_sound(sound_type),
|
||||
FrontendTask::HideDashboard => self.action_hide_dashboard(params.data),
|
||||
FrontendTask::OpenURL(url) => self.action_open_url(url),
|
||||
FrontendTask::RunCommand(program, args) => self.action_run_command(program, args),
|
||||
FrontendTask::MarkTutorialGraduated => self.action_tutorial_graduated(params.data),
|
||||
FrontendTask::BackendTask(task) => self.interface.backend_task(params.data, task)?,
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
@@ -503,6 +573,11 @@ impl<T: 'static> Frontend<T> {
|
||||
FrontendTask::ShowAudioSettings,
|
||||
);
|
||||
|
||||
let btn_power = self.state.fetch_component_as::<ComponentButton>("btn_power")?;
|
||||
self
|
||||
.tasks
|
||||
.handle_button(&btn_power, FrontendTask::ShowPowerSettings(btn_power.base().get_id()));
|
||||
|
||||
// "Recenter playspace" bottom bar button
|
||||
self.tasks.handle_button(
|
||||
&self.state.fetch_component_as::<ComponentButton>("btn_recenter")?,
|
||||
@@ -512,6 +587,18 @@ impl<T: 'static> Frontend<T> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn action_show_power_settings(&mut self, source_id: WidgetID) -> anyhow::Result<()> {
|
||||
self.context_menu_power.open(context_menu::OpenParams {
|
||||
on_custom_attribs: None,
|
||||
position: source_id.into(),
|
||||
blueprint: context_menu::Blueprint::Template {
|
||||
template_name: "context_menu_power_options".into(),
|
||||
template_params: Default::default(),
|
||||
},
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn action_show_audio_settings(&mut self) -> anyhow::Result<()> {
|
||||
self.window_audio_settings.open(&mut WguiWindowParams {
|
||||
position: Vec2::new(64.0, 64.0),
|
||||
@@ -576,4 +663,34 @@ impl<T: 'static> Frontend<T> {
|
||||
url
|
||||
))));
|
||||
}
|
||||
|
||||
fn action_run_command(&mut self, command: String, args: Vec<String>) {
|
||||
if let Err(e) = Command::new(command).args(args).spawn() {
|
||||
self
|
||||
.tasks
|
||||
.push(FrontendTask::PushToast(Translation::from_raw_text_string(format!(
|
||||
"Failed to launch command: {e}"
|
||||
))))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum PowerAction {
|
||||
Suspend,
|
||||
Poweroff,
|
||||
}
|
||||
|
||||
pub fn task_for_power_action(action: PowerAction) -> FrontendTask {
|
||||
for cmd in ["systemctl", "loginctl"] {
|
||||
if is_exec_installed(cmd) {
|
||||
let (command, args) = match action {
|
||||
PowerAction::Suspend => (cmd.to_string(), vec!["suspend".to_string()]),
|
||||
PowerAction::Poweroff => (cmd.to_string(), vec!["poweroff".to_string()]),
|
||||
};
|
||||
log::debug!("Command line for {action:?}: {command} {}", args.join(" "));
|
||||
return FrontendTask::RunCommand(command, args);
|
||||
}
|
||||
}
|
||||
FrontendTask::PushToast(Translation::from_translation_key("POWER_OPTIONS.NOT_SUPPORTED"))
|
||||
}
|
||||
@@ -61,7 +61,7 @@ impl<T> Tab<T> for TabApps<T> {
|
||||
}
|
||||
}
|
||||
|
||||
fn find_entry_from_app_name<'a>(app_id: &str, entries: &'a [DesktopEntry]) -> Option<&'a DesktopEntry> {
|
||||
fn find_entry_from_app_name<'a>(app_id: &str, entries: &'a [Rc<DesktopEntry>]) -> Option<&'a Rc<DesktopEntry>> {
|
||||
entries.iter().find(|&entry| *entry.app_id == *app_id)
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ impl<T> TabApps<T> {
|
||||
|
||||
let mut stale_entries = Vec::<Rc<str>>::new();
|
||||
|
||||
let mut pinned_desktop_entries = Vec::<(PinnedApp, &DesktopEntry)>::new();
|
||||
let mut pinned_desktop_entries = Vec::<(PinnedApp, Rc<DesktopEntry>)>::new();
|
||||
|
||||
// collect pinned desktop entries
|
||||
{
|
||||
@@ -83,7 +83,7 @@ impl<T> TabApps<T> {
|
||||
continue;
|
||||
};
|
||||
|
||||
pinned_desktop_entries.push((pinned_app.clone(), desktop_entry));
|
||||
pinned_desktop_entries.push((pinned_app.clone(), desktop_entry.clone()));
|
||||
}
|
||||
// cleanup:
|
||||
// remove non-existent app ids from pinned apps
|
||||
@@ -119,7 +119,7 @@ impl<T> TabApps<T> {
|
||||
&mut state.parser_state,
|
||||
&doc_params(frontend.globals.clone()),
|
||||
self.pinned_apps_parent,
|
||||
desktop_entry,
|
||||
desktop_entry.as_ref(),
|
||||
Some(tooltip_string),
|
||||
)?;
|
||||
|
||||
@@ -139,8 +139,8 @@ impl<T> TabApps<T> {
|
||||
|
||||
struct AppList {
|
||||
//data: Vec<ParserData>,
|
||||
all_entries: Vec<DesktopEntry>,
|
||||
entries_to_mount: VecDeque<DesktopEntry>,
|
||||
all_entries: Vec<Rc<DesktopEntry>>,
|
||||
entries_to_mount: VecDeque<Rc<DesktopEntry>>,
|
||||
list_parent: WidgetPair,
|
||||
prev_category_name: String,
|
||||
}
|
||||
@@ -150,7 +150,7 @@ fn on_app_click(
|
||||
frontend_tasks: FrontendTasks,
|
||||
tasks: Tasks<Task>,
|
||||
globals: WguiGlobals,
|
||||
entry: DesktopEntry,
|
||||
entry: Rc<DesktopEntry>,
|
||||
state: Rc<RefCell<State>>,
|
||||
pinned_app: Option<PinnedApp>,
|
||||
) -> ButtonClickCallback {
|
||||
@@ -307,14 +307,17 @@ fn get_category_name_score(name: &str) -> u8 {
|
||||
Scores::Unknown as u8
|
||||
}
|
||||
|
||||
fn get_best_category_name(categories: &[Rc<str>]) -> Option<&Rc<str>> {
|
||||
fn get_best_category_name<S>(categories: &[S]) -> Option<&str>
|
||||
where
|
||||
S: AsRef<str>,
|
||||
{
|
||||
let mut best_score: u8 = 0;
|
||||
let mut best_category: Option<&Rc<str>> = None;
|
||||
let mut best_category: Option<&str> = None;
|
||||
|
||||
for cat in categories {
|
||||
let score = get_category_name_score(cat);
|
||||
let score = get_category_name_score(cat.as_ref());
|
||||
if score > best_score {
|
||||
best_category = Some(cat);
|
||||
best_category = Some(cat.as_ref());
|
||||
best_score = score;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::tab::settings::{self, SettingType, Task, horiz_cell, mount_requires_restart};
|
||||
use crate::{
|
||||
tab::settings::{self, SettingType, Task, horiz_cell, mount_requires_restart},
|
||||
util::wgui_simple,
|
||||
};
|
||||
use wgui::{
|
||||
components::{
|
||||
button::{ButtonClickEvent, ComponentButton},
|
||||
checkbox::ComponentCheckbox,
|
||||
slider::ComponentSlider,
|
||||
},
|
||||
components::{button::ComponentButton, checkbox::ComponentCheckbox, slider::ComponentSlider},
|
||||
layout::{Layout, WidgetID},
|
||||
parser::{Fetchable, ParseDocumentParams, ParserState, TemplateParams},
|
||||
task::Tasks,
|
||||
widget::label::WidgetLabel,
|
||||
windowing::context_menu,
|
||||
};
|
||||
use wlx_common::{DesktopBackend, XrBackend, config::GeneralConfig, dash_interface::InterfaceFeats};
|
||||
@@ -297,7 +295,6 @@ where
|
||||
mp.idx += 1;
|
||||
|
||||
let mut params = TemplateParams::new();
|
||||
params.insert("id", &id);
|
||||
|
||||
match setting.get_translation() {
|
||||
Ok(translation) => params.insert("translation", translation),
|
||||
@@ -310,69 +307,63 @@ where
|
||||
|
||||
let id_cell = horiz_cell(mp.layout, parent)?;
|
||||
|
||||
mp.parser_state
|
||||
.instantiate_template(mp.doc_params, "DropdownButton", mp.layout, id_cell, params)?;
|
||||
let setting_str = setting.as_ref();
|
||||
let title = setting.get_enum_title(mp.config);
|
||||
|
||||
let feats = mp.feats;
|
||||
let cells = EnumType::VARIANTS
|
||||
.iter()
|
||||
.filter_map(|item| {
|
||||
if item.get_bool("Hidden").unwrap_or(false) {
|
||||
return None;
|
||||
}
|
||||
if item
|
||||
.get_str("Backend")
|
||||
.is_some_and(|x| XrBackend::try_from(x).unwrap() != feats.xr_backend)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
if item
|
||||
.get_str("Desktop")
|
||||
.is_some_and(|x| DesktopBackend::try_from(x).unwrap() != feats.desktop_backend)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let value = item.as_ref();
|
||||
let title = SettingType::get_enum_title_inner(*item);
|
||||
let tooltip = SettingType::get_enum_tooltip_inner(*item);
|
||||
|
||||
let text = &title.text;
|
||||
let translated = if title.translated { "1" } else { "0" };
|
||||
let action = format!("{setting_str};{id};{value};{text};{translated}");
|
||||
|
||||
Some(context_menu::Cell::Button(context_menu::Button {
|
||||
action_name: Some(action.into()),
|
||||
title,
|
||||
tooltip,
|
||||
..Default::default()
|
||||
}))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let tasks = mp.tasks.clone();
|
||||
wgui_simple::mount_dropdown(wgui_simple::MountDropdownParams {
|
||||
layout: mp.layout,
|
||||
parser_state: mp.parser_state,
|
||||
doc_params: mp.doc_params,
|
||||
parent: id_cell,
|
||||
id,
|
||||
params,
|
||||
current_text: title,
|
||||
cells,
|
||||
on_open: Box::new(move |pos, cells| tasks.push(Task::OpenContextMenu(pos, cells))),
|
||||
})?;
|
||||
|
||||
if setting.requires_restart() {
|
||||
mount_requires_restart(mp.layout, id_cell)?;
|
||||
}
|
||||
|
||||
let setting_str = setting.as_ref();
|
||||
let title = setting.get_enum_title(mp.config);
|
||||
|
||||
{
|
||||
let mut label = mp
|
||||
.parser_state
|
||||
.fetch_widget_as::<WidgetLabel>(&mp.layout.state, &format!("{id}_value"))?;
|
||||
label.set_text_simple(&mut mp.layout.state.globals.get(), title);
|
||||
}
|
||||
|
||||
let btn = mp.parser_state.fetch_component_as::<ComponentButton>(&id)?;
|
||||
btn.on_click(Rc::new({
|
||||
let feats = mp.feats;
|
||||
let tasks = mp.tasks.clone();
|
||||
move |_common, e: ButtonClickEvent| {
|
||||
tasks.push(Task::OpenContextMenu(
|
||||
e.mouse_pos_absolute.unwrap_or_default(),
|
||||
EnumType::VARIANTS
|
||||
.iter()
|
||||
.filter_map(|item| {
|
||||
if item.get_bool("Hidden").unwrap_or(false) {
|
||||
return None;
|
||||
}
|
||||
if item
|
||||
.get_str("Backend")
|
||||
.is_some_and(|x| XrBackend::try_from(x).unwrap() != feats.xr_backend)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
if item
|
||||
.get_str("Desktop")
|
||||
.is_some_and(|x| DesktopBackend::try_from(x).unwrap() != feats.desktop_backend)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let value = item.as_ref();
|
||||
let title = SettingType::get_enum_title_inner(*item);
|
||||
let tooltip = SettingType::get_enum_tooltip_inner(*item);
|
||||
|
||||
let text = &title.text;
|
||||
let translated = if title.translated { "1" } else { "0" };
|
||||
|
||||
Some(context_menu::Cell {
|
||||
action_name: Some(format!("{setting_str};{id};{value};{text};{translated}").into()),
|
||||
title,
|
||||
tooltip,
|
||||
attribs: vec![],
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
}));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use glam::Vec2;
|
||||
use std::{marker::PhantomData, rc::Rc, str::FromStr};
|
||||
use strum::{AsRefStr, EnumProperty, EnumString};
|
||||
use wgui::{
|
||||
@@ -81,11 +80,10 @@ pub(crate) enum Task {
|
||||
ClearPipewireTokens,
|
||||
ClearSavedState,
|
||||
DeleteAllConfigs,
|
||||
OpenContextMenu(Vec2, Vec<context_menu::Cell>),
|
||||
OpenContextMenu(context_menu::Position, Vec<context_menu::Cell>),
|
||||
RemoveAutostartApp(Rc<str>),
|
||||
ResetPlayspace,
|
||||
ResetPlayspaceCenter,
|
||||
RestartSoftware,
|
||||
SavePlayspaceCenter,
|
||||
SetTab(TabNameEnum),
|
||||
SettingUpdated(SettingType),
|
||||
@@ -143,6 +141,7 @@ pub struct TabSettings<T> {
|
||||
tasks: Tasks<Task>,
|
||||
marker: PhantomData<T>,
|
||||
frontend_tasks: FrontendTasks,
|
||||
feats: InterfaceFeats,
|
||||
}
|
||||
|
||||
impl<T> Tab<T> for TabSettings<T> {
|
||||
@@ -231,10 +230,6 @@ impl<T> Tab<T> for TabSettings<T> {
|
||||
frontend.interface.playspace_task(data, DashPlayspaceTask::Reset)?;
|
||||
return Ok(());
|
||||
}
|
||||
Task::RestartSoftware => {
|
||||
frontend.interface.restart(data);
|
||||
return Ok(());
|
||||
}
|
||||
Task::OpenContextMenu(position, cells) => {
|
||||
self.context_menu.open(context_menu::OpenParams {
|
||||
on_custom_attribs: None,
|
||||
@@ -703,10 +698,14 @@ impl<T> TabSettings<T> {
|
||||
fn set_tab(&mut self, frontend: &mut Frontend<T>, data: &mut T, name: TabNameEnum) -> anyhow::Result<()> {
|
||||
let root = self.state.get_widget_id("settings_root")?;
|
||||
frontend.layout.remove_children(root);
|
||||
let globals = frontend.layout.state.globals.clone();
|
||||
self.current_tab = None;
|
||||
let globals = frontend.layout.state.globals.clone();
|
||||
|
||||
let feats = frontend.interface.get_feats(data);
|
||||
let vulkan_devices = if matches!(name, TabNameEnum::Features) {
|
||||
Some(frontend.interface.enumerate_vulkan_devices(data)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut mp = MacroParams {
|
||||
layout: &mut frontend.layout,
|
||||
@@ -715,14 +714,14 @@ impl<T> TabSettings<T> {
|
||||
config: frontend.interface.general_config(data),
|
||||
tasks: self.tasks.clone(),
|
||||
idx: 9001,
|
||||
feats,
|
||||
feats: self.feats,
|
||||
};
|
||||
|
||||
let settings_mount_params = SettingsMountParams {
|
||||
mp: &mut mp,
|
||||
id_parent: root,
|
||||
frontend_tasks: &self.frontend_tasks,
|
||||
feats,
|
||||
feats: self.feats,
|
||||
};
|
||||
|
||||
match name {
|
||||
@@ -730,7 +729,10 @@ impl<T> TabSettings<T> {
|
||||
self.current_tab = Some(Box::new(tab_look_and_feel::State::mount(settings_mount_params)?));
|
||||
}
|
||||
TabNameEnum::Features => {
|
||||
self.current_tab = Some(Box::new(tab_features::State::mount(settings_mount_params)?));
|
||||
self.current_tab = Some(Box::new(tab_features::State::mount(
|
||||
settings_mount_params,
|
||||
vulkan_devices.unwrap(), /* safe */
|
||||
)?));
|
||||
}
|
||||
TabNameEnum::SpaceDrag => {
|
||||
self.current_tab = Some(Box::new(tab_space_drag::State::mount(settings_mount_params)?));
|
||||
@@ -796,6 +798,8 @@ impl<T> TabSettings<T> {
|
||||
|
||||
tasks.push(Task::SetTab(TabNameEnum::LookAndFeel));
|
||||
|
||||
let feats = frontend.interface.get_feats(data);
|
||||
|
||||
Ok(Self {
|
||||
app_button_ids: Vec::new(),
|
||||
tasks,
|
||||
@@ -804,6 +808,7 @@ impl<T> TabSettings<T> {
|
||||
context_menu: ContextMenu::default(),
|
||||
current_tab: None,
|
||||
frontend_tasks: frontend.tasks.clone(),
|
||||
feats,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,10 @@ use wgui::parser::TemplateParams;
|
||||
use wgui::{
|
||||
components::button::ComponentButton, globals::WguiGlobals, layout::WidgetID, parser::Fetchable, task::Tasks,
|
||||
};
|
||||
use wlx_common::XrBackend;
|
||||
use wlx_common::dash_interface::DashBackendTask;
|
||||
|
||||
use crate::frontend::FrontendTask;
|
||||
use crate::tab::settings::{UpdateExtra, horiz_cell, mount_requires_restart};
|
||||
use crate::util::popup_manager::PopupHolder;
|
||||
|
||||
@@ -18,7 +21,8 @@ use crate::{
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Task {
|
||||
OpenInputProfiles,
|
||||
OpenInputProfilesDash,
|
||||
OpenInputProfilesBackend,
|
||||
}
|
||||
|
||||
pub struct State {
|
||||
@@ -34,13 +38,23 @@ impl SettingsTab for State {
|
||||
|
||||
for task in self.tasks.drain() {
|
||||
match task {
|
||||
Task::OpenInputProfiles => {
|
||||
Task::OpenInputProfilesDash => {
|
||||
input_profiles::mount_popup(
|
||||
self.frontend_tasks.clone(),
|
||||
self.globals.clone(),
|
||||
self.popup_input_profiles.clone(),
|
||||
);
|
||||
}
|
||||
Task::OpenInputProfilesBackend => {
|
||||
self
|
||||
.frontend_tasks
|
||||
.push(FrontendTask::BackendTask(DashBackendTask::ShowBindingsUI));
|
||||
|
||||
// overlay sort order doesn't work on steamvr,
|
||||
// so the dashboard would be drawn over the bindings UI
|
||||
// while the bindings UI has input focus
|
||||
self.frontend_tasks.push(FrontendTask::HideDashboard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +67,7 @@ fn create_input_profiles_button(
|
||||
parent: WidgetID,
|
||||
tasks: Tasks<Task>,
|
||||
_popup: &PopupHolder<input_profiles::View>,
|
||||
xr_backend: XrBackend,
|
||||
) -> anyhow::Result<()> {
|
||||
let id = mp.idx.to_string();
|
||||
mp.idx += 1;
|
||||
@@ -71,7 +86,11 @@ fn create_input_profiles_button(
|
||||
btn.on_click(Rc::new({
|
||||
let tasks = tasks.clone();
|
||||
move |_common, _e| {
|
||||
tasks.push(Task::OpenInputProfiles);
|
||||
if xr_backend.is_open_vr() {
|
||||
tasks.push(Task::OpenInputProfilesBackend);
|
||||
} else {
|
||||
tasks.push(Task::OpenInputProfilesDash);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}));
|
||||
@@ -92,9 +111,7 @@ impl State {
|
||||
"APP_SETTINGS.CONTROLS",
|
||||
"@/dashboard/controller.svg",
|
||||
)?;
|
||||
if par.feats.xr_backend.is_open_xr() {
|
||||
create_input_profiles_button(par.mp, c, tasks.clone(), &popup)?;
|
||||
}
|
||||
create_input_profiles_button(par.mp, c, tasks.clone(), &popup, par.feats.xr_backend)?;
|
||||
options_dropdown::<wlx_common::config::AltModifier>(par.mp, c, &SettingType::KeyboardMiddleClick)?;
|
||||
options_dropdown::<wlx_common::config::HandsfreePointer>(par.mp, c, &SettingType::HandsfreePointer)?;
|
||||
options_dropdown::<wlx_common::config::HandsfreeAltTab>(par.mp, c, &SettingType::HandsfreeAltTab)?;
|
||||
|
||||
@@ -1,27 +1,17 @@
|
||||
use wgui::layout::WidgetID;
|
||||
|
||||
use crate::tab::settings::{
|
||||
SettingsMountParams, SettingsTab,
|
||||
macros::{MacroParams, options_category, options_diagnostics_drm_card, options_diagnostics_row},
|
||||
use crate::{
|
||||
tab::settings::{
|
||||
SettingsMountParams, SettingsTab,
|
||||
macros::{MacroParams, options_category, options_diagnostics_drm_card, options_diagnostics_row},
|
||||
},
|
||||
util::is_exec_installed,
|
||||
};
|
||||
|
||||
pub struct State {}
|
||||
|
||||
impl SettingsTab for State {}
|
||||
|
||||
// works good enough
|
||||
fn is_exec_installed(exec: &str) -> bool {
|
||||
let mut cmd = std::process::Command::new("sh");
|
||||
cmd.arg("-c");
|
||||
cmd.arg(format!("command -v {}", exec));
|
||||
|
||||
let Ok(res) = cmd.output() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
res.status.success()
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct LspciInfo {
|
||||
// slot: String,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use wgui::{
|
||||
components::button::{ButtonClickEvent, ComponentButton},
|
||||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
layout::WidgetID,
|
||||
@@ -9,7 +8,7 @@ use wgui::{
|
||||
parser::{Fetchable, TemplateParams},
|
||||
task::Tasks,
|
||||
widget::label::WidgetLabel,
|
||||
windowing::context_menu::{self},
|
||||
windowing::context_menu,
|
||||
};
|
||||
use wlx_common::{async_executor::AsyncExecutor, config::GeneralConfig, dash_interface::ConfigChangeKind};
|
||||
|
||||
@@ -21,6 +20,7 @@ use crate::{
|
||||
},
|
||||
util::{
|
||||
popup_manager::PopupHolder,
|
||||
wgui_simple,
|
||||
whisper::{
|
||||
WHISPER_MODELS, WhisperModel, whisper_any_models_downloaded, whisper_delete_all_models, whisper_model_from_name,
|
||||
whisper_model_path,
|
||||
@@ -104,41 +104,60 @@ impl SettingsTab for State {
|
||||
layout: &mut wgui::layout::Layout,
|
||||
state: &mut wgui::parser::ParserState,
|
||||
) -> anyhow::Result<()> {
|
||||
if let (Some(_), Some(id), Some(model_name)) = {
|
||||
let mut s = action.splitn(3, ';');
|
||||
(s.next(), s.next(), s.next())
|
||||
} {
|
||||
if !model_name.is_empty()
|
||||
&& let Some(model) = whisper_model_from_name(model_name)
|
||||
{
|
||||
let mut common = layout.common();
|
||||
let mut label = state.fetch_widget_as::<WidgetLabel>(common.state, &format!("{id}_value"))?;
|
||||
label.set_text(&mut common, Translation::from_raw_text(model.display_name));
|
||||
let (Some(_), Some(_type), Some(id), Some(value)) = ({
|
||||
let mut s = action.splitn(4, ';');
|
||||
(s.next(), s.next(), s.next(), s.next())
|
||||
}) else {
|
||||
debug_assert!(false);
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
config.whisper_model = model.file_name.into();
|
||||
if !whisper_model_path(model.file_name).exists() {
|
||||
self.show_whisper_model_dialog_box_download(model)?;
|
||||
match _type {
|
||||
"whisper_model" => {
|
||||
let model_name = value;
|
||||
if !model_name.is_empty()
|
||||
&& let Some(model) = whisper_model_from_name(model_name)
|
||||
{
|
||||
let mut common = layout.common();
|
||||
let mut label = state.fetch_widget_as::<WidgetLabel>(common.state, &format!("{id}_value"))?;
|
||||
label.set_text(&mut common, Translation::from_raw_text(model.display_name));
|
||||
|
||||
config.whisper_model = model.file_name.into();
|
||||
if !whisper_model_path(model.file_name).exists() {
|
||||
self.show_whisper_model_dialog_box_download(model)?;
|
||||
} else {
|
||||
change_kind.replace(ConfigChangeKind::Other);
|
||||
}
|
||||
} else {
|
||||
config.whisper_model = "".into();
|
||||
change_kind.replace(ConfigChangeKind::Other);
|
||||
}
|
||||
} else {
|
||||
config.whisper_model = "".into();
|
||||
change_kind.replace(ConfigChangeKind::Other);
|
||||
// re-init the whole tab
|
||||
if whisper_any_models_downloaded().unwrap_or_default() {
|
||||
self.show_whisper_model_dialog_box_cleanup()?;
|
||||
} else {
|
||||
self.parent_tasks.push(ParentTask::SetTab(TabNameEnum::Features));
|
||||
// re-init the whole tab
|
||||
if whisper_any_models_downloaded().unwrap_or_default() {
|
||||
self.show_whisper_model_dialog_box_cleanup()?;
|
||||
} else {
|
||||
self.parent_tasks.push(ParentTask::SetTab(TabNameEnum::Features));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"whisper_gpu" => {
|
||||
let mut common = layout.common();
|
||||
let mut label = state.fetch_widget_as::<WidgetLabel>(common.state, &format!("{id}_value"))?;
|
||||
config.whisper_gpu = value.parse::<u32>().unwrap();
|
||||
label.set_text(
|
||||
&mut common,
|
||||
Translation::from_raw_text_string(config.whisper_gpu.to_string()),
|
||||
);
|
||||
change_kind.replace(ConfigChangeKind::Other);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn mount(par: SettingsMountParams) -> anyhow::Result<State> {
|
||||
pub fn mount(par: SettingsMountParams, vulkan_devices: Vec<String>) -> anyhow::Result<State> {
|
||||
let tasks = Tasks::<Task>::new();
|
||||
let popup_download = PopupHolder::<views::download_file::View>::default();
|
||||
let popup_dialog = PopupHolder::<views::dialog_box::View>::default();
|
||||
@@ -151,7 +170,9 @@ impl State {
|
||||
)?;
|
||||
|
||||
if par.feats.whisper {
|
||||
let c = options_category(par.mp, c, "APP_SETTINGS.SPEECH_TO_TEXT", "@/dashboard/stt.svg")?;
|
||||
whisper_models_dropdown(par.mp, c)?;
|
||||
whisper_gpu_select_dropdown(par.mp, c, vulkan_devices)?;
|
||||
}
|
||||
|
||||
options_checkbox(par.mp, c, SettingType::NotificationsEnabled)?;
|
||||
@@ -287,6 +308,48 @@ impl State {
|
||||
}
|
||||
}
|
||||
|
||||
fn whisper_gpu_select_dropdown(
|
||||
mp: &mut MacroParams,
|
||||
parent: WidgetID,
|
||||
vulkan_devices: Vec<String>,
|
||||
) -> anyhow::Result<()> {
|
||||
let id = mp.idx.to_string();
|
||||
mp.idx += 1;
|
||||
|
||||
let parent = horiz_cell(mp.layout, parent)?;
|
||||
|
||||
let mut params = TemplateParams::new();
|
||||
params.insert("translation", "APP_SETTINGS.WHISPER_GPU_SELECT");
|
||||
|
||||
let current_text = Translation::from_raw_text_string(mp.config.whisper_gpu.to_string());
|
||||
|
||||
let cells = vulkan_devices
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, name)| !name.contains("llvmpipe"))
|
||||
.map(|(idx, name)| {
|
||||
context_menu::Cell::Button(context_menu::Button {
|
||||
action_name: Some(format!(";whisper_gpu;{};{}", id, idx).into()),
|
||||
title: Translation::from_raw_text_string(name.to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
let tasks = mp.tasks.clone();
|
||||
wgui_simple::mount_dropdown(wgui_simple::MountDropdownParams {
|
||||
layout: mp.layout,
|
||||
parser_state: mp.parser_state,
|
||||
doc_params: mp.doc_params,
|
||||
parent,
|
||||
id,
|
||||
params,
|
||||
current_text,
|
||||
cells,
|
||||
on_open: Box::new(move |pos, cells| tasks.push(ParentTask::OpenContextMenu(pos, cells))),
|
||||
})
|
||||
}
|
||||
|
||||
fn whisper_models_dropdown(mp: &mut MacroParams, parent: WidgetID) -> anyhow::Result<()> {
|
||||
let id = mp.idx.to_string();
|
||||
mp.idx += 1;
|
||||
@@ -307,56 +370,49 @@ fn whisper_models_dropdown(mp: &mut MacroParams, parent: WidgetID) -> anyhow::Re
|
||||
});
|
||||
|
||||
let mut params = TemplateParams::new();
|
||||
params.insert("id", &id);
|
||||
params.insert("translation", "APP_SETTINGS.WHISPER_MODEL");
|
||||
params.insert("tooltip", "APP_SETTINGS.WHISPER_MODEL_HELP");
|
||||
|
||||
mp.parser_state
|
||||
.instantiate_template(mp.doc_params, "DropdownButton", mp.layout, parent, params)?;
|
||||
|
||||
{
|
||||
let mut label = mp
|
||||
.parser_state
|
||||
.fetch_widget_as::<WidgetLabel>(&mp.layout.state, &format!("{id}_value"))?;
|
||||
label.set_text_simple(&mut mp.layout.state.globals.get(), current_translation);
|
||||
}
|
||||
|
||||
let btn = mp.parser_state.fetch_component_as::<ComponentButton>(&id)?;
|
||||
btn.on_click(Rc::new({
|
||||
let parent_tasks = mp.tasks.clone();
|
||||
move |_common, e: ButtonClickEvent| {
|
||||
let mut cells = WHISPER_MODELS
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, item)| context_menu::Cell {
|
||||
action_name: Some(format!(";{id};{}", item.file_name).into()),
|
||||
title: Translation::from_raw_text_string(format!(
|
||||
"{}{}",
|
||||
item.display_name,
|
||||
if downloaded_models.contains(&idx) { " ✅" } else { "" }
|
||||
)),
|
||||
tooltip: None,
|
||||
attribs: vec![],
|
||||
let mut cells = WHISPER_MODELS
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, item)| {
|
||||
if downloaded_models.contains(&idx) {
|
||||
context_menu::Cell::Button(context_menu::Button {
|
||||
action_name: Some(format!(";whisper_model;{id};{}", item.file_name).into()),
|
||||
title: Translation::from_raw_text(item.display_name),
|
||||
sprite_src_builtin: Some("@/dashboard/check.svg".into()),
|
||||
..Default::default()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
} else {
|
||||
context_menu::Cell::Button(context_menu::Button {
|
||||
action_name: Some(format!(";whisper_model;{id};{}", item.file_name).into()),
|
||||
title: Translation::from_raw_text(item.display_name),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
cells.insert(
|
||||
0,
|
||||
context_menu::Cell {
|
||||
action_name: Some(format!(";{id};").into()),
|
||||
title: Translation::from_translation_key("APP_SETTINGS.OPTION.NONE"),
|
||||
tooltip: None,
|
||||
attribs: vec![],
|
||||
},
|
||||
);
|
||||
cells.insert(
|
||||
0,
|
||||
context_menu::Cell::Button(context_menu::Button {
|
||||
action_name: Some(format!(";whisper_model;{id};").into()),
|
||||
title: Translation::from_translation_key("APP_SETTINGS.OPTION.NONE"),
|
||||
..Default::default()
|
||||
}),
|
||||
);
|
||||
|
||||
parent_tasks.push(ParentTask::OpenContextMenu(
|
||||
e.mouse_pos_absolute.unwrap_or_default(),
|
||||
cells,
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
}));
|
||||
|
||||
Ok(())
|
||||
let tasks = mp.tasks.clone();
|
||||
wgui_simple::mount_dropdown(wgui_simple::MountDropdownParams {
|
||||
layout: mp.layout,
|
||||
parser_state: mp.parser_state,
|
||||
doc_params: mp.doc_params,
|
||||
parent,
|
||||
id,
|
||||
params,
|
||||
current_text: current_translation,
|
||||
cells,
|
||||
on_open: Box::new(move |pos, cells| tasks.push(ParentTask::OpenContextMenu(pos, cells))),
|
||||
})
|
||||
}
|
||||
@@ -36,13 +36,6 @@ impl State {
|
||||
"@/dashboard/circle.svg",
|
||||
Task::DeleteAllConfigs,
|
||||
)?;
|
||||
options_danger_button(
|
||||
par.mp,
|
||||
c,
|
||||
"APP_SETTINGS.RESTART_SOFTWARE",
|
||||
"@/dashboard/refresh.svg",
|
||||
Task::RestartSoftware,
|
||||
)?;
|
||||
Ok(State {})
|
||||
}
|
||||
}
|
||||
@@ -7,3 +7,16 @@ pub mod steam_utils;
|
||||
pub mod toast_manager;
|
||||
pub mod wgui_simple;
|
||||
pub mod whisper;
|
||||
|
||||
// works good enough
|
||||
pub fn is_exec_installed(exec: &str) -> bool {
|
||||
let mut cmd = std::process::Command::new("sh");
|
||||
cmd.arg("-c");
|
||||
cmd.arg(format!("command -v {}", exec));
|
||||
|
||||
let Ok(res) = cmd.output() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
res.status.success()
|
||||
}
|
||||
@@ -81,7 +81,7 @@ impl BindingsDropdown for XrInputComponent {
|
||||
pub struct ParsedOpenXrInputPath {
|
||||
pub side: XrInputSide,
|
||||
pub subpath: XrInputSubpathKind,
|
||||
pub component: XrInputComponent,
|
||||
pub component: Option<XrInputComponent>,
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for ParsedOpenXrInputPath {
|
||||
@@ -101,13 +101,18 @@ impl TryFrom<&str> for ParsedOpenXrInputPath {
|
||||
bail!("missing input prefix");
|
||||
}
|
||||
|
||||
let (identifier, component) = rest.rsplit_once('/').context("missing identifier or component")?;
|
||||
let (identifier, component) = match rest.rsplit_once('/') {
|
||||
Some((ident, comp)) if !comp.is_empty() => {
|
||||
let comp = XrInputComponent::try_from(comp).context("bad component")?;
|
||||
(ident, Some(comp))
|
||||
}
|
||||
_ => (rest, None),
|
||||
};
|
||||
|
||||
if identifier.is_empty() || component.is_empty() {
|
||||
bail!("identifier or component empty");
|
||||
if identifier.is_empty() {
|
||||
bail!("identifier empty");
|
||||
}
|
||||
|
||||
let component = XrInputComponent::try_from(component).context("bad component")?;
|
||||
let subpath = XrInputSubpathKind::try_from(identifier).context("bad subpath")?;
|
||||
|
||||
Ok(Self {
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
use glam::{Mat4, Vec2};
|
||||
use std::rc::Rc;
|
||||
use wgui::{
|
||||
animation::{Animation, AnimationDuration, AnimationEasing},
|
||||
assets::AssetPathRef,
|
||||
color::WguiColorName,
|
||||
components::{self, button::ButtonClickCallback},
|
||||
components::{
|
||||
self,
|
||||
button::{ButtonClickCallback, ButtonClickEvent, ComponentButton},
|
||||
},
|
||||
i18n::Translation,
|
||||
layout::{Layout, LayoutTask, WidgetID},
|
||||
parser::{Fetchable, ParseDocumentParams},
|
||||
parser::{Fetchable, ParseDocumentParams, ParserState, TemplateParams},
|
||||
renderer_vk::{
|
||||
text::{FontWeight, TextStyle, custom_glyph::CustomGlyphData},
|
||||
util::centered_matrix,
|
||||
@@ -17,6 +21,7 @@ use wgui::{
|
||||
label::{WidgetLabel, WidgetLabelParams},
|
||||
sprite::{WidgetSprite, WidgetSpriteParams},
|
||||
},
|
||||
windowing::context_menu,
|
||||
};
|
||||
|
||||
pub struct CreateButtonParams<'a> {
|
||||
@@ -166,3 +171,39 @@ pub fn create_loading(par: CreateLoadingParams) -> anyhow::Result<WidgetID> {
|
||||
|
||||
Ok(id_root)
|
||||
}
|
||||
|
||||
pub struct MountDropdownParams<'a> {
|
||||
pub layout: &'a mut Layout,
|
||||
pub parser_state: &'a mut ParserState,
|
||||
pub doc_params: &'a ParseDocumentParams<'a>,
|
||||
pub parent: WidgetID,
|
||||
pub id: String,
|
||||
pub params: TemplateParams,
|
||||
pub current_text: Translation,
|
||||
pub cells: Vec<context_menu::Cell>,
|
||||
pub on_open: Box<dyn Fn(context_menu::Position, Vec<context_menu::Cell>)>,
|
||||
}
|
||||
|
||||
pub fn mount_dropdown(par: MountDropdownParams) -> anyhow::Result<()> {
|
||||
let mut params = par.params;
|
||||
params.insert("id", &par.id);
|
||||
|
||||
par
|
||||
.parser_state
|
||||
.instantiate_template(par.doc_params, "DropdownButton", par.layout, par.parent, params)?;
|
||||
|
||||
{
|
||||
let mut label = par
|
||||
.parser_state
|
||||
.fetch_widget_as::<WidgetLabel>(&par.layout.state, &format!("{}_value", par.id))?;
|
||||
label.set_text_simple(&mut par.layout.state.globals.get(), par.current_text);
|
||||
}
|
||||
|
||||
let btn = par.parser_state.fetch_component_as::<ComponentButton>(&par.id)?;
|
||||
btn.on_click(Rc::new(move |_common, e: ButtonClickEvent| {
|
||||
(par.on_open)(e.mouse_pos_absolute.into(), par.cells.clone());
|
||||
Ok(())
|
||||
}));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -53,7 +53,7 @@ struct LaunchParams<'a, T> {
|
||||
pub struct View {
|
||||
#[allow(dead_code)]
|
||||
state: ParserState,
|
||||
entry: DesktopEntry,
|
||||
entry: Rc<DesktopEntry>,
|
||||
tasks: Tasks<Task>,
|
||||
frontend_tasks: FrontendTasks,
|
||||
globals: WguiGlobals,
|
||||
@@ -80,7 +80,7 @@ pub struct View {
|
||||
|
||||
pub struct Params<'a> {
|
||||
pub globals: &'a WguiGlobals,
|
||||
pub entry: DesktopEntry,
|
||||
pub entry: Rc<DesktopEntry>,
|
||||
pub layout: &'a mut Layout,
|
||||
pub parent_id: WidgetID,
|
||||
pub config: &'a GeneralConfig,
|
||||
@@ -119,10 +119,8 @@ impl View {
|
||||
{
|
||||
let mut label_exec = state.fetch_widget_as::<WidgetLabel>(¶ms.layout.state, "label_exec")?;
|
||||
|
||||
label_exec.set_text_simple(
|
||||
&mut params.globals.get(),
|
||||
Translation::from_raw_text_string(format!("{} {}", params.entry.exec_path, params.entry.exec_args)),
|
||||
);
|
||||
let command = shell_words::join(params.entry.exec_argv.iter().map(|x| x.as_ref()));
|
||||
label_exec.set_text_simple(&mut params.globals.get(), Translation::from_raw_text_string(command));
|
||||
}
|
||||
|
||||
let tasks = Tasks::new();
|
||||
@@ -168,7 +166,7 @@ impl View {
|
||||
// TODO: configurable defaults ?
|
||||
let mut res_mode = AppResMode::Res1080;
|
||||
let mut orientation_mode = AppOrientationMode::Wide;
|
||||
let pos_mode = AppPosMode::Anchored;
|
||||
let pos_mode = AppPosMode::Floating;
|
||||
|
||||
if let Some(pinned_app) = ¶ms.pinned_app {
|
||||
res_mode = pinned_app.res_mode;
|
||||
@@ -406,14 +404,19 @@ impl View {
|
||||
env.push("ELECTRON_OZONE_PLATFORM_HINT=wayland".into());
|
||||
}
|
||||
|
||||
let args = match params.compositor_mode {
|
||||
AppCompositorMode::Cage => format!("-- {} {}", params.application.exec_path, params.application.exec_args),
|
||||
AppCompositorMode::Native => params.application.exec_args.to_string(),
|
||||
};
|
||||
|
||||
let exec = match params.compositor_mode {
|
||||
AppCompositorMode::Cage => "cage".to_string(),
|
||||
AppCompositorMode::Native => params.application.exec_path.to_string(),
|
||||
let argv = match params.compositor_mode {
|
||||
AppCompositorMode::Cage => ["cage", "--"]
|
||||
.iter()
|
||||
.copied()
|
||||
.chain(params.application.exec_argv.iter().map(|x| x.as_ref()))
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<_>>(),
|
||||
AppCompositorMode::Native => params
|
||||
.application
|
||||
.exec_argv
|
||||
.iter()
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<_>>(),
|
||||
};
|
||||
|
||||
let pos_mode = match params.pos_mode {
|
||||
@@ -423,7 +426,7 @@ impl View {
|
||||
};
|
||||
|
||||
let mut userdata = HashMap::new();
|
||||
userdata.insert("desktop-entry".to_string(), serde_json::to_string(params.application)?);
|
||||
userdata.insert("desktop-entry".into(), serde_json::to_string(params.application)?);
|
||||
|
||||
let resolution = Self::calculate_resolution(params.res_mode, params.orientation_mode);
|
||||
|
||||
@@ -432,12 +435,11 @@ impl View {
|
||||
params.auto_start,
|
||||
WvrProcessLaunchParams {
|
||||
env,
|
||||
exec,
|
||||
argv,
|
||||
name: params.application.app_name.to_string(),
|
||||
args,
|
||||
resolution,
|
||||
pos_mode,
|
||||
icon: params.application.icon_path.as_ref().map(|x| x.as_ref().to_string()),
|
||||
icon: params.application.icon_path.as_deref().map(String::from),
|
||||
userdata,
|
||||
},
|
||||
)?;
|
||||
@@ -486,7 +488,7 @@ impl View {
|
||||
pub fn mount_popup(
|
||||
frontend_tasks: FrontendTasks,
|
||||
globals: WguiGlobals,
|
||||
entry: DesktopEntry,
|
||||
entry: Rc<DesktopEntry>,
|
||||
popup: PopupHolder<View>,
|
||||
on_app_pins_changed: Box<dyn Fn()>,
|
||||
pinned_app: Option<PinnedApp>,
|
||||
|
||||
+105
-104
@@ -3,17 +3,13 @@ use std::rc::Rc;
|
||||
use strum::EnumProperty;
|
||||
use wgui::{
|
||||
assets::AssetPathRef,
|
||||
components::{
|
||||
button::{ButtonClickEvent, ComponentButton},
|
||||
slider::ComponentSlider,
|
||||
},
|
||||
components::{button::ComponentButton, slider::ComponentSlider},
|
||||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
layout::{Layout, WidgetID},
|
||||
log::LogErr,
|
||||
parser::{Fetchable, ParseDocumentParams, ParserState, TemplateParams},
|
||||
task::Tasks,
|
||||
widget::label::WidgetLabel,
|
||||
windowing::context_menu::{self, TickResult},
|
||||
};
|
||||
use wlx_common::{
|
||||
@@ -39,7 +35,7 @@ use crate::{
|
||||
enum Task {
|
||||
Save,
|
||||
Cancel,
|
||||
OpenContextMenu(glam::Vec2, Vec<context_menu::Cell>),
|
||||
OpenContextMenu(context_menu::Position, Vec<context_menu::Cell>),
|
||||
UpdateThreshold(Rc<str>, XrInputSide, usize, f32),
|
||||
AddChordMember(Rc<str>, XrInputSide),
|
||||
RemoveChordMember(Rc<str>, XrInputSide, usize),
|
||||
@@ -162,6 +158,7 @@ impl ViewTrait for View {
|
||||
"subpath" => {
|
||||
apply_subpath(
|
||||
action_binding_mut(action_mut, side),
|
||||
action_name,
|
||||
side,
|
||||
value,
|
||||
self.controller_profile,
|
||||
@@ -617,7 +614,7 @@ fn chord_member_controls(
|
||||
output_side,
|
||||
member_idx,
|
||||
available_components,
|
||||
parsed.as_ref().map(|x| x.component),
|
||||
parsed.and_then(|x| x.component),
|
||||
)?;
|
||||
|
||||
let tasks = mp.tasks.clone();
|
||||
@@ -638,7 +635,7 @@ fn chord_member_controls(
|
||||
})?;
|
||||
|
||||
// put threshold slider on its own row
|
||||
if parsed.as_ref().is_some_and(|x| x.component.is_analog()) {
|
||||
if parsed.and_then(|x| x.component).is_some_and(|x| x.is_analog()) {
|
||||
chord_threshold_slider(mp, parent, action.clone(), output_side, member_idx, member.threshold())?;
|
||||
}
|
||||
|
||||
@@ -666,13 +663,6 @@ fn input_controls_for_hand(mp: &mut MacroParams, par: InputControlsForHandParams
|
||||
|
||||
let parent = horiz_cell(mp.layout, par.parent)?;
|
||||
|
||||
let available_components: Rc<[XrInputComponent]> = current
|
||||
.as_ref()
|
||||
.and_then(|par| user_path.find_subpath(par.subpath))
|
||||
.map(|subp| subp.components)
|
||||
.unwrap_or_default()
|
||||
.into();
|
||||
|
||||
let available_subpaths: Rc<[XrInputSubpathKind]> = user_path
|
||||
.paths
|
||||
.iter()
|
||||
@@ -689,23 +679,33 @@ fn input_controls_for_hand(mp: &mut MacroParams, par: InputControlsForHandParams
|
||||
current.as_ref().map(|x| x.subpath),
|
||||
)?;
|
||||
|
||||
if &**par.action == "scroll" {
|
||||
clicks_dropdown(mp, parent, par.action.clone(), par.click_type)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let available_components: Rc<[XrInputComponent]> = current
|
||||
.as_ref()
|
||||
.and_then(|par| user_path.find_subpath(par.subpath))
|
||||
.map(|subp| subp.components)
|
||||
.unwrap_or_default()
|
||||
.into();
|
||||
|
||||
if !component_dropdown(
|
||||
mp,
|
||||
parent,
|
||||
par.action.clone(),
|
||||
par.side,
|
||||
available_components,
|
||||
current.as_ref().map(|x| x.component),
|
||||
current.and_then(|x| x.component),
|
||||
)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
clicks_dropdown(mp, parent, par.action.clone(), par.click_type)?;
|
||||
|
||||
if let Some(component) = current.as_ref().map(|x| x.component)
|
||||
if let Some(component) = current.and_then(|x| x.component)
|
||||
&& component.is_analog()
|
||||
&& &**par.action != "scroll"
|
||||
// hax
|
||||
{
|
||||
threshold_slider(mp, parent, par.action.clone(), par.side, par.threshold)?;
|
||||
}
|
||||
@@ -804,20 +804,21 @@ fn chord_hand_dropdown(
|
||||
let cells = [XrInputSide::Left, XrInputSide::Right]
|
||||
.into_iter()
|
||||
.filter(|side| profile.find_userpath(*side).is_some())
|
||||
.map(|side| context_menu::Cell {
|
||||
action_name: Some(
|
||||
format!(
|
||||
"chord_hand;{};{};{};{}",
|
||||
action,
|
||||
output_side.as_ref(),
|
||||
member_idx,
|
||||
side.as_ref()
|
||||
)
|
||||
.into(),
|
||||
),
|
||||
title: side_translation(side),
|
||||
tooltip: None,
|
||||
attribs: vec![],
|
||||
.map(|side| {
|
||||
context_menu::Cell::Button(context_menu::Button {
|
||||
action_name: Some(
|
||||
format!(
|
||||
"chord_hand;{};{};{};{}",
|
||||
action,
|
||||
output_side.as_ref(),
|
||||
member_idx,
|
||||
side.as_ref()
|
||||
)
|
||||
.into(),
|
||||
),
|
||||
title: side_translation(side),
|
||||
..Default::default()
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -842,20 +843,21 @@ fn chord_subpath_dropdown(
|
||||
.unwrap_or_else(|| Translation::from_translation_key("APP_SETTINGS.OPTION.NONE"));
|
||||
let cells = available
|
||||
.iter()
|
||||
.map(|subpath| context_menu::Cell {
|
||||
action_name: Some(
|
||||
format!(
|
||||
"chord_subpath;{};{};{};{}",
|
||||
action,
|
||||
output_side.as_ref(),
|
||||
member_idx,
|
||||
subpath.as_ref()
|
||||
)
|
||||
.into(),
|
||||
),
|
||||
title: subpath.translation(),
|
||||
tooltip: None,
|
||||
attribs: vec![],
|
||||
.map(|subpath| {
|
||||
context_menu::Cell::Button(context_menu::Button {
|
||||
action_name: Some(
|
||||
format!(
|
||||
"chord_subpath;{};{};{};{}",
|
||||
action,
|
||||
output_side.as_ref(),
|
||||
member_idx,
|
||||
subpath.as_ref()
|
||||
)
|
||||
.into(),
|
||||
),
|
||||
title: subpath.translation(),
|
||||
..Default::default()
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -885,20 +887,21 @@ fn chord_component_dropdown(
|
||||
.unwrap_or_else(|| Translation::from_raw_text_rc(Default::default()));
|
||||
let cells = available
|
||||
.iter()
|
||||
.map(|component| context_menu::Cell {
|
||||
action_name: Some(
|
||||
format!(
|
||||
"chord_comp;{};{};{};{}",
|
||||
action,
|
||||
output_side.as_ref(),
|
||||
member_idx,
|
||||
component.as_ref()
|
||||
)
|
||||
.into(),
|
||||
),
|
||||
title: component.translation(),
|
||||
tooltip: None,
|
||||
attribs: vec![],
|
||||
.map(|component| {
|
||||
context_menu::Cell::Button(context_menu::Button {
|
||||
action_name: Some(
|
||||
format!(
|
||||
"chord_comp;{};{};{};{}",
|
||||
action,
|
||||
output_side.as_ref(),
|
||||
member_idx,
|
||||
component.as_ref()
|
||||
)
|
||||
.into(),
|
||||
),
|
||||
title: component.translation(),
|
||||
..Default::default()
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -1003,23 +1006,23 @@ fn create_dropdown<B: 'static + BindingsDropdown>(
|
||||
) -> anyhow::Result<()> {
|
||||
let mut cells = available
|
||||
.iter()
|
||||
.map(|item| context_menu::Cell {
|
||||
action_name: Some(item.action_str(&action, side)),
|
||||
title: item.translation(),
|
||||
tooltip: None,
|
||||
attribs: vec![],
|
||||
.map(|item| {
|
||||
context_menu::Cell::Button(context_menu::Button {
|
||||
action_name: Some(item.action_str(&action, side)),
|
||||
title: item.translation(),
|
||||
..Default::default()
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if let Some(action_str) = B::clear_str(&action, side) {
|
||||
cells.insert(
|
||||
0,
|
||||
context_menu::Cell {
|
||||
context_menu::Cell::Button(context_menu::Button {
|
||||
action_name: Some(action_str),
|
||||
title: Translation::from_translation_key("APP_SETTINGS.OPTION.NONE"),
|
||||
tooltip: None,
|
||||
attribs: vec![],
|
||||
},
|
||||
..Default::default()
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1029,42 +1032,29 @@ fn create_dropdown<B: 'static + BindingsDropdown>(
|
||||
fn create_dropdown_with_cells(
|
||||
mp: &mut MacroParams,
|
||||
parent: WidgetID,
|
||||
mut params: TemplateParams,
|
||||
params: TemplateParams,
|
||||
current_text: Translation,
|
||||
cells: Vec<context_menu::Cell>,
|
||||
) -> anyhow::Result<()> {
|
||||
let id = mp.idx.to_string();
|
||||
mp.idx += 1;
|
||||
params.insert("id", &id);
|
||||
|
||||
mp.parser_state
|
||||
.instantiate_template(mp.doc_params, "DropdownButton", mp.layout, parent, params)?;
|
||||
|
||||
{
|
||||
let mut label = mp
|
||||
.parser_state
|
||||
.fetch_widget_as::<WidgetLabel>(&mp.layout.state, &format!("{id}_value"))?;
|
||||
label.set_text_simple(&mut mp.layout.state.globals.get(), current_text);
|
||||
}
|
||||
|
||||
let btn = mp.parser_state.fetch_component_as::<ComponentButton>(&id)?;
|
||||
btn.on_click(Rc::new({
|
||||
let tasks = mp.tasks.clone();
|
||||
let cells = cells.clone();
|
||||
move |_common, e: ButtonClickEvent| {
|
||||
tasks.push(Task::OpenContextMenu(
|
||||
e.mouse_pos_absolute.unwrap_or_default(),
|
||||
cells.clone(),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
}));
|
||||
|
||||
Ok(())
|
||||
let tasks = mp.tasks.clone();
|
||||
wgui_simple::mount_dropdown(wgui_simple::MountDropdownParams {
|
||||
layout: mp.layout,
|
||||
parser_state: mp.parser_state,
|
||||
doc_params: mp.doc_params,
|
||||
parent,
|
||||
id,
|
||||
params,
|
||||
current_text,
|
||||
cells,
|
||||
on_open: Box::new(move |pos, cells| tasks.push(Task::OpenContextMenu(pos, cells))),
|
||||
})
|
||||
}
|
||||
|
||||
fn apply_subpath(
|
||||
side_mut: &mut Option<OneOrMany<String>>,
|
||||
action_name: &str,
|
||||
side_str: &str,
|
||||
subpath_str: &str,
|
||||
profile: &XrControllerProfile,
|
||||
@@ -1075,6 +1065,12 @@ fn apply_subpath(
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if action_name == "scroll" {
|
||||
*side_mut = Some(OneOrMany::One(format!("/user/hand/{side_str}/input/{subpath_str}")));
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(subpath_obj) = profile.find_userpath(side).and_then(|p| p.find_subpath(subpath)) else {
|
||||
return;
|
||||
};
|
||||
@@ -1087,11 +1083,12 @@ fn apply_subpath(
|
||||
return;
|
||||
};
|
||||
|
||||
let mut parsed_compo = parsed.component;
|
||||
if !subpath_obj.components.contains(&parsed_compo) {
|
||||
parsed_compo = *subpath_obj.components.first().unwrap();
|
||||
let parsed_compo = parsed.component.unwrap_or(*subpath_obj.components.first().unwrap());
|
||||
if subpath_obj.components.contains(&parsed_compo) {
|
||||
parsed_compo
|
||||
} else {
|
||||
*subpath_obj.components.first().unwrap()
|
||||
}
|
||||
parsed_compo
|
||||
} else {
|
||||
*subpath_obj.components.first().unwrap()
|
||||
};
|
||||
@@ -1226,7 +1223,7 @@ fn apply_chord_hand(member: &mut OpenXrInputChordMember, side: XrInputSide, prof
|
||||
};
|
||||
|
||||
let component = parsed
|
||||
.map(|parsed| parsed.component)
|
||||
.and_then(|parsed| parsed.component)
|
||||
.filter(|component| subpath.components.contains(component))
|
||||
.unwrap_or(subpath.components[0]);
|
||||
let path = format!(
|
||||
@@ -1256,8 +1253,12 @@ fn apply_chord_subpath(
|
||||
return;
|
||||
};
|
||||
|
||||
let component = if subpath_obj.components.contains(&parsed.component) {
|
||||
parsed.component
|
||||
let component = if let Some(comp) = parsed.component {
|
||||
if subpath_obj.components.contains(&comp) {
|
||||
comp
|
||||
} else {
|
||||
first_component
|
||||
}
|
||||
} else {
|
||||
first_component
|
||||
};
|
||||
|
||||
@@ -32,6 +32,21 @@ enum Task {
|
||||
ActionClicked(&'static str),
|
||||
}
|
||||
|
||||
pub fn get_entries_yes_no() -> Vec<ButtonEntry> {
|
||||
vec![
|
||||
ButtonEntry {
|
||||
content: Translation::from_translation_key("YES"),
|
||||
icon: "@/dashboard/check.svg",
|
||||
action: "yes",
|
||||
},
|
||||
ButtonEntry {
|
||||
content: Translation::from_translation_key("NO"),
|
||||
icon: "@/dashboard/cancel.svg",
|
||||
action: "no",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
pub struct View {
|
||||
tasks: Tasks<Task>,
|
||||
|
||||
@@ -52,6 +67,13 @@ fn doc_params(globals: &WguiGlobals) -> ParseDocumentParams<'_> {
|
||||
|
||||
impl ViewTrait for View {
|
||||
fn update(&mut self, _par: &mut ViewUpdateParams) -> anyhow::Result<()> {
|
||||
self.tick();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl View {
|
||||
pub fn tick(&mut self) {
|
||||
for task in self.tasks.drain() {
|
||||
match task {
|
||||
Task::ActionClicked(action) => {
|
||||
@@ -65,11 +87,8 @@ impl ViewTrait for View {
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl View {
|
||||
pub fn new(
|
||||
layout: &mut Layout,
|
||||
id_parent: WidgetID,
|
||||
|
||||
@@ -92,7 +92,7 @@ impl ViewTrait for View {
|
||||
par.layout,
|
||||
self.id_loading_parent,
|
||||
Vec2::splat(32.0),
|
||||
AssetPathRef::BuiltIn("@/dashboard/check.svg"),
|
||||
AssetPathRef::BuiltIn("dashboard/check.svg"),
|
||||
)?;
|
||||
|
||||
// "Close window" button
|
||||
@@ -114,7 +114,7 @@ impl ViewTrait for View {
|
||||
par.layout,
|
||||
self.id_loading_parent,
|
||||
Vec2::splat(32.0),
|
||||
AssetPathRef::BuiltIn("@/dashboard/error.svg"),
|
||||
AssetPathRef::BuiltIn("dashboard/error.svg"),
|
||||
)?;
|
||||
}
|
||||
Task::Close => {
|
||||
|
||||
@@ -1,6 +1,32 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Do not run this script if you don't have at least 32 GiB of system memory.
|
||||
# This is the translation tool intended for machine-translating strings.
|
||||
#
|
||||
# ###########################
|
||||
# # Adding new translations #
|
||||
# ###########################
|
||||
#
|
||||
# If you want to add a new language, follow these steps:
|
||||
#
|
||||
# 1. Include the language in `wlx-common/src/locale.rs`:
|
||||
# - add your entry in `enum Language`
|
||||
# - add your entry in Language::code() function
|
||||
# - add your language code in Language::add_codes() function
|
||||
#
|
||||
# 2. Create a new template file
|
||||
# - Add your language code in `scripts/translator/templates/<lang code>.json`
|
||||
# (you can copy one of nearby files and adapt them accordingly)
|
||||
#
|
||||
# 3. Modify this very file
|
||||
# - Go to the bottom and add your language code entry here too
|
||||
#
|
||||
# 4. Run the script (it will hint you which LANG_PATHs are supported)
|
||||
#
|
||||
# ############################################
|
||||
# # Running machine translations (local LLM) #
|
||||
# ############################################
|
||||
#
|
||||
# Do not run this script if you don't have at least ~16 GiB of free system memory.
|
||||
# Submitting PRs that lack translated strings is okay if you don't meet the
|
||||
# system requirements to run this script, or if you simply prefer not to; we are
|
||||
# regularly updating the missing translation strings anyway.
|
||||
@@ -8,15 +34,18 @@
|
||||
# Base language: English (en.json)
|
||||
#
|
||||
# This tool expects llama.cpp server being present (llama-server) at port 8080.
|
||||
#
|
||||
# Currently we're using Gemma 4 for machine translations.
|
||||
# Model URL:
|
||||
# https://huggingface.co/unsloth/gemma-4-31B-it-GGUF
|
||||
# Recommended quantization: Q4_K_M
|
||||
#
|
||||
|
||||
set -e
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
bun install
|
||||
|
||||
# model URL:
|
||||
# https://huggingface.co/unsloth/gemma-4-31B-it-GGUF
|
||||
# you can specify your own if you want to.
|
||||
if [ -z "${MODEL}" ]; then
|
||||
export MODEL="gemma-4-31B-it-Q4_K_M"
|
||||
fi
|
||||
@@ -31,4 +60,5 @@ TEMPLATE="de" bun main.ts
|
||||
TEMPLATE="ja" bun main.ts
|
||||
TEMPLATE="es" bun main.ts
|
||||
TEMPLATE="it" bun main.ts
|
||||
TEMPLATE="sv" bun main.ts
|
||||
TEMPLATE="zh_CN" bun main.ts
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"full_name": "Swedish",
|
||||
"examples": [
|
||||
{
|
||||
"key": "BAR.OPACITY",
|
||||
"en": "Opacity",
|
||||
"translated": "Opacitet"
|
||||
},
|
||||
{
|
||||
"key": "PANEL.CLOSE",
|
||||
"en": "Close panel",
|
||||
"translated": "Stäng panelen"
|
||||
},
|
||||
{
|
||||
"key": "SETTINGS.LEFT_HANDED_MODE",
|
||||
"en": "Left-handed mode",
|
||||
"translated": "Vänsterhänt läge"
|
||||
}
|
||||
]
|
||||
}
|
||||
+8
-5
@@ -1,5 +1,5 @@
|
||||
use glam::{Vec2, vec2};
|
||||
use std::rc::Rc;
|
||||
use std::{rc::Rc, sync::Arc};
|
||||
use testbed::{Testbed, testbed_any::TestbedAny};
|
||||
use tracing_subscriber::EnvFilter;
|
||||
use tracing_subscriber::filter::LevelFilter;
|
||||
@@ -8,7 +8,7 @@ use tracing_subscriber::util::SubscriberInitExt;
|
||||
use vulkan::init_window;
|
||||
use wgui::{
|
||||
event::{MouseButtonEvent, MouseButtonIndex, MouseMotionEvent, MouseWheelEvent},
|
||||
gfx::{CommandBufferUsage, PresentStatus, SwapchainError, WGfxClearMode},
|
||||
gfx::{CommandBufferUsage, PresentStatus, SwapchainError, WGfx, WGfxClearMode},
|
||||
renderer_vk::{self},
|
||||
};
|
||||
use winit::{
|
||||
@@ -48,13 +48,16 @@ fn init_logging() {
|
||||
.init();
|
||||
}
|
||||
|
||||
fn load_testbed(audio: &mut Box<dyn audio::AudioProvider>) -> anyhow::Result<Box<dyn Testbed>> {
|
||||
fn load_testbed(
|
||||
gfx: &Arc<WGfx>,
|
||||
audio: &mut Box<dyn audio::AudioProvider>,
|
||||
) -> anyhow::Result<Box<dyn Testbed>> {
|
||||
let mut assets = Box::new(assets::Asset {});
|
||||
audio.register_wgui_samples(assets.as_mut())?;
|
||||
|
||||
let name = std::env::var("TESTBED").unwrap_or_default();
|
||||
Ok(match name.as_str() {
|
||||
"dashboard" => Box::new(TestbedDashboard::new()?),
|
||||
"dashboard" => Box::new(TestbedDashboard::new(gfx.clone())?),
|
||||
"" => Box::new(TestbedGeneric::new(assets)?),
|
||||
_ => Box::new(TestbedAny::new(assets, &name)?),
|
||||
})
|
||||
@@ -75,7 +78,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut scale = window.scale_factor() as f32;
|
||||
|
||||
let mut audio: Box<dyn audio::AudioProvider> = Box::new(audio::RealAudioProvider::new());
|
||||
let mut testbed = load_testbed(&mut audio)?;
|
||||
let mut testbed = load_testbed(&gfx, &mut audio)?;
|
||||
|
||||
let mut mouse = Vec2::ZERO;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::rc::Rc;
|
||||
use std::{rc::Rc, sync::Arc};
|
||||
|
||||
use crate::testbed::{Testbed, TestbedUpdateParams};
|
||||
use dash_frontend::frontend::{self, FrontendUpdateParams};
|
||||
use wgui::{layout::Layout, theme::WguiTheme};
|
||||
use wgui::{gfx::WGfx, i18n::Locale, layout::Layout, theme::WguiTheme};
|
||||
use wlx_common::{
|
||||
async_executor::AsyncExecutor, dash_interface_emulated::DashInterfaceEmulated,
|
||||
locale::WayVRLangProvider,
|
||||
@@ -14,9 +14,10 @@ pub struct TestbedDashboard {
|
||||
}
|
||||
|
||||
impl TestbedDashboard {
|
||||
pub fn new() -> anyhow::Result<Self> {
|
||||
let interface = DashInterfaceEmulated::new();
|
||||
pub fn new(gfx: Arc<WGfx>) -> anyhow::Result<Self> {
|
||||
let lang_provider = WayVRLangProvider::default();
|
||||
let locale = Locale::from_env(&lang_provider);
|
||||
let interface = DashInterfaceEmulated::new(gfx, &locale);
|
||||
let palette_name = std::env::var("PALETTE").unwrap_or_else(|_| "Default".to_string());
|
||||
let executor = wlx_common::async_executor::create_local();
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ use wlx_common::locale::WayVRLangProvider;
|
||||
#[derive(Clone)]
|
||||
pub enum TestbedTask {
|
||||
ShowPopup,
|
||||
ShowContextMenu(Vec2),
|
||||
ShowContextMenu(context_menu::Position),
|
||||
}
|
||||
|
||||
struct Data {
|
||||
@@ -196,7 +196,7 @@ impl TestbedGeneric {
|
||||
button_context_menu.on_click({
|
||||
let tasks = testbed.tasks.clone();
|
||||
Rc::new(move |_common, m| {
|
||||
tasks.push(TestbedTask::ShowContextMenu(m.boundary.bottom_left()));
|
||||
tasks.push(TestbedTask::ShowContextMenu(m.boundary.into()));
|
||||
Ok(())
|
||||
})
|
||||
});
|
||||
@@ -212,7 +212,9 @@ impl TestbedGeneric {
|
||||
) -> anyhow::Result<()> {
|
||||
match task {
|
||||
TestbedTask::ShowPopup => self.show_popup(params, data)?,
|
||||
TestbedTask::ShowContextMenu(position) => self.show_context_menu(params, data, *position),
|
||||
TestbedTask::ShowContextMenu(position) => {
|
||||
self.show_context_menu(params, data, position.clone())
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -251,7 +253,7 @@ impl TestbedGeneric {
|
||||
&mut self,
|
||||
_params: &mut TestbedUpdateParams,
|
||||
data: &mut Data,
|
||||
position: Vec2,
|
||||
position: context_menu::Position,
|
||||
) {
|
||||
data.context_menu.open(context_menu::OpenParams {
|
||||
on_custom_attribs: Some(Rc::new(move |custom_attribs| {
|
||||
|
||||
@@ -49,9 +49,8 @@ pub enum HandsfreeParams {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct WvrProcessLaunchParams {
|
||||
pub name: String,
|
||||
pub exec: String,
|
||||
pub argv: Vec<String>,
|
||||
pub env: Vec<String>,
|
||||
pub args: String,
|
||||
pub icon: Option<String>,
|
||||
pub resolution: [u32; 2],
|
||||
pub pos_mode: PositionMode,
|
||||
|
||||
+5
-2
@@ -54,7 +54,8 @@ strum.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
uuid = { workspace = true }
|
||||
wayland-client = { workspace = true }
|
||||
wayland-protocols = { workspace = true }
|
||||
wayland-protocols = { workspace = true, features = ["server", "staging"] }
|
||||
wayland-scanner = "0.31.10"
|
||||
wayland-protocols-misc = { workspace = true }
|
||||
winit = { workspace = true, optional = true }
|
||||
xdg.workspace = true
|
||||
@@ -63,6 +64,7 @@ bytes = { workspace = true }
|
||||
chrono-tz = "0.10.4"
|
||||
config = "0.15.25"
|
||||
futures = "0.3.32"
|
||||
image.workspace = true
|
||||
image_dds = {
|
||||
version = "0.7.2",
|
||||
default-features = false,
|
||||
@@ -96,7 +98,8 @@ rosc = { version = "0.11.4", optional = true }
|
||||
serde_yaml = "0.9.33"
|
||||
signal-hook = "0.4.4"
|
||||
smithay = {
|
||||
version = "0.7.0",
|
||||
git = "https://github.com/Smithay/smithay.git",
|
||||
rev = "0d66849533b475c40c0c461ec182d0f939424452",
|
||||
default-features = false,
|
||||
features = [
|
||||
"backend_vulkan",
|
||||
|
||||
@@ -0,0 +1,333 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="xdg_session_management_v1">
|
||||
<copyright>
|
||||
Copyright 2018 Mike Blumenkrantz
|
||||
Copyright 2018 Samsung Electronics Co., Ltd
|
||||
Copyright 2018 Red Hat Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the next
|
||||
paragraph) shall be included in all copies or substantial portions of the
|
||||
Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<description summary="Protocol for managing application sessions">
|
||||
This description provides a high-level overview of the interplay between
|
||||
the interfaces defined this protocol. For details, see the protocol
|
||||
specification.
|
||||
|
||||
The xdg_session_manager protocol declares interfaces necessary to
|
||||
allow clients to restore toplevel state from previous executions. The
|
||||
xdg_session_manager_v1.get_session request can be used to obtain a
|
||||
xdg_session_v1 resource representing the state of a set of toplevels.
|
||||
|
||||
Clients may obtain the session string to use in future calls through
|
||||
the xdg_session_v1.created event. Compositors will use this string
|
||||
as an identifiable token for future runs, possibly storing data about
|
||||
the related toplevels in persistent storage. Clients that wish to
|
||||
track sessions in multiple environments may use the $XDG_CURRENT_DESKTOP
|
||||
environment variable.
|
||||
|
||||
Toplevels are managed through the xdg_session_v1.add_toplevel and
|
||||
xdg_session_v1.remove_toplevel pair of requests. Clients will explicitly
|
||||
request a toplevel to be restored according to prior state through the
|
||||
xdg_session_v1.restore_toplevel request before the toplevel is mapped.
|
||||
|
||||
Compositors may store session information up to any arbitrary level, and
|
||||
apply any limits and policies to the amount of data stored and its lifetime.
|
||||
Clients must account for missing sessions and partial session restoration.
|
||||
|
||||
Warning! The protocol described in this file is currently in the testing
|
||||
phase. Backward compatible changes may be added together with the
|
||||
corresponding interface version bump. Backward incompatible changes can
|
||||
only be done by creating a new major version of the extension.
|
||||
</description>
|
||||
|
||||
<interface name="xdg_session_manager_v1" version="1">
|
||||
<description summary="manage sessions for applications">
|
||||
The xdg_session_manager_v1 interface defines base requests for creating and
|
||||
managing a session for an application. Sessions persist across application
|
||||
and compositor restarts unless explicitly destroyed. A session is created
|
||||
for the purpose of maintaining an application's xdg_toplevel surfaces
|
||||
across compositor or application restarts. The compositor should remember
|
||||
as many states as possible for surfaces in a given session, but there is
|
||||
no requirement for which states must be remembered.
|
||||
|
||||
Policies such as cache eviction are declared an implementation detail of
|
||||
the compositor. Clients should account for no longer existing sessions.
|
||||
</description>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="in_use" summary="a requested session is already in use"
|
||||
value="1"/>
|
||||
<entry name="invalid_session_id" summary="invalid session identifier"
|
||||
value="2"/>
|
||||
<entry name="invalid_reason" summary="invalid reason" value="3"/>
|
||||
</enum>
|
||||
|
||||
<enum name="reason">
|
||||
<description summary="reason for getting a session">
|
||||
The reason may determine in what way a session restores the window
|
||||
management state of associated toplevels.
|
||||
|
||||
For example newly launched applications might be launched on the active
|
||||
workspace with restored size and position, while a recovered
|
||||
application might restore additional state such as active workspace and
|
||||
stacking order.
|
||||
</description>
|
||||
<entry name="launch" value="1">
|
||||
<description summary="an app is newly launched">
|
||||
A new app instance is launched, for example from an app launcher.
|
||||
</description>
|
||||
</entry>
|
||||
<entry name="recover" value="2">
|
||||
<description summary="an app recovered">
|
||||
An app instance is recovering from for example a compositor or app crash.
|
||||
</description>
|
||||
</entry>
|
||||
<entry name="session_restore" value="3">
|
||||
<description summary="an app restored">
|
||||
An app instance is restored, for example part of a restored session, or
|
||||
restored from having been temporarily terminated due to resource
|
||||
constraints.
|
||||
</description>
|
||||
</entry>
|
||||
</enum>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="Destroy this object">
|
||||
Destroy the manager object. The existing session objects will be
|
||||
unaffected.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="get_session">
|
||||
<description summary="create or restore a session">
|
||||
Create a session object corresponding to either an existing session
|
||||
identified by the given session identifier string or a new session.
|
||||
While the session object exists, the session is considered to be "in
|
||||
use".
|
||||
|
||||
If an identifier string represents a session that is currently actively
|
||||
in use by the same client, an 'in_use' error is raised. If some
|
||||
other client is currently using the same session, the new session will
|
||||
replace managing the associated state.
|
||||
|
||||
If the reason is not a valid enum entry, the 'invalid_reason' protocol
|
||||
error is raised.
|
||||
|
||||
NULL is passed to initiate a new session. If a session_id is passed
|
||||
which does not represent a valid session, the compositor treats it as if
|
||||
NULL had been passed.
|
||||
|
||||
The session id string must be UTF-8 encoded. It is also limited by the
|
||||
maximum length of wayland messages (around 4KB). The 'invalid_session_id'
|
||||
protocol error will be raised if an invalid string is provided.
|
||||
|
||||
A client is allowed to have any number of in use sessions at the same
|
||||
time.
|
||||
</description>
|
||||
<arg name="id" type="new_id" interface="xdg_session_v1"/>
|
||||
<arg name="reason" type="uint" enum="reason"
|
||||
summary="reason for session"/>
|
||||
<arg name="session_id" type="string"
|
||||
summary="the session to restore"
|
||||
allow-null="true"/>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="xdg_session_v1" version="1">
|
||||
<description summary="A session for an application">
|
||||
A xdg_session_v1 object represents a session for an application. While the
|
||||
object exists, all surfaces which have been added to the session will
|
||||
have states stored by the compositor which can be reapplied at a later
|
||||
time. Two sessions cannot exist for the same identifier string.
|
||||
|
||||
States for surfaces added to a session are automatically updated by the
|
||||
compositor when they are changed.
|
||||
</description>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="name_in_use"
|
||||
summary="toplevel name is already in use"
|
||||
value="1"/>
|
||||
<entry name="already_mapped"
|
||||
summary="toplevel was already mapped when restored"
|
||||
value="2"/>
|
||||
<entry name="invalid_name"
|
||||
summary="provided toplevel name is invalid"
|
||||
value="3"/>
|
||||
<entry name="already_added"
|
||||
summary="toplevel already added"
|
||||
value="4"/>
|
||||
</enum>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="Destroy the session">
|
||||
Destroy a session object, preserving the current state but not continuing
|
||||
to make further updates if state changes occur. This makes the associated
|
||||
xdg_toplevel_session_v1 objects inert.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="remove" type="destructor">
|
||||
<description summary="Remove the session">
|
||||
Remove the session, making it no longer available for restoration. A
|
||||
compositor should in response to this request remove the data related to
|
||||
this session from its storage.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="add_toplevel">
|
||||
<description summary="add a new surface to the session">
|
||||
Attempt to add a given surface to the session. The passed name is used
|
||||
to identify what window is being restored, and may be used to store
|
||||
window specific state within the session.
|
||||
|
||||
The name given to the toplevel must not correspond to any previously
|
||||
existing toplevel names in the session. If the name matches an already
|
||||
known toplevel name in the session, a 'name_in_use' protocol error will
|
||||
be raised.
|
||||
|
||||
The toplevel object must not be added more than once to any session
|
||||
created by the client, otherwise the 'already_added' protocol error
|
||||
will be raised.
|
||||
|
||||
This request will return a xdg_toplevel_session_v1 for later
|
||||
manipulation. As this resource is created from an empty initial state,
|
||||
compositors must not emit a xdg_toplevel_session_v1.restored event for
|
||||
resources created through this request.
|
||||
|
||||
The name string must be UTF-8 encoded. It is also limited by the maximum
|
||||
length of wayland messages (around 4KB). The 'invalid_name' protocol
|
||||
error will be raised if an invalid string is provided.
|
||||
</description>
|
||||
<arg name="id" type="new_id" interface="xdg_toplevel_session_v1"/>
|
||||
<arg name="toplevel" type="object" interface="xdg_toplevel"/>
|
||||
<arg name="name" type="string" summary="name identifying the toplevel"/>
|
||||
</request>
|
||||
|
||||
<request name="restore_toplevel">
|
||||
<description summary="restore a surface state">
|
||||
Inform the compositor that the toplevel associated with the passed name
|
||||
should have its window management state restored.
|
||||
|
||||
If the toplevel name was previously granted to another xdg_toplevel,
|
||||
the 'name_in_use' protocol error will be raised.
|
||||
|
||||
The toplevel object must not be added more than once to any session
|
||||
created by the client, otherwise the 'already_added' protocol error
|
||||
will be raised.
|
||||
|
||||
This request must be called prior to the first commit on the associated
|
||||
wl_surface after creating the toplevel, otherwise an 'already_mapped'
|
||||
error is raised.
|
||||
|
||||
As part of the initial configure sequence, if the toplevel was
|
||||
successfully restored, a xdg_toplevel_session_v1.restored event is
|
||||
emitted. If the toplevel name was not known in the session, this request
|
||||
will be equivalent to the xdg_toplevel_session_v1.add_toplevel request,
|
||||
and no such event will be emitted. See the xdg_toplevel_session_v1.restored
|
||||
event for further details.
|
||||
|
||||
The name string must be UTF-8 encoded. It is also limited by the maximum
|
||||
length of wayland messages (around 4KB). The 'invalid_name' protocol
|
||||
error will be raised if an invalid string is provided.
|
||||
</description>
|
||||
<arg name="id" type="new_id" interface="xdg_toplevel_session_v1"/>
|
||||
<arg name="toplevel" type="object" interface="xdg_toplevel"/>
|
||||
<arg name="name" type="string" summary="name identifying the toplevel"/>
|
||||
</request>
|
||||
|
||||
<request name="remove_toplevel">
|
||||
<description summary="remove a surface from the session">
|
||||
Remove a specified surface from the session and render any related
|
||||
xdg_toplevel_session_v1 object inert. The compositor should remove any
|
||||
data related to the toplevel in the corresponding session from its internal
|
||||
storage.
|
||||
|
||||
The window is specified by its name in the session. The name string
|
||||
must be encoded in UTF-8, and it is limited in size by the maximum
|
||||
length of wayland messages (around 4KB).
|
||||
</description>
|
||||
<arg name="name" type="string" summary="name identifying the toplevel"/>
|
||||
</request>
|
||||
|
||||
<event name="created">
|
||||
<description summary="newly-created session id">
|
||||
Emitted at most once some time after getting a new session object. It
|
||||
means that no previous state was restored, and a new session was created.
|
||||
The passed id can be persistently stored and used to restore previous
|
||||
sessions.
|
||||
</description>
|
||||
<arg name="session_id" type="string"/>
|
||||
</event>
|
||||
|
||||
<event name="restored">
|
||||
<description summary="the session has been restored">
|
||||
Emitted at most once some time after getting a new session object. It
|
||||
means that previous state was at least partially restored. The same id
|
||||
can again be used to restore previous sessions.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<event name="replaced">
|
||||
<description summary="the session has been replaced">
|
||||
Emitted at most once, if the session was taken over by some other
|
||||
client. When this happens, the session and all its toplevel session
|
||||
objects become inert, and should be destroyed.
|
||||
</description>
|
||||
</event>
|
||||
</interface>
|
||||
|
||||
<interface name="xdg_toplevel_session_v1" version="1">
|
||||
<description summary="A session for an application">
|
||||
A xdg_toplevel_session_v1 resource acts as a handle for the given
|
||||
toplevel in the session. It allows for receiving events after a
|
||||
toplevel state was restored, and has the requests to manage them.
|
||||
</description>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="Destroy the object">
|
||||
Destroy the object. This has no effect over window management of the
|
||||
associated toplevel.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="rename">
|
||||
<description summary="change the name of toplevel session">
|
||||
Renames the toplevel session. The new name can be used in subsequent requests
|
||||
to identify this session object. The state associated with this toplevel
|
||||
session will be preserved.
|
||||
|
||||
If the xdg_session_v1 already contains a toplevel with the specified name,
|
||||
the 'name_in_use' protocol error will be raised.
|
||||
</description>
|
||||
<arg name="name" type="string" summary="new name to identify the toplevel"/>
|
||||
</request>
|
||||
|
||||
<event name="restored">
|
||||
<description summary="a toplevel's session has been restored">
|
||||
The "restored" event is emitted prior to the first
|
||||
xdg_toplevel.configure for the toplevel. It will only be emitted after
|
||||
xdg_session_v1.restore_toplevel, and the initial empty surface state has
|
||||
been applied, and it indicates that the surface's session is being
|
||||
restored with this configure event.
|
||||
</description>
|
||||
</event>
|
||||
</interface>
|
||||
</protocol>
|
||||
@@ -43,14 +43,14 @@
|
||||
"MOVE_PRESS_AND_DRAG": "Bewegen (drücken & ziehen)",
|
||||
"OPACITY": "Undurchsichtigkeit",
|
||||
"POS_ANCHORED": "Verankert: Bewegt sich gemeinsam mit dem Set. Standard.",
|
||||
"POS_FLOATING": "Schwebend: Bewegt sich unabhängig, zentriert sich beim Anzeigen neu.",
|
||||
"POS_HAND_L": "Folgt der linken Hand.",
|
||||
"POS_HAND_R": "Rechter Hand folgen.",
|
||||
"POS_HMD": "Folgt dem HMD.",
|
||||
"POS_STATIC": "Statisch: Nicht Teil eines Sets, kein Recenter.",
|
||||
"POSITIONING": "Positionierung",
|
||||
"RESIZE_PRESS_AND_DRAG": "Größe ändern (drücken & ziehen)",
|
||||
"RESIZE_CORNER": "Zum Vergrößern ziehen"
|
||||
"RESIZE_CORNER": "Zum Vergrößern ziehen",
|
||||
"POS_FLOATING": "Schwebend: Bewegt sich unabhängig, wird mit dem Anker gespeichert."
|
||||
},
|
||||
"DEFINITIONS": {
|
||||
"ANCHORED": "Verankert",
|
||||
@@ -143,7 +143,11 @@
|
||||
"NO_SET_SELECTED": "Kein Set ausgewählt.",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "Einen Controller auf den Boden legen!",
|
||||
"NO_KEYMAPS_CONFIGURED": "Keine Tastaturbelegungen konfiguriert",
|
||||
"NO_KEYMAPS_CONFIGURED_HELP": "Besuch wayvr.org, um zu erfahren, wie du deine Tastaturlayouts einrichtest!"
|
||||
"NO_KEYMAPS_CONFIGURED_HELP": "Besuch wayvr.org, um zu erfahren, wie du deine Tastaturlayouts einrichtest!",
|
||||
"STEAMVR_DASH_TITLE": "SteamVR Dashboard geöffnet",
|
||||
"STEAMVR_DASH_TEXT": "SteamVR-Dashboard schließen, um WayVR zu nutzen.",
|
||||
"WAYVR_RUNNING_TITLE": "WayVR läuft!",
|
||||
"WAYVR_RUNNING_TEXT": "Schau auf dein Handgelenk für die Watch oder nutze die Ein/Aus-Belegung, um zu starten!"
|
||||
},
|
||||
"WATCH": {
|
||||
"ADD_NEW_SET": "Neues Set hinzufügen",
|
||||
@@ -177,7 +181,5 @@
|
||||
"SELECT_SCREEN_CAST_FOR": "Screen-Cast für Display {DISPLAY} auswählen",
|
||||
"CANNOT_CAPTURE_KBD_MOUSE": "Tastatur/Maus-Erfassung konnte nicht initialisiert werden!\nPrüfe, ob dein Benutzer in der Gruppe 'input' ist.",
|
||||
"WE_ARE_GRABBING": "WayVR steuert jetzt deine Tastatur und Maus!\nDrücke Super+Z, um die Steuerung freizugeben."
|
||||
},
|
||||
"STEAMVR_DASH_TOAST_TITLE": "SteamVR Dashboard geöffnet",
|
||||
"STEAMVR_DASH_TOAST_TEXT": "Schließe das SteamVR-Dashboard, um WayVR zu nutzen."
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ the screens were likely selected wrong. See readme.",Tooltip to explain possible
|
||||
EDIT_MODE.MOVE_PRESS_AND_DRAG,Move (press & drag),Tooltip hinting to move the window by pressing and dragging with the pointer
|
||||
EDIT_MODE.OPACITY,Opacity,Title. Adjust transparency of the window
|
||||
EDIT_MODE.POS_ANCHORED,Anchored: Moves together with set. Default.,"Tooltip for a an option that changes movement/positioning behavior. “Anchored” was previously translated as “${DEFINITIONS.ANCHORED}”, “Set” was previously translated as “${DEFINITIONS.SET}”"
|
||||
EDIT_MODE.POS_FLOATING,"Floating: Moves independently, recenters when shown.",Tooltip for a an option that changes movement/positioning behavior. “Floating” was previously translated as “${DEFINITIONS.FLOATING}”
|
||||
EDIT_MODE.POS_FLOATING,"Floating: Moves independently, Saves with the anchor.",Tooltip for a an option that changes movement/positioning behavior. “Floating” was previously translated as “${DEFINITIONS.FLOATING}”
|
||||
EDIT_MODE.POS_HAND_L,Follow the left hand.,Tooltip for a an option that changes movement/positioning behavior.
|
||||
EDIT_MODE.POS_HAND_R,Follow the right hand.,Tooltip for a an option that changes movement/positioning behavior.
|
||||
EDIT_MODE.POS_HMD,Follow the HMD.,Tooltip for a an option that changes movement/positioning behavior.
|
||||
@@ -114,8 +114,10 @@ SCREENCAST.FAIL,Could not initialize screen cast source.,Error message on a wind
|
||||
TOAST.TITLE_SCREENCAST_FAIL,Could not start screen cast source!,Error message on a window that normally displays screen casts.
|
||||
TOAST.CANNOT_ADD_SET,Cannot add set!,Error message telling the user the set could not be added. “Set” was previously translated as “${DEFINITIONS.SET}”
|
||||
TOAST.CANNOT_REMOVE_SET,Cannot remove set!,Error message telling the user the set could not be removed. “Set” was previously translated as “${DEFINITIONS.SET}”
|
||||
STEAMVR_DASH_TOAST_TITLE,SteamVR Dashboard Open,Title of toast shown when SteamVR dashboard is visible
|
||||
STEAMVR_DASH_TOAST_TEXT,Close SteamVR dashboard to start using WayVR.,Body of toast shown when SteamVR dashboard is visible
|
||||
TOAST.STEAMVR_DASH_TITLE,SteamVR Dashboard Open,Title of toast shown when SteamVR dashboard is visible
|
||||
TOAST.STEAMVR_DASH_TEXT,Close SteamVR dashboard to start using WayVR.,Body of toast shown when SteamVR dashboard is visible
|
||||
TOAST.WAYVR_RUNNING_TITLE,WayVR is running!,Title of toast shown when WayVR is started but its windows are hidden by default
|
||||
TOAST.WAYVR_RUNNING_TEXT,"Check your wrist for the watch, or use show-hide binding to get started!",Body of toast shown when WayVR is started but its windows are hidden by default
|
||||
TOAST.DEFAULT_TITLE,Notification,Default title of notifications
|
||||
TOAST.EMPTY_SET,Empty set!,Title of a pop-up when the user switches to an empty set. “Set” was previously translated as “${DEFINITIONS.SET}”
|
||||
TOAST.ERROR,Error,Default title for error pop-ups
|
||||
|
||||
|
@@ -73,7 +73,7 @@
|
||||
"MOVE_PRESS_AND_DRAG": "Move (press & drag)",
|
||||
"OPACITY": "Opacity",
|
||||
"POS_ANCHORED": "Anchored: Moves together with set. Default.",
|
||||
"POS_FLOATING": "Floating: Moves independently, recenters when shown.",
|
||||
"POS_FLOATING": "Floating: Moves independently, Saves with the anchor.",
|
||||
"POS_HAND_L": "Follow the left hand.",
|
||||
"POS_HAND_R": "Follow the right hand.",
|
||||
"POS_HMD": "Follow the HMD.",
|
||||
@@ -133,6 +133,10 @@
|
||||
"TITLE_SCREENCAST_FAIL": "Could not start screen cast source!",
|
||||
"CANNOT_ADD_SET": "Cannot add set!",
|
||||
"CANNOT_REMOVE_SET": "Cannot remove set!",
|
||||
"STEAMVR_DASH_TITLE": "SteamVR Dashboard Open",
|
||||
"STEAMVR_DASH_TEXT": "Close SteamVR dashboard to start using WayVR.",
|
||||
"WAYVR_RUNNING_TITLE": "WayVR is running!",
|
||||
"WAYVR_RUNNING_TEXT": "Check your wrist for the watch, or use show-hide binding to get started!",
|
||||
"DEFAULT_TITLE": "Notification",
|
||||
"EMPTY_SET": "Empty set!",
|
||||
"ERROR": "Error",
|
||||
@@ -145,8 +149,6 @@
|
||||
"NO_SET_SELECTED": "No set is selected.",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "Place one controller on the floor!"
|
||||
},
|
||||
"STEAMVR_DASH_TOAST_TITLE": "SteamVR Dashboard Open",
|
||||
"STEAMVR_DASH_TOAST_TEXT": "Close SteamVR dashboard to start using WayVR.",
|
||||
"WATCH": {
|
||||
"ADD_NEW_SET": "Add a new set",
|
||||
"CLEANUP_MIRRORS": "Remove mirrors that are\nnot currently visible",
|
||||
|
||||
@@ -43,14 +43,14 @@
|
||||
"MOVE_PRESS_AND_DRAG": "Mover (presiona y arrastra)",
|
||||
"OPACITY": "Opacidad",
|
||||
"POS_ANCHORED": "Anclado: Se mueve junto con el set. Por defecto.",
|
||||
"POS_FLOATING": "Flotante: Se mueve independientemente, se recentra al mostrarse.",
|
||||
"POS_HAND_L": "Sigue la mano izquierda.",
|
||||
"POS_HAND_R": "Sigue la mano derecha.",
|
||||
"POS_HMD": "Sigue el HMD.",
|
||||
"POS_STATIC": "Estático: No pertenece a ningún set, sin recentrar.",
|
||||
"POSITIONING": "Posicionamiento",
|
||||
"RESIZE_PRESS_AND_DRAG": "Redimensionar (presiona y arrastra)",
|
||||
"RESIZE_CORNER": "Arrastrar para redimensionar"
|
||||
"RESIZE_CORNER": "Arrastrar para redimensionar",
|
||||
"POS_FLOATING": "Flotante: Se mueve independientemente, se guarda con el ancla."
|
||||
},
|
||||
"DEFINITIONS": {
|
||||
"ANCHORED": "Anclado",
|
||||
@@ -143,7 +143,11 @@
|
||||
"NO_SET_SELECTED": "No hay ningún set seleccionado.",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "¡Coloca un controlador en el suelo!",
|
||||
"NO_KEYMAPS_CONFIGURED": "Sin distribuciones de teclado configuradas",
|
||||
"NO_KEYMAPS_CONFIGURED_HELP": "¡Consulta wayvr.org para configurar tus diseños de teclado!"
|
||||
"NO_KEYMAPS_CONFIGURED_HELP": "¡Consulta wayvr.org para configurar tus diseños de teclado!",
|
||||
"STEAMVR_DASH_TITLE": "Panel de SteamVR abierto",
|
||||
"STEAMVR_DASH_TEXT": "Cierra el tablero de SteamVR para empezar a usar WayVR.",
|
||||
"WAYVR_RUNNING_TITLE": "¡WayVR está ejecutándose!",
|
||||
"WAYVR_RUNNING_TEXT": "¡Mira tu muñeca para ver el reloj o usa el mando de mostrar/ocultar para empezar!"
|
||||
},
|
||||
"WATCH": {
|
||||
"ADD_NEW_SET": "Añadir un set nuevo",
|
||||
@@ -177,7 +181,5 @@
|
||||
"SELECT_SCREEN_CAST_FOR": "Seleccionar transmisión de pantalla para la pantalla {DISPLAY}",
|
||||
"CANNOT_CAPTURE_KBD_MOUSE": "¡No se pudo inicializar la captura de teclado/ratón!\nComprueba que tu usuario esté en el grupo 'input'.",
|
||||
"WE_ARE_GRABBING": "¡WayVR tiene el control de tu teclado y ratón!\nPulsa Super+Z para liberarlos."
|
||||
},
|
||||
"STEAMVR_DASH_TOAST_TITLE": "Panel de SteamVR abierto",
|
||||
"STEAMVR_DASH_TOAST_TEXT": "Cierra el tablero de SteamVR para empezar a usar WayVR."
|
||||
}
|
||||
}
|
||||
@@ -43,14 +43,14 @@
|
||||
"MOVE_PRESS_AND_DRAG": "Sposta (premi e trascina)",
|
||||
"OPACITY": "Opacità",
|
||||
"POS_ANCHORED": "Ancorato: Si muove insieme al set. Predefinito.",
|
||||
"POS_FLOATING": "Fluttuante: si muove indipendentemente, si ricentra quando appare.",
|
||||
"POS_HAND_L": "Segui la mano sinistra.",
|
||||
"POS_HAND_R": "Segui la mano destra.",
|
||||
"POS_HMD": "Segue l'HMD.",
|
||||
"POS_STATIC": "Statico: non fa parte di alcun set, nessun ricentramento.",
|
||||
"POSITIONING": "Posizionamento",
|
||||
"RESIZE_PRESS_AND_DRAG": "Ridimensiona (premi e trascina)",
|
||||
"RESIZE_CORNER": "Trascina per ridimensionare"
|
||||
"RESIZE_CORNER": "Trascina per ridimensionare",
|
||||
"POS_FLOATING": "Fluttuante: si muove indipendentemente, salva con l'ancora."
|
||||
},
|
||||
"DEFINITIONS": {
|
||||
"ANCHORED": "Ancorato",
|
||||
@@ -143,7 +143,11 @@
|
||||
"NO_SET_SELECTED": "Nessun set selezionato.",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "Appoggia un controller sul pavimento!",
|
||||
"NO_KEYMAPS_CONFIGURED": "Nessuna disposizione tastiera configurata",
|
||||
"NO_KEYMAPS_CONFIGURED_HELP": "Consulta wayvr.org per configurare i layout della tastiera!"
|
||||
"NO_KEYMAPS_CONFIGURED_HELP": "Consulta wayvr.org per configurare i layout della tastiera!",
|
||||
"STEAMVR_DASH_TITLE": "Dashboard SteamVR Aperta",
|
||||
"STEAMVR_DASH_TEXT": "Chiudi il pannello di SteamVR per iniziare a usare WayVR.",
|
||||
"WAYVR_RUNNING_TITLE": "WayVR è attivo!",
|
||||
"WAYVR_RUNNING_TEXT": "Controlla il polso per l'orologio, o usa il comando show-hide per iniziare!"
|
||||
},
|
||||
"WATCH": {
|
||||
"ADD_NEW_SET": "Aggiungi un nuovo set",
|
||||
@@ -177,7 +181,5 @@
|
||||
"SELECT_SCREEN_CAST_FOR": "Seleziona la trasmissione schermo per il display {DISPLAY}",
|
||||
"CANNOT_CAPTURE_KBD_MOUSE": "Impossibile inizializzare la cattura di tastiera/mouse!\nControlla che il tuo utente sia nel gruppo 'input'.",
|
||||
"WE_ARE_GRABBING": "WayVR sta controllando tastiera e mouse!\nPremi Super+Z per rilasciare il controllo."
|
||||
},
|
||||
"STEAMVR_DASH_TOAST_TITLE": "Dashboard di SteamVR aperta",
|
||||
"STEAMVR_DASH_TOAST_TEXT": "Chiudi il pannello di SteamVR per iniziare a usare WayVR."
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,6 @@
|
||||
"LOCK_INTERACTION": "インタラクションをロック",
|
||||
"MOVE_PRESS_AND_DRAG": "移動(押しながらドラッグ)",
|
||||
"OPACITY": "不透明度",
|
||||
"POS_FLOATING": "フローティング:独立して移動し、表示時に中心位置がリセットされる。",
|
||||
"POS_HAND_L": "左手に追従",
|
||||
"POS_HAND_R": "右手に従う",
|
||||
"POS_HMD": "HMDに従う",
|
||||
@@ -50,7 +49,8 @@
|
||||
"POSITIONING": "配置",
|
||||
"RESIZE_PRESS_AND_DRAG": "リサイズ (押し込みながらドラッグ)",
|
||||
"RESIZE_CORNER": "ドラッグしてリサイズ",
|
||||
"POS_ANCHORED": "セット追従:セットと一緒に移動。デフォルト。"
|
||||
"POS_ANCHORED": "セット追従:セットと一緒に移動。デフォルト。",
|
||||
"POS_FLOATING": "フローティング:独立して移動し、アンカーと共に保存される。"
|
||||
},
|
||||
"DEFINITIONS": {
|
||||
"ANCHORED": "セット追従",
|
||||
@@ -143,7 +143,11 @@
|
||||
"NO_SET_SELECTED": "セットが選択されていません。",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "コントローラーを片方、床に置いてください!",
|
||||
"NO_KEYMAPS_CONFIGURED": "キーボードレイアウトが設定されていない",
|
||||
"NO_KEYMAPS_CONFIGURED_HELP": "キーボードレイアウトの設定方法は wayvr.org を確認してね!"
|
||||
"NO_KEYMAPS_CONFIGURED_HELP": "キーボードレイアウトの設定方法は wayvr.org を確認してね!",
|
||||
"STEAMVR_DASH_TITLE": "SteamVRダッシュボード展開中",
|
||||
"STEAMVR_DASH_TEXT": "WayVRを使用するには、SteamVRダッシュボードを閉じてください。",
|
||||
"WAYVR_RUNNING_TITLE": "WayVR 実行中!",
|
||||
"WAYVR_RUNNING_TEXT": "手首のウォッチを確認するか、表示・非表示のバインドを使用して開始してください!"
|
||||
},
|
||||
"WATCH": {
|
||||
"ADD_NEW_SET": "新しいセットを追加",
|
||||
@@ -177,7 +181,5 @@
|
||||
"SELECT_SCREEN_CAST_FOR": "ディスプレイ {DISPLAY} のスクリーンキャストを選択してください",
|
||||
"CANNOT_CAPTURE_KBD_MOUSE": "キーボード/マウスのキャプチャを初期化できませんでした!\nユーザーが 'input' グループに所属しているか確認してください。",
|
||||
"WE_ARE_GRABBING": "WayVR がキーボードとマウスを制御しています。\n解除するには、Super+Zを押してください。"
|
||||
},
|
||||
"STEAMVR_DASH_TOAST_TITLE": "SteamVRダッシュボード展開中",
|
||||
"STEAMVR_DASH_TOAST_TEXT": "WayVRを使用するには、SteamVRダッシュボードを閉じてください。"
|
||||
}
|
||||
}
|
||||
@@ -43,14 +43,14 @@
|
||||
"MOVE_PRESS_AND_DRAG": "Przesuń (przyciśnij i przeciągnij)",
|
||||
"OPACITY": "Przezroczystość",
|
||||
"POS_ANCHORED": "Przytwierdzone: Przesuwa się wraz z zestawem. Domyślnie.",
|
||||
"POS_FLOATING": "Pływający: przesuwa się niezależnie, centruje się po wyświetleniu.",
|
||||
"POS_HAND_L": "Podążaj za lewą ręką.",
|
||||
"POS_HAND_R": "Podążaj za prawą ręką.",
|
||||
"POS_HMD": "Podążaj za HMD.",
|
||||
"POS_STATIC": "Stacjonarny: Poza jakimkolwiek zestawem, brak recentrowania.",
|
||||
"POSITIONING": "Pozycjonowanie",
|
||||
"RESIZE_PRESS_AND_DRAG": "Zmień rozmiar (naciśnij i przeciągnij)",
|
||||
"RESIZE_CORNER": "Przeciągnij, aby zmienić rozmiar"
|
||||
"RESIZE_CORNER": "Przeciągnij, aby zmienić rozmiar",
|
||||
"POS_FLOATING": "Pływający: przesuwa się niezależnie, zapisuje wraz z kotwicą."
|
||||
},
|
||||
"DEFINITIONS": {
|
||||
"ANCHORED": "Przytwierdzone",
|
||||
@@ -143,7 +143,11 @@
|
||||
"NO_SET_SELECTED": "Żaden zestaw nie jest wybrany.",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "Połóż jeden kontroler na podłodze!",
|
||||
"NO_KEYMAPS_CONFIGURED": "Brak skonfigurowanych układów klawiatury",
|
||||
"NO_KEYMAPS_CONFIGURED_HELP": "Odwiedź stronę wayvr.org, aby dowiedzieć się, jak skonfigurować układy klawiatury!"
|
||||
"NO_KEYMAPS_CONFIGURED_HELP": "Odwiedź stronę wayvr.org, aby dowiedzieć się, jak skonfigurować układy klawiatury!",
|
||||
"STEAMVR_DASH_TITLE": "Panel SteamVR otwarty",
|
||||
"STEAMVR_DASH_TEXT": "Zamknij pulpit SteamVR, aby zacząć korzystać z WayVR.",
|
||||
"WAYVR_RUNNING_TITLE": "WayVR działa!",
|
||||
"WAYVR_RUNNING_TEXT": "Spójrz na nadgarstek, by zobaczyć zegarek, lub użyj przycisku pokaż/ukryj, aby zacząć!"
|
||||
},
|
||||
"WATCH": {
|
||||
"ADD_NEW_SET": "Dodaj nowy zestaw",
|
||||
@@ -177,7 +181,5 @@
|
||||
"SELECT_SCREEN_CAST_FOR": "Wybierz przesyłanie ekranu dla wyświetlacza {DISPLAY}",
|
||||
"CANNOT_CAPTURE_KBD_MOUSE": "Nie można zainicjować przechwytywania klawiatury/myszy!\nSprawdź, czy Twój użytkownik należy do grupy 'input'.",
|
||||
"WE_ARE_GRABBING": "WayVR przejął kontrolę nad klawiaturą i myszą!\nNaciśnij Super+Z, aby zwolnić."
|
||||
},
|
||||
"STEAMVR_DASH_TOAST_TITLE": "Panel SteamVR otwarty",
|
||||
"STEAMVR_DASH_TOAST_TEXT": "Zamknij pulpit SteamVR, aby zacząć korzystać z WayVR."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
{
|
||||
"DEFINITIONS": {
|
||||
"ANCHORED": "Förankrad",
|
||||
"FLOATING": "Flytande",
|
||||
"STATIC": "Statisk",
|
||||
"SET": "Set",
|
||||
"WATCH": "Klocka",
|
||||
"DASHBOARD": "Kontrollpanel",
|
||||
"EDIT_MODE": "Redigeringsläge",
|
||||
"MIRROR": "Spegling",
|
||||
"SETTINGS": "Inställningar",
|
||||
"KEYBOARD": "Tangentbord",
|
||||
"TASKBAR": "Uppgiftspanel",
|
||||
"FEATURES": "Funktioner",
|
||||
"OVERLAY": "Overlay"
|
||||
},
|
||||
"ANCHOR": {
|
||||
"CENTER": "Center"
|
||||
},
|
||||
"BAR": {
|
||||
"ADD_MIRROR": "Lägg till ny spegling-overlay",
|
||||
"ADD_NEW_SET": "Lägg till ny set",
|
||||
"ADD_PASSTHRU": "Lägg till ny passthrough-utskärning",
|
||||
"CLOSE_APP": "Stäng appen",
|
||||
"CLOSE_OVERLAY": "Stäng {$DEFINITIONS.OVERLAY}",
|
||||
"DELETE_CURRENT_SET": "Ta bort nuvarande set",
|
||||
"EDIT_MODE_TOGGLE": "Växla redigeringsläge",
|
||||
"FORCE_CLOSE_APP": "Tvinga stäng app",
|
||||
"HANDSFREE": {
|
||||
"EYE_ONLY": "Ögonsiktet",
|
||||
"EYE_TRACKING": "Öga + pinch",
|
||||
"HMD": "HMD + pinch",
|
||||
"HMD_ONLY": "HMD-laser",
|
||||
"NONE": "Av",
|
||||
"TITLE": "Händsfree-läge"
|
||||
},
|
||||
"RELOAD_FROM_DISK": "Ladda om XML från disk",
|
||||
"RESET_POSITION": "Återställ position",
|
||||
"TOGGLE_VISIBILITY": "Växla synlighet"
|
||||
},
|
||||
"DEFAULT": "Standard",
|
||||
"DISABLED": "Inaktiverat",
|
||||
"DONE": "Klar",
|
||||
"EDIT_MODE": {
|
||||
"ADJUST_CURVATURE": "Justera krökning",
|
||||
"ALIGN_TO_HMD": "Rikta mot HMD",
|
||||
"ALPHA_BLEND_MODE": "Alfablendingsläge",
|
||||
"ANGLE_FADE": "Vinkeltoning",
|
||||
"ANGLE_FADE_HELP": "Tona ut när den inte är vänd mot HMD",
|
||||
"BLENDING_ADDITIVE": "Additiv blandning",
|
||||
"BLOCK_INPUT": "Blockera spel-input",
|
||||
"CURVATURE": "Krökning",
|
||||
"DELETE": "Håll inne för att ta bort från nuvarande set",
|
||||
"DISABLE_GRAB": "Inaktivera grepp",
|
||||
"GLOBAL": "Alltid synlig",
|
||||
"HINT_POINT_WINDOW": "Peka på ett fönster för att ändra dess parametrar.\nNär du är klar, lämna redigeringsläget via knappen till höger.",
|
||||
"INTERPOLATION": "Interpolation",
|
||||
"KEYBOARD": "Tangentbord",
|
||||
"LEAVE": "Lämna redigeringsläge",
|
||||
"LOCK_INTERACTION": "Lås interaktion",
|
||||
"MOUSE": {
|
||||
"FLIP180": "Speglad 180°",
|
||||
"FLIP270": "Speglad 270°",
|
||||
"FLIP90": "Speglad 90°",
|
||||
"FLIPPED": "Speglad",
|
||||
"NORMAL": "Normal",
|
||||
"ROTATE180": "Roterad 180°",
|
||||
"ROTATE270": "Roterad 270°",
|
||||
"ROTATE90": "Roterad 90°",
|
||||
"TITLE": "Musfixar",
|
||||
"WRONG_SCREEN_SELECTION_HELP": "Om markören rör sig på en helt annan skärm\nhar skärmarna troligtvis valts fel. Se readme."
|
||||
},
|
||||
"MOVE_PRESS_AND_DRAG": "Flytta (tryck & dra)",
|
||||
"OPACITY": "Opacitet",
|
||||
"POS_ANCHORED": "Förankrad: Flyttas tillsammans med set. Standard.",
|
||||
"POS_HAND_L": "Följ vänster hand.",
|
||||
"POS_HAND_R": "Följ höger hand.",
|
||||
"POS_HMD": "Följ HMD.",
|
||||
"POS_STATIC": "Statisk: Inte en del av något set, ingen återcentrering.",
|
||||
"POSITIONING": "Positionering",
|
||||
"RESIZE_PRESS_AND_DRAG": "Ändra storlek (tryck & dra)",
|
||||
"RESIZE_CORNER": "Dra för att ändra storlek",
|
||||
"STEREO_3D_MODE": {
|
||||
"ADJUST_MOUSE": "Justera mus",
|
||||
"FULL_FRAME": "Full 3D",
|
||||
"FULL_FRAME_BAT": "Full-BAT",
|
||||
"FULL_FRAME_SBS": "Full-SBS",
|
||||
"FULL_FRAME_TAB": "Full-TAB",
|
||||
"SPLIT_BOTTOM_TOP": "NEDRE→ÖVRE",
|
||||
"SPLIT_LEFT_RIGHT": "VÄNSTRER→HÖGER",
|
||||
"SPLIT_RIGHT_LEFT": "HÖGER→VÄNSTER",
|
||||
"SPLIT_TOP_BOTTOM": "TOPP→BOTTEN",
|
||||
"TITLE": "3D-stereoläge"
|
||||
},
|
||||
"POS_FLOATING": "Flytande: Flyttas oberoende, sparas med ankaret."
|
||||
},
|
||||
"ALTTAB": {
|
||||
"RELEASE_TO_FOCUS": "Sikta på en app och släpp Super för att fokusera på den.",
|
||||
"WHILE_AIMING": "Vid sikte",
|
||||
"CLICK": "VR-pekarklick",
|
||||
"GRABALL": "Flytta hela setet",
|
||||
"GRAB": "Flytta enskild overlay",
|
||||
"DASH": "Växla WayVR Dashboard",
|
||||
"EDIT": "Växla redigeringsläge",
|
||||
"KEYBOARD": "Växla tangentbord",
|
||||
"SET": "Byt set",
|
||||
"RECENTER": "Visa/dölj/centrera",
|
||||
"GENERAL_BINDS": "Allmänna bindningar",
|
||||
"TOGGLE_CAPTURE": "Återlämna kontrollen till skrivbordet",
|
||||
"FOCUS_MODE": "Fokusläge (håll in Super)",
|
||||
"CLOSE": "Stäng fokuserad app",
|
||||
"MOUSE": "Klick och skroll med fysisk mus översätts även till VR-pekaren"
|
||||
},
|
||||
"GRAB": {
|
||||
"ADJUST_DISTANCE": "Justera avstånd",
|
||||
"ADJUST_SIZE": "Justera storlek",
|
||||
"GRABBING_ANCHORED": "Förankrade överlägg rör sig med setet. Separera ett enskilt fönster genom att greppa det med den andra handen samtidigt som du håller i setet.",
|
||||
"GRABBING_ANCHORED_EDIT": "Det här överlägget kommer att förbli förankrat vid centreringsmarkören.",
|
||||
"GRABBING_FLOATING": "Det här överlägget är flytande och kommer att stanna på plats, om det inte centreras om.",
|
||||
"GRABBING_FOLLOW": "Detta overlay följer den enhet den är kopplad till.",
|
||||
"GRABBING_STATIC": "Det här överlägget är statiskt och kommer att stanna kvar på platsen, oavsett om du centrerar om.",
|
||||
"GRABBING_WATCH": "För att byta hand, flytta klockan framför dig och greppa den med den andra handen.",
|
||||
"UNRESTRICTED_MOVEMENT": "Obegränsad rörelse"
|
||||
},
|
||||
"SCREENCAST": {
|
||||
"PENDING": "Vänligen vänta...",
|
||||
"QUEUED": "Väntar på att en annan overlays skärmdelningsförfrågan ska godkännas.",
|
||||
"WAITING_FOR_USER_DESC": "Kontrollera skrivbordet efter ett popup-fönster för skärmdelning.\nVälj följande:",
|
||||
"WAITING_FOR_USER": "Kontrollera din skrivbordsdisplay för ett popup-fönster om skärmdelning.",
|
||||
"FAIL": "Kunde inte initiera källan för skärmöverföring."
|
||||
},
|
||||
"TOAST": {
|
||||
"TITLE_SCREENCAST_FAIL": "Kunde inte starta källan för skärmdelning!",
|
||||
"CANNOT_ADD_SET": "Kunde inte lägga till set!",
|
||||
"CANNOT_REMOVE_SET": "Kan inte ta bort set!",
|
||||
"STEAMVR_DASH_TITLE": "SteamVR-kontrollpanelen är öppen",
|
||||
"STEAMVR_DASH_TEXT": "Stäng SteamVR-panelen för att börja använda WayVR.",
|
||||
"WAYVR_RUNNING_TITLE": "WayVR körs!",
|
||||
"WAYVR_RUNNING_TEXT": "Kolla handleden för klockan, eller använd bindningen för visa/dölj för att komma igång!",
|
||||
"DEFAULT_TITLE": "Avisering",
|
||||
"EMPTY_SET": "Tomt set!",
|
||||
"ERROR": "Fel",
|
||||
"FIXING_FLOOR_IN_X_SECS": "Justerar golvet om {SECONDS} sekunder...",
|
||||
"LAST_EXISTING_SET": "Detta är det sista befintliga setet.",
|
||||
"LETS_ADD_OVERLAYS": "Låt oss lägga till några overlays från uppgiftspanelen!",
|
||||
"MAXIMUM_SETS_REACHED": "Maximalt antal set har uppnåtts.",
|
||||
"NO_KEYMAPS_CONFIGURED": "Inga tangentbordslayouts konfigurerade",
|
||||
"NO_KEYMAPS_CONFIGURED_HELP": "Kolla wayvr.org för att se hur du ställer in dina tangentbordslayouter!",
|
||||
"NO_SET_SELECTED": "Inget Set är valt.",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "Placera en kontroller på golvet!"
|
||||
},
|
||||
"WATCH": {
|
||||
"ADD_NEW_SET": "Lägg till ett nytt set",
|
||||
"CLEANUP_MIRRORS": "Ta bort speglingar som\ninte är synliga just nu",
|
||||
"DASHBOARD": "Kontrollpanel",
|
||||
"EDIT_MODE": "Redigeringsläge",
|
||||
"FIX_FLOOR": "Justera golvnivå",
|
||||
"LONG_PRESS_TO_DELETE_SET": "Håll för att ta bort",
|
||||
"RECENTER": "Centrera playspace",
|
||||
"SWITCH_TO_SET": "Byt till set",
|
||||
"TOGGLE_FOR_CURRENT_SET": "Växla för nuvarande set"
|
||||
},
|
||||
"WHISPER": {
|
||||
"TRANSCRIBE": "Håll ned för att transkribera text",
|
||||
"PASTE": "Kopiera transkribering till urklipp",
|
||||
"PASTE_AND_GO": "Kopiera transkribering och skicka Ctrl-V till fokuserad app",
|
||||
"SEND_OSC": "Skicka transkription till OSC-chatt",
|
||||
"UNLOAD_AND_CLOSE": "Avlasta modell och stäng",
|
||||
"MODEL_NOT_DOWNLOADED": "Tal-till-text är inte konfigurerat!",
|
||||
"DOWNLOAD_GUIDANCE": "Konfigurera tal-till-text under inställningar, funktioner!",
|
||||
"INITIAL_TEXT": "Håll inne mikroknappen för att börja transkribera...",
|
||||
"INIT_ERROR": "Fel vid start av Whisper-motorn",
|
||||
"ENTER_KEY": "Enter-tangent"
|
||||
},
|
||||
"NOTIFICATION": {
|
||||
"COUD_NOT_CONNECT_TO_VR_RUNTIME": "Kunde inte ansluta till VR-runtime.",
|
||||
"NOT_RUNNING_XR_VR": "Se till att Monado, WiVRn eller SteamVR är igång.",
|
||||
"NOT_RUNNING_VR": "Se till att Monado, WiVRn eller SteamVR är igång.",
|
||||
"NOT_RUNNING_XR": "Se till att Monado eller WiVRn är igång.",
|
||||
"INVALID_ARGS": "Kontrollera dina startargument.",
|
||||
"SELECT_SCREEN_CAST_FOR": "Välj skärmdelning för display {DISPLAY}",
|
||||
"CANNOT_CAPTURE_KBD_MOUSE": "Kunde inte initiera fångst av tangentbord/mus!\nKontrollera att din användare tillhör gruppen 'input'.",
|
||||
"WE_ARE_GRABBING": "WayVR styr nu ditt tangentbord och din mus!\nTryck Super+Z för att släppa."
|
||||
}
|
||||
}
|
||||
@@ -43,14 +43,14 @@
|
||||
"MOVE_PRESS_AND_DRAG": "移动 (按下并拖拽)",
|
||||
"OPACITY": "不透明度",
|
||||
"POS_ANCHORED": "同步移动:随套装一起移动。默认。",
|
||||
"POS_FLOATING": "悬浮:独立移动,显示时自动居中。",
|
||||
"POS_HAND_L": "跟随左手",
|
||||
"POS_HAND_R": "跟随右手。",
|
||||
"POS_HMD": "跟随 HMD",
|
||||
"POS_STATIC": "静态:不属于任何套装,不重新居中。",
|
||||
"POSITIONING": "位置设定",
|
||||
"RESIZE_PRESS_AND_DRAG": "调整大小(按住并拖动)",
|
||||
"RESIZE_CORNER": "拖动以调整大小"
|
||||
"RESIZE_CORNER": "拖动以调整大小",
|
||||
"POS_FLOATING": "悬浮:独立移动,随锚点保存。"
|
||||
},
|
||||
"DEFINITIONS": {
|
||||
"ANCHORED": "同步移动",
|
||||
@@ -143,7 +143,11 @@
|
||||
"NO_SET_SELECTED": "未选择套装。",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "将一只控制器放在地面上!",
|
||||
"NO_KEYMAPS_CONFIGURED": "尚未配置键盘布局",
|
||||
"NO_KEYMAPS_CONFIGURED_HELP": "请访问 wayvr.org 了解如何设置键盘布局!"
|
||||
"NO_KEYMAPS_CONFIGURED_HELP": "请访问 wayvr.org 了解如何设置键盘布局!",
|
||||
"STEAMVR_DASH_TITLE": "SteamVR 控制面板已开启",
|
||||
"STEAMVR_DASH_TEXT": "关闭 SteamVR 仪表板以开始使用 WayVR。",
|
||||
"WAYVR_RUNNING_TITLE": "WayVR 正在运行!",
|
||||
"WAYVR_RUNNING_TEXT": "查看手腕上的手表,或使用显示/隐藏快捷键开始!"
|
||||
},
|
||||
"WATCH": {
|
||||
"ADD_NEW_SET": "添加新套装",
|
||||
@@ -177,7 +181,5 @@
|
||||
"SELECT_SCREEN_CAST_FOR": "为显示器 {DISPLAY} 选择屏幕投射",
|
||||
"CANNOT_CAPTURE_KBD_MOUSE": "无法初始化键盘/鼠标捕获!\n请检查您的用户是否在 'input' 组中。",
|
||||
"WE_ARE_GRABBING": "WayVR 正在接管你的键盘和鼠标!\n按 Super+Z 释放。"
|
||||
},
|
||||
"STEAMVR_DASH_TOAST_TITLE": "SteamVR 控制面板已开启",
|
||||
"STEAMVR_DASH_TOAST_TEXT": "关闭 SteamVR 仪表板以开始使用 WayVR。"
|
||||
}
|
||||
}
|
||||
@@ -874,11 +874,11 @@ fn start_grab(par: StartGrabParams) {
|
||||
// - not in editmode
|
||||
// - not using grab_float
|
||||
// - grabbing with one hand. (grabbing with the 2nd hand will grab the individual overlay instead)
|
||||
let grab_anchor = !par.edit_mode
|
||||
&& !par.grab_float
|
||||
&& !app.anchor_grabbed
|
||||
&& matches!(state.positioning, Positioning::Anchored)
|
||||
&& !(app.session.config.middle_grabs_single
|
||||
let grab_anchor = !(par.edit_mode
|
||||
|| par.grab_float
|
||||
|| app.anchor_grabbed
|
||||
|| !matches!(state.positioning, Positioning::Anchored)
|
||||
|| app.session.config.middle_grabs_single
|
||||
&& pointer.interaction.mode == PointerMode::Middle);
|
||||
|
||||
let relative_grab_transform = if grab_anchor {
|
||||
|
||||
@@ -138,6 +138,10 @@ impl OpenVrInputSource {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn open_bindings(&mut self, input: &mut InputManager) {
|
||||
let _ = input.open_binding_ui(None, None, self.hands[0].input_hnd, false);
|
||||
}
|
||||
|
||||
pub fn haptics(&mut self, input: &mut InputManager, hand: usize, haptics: &Haptics) {
|
||||
let action_handle = self.hands[hand].haptics_hnd;
|
||||
let _ = input.trigger_haptic_vibration_action(
|
||||
|
||||
@@ -214,9 +214,9 @@ pub fn openvr_run(args: &Args, params: RunParams) -> Result<(), BackendError> {
|
||||
let id = Toast::new(
|
||||
ToastTopic::Priority,
|
||||
Some(Translation::from_translation_key(
|
||||
"STEAMVR_DASH_TOAST_TITLE",
|
||||
"TOAST.STEAMVR_DASH_TITLE",
|
||||
)),
|
||||
Translation::from_translation_key("STEAMVR_DASH_TOAST_TEXT"),
|
||||
Translation::from_translation_key("TOAST.STEAMVR_DASH_TEXT"),
|
||||
)
|
||||
.with_timeout(300.0) // we'll zero this if dash gets hidden
|
||||
.submit(&mut app);
|
||||
@@ -227,6 +227,16 @@ pub fn openvr_run(args: &Args, params: RunParams) -> Result<(), BackendError> {
|
||||
dashboard_hidden = true;
|
||||
if let Some(id) = dash_toast_id {
|
||||
Toast::expire_now(&mut app, id);
|
||||
} else if !args.show {
|
||||
Toast::new(
|
||||
ToastTopic::Priority,
|
||||
Some(Translation::from_translation_key(
|
||||
"TOAST.WAYVR_RUNNING_TITLE",
|
||||
)),
|
||||
Translation::from_translation_key("TOAST.WAYVR_RUNNING_TEXT"),
|
||||
)
|
||||
.with_timeout(5.0)
|
||||
.submit(&mut app);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,6 +270,13 @@ pub fn openvr_run(args: &Args, params: RunParams) -> Result<(), BackendError> {
|
||||
OpenVrTask::ColorGain(channel, value) => {
|
||||
let _ = adjust_gain(&mut settings_mgr, channel, value);
|
||||
}
|
||||
OpenVrTask::ShowBindingsUI => {
|
||||
// hide current set because overlay sort order is broken
|
||||
// and will cover the bindings UI while not taking input
|
||||
overlays.switch_to_set(&mut app, None, false);
|
||||
|
||||
input_source.open_bindings(&mut input_mgr);
|
||||
}
|
||||
},
|
||||
#[cfg(feature = "openxr")]
|
||||
TaskType::OpenXR(_) => {}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use core::f32;
|
||||
use std::sync::Arc;
|
||||
|
||||
use glam::Vec4;
|
||||
@@ -271,7 +270,7 @@ impl OverlayWindowData<OpenVrOverlayData> {
|
||||
};
|
||||
|
||||
let Some(view) = self.data.image_view.as_ref() else {
|
||||
log::debug!("{}: Not rendered", self.config.name);
|
||||
log::trace!("{}: Not rendered", self.config.name);
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -339,15 +339,6 @@ impl PlayspaceMover {
|
||||
);
|
||||
self.universe = new_universe;
|
||||
}
|
||||
|
||||
if self.drag.is_some() {
|
||||
log::info!("Space drag interrupted by external change");
|
||||
self.drag = None;
|
||||
}
|
||||
if self.rotate.is_some() {
|
||||
log::info!("Space rotate interrupted by external change");
|
||||
self.rotate = None;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_universe(&self) -> ETrackingUniverseOrigin {
|
||||
|
||||
@@ -723,7 +723,7 @@ fn for_each_path(spec: Option<&OneOrMany<String>>, mut f: impl FnMut(&str)) {
|
||||
}
|
||||
}
|
||||
|
||||
fn button_actions(profile: &OpenXrInputProfile) -> [Option<&OpenXrInputAction>; 11] {
|
||||
const fn button_actions(profile: &OpenXrInputProfile) -> [Option<&OpenXrInputAction>; 11] {
|
||||
[
|
||||
profile.click.as_ref(),
|
||||
profile.alt_click.as_ref(),
|
||||
@@ -925,7 +925,7 @@ fn add_button_bindings(
|
||||
|
||||
for (side, hand) in hands.iter_mut().enumerate() {
|
||||
for binding in build_button_bindings(action, side, profile_index, physical_input_map) {
|
||||
field(&mut **hand).add_binding(binding);
|
||||
field(hand).add_binding(binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +179,18 @@ pub fn openxr_run(args: &Args, params: RunParams) -> Result<(), BackendError> {
|
||||
// declared last so rust drops this before all long-lived XR objects
|
||||
let _gpu_idle_before_xr_drop = OpenXrGpuIdleGuard(app.gfx.clone());
|
||||
|
||||
if !args.show {
|
||||
Toast::new(
|
||||
ToastTopic::Priority,
|
||||
Some(Translation::from_translation_key(
|
||||
"TOAST.WAYVR_RUNNING_TITLE",
|
||||
)),
|
||||
Translation::from_translation_key("TOAST.WAYVR_RUNNING_TEXT"),
|
||||
)
|
||||
.with_timeout(5.0)
|
||||
.submit(&mut app);
|
||||
}
|
||||
|
||||
'main_loop: loop {
|
||||
let now = Instant::now();
|
||||
app.delta_time = (now.duration_since(last_frame_time).as_secs_f32()).clamp(0.001, 0.2); // 5 - 1000 fps
|
||||
|
||||
@@ -21,7 +21,7 @@ struct MoverData<T> {
|
||||
|
||||
pub(super) struct PlayspaceMover {
|
||||
drag: Option<MoverData<Vec3A>>,
|
||||
rotate: Option<MoverData<Quat>>,
|
||||
rotate: Option<MoverData<Affine3A>>,
|
||||
gravity: SpaceGravity,
|
||||
playspace_state: PlayspaceState,
|
||||
}
|
||||
@@ -93,32 +93,37 @@ impl PlayspaceMover {
|
||||
return;
|
||||
}
|
||||
|
||||
let pose_quat = Quat::from_affine3(&data.pose).normalize();
|
||||
let new_hand = (pose_quat
|
||||
* Quat::from_affine3(&app.input_state.pointers[data.hand].raw_pose))
|
||||
.normalize();
|
||||
let new_hand = data.pose * app.input_state.pointers[data.hand].raw_pose;
|
||||
let before_pose = data.pose;
|
||||
|
||||
let dq = (pose_quat.conjugate() * (new_hand * data.hand_pose.conjugate()) * pose_quat)
|
||||
.normalize();
|
||||
let mut space_transform = if app.session.config.space_rotate_unlocked {
|
||||
Affine3A::from_quat(dq)
|
||||
if app.session.config.space_rotate_unlocked {
|
||||
// pivot on the hand so the body orbits it
|
||||
let pose = new_hand * data.hand_pose.inverse() * data.pose;
|
||||
data.pose = Affine3A::from_rotation_translation(
|
||||
Quat::from_affine3(&pose).normalize(),
|
||||
pose.translation.into(),
|
||||
);
|
||||
} else {
|
||||
let pose_quat = Quat::from_affine3(&data.pose).normalize();
|
||||
let dq = (pose_quat.conjugate()
|
||||
* Quat::from_affine3(&(new_hand * data.hand_pose.inverse()))
|
||||
* pose_quat)
|
||||
.normalize();
|
||||
let rel_y = f32::atan2(
|
||||
2.0 * dq.y.mul_add(dq.w, dq.x * dq.z),
|
||||
2.0f32.mul_add(dq.w.mul_add(dq.w, dq.x * dq.x), -1.0),
|
||||
);
|
||||
|
||||
Affine3A::from_rotation_y(rel_y)
|
||||
};
|
||||
let offset = (space_transform.transform_vector3a(app.input_state.hmd.translation)
|
||||
- app.input_state.hmd.translation)
|
||||
* -1.0;
|
||||
let mut space_transform = Affine3A::from_rotation_y(rel_y);
|
||||
space_transform.translation = (space_transform
|
||||
.transform_vector3a(app.input_state.hmd.translation)
|
||||
- app.input_state.hmd.translation)
|
||||
* -1.0;
|
||||
|
||||
space_transform.translation = offset;
|
||||
data.pose *= space_transform;
|
||||
data.pose.matrix3 = Mat3A::from_quat(Quat::from_affine3(&data.pose).normalize());
|
||||
}
|
||||
|
||||
let before_pose = data.pose;
|
||||
data.pose *= space_transform;
|
||||
data.pose.matrix3 = Mat3A::from_quat(Quat::from_affine3(&data.pose).normalize());
|
||||
if !app.session.config.space_drag_affects_world {
|
||||
playspace_common::shift_world(overlays, &mut app.anchor, &before_pose, &data.pose);
|
||||
}
|
||||
@@ -126,34 +131,32 @@ impl PlayspaceMover {
|
||||
|
||||
apply_offset(data.pose, &mut monado.ipc);
|
||||
self.rotate = Some(data);
|
||||
} else {
|
||||
// start space rotate
|
||||
for (i, pointer) in app.input_state.pointers.iter().enumerate() {
|
||||
if pointer.now.space_rotate {
|
||||
let Ok(pose) = monado
|
||||
.ipc
|
||||
.get_reference_space_offset(ReferenceSpaceType::Stage)
|
||||
.log_err("Could not get reference space offset.")
|
||||
.map(|p| {
|
||||
Affine3A::from_rotation_translation(
|
||||
p.orientation.into(),
|
||||
p.position.into(),
|
||||
)
|
||||
})
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let hand_pose = Quat::from_affine3(&(pose * pointer.raw_pose)).normalize();
|
||||
self.rotate = Some(MoverData {
|
||||
pose,
|
||||
hand: i,
|
||||
hand_pose,
|
||||
});
|
||||
self.drag = None;
|
||||
log::info!("Start space rotate");
|
||||
// drag and rotate can share a button
|
||||
return;
|
||||
}
|
||||
// start space rotate
|
||||
for (i, pointer) in app.input_state.pointers.iter().enumerate() {
|
||||
if pointer.now.space_rotate {
|
||||
let Ok(pose) = monado
|
||||
.ipc
|
||||
.get_reference_space_offset(ReferenceSpaceType::Stage)
|
||||
.log_err("Could not get reference space offset.")
|
||||
.map(|p| {
|
||||
Affine3A::from_rotation_translation(p.orientation.into(), p.position.into())
|
||||
})
|
||||
else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let hand_pose = pose * pointer.raw_pose;
|
||||
self.rotate = Some(MoverData {
|
||||
pose,
|
||||
hand: i,
|
||||
hand_pose,
|
||||
});
|
||||
self.drag = None;
|
||||
log::info!("Start space rotate");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -148,7 +148,7 @@ impl WlxSwapchain {
|
||||
|
||||
struct RetiredSwapchain {
|
||||
// keep alive until completion is signaled
|
||||
_swapchain: WlxSwapchain,
|
||||
swapchain: WlxSwapchain,
|
||||
completion: Option<GpuCompletionMarker>,
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ impl SwapchainRetirementQueue {
|
||||
/// queue the swapchain for non-blocking retirement
|
||||
pub(super) fn retire(&mut self, swapchain: WlxSwapchain, gfx: &Arc<WGfx>) {
|
||||
self.retired.push(RetiredSwapchain {
|
||||
_swapchain: swapchain,
|
||||
swapchain,
|
||||
completion: None,
|
||||
});
|
||||
|
||||
@@ -177,7 +177,7 @@ impl SwapchainRetirementQueue {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
retired._swapchain.ensure_image_released()?;
|
||||
retired.swapchain.ensure_image_released()?;
|
||||
retired.completion = Some(gfx.submit_graphics_completion_marker()?);
|
||||
Ok(())
|
||||
}
|
||||
@@ -185,12 +185,12 @@ impl SwapchainRetirementQueue {
|
||||
pub(super) fn collect(&mut self, gfx: &Arc<WGfx>) {
|
||||
let mut idx = 0;
|
||||
while idx < self.retired.len() {
|
||||
if self.retired[idx].completion.is_none() {
|
||||
if let Err(e) = Self::arm(&mut self.retired[idx], gfx) {
|
||||
log::warn!("Failed re-arming retired OpenXR swapchain: {e:#}");
|
||||
idx += 1;
|
||||
continue;
|
||||
}
|
||||
if self.retired[idx].completion.is_none()
|
||||
&& let Err(e) = Self::arm(&mut self.retired[idx], gfx)
|
||||
{
|
||||
log::warn!("Failed re-arming retired OpenXR swapchain: {e:#}");
|
||||
idx += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
let complete = match self.retired[idx]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::{
|
||||
cmp,
|
||||
collections::{BinaryHeap, VecDeque},
|
||||
sync::Arc,
|
||||
sync::atomic::{self, AtomicUsize},
|
||||
time::Instant,
|
||||
};
|
||||
@@ -10,6 +11,7 @@ use strum::EnumIs;
|
||||
|
||||
use crate::{
|
||||
backend::input,
|
||||
backend::wayvr as wvr,
|
||||
state::AppState,
|
||||
windowing::{
|
||||
OverlayID, OverlaySelector,
|
||||
@@ -56,6 +58,7 @@ pub enum InputTask {
|
||||
#[allow(dead_code)]
|
||||
pub enum OpenVrTask {
|
||||
ColorGain(ColorChannel, f32),
|
||||
ShowBindingsUI,
|
||||
}
|
||||
|
||||
#[cfg(feature = "openxr")]
|
||||
@@ -108,6 +111,8 @@ pub enum SpawnPos {
|
||||
Spread,
|
||||
/// Spawn relative to a different overlay
|
||||
Parent(OverlaySelector),
|
||||
/// Resolve parent or app-id at the point of spawn
|
||||
WlDefault(wvr::window::WindowHandle, Option<OverlayID>),
|
||||
}
|
||||
|
||||
pub enum GlobalChange {
|
||||
@@ -137,6 +142,10 @@ pub enum OverlayTask {
|
||||
Drop(OverlaySelector),
|
||||
DropImmediate(OverlaySelector),
|
||||
DropExpired(OverlayID),
|
||||
SetOverlayName(OverlayID, Arc<str>, bool, bool),
|
||||
ForgetWindowState(OverlayID, Arc<str>),
|
||||
RenameWindowState(Arc<str>, Arc<str>),
|
||||
ClearWindowState(Arc<str>),
|
||||
}
|
||||
|
||||
pub type GlobalTask = dyn FnOnce(&mut AppState) + Send;
|
||||
|
||||
@@ -10,10 +10,13 @@ use anyhow::Context;
|
||||
use glam::{DVec2, Vec2};
|
||||
use slotmap::DenseSlotMap;
|
||||
use smithay::{
|
||||
backend::input::{Axis, AxisSource, ButtonState, Keycode},
|
||||
backend::input::{Axis, AxisSource, ButtonState, InputTime, Keycode},
|
||||
input::{
|
||||
keyboard::KeyboardHandle,
|
||||
pointer::{AxisFrame, ButtonEvent, MotionEvent, PointerHandle, RelativeMotionEvent},
|
||||
pointer::{
|
||||
AxisFrame, ButtonEvent, CursorImageStatus, MotionEvent, PointerHandle,
|
||||
RelativeMotionEvent,
|
||||
},
|
||||
},
|
||||
reexports::wayland_server::{self, protocol::wl_surface::WlSurface},
|
||||
utils::{Logical, Point, SerialCounter},
|
||||
@@ -21,8 +24,6 @@ use smithay::{
|
||||
use wgui::log::LogErr;
|
||||
use xkbcommon::xkb;
|
||||
|
||||
use crate::backend::wayvr::{ExternalProcessRequest, WayVRTask};
|
||||
|
||||
use super::{
|
||||
ProcessWayVREnv,
|
||||
comp::{self, ClientState},
|
||||
@@ -32,6 +33,7 @@ use super::{
|
||||
pub struct WayVRClient {
|
||||
pub client: wayland_server::Client,
|
||||
pub pid: u32,
|
||||
pub process: process::ProcessHandle,
|
||||
}
|
||||
|
||||
pub struct WayVRCompositor {
|
||||
@@ -160,16 +162,17 @@ impl WayVRCompositor {
|
||||
let process_env = get_wayvr_env_from_pid(credentials.pid)?;
|
||||
|
||||
// Find suitable auth key from the process list
|
||||
for (_, process) in processes {
|
||||
if let process::Process::Managed(process) = &process
|
||||
for (handle, process) in processes.iter() {
|
||||
if let process::Process::Managed(process) = process
|
||||
&& let Some(auth_key) = &process_env.display_auth
|
||||
{
|
||||
// Find process with matching auth key
|
||||
if process.auth_key.as_str() == auth_key {
|
||||
// Add client
|
||||
// Associate client now, instead of trying to match env later
|
||||
self.add_client(WayVRClient {
|
||||
client,
|
||||
pid: credentials.pid as u32,
|
||||
process: handle,
|
||||
});
|
||||
return Ok(());
|
||||
}
|
||||
@@ -178,18 +181,15 @@ impl WayVRCompositor {
|
||||
|
||||
// This is a new process which we didn't met before.
|
||||
// Treat external processes exclusively (spawned by the user or external program)
|
||||
log::warn!(
|
||||
"External process ID {} connected to this Wayland server",
|
||||
credentials.pid
|
||||
);
|
||||
|
||||
self.state
|
||||
.wayvr_tasks
|
||||
.send(WayVRTask::NewExternalProcess(ExternalProcessRequest {
|
||||
env: process_env,
|
||||
client,
|
||||
pid: credentials.pid as u32,
|
||||
}));
|
||||
let pid = credentials.pid as u32;
|
||||
log::warn!("External process ID {pid} connected to this Wayland server");
|
||||
let process =
|
||||
processes.insert(process::Process::External(process::ExternalProcess { pid }));
|
||||
self.add_client(WayVRClient {
|
||||
client,
|
||||
pid,
|
||||
process,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -239,7 +239,7 @@ impl WayVRCompositor {
|
||||
Keycode::new(virtual_key),
|
||||
state,
|
||||
self.serial_counter.next_serial(),
|
||||
super::time::get_millis() as u32,
|
||||
InputTime::from_millis(super::time::get_millis() as u32),
|
||||
|_, _, _| smithay::input::keyboard::FilterResult::Forward,
|
||||
);
|
||||
}
|
||||
@@ -253,7 +253,7 @@ impl WayVRCompositor {
|
||||
keycode,
|
||||
smithay::backend::input::KeyState::Released,
|
||||
self.serial_counter.next_serial(),
|
||||
super::time::get_millis() as u32,
|
||||
InputTime::from_millis(super::time::get_millis() as u32),
|
||||
|_, _, _| smithay::input::keyboard::FilterResult::Forward,
|
||||
);
|
||||
}
|
||||
@@ -286,13 +286,15 @@ impl WayVRCompositor {
|
||||
(surface, focus_location)
|
||||
});
|
||||
|
||||
let previous_focus = self.seat_pointer.current_focus();
|
||||
let time = InputTime::from_millis(super::time::get_millis() as u32);
|
||||
self.seat_pointer.motion(
|
||||
&mut self.state,
|
||||
focus.clone(),
|
||||
&MotionEvent {
|
||||
location,
|
||||
serial: self.serial_counter.next_serial(),
|
||||
time: super::time::get_millis() as u32,
|
||||
time,
|
||||
},
|
||||
);
|
||||
self.seat_pointer.relative_motion(
|
||||
@@ -301,11 +303,18 @@ impl WayVRCompositor {
|
||||
&RelativeMotionEvent {
|
||||
delta,
|
||||
delta_unaccel,
|
||||
utime: super::time::get_millis(),
|
||||
time,
|
||||
},
|
||||
);
|
||||
|
||||
self.seat_pointer.frame(&mut self.state);
|
||||
self.reset_cursor_on_focus_change(previous_focus);
|
||||
}
|
||||
|
||||
fn reset_cursor_on_focus_change(&mut self, previous_focus: Option<WlSurface>) {
|
||||
if previous_focus != self.seat_pointer.current_focus() {
|
||||
self.state.cursor_image = CursorImageStatus::default_named();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_pointer_button(&mut self, index: super::MouseIndex, pressed: bool) {
|
||||
@@ -316,13 +325,14 @@ impl WayVRCompositor {
|
||||
};
|
||||
|
||||
let serial = self.serial_counter.next_serial();
|
||||
let time = super::time::get_millis() as u32;
|
||||
let time = InputTime::from_millis(super::time::get_millis() as u32);
|
||||
let button = match index {
|
||||
super::MouseIndex::Left => 0x110,
|
||||
super::MouseIndex::Center => 0x112,
|
||||
super::MouseIndex::Right => 0x111,
|
||||
};
|
||||
|
||||
let previous_focus = self.seat_pointer.current_focus();
|
||||
self.seat_pointer.button(
|
||||
&mut self.state,
|
||||
&ButtonEvent {
|
||||
@@ -334,13 +344,14 @@ impl WayVRCompositor {
|
||||
);
|
||||
|
||||
self.seat_pointer.frame(&mut self.state);
|
||||
self.reset_cursor_on_focus_change(previous_focus);
|
||||
}
|
||||
|
||||
pub fn send_pointer_axis_wheel_raw(&mut self, delta: super::WheelDelta) {
|
||||
// 15 logical axis units for one wheel detent of 120 v120 units
|
||||
const AXIS_VALUE_PER_DETENT: f64 = 15.0;
|
||||
|
||||
let time = super::time::get_millis() as u32;
|
||||
let time = InputTime::from_millis(super::time::get_millis() as u32);
|
||||
|
||||
let v120_x = delta.x as i32;
|
||||
let v120_y = delta.y as i32;
|
||||
@@ -374,7 +385,7 @@ impl WayVRCompositor {
|
||||
}
|
||||
|
||||
pub fn send_pointer_axis_wheel_accumulated(&mut self, delta: super::WheelDelta) {
|
||||
let time = super::time::get_millis() as u32;
|
||||
let time = InputTime::from_millis(super::time::get_millis() as u32);
|
||||
|
||||
let multiplier = 32.0;
|
||||
let delta_x = (delta.x * multiplier) as i32;
|
||||
|
||||
+163
-46
@@ -6,20 +6,20 @@ use smithay::desktop::{
|
||||
PopupKeyboardGrab, PopupKind, PopupManager, PopupPointerGrab, PopupUngrabStrategy,
|
||||
find_popup_root_surface, get_popup_toplevel_coords,
|
||||
};
|
||||
use smithay::input::dnd::{DnDGrab, DndGrabHandler, DndTarget, GrabType, Source};
|
||||
use smithay::input::pointer::{CursorImageStatus, Focus};
|
||||
use smithay::input::{Seat, SeatHandler, SeatState};
|
||||
use smithay::output::Output;
|
||||
use smithay::reexports::rustix::fs::{OFlags, fcntl_setfl};
|
||||
use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1;
|
||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
|
||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::XdgToplevel;
|
||||
use smithay::reexports::wayland_protocols_misc::server_decoration::server::org_kde_kwin_server_decoration;
|
||||
use smithay::reexports::wayland_server::Resource;
|
||||
use smithay::reexports::wayland_server::backend::ObjectId;
|
||||
use smithay::reexports::wayland_server::protocol::{
|
||||
wl_buffer, wl_callback, wl_data_source, wl_output, wl_seat,
|
||||
};
|
||||
use smithay::reexports::wayland_server::protocol::{wl_buffer, wl_callback, wl_output, wl_seat};
|
||||
use smithay::reexports::wayland_server::{self, DisplayHandle};
|
||||
use smithay::utils::{Logical, Rectangle, Serial, Size};
|
||||
use smithay::utils::{Logical, Point, Rectangle, Serial, Size};
|
||||
use smithay::wayland::buffer::BufferHandler;
|
||||
use smithay::wayland::compositor::{self, BufferAssignment, SurfaceAttributes, send_surface_state};
|
||||
use smithay::wayland::dmabuf::{
|
||||
@@ -27,10 +27,11 @@ use smithay::wayland::dmabuf::{
|
||||
};
|
||||
use smithay::wayland::fractional_scale::with_fractional_scale;
|
||||
use smithay::wayland::output::OutputHandler;
|
||||
use smithay::wayland::pointer_constraints::PointerConstraintsHandler;
|
||||
use smithay::wayland::relative_pointer::RelativePointerManagerState;
|
||||
use smithay::wayland::selection::data_device::{
|
||||
ClientDndGrabHandler, DataDeviceHandler, DataDeviceState, ServerDndGrabHandler,
|
||||
set_data_device_focus, set_data_device_selection,
|
||||
DataDeviceHandler, DataDeviceState, WaylandDndGrabHandler, set_data_device_focus,
|
||||
set_data_device_selection,
|
||||
};
|
||||
use smithay::wayland::selection::{self, SelectionHandler};
|
||||
use smithay::wayland::selection::{
|
||||
@@ -46,12 +47,8 @@ use smithay::wayland::shell::xdg::{
|
||||
use smithay::wayland::shm::{ShmHandler, ShmState, shm_format_to_fourcc, with_buffer_contents};
|
||||
use smithay::wayland::single_pixel_buffer::get_single_pixel_buffer;
|
||||
use smithay::wayland::viewporter::ViewporterState;
|
||||
use smithay::{
|
||||
delegate_compositor, delegate_data_control, delegate_data_device, delegate_dmabuf,
|
||||
delegate_ext_data_control, delegate_kde_decoration, delegate_output,
|
||||
delegate_primary_selection, delegate_relative_pointer, delegate_seat, delegate_shm,
|
||||
delegate_single_pixel_buffer, delegate_viewporter, delegate_xdg_decoration, delegate_xdg_shell,
|
||||
};
|
||||
use smithay::wayland::xdg_toplevel_icon::{XdgToplevelIconHandler, XdgToplevelIconManager};
|
||||
use smithay::wayland::xdg_toplevel_tag::{XdgToplevelTagHandler, XdgToplevelTagManager};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
@@ -62,6 +59,8 @@ use wayland_server::backend::{ClientData, ClientId, DisconnectReason};
|
||||
use wayland_server::protocol::wl_surface::WlSurface;
|
||||
use wlx_capture::DrmFourcc;
|
||||
|
||||
use super::icon::{self, ToplevelIconSnapshot};
|
||||
use super::session::SessionRegistry;
|
||||
use crate::backend::wayvr::image_importer::ImageImporter;
|
||||
use crate::backend::wayvr::{SurfaceBufWithImage, WAYVR_SCREEN_RES, time};
|
||||
use crate::ipc::event_queue::SyncEventQueue;
|
||||
@@ -74,6 +73,14 @@ pub struct Application {
|
||||
pub dmabuf_state: (DmabufState, DmabufGlobal, Option<DmabufFeedback>),
|
||||
pub compositor: compositor::CompositorState,
|
||||
pub xdg_shell: XdgShellState,
|
||||
#[allow(dead_code)]
|
||||
pub xdg_toplevel_tags: XdgToplevelTagManager,
|
||||
#[allow(dead_code)]
|
||||
pub xdg_toplevel_icons: XdgToplevelIconManager,
|
||||
pub pending_icon_changes: HashMap<ObjectId, ToplevelIconSnapshot>,
|
||||
pub current_icons: HashMap<ObjectId, ToplevelIconSnapshot>,
|
||||
pub sessions: SessionRegistry,
|
||||
pub toplevel_tags: HashMap<ObjectId, Arc<str>>,
|
||||
pub seat: Seat<Application>,
|
||||
pub seat_state: SeatState<Application>,
|
||||
pub shm: ShmState,
|
||||
@@ -235,7 +242,7 @@ impl Application {
|
||||
}
|
||||
}
|
||||
|
||||
fn drm_format_has_alpha(format: DrmFourcc) -> bool {
|
||||
const fn drm_format_has_alpha(format: DrmFourcc) -> bool {
|
||||
matches!(
|
||||
format,
|
||||
DrmFourcc::Abgr8888 | DrmFourcc::Argb8888 | DrmFourcc::Abgr2101010
|
||||
@@ -256,6 +263,43 @@ impl compositor::CompositorHandler for Application {
|
||||
|
||||
#[allow(clippy::significant_drop_tightening)]
|
||||
fn commit(&mut self, surface: &WlSurface) {
|
||||
if let Some(icon) = self.pending_icon_changes.remove(&surface.id()) {
|
||||
if icon.is_empty() {
|
||||
self.current_icons.remove(&surface.id());
|
||||
} else {
|
||||
self.current_icons.insert(surface.id(), icon);
|
||||
}
|
||||
if let Some(toplevel) = self
|
||||
.xdg_shell
|
||||
.toplevel_surfaces()
|
||||
.iter()
|
||||
.find(|t| t.wl_surface() == surface)
|
||||
{
|
||||
self.wayvr_tasks
|
||||
.send(WayVRTask::IconChange(toplevel.clone()));
|
||||
}
|
||||
}
|
||||
if let Some(toplevel) = self
|
||||
.xdg_shell
|
||||
.toplevel_surfaces()
|
||||
.iter()
|
||||
.find(|toplevel| toplevel.wl_surface() == surface)
|
||||
{
|
||||
// first commit is special in terms of restoring session
|
||||
if self
|
||||
.sessions
|
||||
.committed_toplevels
|
||||
.insert(toplevel.xdg_toplevel().id())
|
||||
{
|
||||
log::debug!(
|
||||
"first toplevel commit, surface: {:?}, toplevel: {:?}",
|
||||
surface.id(),
|
||||
toplevel.xdg_toplevel().id()
|
||||
);
|
||||
self.wayvr_tasks
|
||||
.send(WayVRTask::FirstToplevelCommit(toplevel.clone()));
|
||||
}
|
||||
}
|
||||
self.popups_commit(surface);
|
||||
|
||||
smithay::wayland::compositor::with_states(surface, |states| {
|
||||
@@ -397,33 +441,75 @@ impl BufferHandler for Application {
|
||||
fn buffer_destroyed(&mut self, _buffer: &wl_buffer::WlBuffer) {}
|
||||
}
|
||||
|
||||
impl ClientDndGrabHandler for Application {
|
||||
fn started(
|
||||
impl PointerConstraintsHandler for Application {}
|
||||
|
||||
impl WaylandDndGrabHandler for Application {
|
||||
fn dnd_requested<S: Source>(
|
||||
&mut self,
|
||||
_source: Option<wl_data_source::WlDataSource>,
|
||||
source: S,
|
||||
icon: Option<WlSurface>,
|
||||
_seat: Seat<Self>,
|
||||
seat: Seat<Self>,
|
||||
serial: Serial,
|
||||
type_: GrabType,
|
||||
) {
|
||||
let pointer = self.pointer_button_owner.unwrap_or(self.vr_pointer);
|
||||
self.dnd_pointer = Some(pointer);
|
||||
self.wayvr_tasks
|
||||
.send(WayVRTask::DndStarted { icon, pointer });
|
||||
}
|
||||
|
||||
fn dropped(&mut self, _target: Option<WlSurface>, _validated: bool, _seat: Seat<Self>) {
|
||||
match type_ {
|
||||
GrabType::Pointer => {
|
||||
let Some(pointer) = seat.get_pointer() else {
|
||||
log::warn!("dnd_requested: seat has no pointer");
|
||||
return;
|
||||
};
|
||||
let Some(start_data) = pointer.grab_start_data() else {
|
||||
log::warn!("dnd_requested: no pointer grab start data");
|
||||
return;
|
||||
};
|
||||
pointer.set_grab(
|
||||
self,
|
||||
DnDGrab::new_pointer(&self.display_handle, start_data, source, seat),
|
||||
serial,
|
||||
Focus::Keep,
|
||||
);
|
||||
}
|
||||
GrabType::Touch => {
|
||||
let Some(touch) = seat.get_touch() else {
|
||||
log::warn!("dnd_requested: seat has no touch");
|
||||
return;
|
||||
};
|
||||
let Some(start_data) = touch.grab_start_data() else {
|
||||
log::warn!("dnd_requested: no touch grab start data");
|
||||
return;
|
||||
};
|
||||
touch.set_grab(
|
||||
self,
|
||||
DnDGrab::new_touch(&self.display_handle, start_data, source, seat),
|
||||
serial,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DndGrabHandler for Application {
|
||||
fn dropped(
|
||||
&mut self,
|
||||
_target: Option<DndTarget<'_, Self>>,
|
||||
_validated: bool,
|
||||
_seat: Seat<Self>,
|
||||
_location: Point<f64, Logical>,
|
||||
) {
|
||||
self.dnd_pointer = None;
|
||||
self.pointer_button_owner = None;
|
||||
self.wayvr_tasks.send(WayVRTask::DndDropped);
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerDndGrabHandler for Application {
|
||||
fn send(&mut self, _mime_type: String, _fd: OwnedFd, _seat: Seat<Self>) {}
|
||||
}
|
||||
|
||||
impl DataDeviceHandler for Application {
|
||||
fn data_device_state(&self) -> &DataDeviceState {
|
||||
&self.data_device
|
||||
fn data_device_state(&mut self) -> &mut DataDeviceState {
|
||||
&mut self.data_device
|
||||
}
|
||||
}
|
||||
|
||||
@@ -489,6 +575,11 @@ impl XdgShellHandler for Application {
|
||||
|
||||
fn new_toplevel(&mut self, surface: ToplevelSurface) {
|
||||
let wl_surface = surface.wl_surface();
|
||||
log::debug!(
|
||||
"new_toplevel:, surface: {:?}, toplevel: {:?}",
|
||||
wl_surface.id(),
|
||||
surface.xdg_toplevel().id()
|
||||
);
|
||||
self.send_initial_surface_state(wl_surface);
|
||||
if let Some(client) = wl_surface.client() {
|
||||
self.wayvr_tasks
|
||||
@@ -497,6 +588,10 @@ impl XdgShellHandler for Application {
|
||||
}
|
||||
|
||||
fn toplevel_destroyed(&mut self, surface: ToplevelSurface) {
|
||||
self.pending_icon_changes.remove(&surface.wl_surface().id());
|
||||
self.current_icons.remove(&surface.wl_surface().id());
|
||||
self.toplevel_tags.remove(&surface.xdg_toplevel().id());
|
||||
self.sessions.forget_toplevel(&surface);
|
||||
self.output.leave(surface.wl_surface());
|
||||
self.no_server_decorations
|
||||
.remove(&surface.wl_surface().id());
|
||||
@@ -653,6 +748,16 @@ impl XdgShellHandler for Application {
|
||||
}
|
||||
}
|
||||
|
||||
fn app_id_changed(&mut self, surface: ToplevelSurface) {
|
||||
self.wayvr_tasks
|
||||
.send(WayVRTask::WindowIdentityChanged(surface));
|
||||
}
|
||||
|
||||
fn parent_changed(&mut self, surface: ToplevelSurface) {
|
||||
self.wayvr_tasks
|
||||
.send(WayVRTask::WindowIdentityChanged(surface));
|
||||
}
|
||||
|
||||
fn title_changed(&mut self, surface: ToplevelSurface) {
|
||||
if let Some(client) = surface.wl_surface().client() {
|
||||
self.wayvr_tasks
|
||||
@@ -661,6 +766,32 @@ impl XdgShellHandler for Application {
|
||||
}
|
||||
}
|
||||
|
||||
impl XdgToplevelIconHandler for Application {
|
||||
fn set_icon(&mut self, toplevel: XdgToplevel, surface: WlSurface) {
|
||||
log::debug!(
|
||||
"set_icon, surface: {:?}, toplevel: {:?}",
|
||||
surface.id(),
|
||||
toplevel.id()
|
||||
);
|
||||
let icon = icon::snapshot_pending_icon(&surface);
|
||||
self.pending_icon_changes.insert(surface.id(), icon);
|
||||
}
|
||||
}
|
||||
|
||||
impl XdgToplevelTagHandler for Application {
|
||||
fn set_tag(&mut self, toplevel: XdgToplevel, tag: String) {
|
||||
if tag.is_empty() {
|
||||
self.toplevel_tags.remove(&toplevel.id());
|
||||
} else {
|
||||
self.toplevel_tags.insert(toplevel.id(), tag.into());
|
||||
}
|
||||
if let Some(surface) = self.xdg_shell.get_toplevel(&toplevel) {
|
||||
self.wayvr_tasks
|
||||
.send(WayVRTask::WindowIdentityChanged(surface));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ShmHandler for Application {
|
||||
fn shm_state(&self) -> &ShmState {
|
||||
&self.shm
|
||||
@@ -695,20 +826,20 @@ impl DmabufHandler for Application {
|
||||
}
|
||||
|
||||
impl PrimarySelectionHandler for Application {
|
||||
fn primary_selection_state(&self) -> &PrimarySelectionState {
|
||||
&self.primary_selection_state
|
||||
fn primary_selection_state(&mut self) -> &mut PrimarySelectionState {
|
||||
&mut self.primary_selection_state
|
||||
}
|
||||
}
|
||||
|
||||
impl selection_wlr::DataControlHandler for Application {
|
||||
fn data_control_state(&self) -> &selection_wlr::DataControlState {
|
||||
&self.wlr_data_control_state
|
||||
fn data_control_state(&mut self) -> &mut selection_wlr::DataControlState {
|
||||
&mut self.wlr_data_control_state
|
||||
}
|
||||
}
|
||||
|
||||
impl selection_ext::DataControlHandler for Application {
|
||||
fn data_control_state(&self) -> &selection_ext::DataControlState {
|
||||
&self.ext_data_control_state
|
||||
fn data_control_state(&mut self) -> &mut selection_ext::DataControlState {
|
||||
&mut self.ext_data_control_state
|
||||
}
|
||||
}
|
||||
impl XdgDecorationHandler for Application {
|
||||
@@ -763,21 +894,7 @@ impl KdeDecorationHandler for Application {
|
||||
}
|
||||
}
|
||||
|
||||
delegate_dmabuf!(Application);
|
||||
delegate_xdg_shell!(Application);
|
||||
delegate_compositor!(Application);
|
||||
delegate_viewporter!(Application);
|
||||
delegate_shm!(Application);
|
||||
delegate_seat!(Application);
|
||||
delegate_data_device!(Application);
|
||||
delegate_output!(Application);
|
||||
delegate_primary_selection!(Application);
|
||||
delegate_data_control!(Application);
|
||||
delegate_ext_data_control!(Application);
|
||||
delegate_xdg_decoration!(Application);
|
||||
delegate_kde_decoration!(Application);
|
||||
delegate_single_pixel_buffer!(Application);
|
||||
delegate_relative_pointer!(Application);
|
||||
smithay::delegate_dispatch2!(Application);
|
||||
|
||||
const fn wl_transform_to_frame_transform(
|
||||
transform: wl_output::Transform,
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
use glam::{Affine2, Vec2};
|
||||
use smithay::{
|
||||
desktop::PopupManager,
|
||||
desktop::{PopupKind, PopupManager},
|
||||
reexports::wayland_server::{Resource, backend::ObjectId, protocol::wl_surface::WlSurface},
|
||||
utils::{Logical, Point},
|
||||
wayland::{
|
||||
compositor::{
|
||||
SUBSURFACE_ROLE, SubsurfaceCachedState, SurfaceAttributes, TraversalAction,
|
||||
with_states, with_surface_tree_upward,
|
||||
},
|
||||
shell::xdg::XdgPopupSurfaceData,
|
||||
wayland::compositor::{
|
||||
SUBSURFACE_ROLE, SubsurfaceCachedState, SurfaceAttributes, TraversalAction, with_states,
|
||||
with_surface_tree_upward,
|
||||
},
|
||||
};
|
||||
use std::{ops::RangeInclusive, rc::Rc, sync::Arc};
|
||||
@@ -383,17 +380,9 @@ pub fn build_hit_context(
|
||||
let mut popup_roots = Vec::new();
|
||||
|
||||
for (popup, point) in PopupManager::popups_for_surface(toplevel) {
|
||||
let configured = with_states(popup.wl_surface(), |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<XdgPopupSurfaceData>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.configured
|
||||
});
|
||||
|
||||
if !configured {
|
||||
if let PopupKind::Xdg(popup) = &popup
|
||||
&& !popup.is_initial_configure_sent()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
use smithay::{
|
||||
reexports::wayland_server::{
|
||||
Resource,
|
||||
backend::ObjectId,
|
||||
protocol::{wl_shm, wl_surface::WlSurface},
|
||||
},
|
||||
wayland::{
|
||||
compositor::with_states, shm::with_buffer_contents,
|
||||
xdg_toplevel_icon::ToplevelIconCachedState,
|
||||
},
|
||||
};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use wlx_common::desktop_finder::DesktopFinder;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ToplevelIconSnapshot {
|
||||
name: Option<Arc<str>>,
|
||||
png_path: Option<Arc<str>>,
|
||||
}
|
||||
|
||||
impl ToplevelIconSnapshot {
|
||||
pub fn resolve(&self, finder: &DesktopFinder) -> Option<Arc<str>> {
|
||||
self.png_path.clone().or_else(|| {
|
||||
self.name
|
||||
.as_deref()
|
||||
.and_then(|name| finder.resolve_icon(name))
|
||||
.map(Into::into)
|
||||
})
|
||||
}
|
||||
|
||||
pub const fn is_empty(&self) -> bool {
|
||||
self.name.is_none() && self.png_path.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toplevel_icon(
|
||||
surface: &WlSurface,
|
||||
icons: &HashMap<ObjectId, ToplevelIconSnapshot>,
|
||||
finder: &DesktopFinder,
|
||||
) -> Option<Arc<str>> {
|
||||
icons
|
||||
.get(&surface.id())
|
||||
.and_then(|icon| icon.resolve(finder))
|
||||
}
|
||||
|
||||
pub fn snapshot_pending_icon(surface: &WlSurface) -> ToplevelIconSnapshot {
|
||||
let (name, buffers) = with_states(surface, |states| {
|
||||
let mut icon = states.cached_state.get::<ToplevelIconCachedState>();
|
||||
let pending = icon.pending();
|
||||
(
|
||||
pending.icon_name().map(Arc::<str>::from),
|
||||
pending.buffers().to_vec(),
|
||||
)
|
||||
});
|
||||
|
||||
for (buffer, _scale) in buffers {
|
||||
let mut png_path = None;
|
||||
let _ = with_buffer_contents(&buffer, |data, size, info| {
|
||||
let data = unsafe { std::slice::from_raw_parts(data, size) };
|
||||
let (w, h) = (info.width as usize, info.height as usize);
|
||||
if info.width <= 0
|
||||
|| info.height <= 0
|
||||
|| info.stride <= 0
|
||||
|| info.offset < 0
|
||||
|| w != h
|
||||
|| w > 1024
|
||||
{
|
||||
return;
|
||||
}
|
||||
let stride = info.stride as usize;
|
||||
let offset = info.offset as usize;
|
||||
if stride < w * 4
|
||||
|| offset
|
||||
.checked_add((h - 1).saturating_mul(stride) + w * 4)
|
||||
.is_none_or(|end| end > size)
|
||||
{
|
||||
return;
|
||||
}
|
||||
let has_alpha = match info.format {
|
||||
wl_shm::Format::Argb8888 => true,
|
||||
wl_shm::Format::Xrgb8888 => false,
|
||||
_ => return,
|
||||
};
|
||||
let mut rgba = vec![0u8; w * h * 4];
|
||||
for y in 0..h {
|
||||
for x in 0..w {
|
||||
let pos = offset + y * stride + x * 4;
|
||||
let dst = (y * w + x) * 4;
|
||||
rgba[dst] = data[pos + 2];
|
||||
rgba[dst + 1] = data[pos + 1];
|
||||
rgba[dst + 2] = data[pos];
|
||||
rgba[dst + 3] = if has_alpha { data[pos + 3] } else { 255 };
|
||||
}
|
||||
}
|
||||
let path = wlx_common::cache_dir::get_path(&format!(
|
||||
"icons/wayland-{}.png",
|
||||
uuid::Uuid::new_v4()
|
||||
));
|
||||
if let Some(parent) = path.parent() {
|
||||
let _ = std::fs::create_dir_all(parent);
|
||||
}
|
||||
if image::save_buffer_with_format(
|
||||
&path,
|
||||
&rgba,
|
||||
w as u32,
|
||||
h as u32,
|
||||
image::ColorType::Rgba8,
|
||||
image::ImageFormat::Png,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
png_path = Some(path.to_string_lossy().into_owned().into());
|
||||
}
|
||||
});
|
||||
if let Some(png_path) = png_path {
|
||||
return ToplevelIconSnapshot {
|
||||
name,
|
||||
png_path: Some(png_path),
|
||||
};
|
||||
}
|
||||
}
|
||||
ToplevelIconSnapshot {
|
||||
name,
|
||||
png_path: None,
|
||||
}
|
||||
}
|
||||
+388
-70
@@ -1,15 +1,19 @@
|
||||
pub mod client;
|
||||
mod comp;
|
||||
pub mod hit_test;
|
||||
mod icon;
|
||||
mod image_importer;
|
||||
mod input_capture;
|
||||
pub mod process;
|
||||
mod session;
|
||||
mod session_protocol;
|
||||
mod time;
|
||||
pub mod window;
|
||||
|
||||
use anyhow::Context;
|
||||
use comp::Application;
|
||||
use glam::{DVec2, Vec2};
|
||||
use session::{SessionRegistry, app_key, tag_key};
|
||||
use slotmap::{DenseSlotMap, SecondaryMap};
|
||||
use smallvec::SmallVec;
|
||||
use smithay::{
|
||||
@@ -19,7 +23,7 @@ use smithay::{
|
||||
reexports::{
|
||||
wayland_protocols_misc::server_decoration::server::org_kde_kwin_server_decoration_manager as kde_decoration,
|
||||
wayland_server::{
|
||||
self,
|
||||
self, Resource,
|
||||
backend::ClientId,
|
||||
protocol::{wl_buffer, wl_surface::WlSurface},
|
||||
},
|
||||
@@ -118,22 +122,19 @@ pub struct ProcessWayVREnv {
|
||||
pub display_name: Option<String>, // Externally spawned process by a user script
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExternalProcessRequest {
|
||||
#[allow(dead_code)]
|
||||
pub env: ProcessWayVREnv,
|
||||
pub client: wayland_server::Client,
|
||||
pub pid: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum WayVRTask {
|
||||
NewToplevel(ClientId, ToplevelSurface),
|
||||
FirstToplevelCommit(ToplevelSurface),
|
||||
DropToplevel(ClientId, ToplevelSurface),
|
||||
MinimizeRequest(ClientId, ToplevelSurface),
|
||||
TitleChange(ClientId, ToplevelSurface),
|
||||
IconChange(ToplevelSurface),
|
||||
WindowIdentityChanged(ToplevelSurface),
|
||||
FreezeWindowState(ToplevelSurface, Arc<str>),
|
||||
RenameWindowState(Arc<str>, Arc<str>),
|
||||
ForgetWindowState(Arc<str>),
|
||||
VisibilityChange(window::WindowHandle, bool),
|
||||
NewExternalProcess(ExternalProcessRequest),
|
||||
ProcessTerminationRequest(process::ProcessHandle, KillSignal),
|
||||
CloseWindowRequest(window::WindowHandle),
|
||||
DndStarted {
|
||||
@@ -173,16 +174,16 @@ pub enum PointerFocusTarget {
|
||||
None,
|
||||
}
|
||||
|
||||
pub enum TickTask {
|
||||
NewExternalProcess(ExternalProcessRequest), // Call WayVRCompositor::add_client after receiving this message
|
||||
}
|
||||
|
||||
const KEY_REPEAT_DELAY: i32 = 500;
|
||||
const KEY_REPEAT_RATE: i32 = 50;
|
||||
const WAYVR_SCREEN_RES: [i32; 2] = [2560, 1440];
|
||||
|
||||
impl WvrServerState {
|
||||
pub fn new(gfx: Arc<WGfx>, gfx_extras: &WGfxExtras) -> anyhow::Result<Self> {
|
||||
pub fn new(
|
||||
gfx: Arc<WGfx>,
|
||||
gfx_extras: &WGfxExtras,
|
||||
config: &GeneralConfig,
|
||||
) -> anyhow::Result<Self> {
|
||||
const fn filter_allow_any(_: &wayland_server::Client) -> bool {
|
||||
true
|
||||
}
|
||||
@@ -191,6 +192,26 @@ impl WvrServerState {
|
||||
let dh = display.handle();
|
||||
let compositor = compositor::CompositorState::new::<Application>(&dh);
|
||||
let xdg_shell = XdgShellState::new::<Application>(&dh);
|
||||
let _session_global = dh.create_global::<Application,
|
||||
session_protocol::server::xdg_session_manager_v1::XdgSessionManagerV1, _>(1, ());
|
||||
let xdg_toplevel_tags =
|
||||
smithay::wayland::xdg_toplevel_tag::XdgToplevelTagManager::new::<Application>(&dh);
|
||||
let mut xdg_toplevel_icons =
|
||||
smithay::wayland::xdg_toplevel_icon::XdgToplevelIconManager::new::<Application>(&dh);
|
||||
xdg_toplevel_icons.add_icon_size(64);
|
||||
if std::env::var("WAYVR_DISABLE_TOPLEVEL_ICONS")
|
||||
.ok()
|
||||
.as_deref()
|
||||
.is_some_and(|value| !value.is_empty() && value != "0")
|
||||
{
|
||||
dh.disable_global::<Application>(xdg_toplevel_icons.global());
|
||||
}
|
||||
let stored_keys: HashSet<Arc<str>> = config
|
||||
.sets
|
||||
.iter()
|
||||
.flat_map(|set| set.overlays.keys().chain(set.hidden_overlays.keys()))
|
||||
.cloned()
|
||||
.collect();
|
||||
let mut seat_state = SeatState::new();
|
||||
let shm = ShmState::new::<Application>(&dh, Vec::new());
|
||||
let data_device = DataDeviceState::new::<Application>(&dh);
|
||||
@@ -223,6 +244,7 @@ impl WvrServerState {
|
||||
subpixel: smithay::output::Subpixel::None,
|
||||
make: String::from("Completely Legit"),
|
||||
model: String::from("Virtual WayVR Display"),
|
||||
serial_number: String::new(),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -282,6 +304,12 @@ impl WvrServerState {
|
||||
display_handle: dh,
|
||||
compositor,
|
||||
xdg_shell,
|
||||
xdg_toplevel_tags,
|
||||
xdg_toplevel_icons,
|
||||
pending_icon_changes: HashMap::new(),
|
||||
current_icons: HashMap::new(),
|
||||
sessions: SessionRegistry::new(stored_keys),
|
||||
toplevel_tags: HashMap::new(),
|
||||
seat,
|
||||
seat_state,
|
||||
shm,
|
||||
@@ -325,26 +353,29 @@ impl WvrServerState {
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
#[allow(clippy::significant_drop_tightening)]
|
||||
pub fn tick_events(app: &mut AppState) -> anyhow::Result<Vec<TickTask>> {
|
||||
let mut tasks: Vec<TickTask> = Vec::new();
|
||||
|
||||
pub fn tick_events(app: &mut AppState) -> anyhow::Result<()> {
|
||||
let Some(wvr_server) = app.wvr_server.as_mut() else {
|
||||
return Ok(tasks);
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
app.ipc_server.tick(&mut ipc_server::TickParams {
|
||||
wvr_server,
|
||||
input_state: &app.input_state,
|
||||
tasks: &mut tasks,
|
||||
signals: &app.wayvr_signals,
|
||||
hid_wrapper: &mut app.hid_provider,
|
||||
});
|
||||
|
||||
// Tick all child processes
|
||||
// Keep launch records while wl client is attached
|
||||
let attached_processes: HashSet<_> = wvr_server
|
||||
.manager
|
||||
.clients
|
||||
.iter()
|
||||
.map(|client| client.process)
|
||||
.collect();
|
||||
let mut to_remove: SmallVec<[process::ProcessHandle; 2]> = SmallVec::new();
|
||||
|
||||
for (handle, process) in &mut wvr_server.processes {
|
||||
if !process.is_running() {
|
||||
if !process.is_running() && !attached_processes.contains(&handle) {
|
||||
to_remove.push(handle);
|
||||
}
|
||||
}
|
||||
@@ -356,10 +387,11 @@ impl WvrServerState {
|
||||
|
||||
while let Some(task) = wvr_server.tasks.read() {
|
||||
match task {
|
||||
WayVRTask::NewExternalProcess(req) => {
|
||||
tasks.push(TickTask::NewExternalProcess(req));
|
||||
}
|
||||
WayVRTask::NewToplevel(client_id, toplevel) => {
|
||||
log::debug!(
|
||||
"NewToplevel: client: {client_id:?}, toplevel: {:?}",
|
||||
toplevel.xdg_toplevel().id()
|
||||
);
|
||||
let toplevel = Rc::new(toplevel);
|
||||
|
||||
// Attach newly created toplevel surfaces to displays
|
||||
@@ -368,15 +400,14 @@ impl WvrServerState {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(process_handle) =
|
||||
process::find_by_pid(&wvr_server.processes, client.pid)
|
||||
else {
|
||||
let process_handle = client.process;
|
||||
if !wvr_server.processes.contains_key(process_handle) {
|
||||
log::error!(
|
||||
"WayVR window creation failed: Unexpected process ID {}. It wasn't registered before.",
|
||||
"WayVR window creation failed: Process for PID {} was removed after the client connected.",
|
||||
client.pid
|
||||
);
|
||||
continue;
|
||||
};
|
||||
}
|
||||
|
||||
let output_bounds = wvr_server.manager.state.output_logical_size();
|
||||
|
||||
@@ -428,6 +459,29 @@ impl WvrServerState {
|
||||
),
|
||||
};
|
||||
|
||||
let (xdg_title, app_id): (Option<String>, Option<String>) =
|
||||
with_states(toplevel.wl_surface(), |states| {
|
||||
states.data_map.get::<XdgToplevelSurfaceData>().map_or(
|
||||
(None, None),
|
||||
|t| {
|
||||
let t = t.lock().unwrap();
|
||||
(t.title.clone(), t.app_id.clone())
|
||||
},
|
||||
)
|
||||
});
|
||||
let app_id: Option<Arc<str>> =
|
||||
app_id.filter(|s| !s.is_empty()).map(Into::into);
|
||||
let parent = toplevel.parent().and_then(|surface| {
|
||||
wvr_server
|
||||
.wm
|
||||
.windows
|
||||
.iter()
|
||||
.find(|(_, window)| *window.toplevel.wl_surface() == surface)
|
||||
.map(|(handle, _)| handle)
|
||||
});
|
||||
let (name, restore_on_spawn) =
|
||||
wvr_server.window_identity(&toplevel, app_id.as_deref(), parent, None);
|
||||
|
||||
let window_handle = wvr_server.wm.create_window(CreateWindowParams {
|
||||
toplevel: toplevel.clone(),
|
||||
process: process_handle,
|
||||
@@ -436,6 +490,10 @@ impl WvrServerState {
|
||||
max_size,
|
||||
size_x: fallback_size.w as _,
|
||||
size_y: fallback_size.h as _,
|
||||
app_id: app_id.clone(),
|
||||
parent,
|
||||
name: name.clone(),
|
||||
restore_on_spawn,
|
||||
});
|
||||
|
||||
toplevel.with_pending_state(|state| {
|
||||
@@ -444,42 +502,41 @@ impl WvrServerState {
|
||||
state.size = Some(fallback_size);
|
||||
state.states.set(xdg_toplevel::State::Activated);
|
||||
});
|
||||
toplevel.send_configure();
|
||||
// xdg-shell requires an empty commit before initial configure
|
||||
if wvr_server
|
||||
.manager
|
||||
.state
|
||||
.sessions
|
||||
.committed_toplevels
|
||||
.contains(&toplevel.xdg_toplevel().id())
|
||||
{
|
||||
wvr_server.send_first_configure(&toplevel);
|
||||
}
|
||||
|
||||
let mut title: Arc<str> = fallback_title
|
||||
.unwrap_or_else(|| format!("P{}", client.pid))
|
||||
.into();
|
||||
.map(Arc::from)
|
||||
.or_else(|| name.clone())
|
||||
.unwrap_or_else(|| format!("P{}", client.pid).into());
|
||||
|
||||
let spawn_pos = wvr_server
|
||||
.last_process_overlay(process_handle)
|
||||
.map_or(SpawnPos::Spread, |oid| {
|
||||
SpawnPos::Parent(OverlaySelector::Id(oid))
|
||||
});
|
||||
let spawn_pos = SpawnPos::WlDefault(
|
||||
window_handle,
|
||||
wvr_server.last_process_overlay(process_handle),
|
||||
);
|
||||
|
||||
let mut icon = icon;
|
||||
|
||||
// Try to get title from xdg_toplevel, unless it's running in cage
|
||||
if !is_cage {
|
||||
let mut needs_title = true;
|
||||
let (xdg_title, app_id): (Option<String>, Option<String>) =
|
||||
with_states(toplevel.wl_surface(), |states| {
|
||||
states.data_map.get::<XdgToplevelSurfaceData>().map_or(
|
||||
(None, None),
|
||||
|t| {
|
||||
let t = t.lock().unwrap();
|
||||
(t.title.clone(), t.app_id.clone())
|
||||
},
|
||||
)
|
||||
});
|
||||
if let Some(xdg_title) = xdg_title {
|
||||
needs_title = false;
|
||||
title = xdg_title.into();
|
||||
}
|
||||
|
||||
// Try to get title & icon from desktop entry
|
||||
if let Some(app_id) = app_id
|
||||
if let Some(app_id) = app_id.as_deref()
|
||||
&& let Some(desktop_entry) =
|
||||
app.desktop_finder.get_cached_entry(&app_id)
|
||||
app.desktop_finder.get_cached_entry(app_id)
|
||||
{
|
||||
if needs_title {
|
||||
title = desktop_entry.app_name.as_ref().into();
|
||||
@@ -492,6 +549,13 @@ impl WvrServerState {
|
||||
}
|
||||
}
|
||||
|
||||
let icon = icon::toplevel_icon(
|
||||
toplevel.wl_surface(),
|
||||
&wvr_server.manager.state.current_icons,
|
||||
&app.desktop_finder,
|
||||
)
|
||||
.or(icon);
|
||||
|
||||
// Fall back to identicon
|
||||
let icon = match icon {
|
||||
Some(icon) => icon,
|
||||
@@ -502,8 +566,34 @@ impl WvrServerState {
|
||||
OverlaySelector::Nothing,
|
||||
spawn_pos,
|
||||
Box::new(move |app: &mut AppState| {
|
||||
let (identity, restore_on_spawn) = app
|
||||
.wvr_server
|
||||
.as_ref()
|
||||
.and_then(|wvr| wvr.wm.windows.get(window_handle))
|
||||
.map_or((None, false), |w| {
|
||||
(w.name.clone(), w.restore_on_spawn)
|
||||
});
|
||||
let persistent = identity.is_some();
|
||||
let name =
|
||||
identity.unwrap_or_else(|| window_handle.transient_name());
|
||||
let icon = app
|
||||
.wvr_server
|
||||
.as_ref()
|
||||
.and_then(|wvr| {
|
||||
wvr.wm.windows.get(window_handle).and_then(|window| {
|
||||
icon::toplevel_icon(
|
||||
window.toplevel.wl_surface(),
|
||||
&wvr.manager.state.current_icons,
|
||||
&app.desktop_finder,
|
||||
)
|
||||
})
|
||||
})
|
||||
.unwrap_or(icon);
|
||||
create_wl_window_overlay(
|
||||
name,
|
||||
title,
|
||||
persistent,
|
||||
restore_on_spawn,
|
||||
app,
|
||||
window_handle,
|
||||
icon,
|
||||
@@ -517,6 +607,16 @@ impl WvrServerState {
|
||||
)));
|
||||
}
|
||||
}
|
||||
WayVRTask::FirstToplevelCommit(toplevel) => {
|
||||
let has_window = wvr_server.wm.find_window_handle(&toplevel).is_some();
|
||||
log::debug!(
|
||||
"FirstToplevelCommit toplevel: {:?}, has_window: {has_window}",
|
||||
toplevel.xdg_toplevel().id()
|
||||
);
|
||||
if has_window {
|
||||
wvr_server.send_first_configure(&toplevel);
|
||||
}
|
||||
}
|
||||
WayVRTask::DropToplevel(client_id, toplevel) => {
|
||||
for client in &wvr_server.manager.clients {
|
||||
if client.client.id() != client_id {
|
||||
@@ -557,9 +657,81 @@ impl WvrServerState {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(key) = wvr_server
|
||||
.wm
|
||||
.windows
|
||||
.get(window_handle)
|
||||
.and_then(|w| w.name.clone())
|
||||
{
|
||||
wvr_server.manager.state.sessions.stored_keys.insert(key);
|
||||
}
|
||||
wvr_server.wm.remove_window(window_handle);
|
||||
}
|
||||
}
|
||||
WayVRTask::WindowIdentityChanged(toplevel) => {
|
||||
let Some(handle) = wvr_server.wm.find_window_handle(&toplevel) else {
|
||||
continue;
|
||||
};
|
||||
let app_id: Option<Arc<str>> = with_states(toplevel.wl_surface(), |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<XdgToplevelSurfaceData>()
|
||||
.and_then(|d| {
|
||||
d.lock()
|
||||
.unwrap()
|
||||
.app_id
|
||||
.as_deref()
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(Into::into)
|
||||
})
|
||||
});
|
||||
let parent = toplevel.parent().and_then(|surface| {
|
||||
wvr_server
|
||||
.wm
|
||||
.windows
|
||||
.iter()
|
||||
.find(|(_, w)| *w.toplevel.wl_surface() == surface)
|
||||
.map(|(id, _)| id)
|
||||
});
|
||||
let (key, restore) = wvr_server.window_identity(
|
||||
&toplevel,
|
||||
app_id.as_deref(),
|
||||
parent,
|
||||
Some(handle),
|
||||
);
|
||||
if let Some(window) = wvr_server.wm.windows.get_mut(handle) {
|
||||
window.app_id = app_id;
|
||||
window.parent = parent;
|
||||
if window.name != key || window.restore_on_spawn != restore {
|
||||
window.name.clone_from(&key);
|
||||
window.restore_on_spawn = restore;
|
||||
if let Some(oid) = wvr_server.window_to_overlay.get(&handle) {
|
||||
let persistent = key.is_some();
|
||||
let name = key.unwrap_or_else(|| handle.transient_name());
|
||||
app.tasks
|
||||
.enqueue(TaskType::Overlay(OverlayTask::SetOverlayName(
|
||||
*oid, name, persistent, restore,
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
WayVRTask::FreezeWindowState(toplevel, key) => {
|
||||
if let Some(handle) = wvr_server.wm.find_window_handle(&toplevel)
|
||||
&& let Some(oid) = wvr_server.window_to_overlay.get(&handle)
|
||||
{
|
||||
app.tasks
|
||||
.enqueue(TaskType::Overlay(OverlayTask::ForgetWindowState(*oid, key)));
|
||||
}
|
||||
}
|
||||
WayVRTask::RenameWindowState(old, new) => {
|
||||
app.tasks
|
||||
.enqueue(TaskType::Overlay(OverlayTask::RenameWindowState(old, new)));
|
||||
}
|
||||
WayVRTask::ForgetWindowState(key) => {
|
||||
app.tasks
|
||||
.enqueue(TaskType::Overlay(OverlayTask::ClearWindowState(key)));
|
||||
}
|
||||
WayVRTask::VisibilityChange(window_handle, visible) => {
|
||||
if let Some(oid) = wvr_server.window_to_overlay.get(&window_handle) {
|
||||
app.tasks
|
||||
@@ -594,6 +766,46 @@ impl WvrServerState {
|
||||
}
|
||||
}
|
||||
}
|
||||
WayVRTask::IconChange(toplevel) => {
|
||||
if let Some(handle) = wvr_server.wm.find_window_handle(&toplevel)
|
||||
&& let Some(oid) = wvr_server.window_to_overlay.get(&handle)
|
||||
{
|
||||
let icon = icon::toplevel_icon(
|
||||
toplevel.wl_surface(),
|
||||
&wvr_server.manager.state.current_icons,
|
||||
&app.desktop_finder,
|
||||
);
|
||||
let fallback = wvr_server
|
||||
.wm
|
||||
.windows
|
||||
.get(handle)
|
||||
.and_then(|w| w.app_id.as_deref())
|
||||
.and_then(|id| app.desktop_finder.get_cached_entry(id))
|
||||
.and_then(|entry| entry.icon_path.as_ref())
|
||||
.map(|s| Arc::<str>::from(s.as_ref()));
|
||||
let icon = icon.or(fallback).or_else(|| {
|
||||
let title =
|
||||
with_states(toplevel.wl_surface(), |states| {
|
||||
states.data_map.get::<XdgToplevelSurfaceData>().and_then(
|
||||
|data| data.lock().ok().and_then(|data| data.title.clone()),
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|| "window".to_owned());
|
||||
DesktopFinder::create_icon(&title).ok().map(Into::into)
|
||||
});
|
||||
if let Some(icon) = icon {
|
||||
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
|
||||
OverlaySelector::Id(*oid),
|
||||
Box::new(move |app, owc| {
|
||||
let _ = owc.backend.notify(
|
||||
app,
|
||||
OverlayEventData::WvrCommand(WvrCommand::SetIcon(icon)),
|
||||
);
|
||||
}),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
WayVRTask::TitleChange(client_id, toplevel) => {
|
||||
for client in &wvr_server.manager.clients {
|
||||
if client.client.id() != client_id {
|
||||
@@ -601,19 +813,33 @@ impl WvrServerState {
|
||||
}
|
||||
let Some(window_handle) = wvr_server.wm.find_window_handle(&toplevel)
|
||||
else {
|
||||
log::warn!("MinimizeRequest: Couldn't find matching window handle");
|
||||
log::warn!("TitleChange: Couldn't find matching window handle");
|
||||
continue;
|
||||
};
|
||||
if let Some(oid) = wvr_server.window_to_overlay.get(&window_handle) {
|
||||
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
|
||||
OverlaySelector::Id(*oid),
|
||||
Box::new(|app, owc| {
|
||||
let _ = owc.backend.notify(
|
||||
app,
|
||||
OverlayEventData::WvrCommand(WvrCommand::ReloadTitle),
|
||||
);
|
||||
}),
|
||||
)));
|
||||
let title: Option<Arc<str>> =
|
||||
with_states(toplevel.wl_surface(), |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<XdgToplevelSurfaceData>()
|
||||
.and_then(|data| {
|
||||
data.lock().ok().and_then(|data| data.title.clone())
|
||||
})
|
||||
.filter(|title| !title.is_empty())
|
||||
.map(Into::into)
|
||||
});
|
||||
if let Some(title) = title {
|
||||
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
|
||||
OverlaySelector::Id(*oid),
|
||||
Box::new(move |app, owc| {
|
||||
owc.title = Some(title);
|
||||
let _ = owc.backend.notify(
|
||||
app,
|
||||
OverlayEventData::WvrCommand(WvrCommand::ReloadTitle),
|
||||
);
|
||||
}),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -669,7 +895,7 @@ impl WvrServerState {
|
||||
Box::new(move |app| {
|
||||
create_drag_item(app, icon, pointer)
|
||||
.inspect_err(|e| {
|
||||
log::warn!("Could not create drag item overlay: {e:?}")
|
||||
log::warn!("Could not create drag item overlay: {e:?}");
|
||||
})
|
||||
.ok()
|
||||
}),
|
||||
@@ -703,7 +929,7 @@ impl WvrServerState {
|
||||
|
||||
wvr_server.ticks += 1;
|
||||
|
||||
Ok(tasks)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn config_changed(&mut self, config: &GeneralConfig) {
|
||||
@@ -746,6 +972,74 @@ impl WvrServerState {
|
||||
.copied()
|
||||
}
|
||||
|
||||
/// session restore send as part of first configure
|
||||
fn send_first_configure(&self, toplevel: &ToplevelSurface) {
|
||||
if toplevel.is_initial_configure_sent() {
|
||||
return;
|
||||
}
|
||||
if let Some(s) = self.manager.state.sessions.get_toplevel(toplevel)
|
||||
&& s.restore_requested
|
||||
&& self.manager.state.sessions.stored_keys.contains(&s.key)
|
||||
&& let Some(resource) = s.restore_resource.as_ref()
|
||||
{
|
||||
resource.restored();
|
||||
}
|
||||
let serial = toplevel.send_configure();
|
||||
log::debug!(
|
||||
"Initial configure sent: to toplevel: {:?}, serial: {serial:?}",
|
||||
toplevel.xdg_toplevel().id()
|
||||
);
|
||||
}
|
||||
|
||||
/// Identifier key based on xdg session or app_id+tags or app_id+pid
|
||||
fn window_identity(
|
||||
&self,
|
||||
toplevel: &ToplevelSurface,
|
||||
app_id: Option<&str>,
|
||||
parent: Option<window::WindowHandle>,
|
||||
except: Option<window::WindowHandle>,
|
||||
) -> (Option<Arc<str>>, bool) {
|
||||
let session = self.manager.state.sessions.get_toplevel(toplevel);
|
||||
let tag = self
|
||||
.manager
|
||||
.state
|
||||
.toplevel_tags
|
||||
.get(&toplevel.xdg_toplevel().id());
|
||||
let identity = session
|
||||
.map(|s| (s.key.clone(), s.restore_requested))
|
||||
.or_else(|| {
|
||||
app_id
|
||||
.zip(tag)
|
||||
.map(|(app_id, tag)| (tag_key(app_id, tag), true))
|
||||
})
|
||||
.or_else(|| {
|
||||
let app_id = app_id?;
|
||||
if parent.is_some()
|
||||
|| self.wm.windows.iter().any(|(id, w)| {
|
||||
Some(id) != except
|
||||
&& w.parent.is_none()
|
||||
&& w.app_id.as_deref() == Some(app_id)
|
||||
})
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some((app_key(app_id), true))
|
||||
}
|
||||
});
|
||||
match identity {
|
||||
Some((key, restore))
|
||||
if !self
|
||||
.wm
|
||||
.windows
|
||||
.iter()
|
||||
.any(|(id, w)| Some(id) != except && w.name.as_ref() == Some(&key)) =>
|
||||
{
|
||||
(Some(key), restore)
|
||||
}
|
||||
_ => (None, false),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_removed(&mut self, tasks: &mut TaskContainer, process: process::ProcessHandle) {
|
||||
let mut to_remove = vec![];
|
||||
|
||||
@@ -1080,13 +1374,41 @@ impl WvrServerState {
|
||||
}
|
||||
|
||||
/// Use HidWrapper::set_input_focus instead!!!
|
||||
pub const fn set_input_focus(&mut self, has_focus: bool) {
|
||||
pub fn set_input_focus(&mut self, has_focus: bool) {
|
||||
if self.has_input_focus && !has_focus {
|
||||
let pos = self
|
||||
.wm
|
||||
.mouse
|
||||
.as_ref()
|
||||
.map_or(DVec2::ZERO, |mouse| mouse.pos);
|
||||
self.manager
|
||||
.send_mouse_move(None, pos, DVec2::ZERO, DVec2::ZERO);
|
||||
self.manager.state.cursor_image = CursorImageStatus::default_named();
|
||||
}
|
||||
self.has_input_focus = has_focus;
|
||||
if !has_focus {
|
||||
self.wm.mouse = None;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn leave_overlay(&mut self, window: window::WindowHandle, pointer: usize) {
|
||||
if self.manager.state.vr_pointer != pointer || self.dnd_active_for_pointer(pointer) {
|
||||
return;
|
||||
}
|
||||
let Some(mouse) = self
|
||||
.wm
|
||||
.mouse
|
||||
.as_ref()
|
||||
.filter(|mouse| mouse.hover_window == window)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let pos = mouse.pos;
|
||||
self.manager
|
||||
.send_mouse_move(None, pos, DVec2::ZERO, DVec2::ZERO);
|
||||
self.wm.mouse = None;
|
||||
}
|
||||
|
||||
pub const fn get_focused_window(&self) -> Option<window::WindowHandle> {
|
||||
if !self.has_input_focus {
|
||||
return None;
|
||||
@@ -1226,7 +1548,7 @@ impl WvrServerState {
|
||||
self.manager.send_pointer_button(index, pressed);
|
||||
}
|
||||
|
||||
pub fn set_vr_pointer(&mut self, pointer: usize) {
|
||||
pub const fn set_vr_pointer(&mut self, pointer: usize) {
|
||||
self.manager.state.vr_pointer = pointer;
|
||||
}
|
||||
|
||||
@@ -1241,7 +1563,7 @@ impl WvrServerState {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dnd_active(&self) -> bool {
|
||||
pub const fn dnd_active(&self) -> bool {
|
||||
self.manager.state.dnd_pointer.is_some()
|
||||
}
|
||||
|
||||
@@ -1307,11 +1629,6 @@ impl WvrServerState {
|
||||
self.manager.state.set_clipboard_text(content);
|
||||
}
|
||||
|
||||
pub fn add_external_process(&mut self, pid: u32) -> process::ProcessHandle {
|
||||
self.processes
|
||||
.insert(process::Process::External(process::ExternalProcess { pid }))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn spawn_process(
|
||||
&mut self,
|
||||
@@ -1468,6 +1785,7 @@ impl SurfaceBufWithImage {
|
||||
|
||||
let mut guard = surface_data.cached_state.get::<ViewportCachedState>();
|
||||
let viewport = *guard.current();
|
||||
drop(guard);
|
||||
let Some(src) = viewport.src else {
|
||||
return [0.0, 0.0, 1.0, 1.0];
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{collections::HashMap, io::Read, sync::Arc};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use slotmap::{DenseSlotMap, new_key_type};
|
||||
use slotmap::new_key_type;
|
||||
use wayvr_ipc::{packet_client, packet_server};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -87,30 +87,15 @@ impl Process {
|
||||
|
||||
impl Drop for WayVRProcess {
|
||||
fn drop(&mut self) {
|
||||
log::info!(
|
||||
"Sending SIGTERM (graceful exit) to process {} ({})",
|
||||
self.child.id(),
|
||||
self.exec_path.as_str()
|
||||
);
|
||||
self.kill(libc::SIGTERM);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_process_env_value(pid: i32, key: &str) -> anyhow::Result<Option<String>> {
|
||||
let path = format!("/proc/{pid}/environ");
|
||||
let mut env_data = String::new();
|
||||
std::fs::File::open(path)?.read_to_string(&mut env_data)?;
|
||||
let lines: Vec<&str> = env_data.split('\0').filter(|s| !s.is_empty()).collect();
|
||||
|
||||
for line in lines {
|
||||
if let Some(cell) = line.split_once('=')
|
||||
&& cell.0 == key
|
||||
{
|
||||
return Ok(Some(String::from(cell.1)));
|
||||
if self.is_running() {
|
||||
log::info!(
|
||||
"Sending SIGTERM (graceful exit) to process {} ({})",
|
||||
self.child.id(),
|
||||
self.exec_path.as_str()
|
||||
);
|
||||
self.kill(libc::SIGTERM);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
impl WayVRProcess {
|
||||
@@ -127,9 +112,11 @@ impl WayVRProcess {
|
||||
}
|
||||
|
||||
fn kill(&mut self, signal: i32) {
|
||||
unsafe {
|
||||
// Gracefully stop process
|
||||
libc::kill(self.child.id() as i32, signal);
|
||||
if self.is_running() {
|
||||
unsafe {
|
||||
// Gracefully stop process
|
||||
libc::kill(self.child.id() as i32, signal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,43 +166,6 @@ new_key_type! {
|
||||
pub struct ProcessHandle;
|
||||
}
|
||||
|
||||
pub fn find_by_pid(
|
||||
processes: &DenseSlotMap<ProcessHandle, Process>,
|
||||
pid: u32,
|
||||
) -> Option<ProcessHandle> {
|
||||
log::debug!("Finding process with PID {pid}");
|
||||
|
||||
for (handle, process) in processes {
|
||||
match &process {
|
||||
Process::Managed(wayvr_process) => {
|
||||
if wayvr_process.child.id() == pid {
|
||||
return Some(handle);
|
||||
}
|
||||
}
|
||||
Process::External(external_process) => {
|
||||
if external_process.pid == pid {
|
||||
return Some(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log::debug!("Finding by PID failed, trying WAYVR_DISPLAY_AUTH...");
|
||||
|
||||
if let Ok(Some(value)) = get_process_env_value(pid as i32, "WAYVR_DISPLAY_AUTH") {
|
||||
for (handle, process) in processes {
|
||||
if let Process::Managed(wayvr_process) = &process
|
||||
&& wayvr_process.auth_key == value
|
||||
{
|
||||
return Some(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log::debug!("Process find with PID {pid} failed");
|
||||
None
|
||||
}
|
||||
|
||||
impl ProcessHandle {
|
||||
pub fn from_packet(handle: packet_server::WvrProcessHandle) -> Self {
|
||||
Self::from(slotmap::KeyData::from_ffi(handle.user))
|
||||
|
||||
@@ -0,0 +1,419 @@
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use smithay::{
|
||||
reexports::wayland_server::{
|
||||
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, WEnum,
|
||||
backend::ObjectId,
|
||||
},
|
||||
wayland::shell::xdg::ToplevelSurface,
|
||||
};
|
||||
|
||||
use super::{
|
||||
WayVRTask,
|
||||
comp::Application,
|
||||
session_protocol::server::{
|
||||
xdg_session_manager_v1::{self, XdgSessionManagerV1},
|
||||
xdg_session_v1::{self, XdgSessionV1},
|
||||
xdg_toplevel_session_v1::{self, XdgToplevelSessionV1},
|
||||
},
|
||||
};
|
||||
|
||||
const SESSION_PREFIX: &str = "wayvr/session/";
|
||||
|
||||
pub(super) fn session_key(session: &str, name: &str) -> Arc<str> {
|
||||
format!("{SESSION_PREFIX}{}:{session}/{name}", session.len()).into()
|
||||
}
|
||||
|
||||
fn session_prefix(session: &str) -> String {
|
||||
format!("{SESSION_PREFIX}{}:{session}/", session.len())
|
||||
}
|
||||
|
||||
pub(super) fn tag_key(app_id: &str, tag: &str) -> Arc<str> {
|
||||
format!("wayvr/tag/{}:{app_id}/{}:{tag}", app_id.len(), tag.len()).into()
|
||||
}
|
||||
|
||||
pub(super) fn app_key(app_id: &str) -> Arc<str> {
|
||||
format!("wayvr/app/{}:{app_id}", app_id.len()).into()
|
||||
}
|
||||
|
||||
pub struct WindowSession {
|
||||
pub key: Arc<str>,
|
||||
pub restore_requested: bool,
|
||||
pub restore_resource: Option<XdgToplevelSessionV1>,
|
||||
session_id: String,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SessionRegistry {
|
||||
sessions: HashMap<String, Option<XdgSessionV1>>,
|
||||
toplevels: HashMap<ObjectId, WindowSession>,
|
||||
pub stored_keys: HashSet<Arc<str>>,
|
||||
pub committed_toplevels: HashSet<ObjectId>,
|
||||
}
|
||||
|
||||
impl SessionRegistry {
|
||||
pub fn new(saved_keys: HashSet<Arc<str>>) -> Self {
|
||||
Self {
|
||||
stored_keys: saved_keys,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_toplevel(&self, toplevel: &ToplevelSurface) -> Option<&WindowSession> {
|
||||
self.toplevels.get(&toplevel.xdg_toplevel().id())
|
||||
}
|
||||
|
||||
pub fn forget_toplevel(&mut self, toplevel: &ToplevelSurface) {
|
||||
self.toplevels.remove(&toplevel.xdg_toplevel().id());
|
||||
self.committed_toplevels
|
||||
.remove(&toplevel.xdg_toplevel().id());
|
||||
}
|
||||
}
|
||||
|
||||
impl Application {
|
||||
fn detach_session(&mut self, session_id: &str, forget: bool) {
|
||||
let detached: Vec<_> = self
|
||||
.sessions
|
||||
.toplevels
|
||||
.iter()
|
||||
.filter(|(_, w)| w.session_id == session_id)
|
||||
.map(|(id, w)| (id.clone(), w.key.clone()))
|
||||
.collect();
|
||||
|
||||
for (id, key) in detached {
|
||||
self.sessions.toplevels.remove(&id);
|
||||
let surface = self
|
||||
.xdg_shell
|
||||
.toplevel_surfaces()
|
||||
.iter()
|
||||
.find(|surface| surface.xdg_toplevel().id() == id)
|
||||
.cloned();
|
||||
|
||||
if forget {
|
||||
self.sessions.stored_keys.remove(&key);
|
||||
} else {
|
||||
self.sessions.stored_keys.insert(key.clone());
|
||||
if let Some(surface) = &surface {
|
||||
self.wayvr_tasks
|
||||
.send(WayVRTask::FreezeWindowState(surface.clone(), key.clone()));
|
||||
}
|
||||
}
|
||||
if let Some(surface) = surface {
|
||||
self.wayvr_tasks
|
||||
.send(WayVRTask::WindowIdentityChanged(surface));
|
||||
}
|
||||
if forget {
|
||||
self.wayvr_tasks.send(WayVRTask::ForgetWindowState(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_session_toplevel(&mut self, session_id: &str, name: &str) {
|
||||
let key = session_key(session_id, name);
|
||||
let live = self
|
||||
.sessions
|
||||
.toplevels
|
||||
.iter()
|
||||
.find(|(_, w)| w.session_id == session_id && w.key == key)
|
||||
.map(|(id, _)| id.clone());
|
||||
if let Some(id) = live {
|
||||
self.sessions.toplevels.remove(&id);
|
||||
if let Some(surface) = self
|
||||
.xdg_shell
|
||||
.toplevel_surfaces()
|
||||
.iter()
|
||||
.find(|surface| surface.xdg_toplevel().id() == id)
|
||||
.cloned()
|
||||
{
|
||||
self.wayvr_tasks
|
||||
.send(WayVRTask::WindowIdentityChanged(surface));
|
||||
}
|
||||
}
|
||||
self.sessions.stored_keys.remove(&key);
|
||||
self.wayvr_tasks.send(WayVRTask::ForgetWindowState(key));
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobalDispatch<XdgSessionManagerV1, ()> for Application {
|
||||
fn bind(
|
||||
_state: &mut Self,
|
||||
_dh: &DisplayHandle,
|
||||
_client: &Client,
|
||||
resource: New<XdgSessionManagerV1>,
|
||||
_data: &(),
|
||||
data_init: &mut DataInit<'_, Self>,
|
||||
) {
|
||||
data_init.init(resource, ());
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<XdgSessionManagerV1, ()> for Application {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
client: &Client,
|
||||
resource: &XdgSessionManagerV1,
|
||||
request: xdg_session_manager_v1::Request,
|
||||
_data: &(),
|
||||
_dh: &DisplayHandle,
|
||||
data_init: &mut DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
xdg_session_manager_v1::Request::GetSession {
|
||||
id,
|
||||
reason,
|
||||
session_id,
|
||||
} => {
|
||||
if let WEnum::Unknown(_) = reason {
|
||||
resource.post_error(
|
||||
xdg_session_manager_v1::Error::InvalidReason,
|
||||
"unrecognized session reason",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let known = session_id.as_ref().is_some_and(|token| {
|
||||
state.sessions.sessions.contains_key(token)
|
||||
|| state
|
||||
.sessions
|
||||
.stored_keys
|
||||
.iter()
|
||||
.any(|key| key.starts_with(session_prefix(token).as_str()))
|
||||
});
|
||||
let token = if known {
|
||||
session_id.unwrap()
|
||||
} else {
|
||||
uuid::Uuid::new_v4().to_string()
|
||||
};
|
||||
|
||||
if let Some(Some(old)) = state.sessions.sessions.get(&token) {
|
||||
if old.client().is_some_and(|c| c.id() == client.id()) {
|
||||
resource.post_error(
|
||||
xdg_session_manager_v1::Error::InUse,
|
||||
"session is already in use by this client",
|
||||
);
|
||||
return;
|
||||
}
|
||||
old.replaced();
|
||||
state.detach_session(&token, false);
|
||||
}
|
||||
|
||||
let session_resource = data_init.init(id, token.clone());
|
||||
if known {
|
||||
session_resource.restored();
|
||||
} else {
|
||||
session_resource.created(token.clone());
|
||||
}
|
||||
state
|
||||
.sessions
|
||||
.sessions
|
||||
.insert(token, Some(session_resource));
|
||||
}
|
||||
xdg_session_manager_v1::Request::Destroy => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<XdgSessionV1, String> for Application {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_client: &Client,
|
||||
resource: &XdgSessionV1,
|
||||
request: xdg_session_v1::Request,
|
||||
session_id: &String,
|
||||
_dh: &DisplayHandle,
|
||||
data_init: &mut DataInit<'_, Self>,
|
||||
) {
|
||||
if state
|
||||
.sessions
|
||||
.sessions
|
||||
.get(session_id)
|
||||
.and_then(Option::as_ref)
|
||||
!= Some(resource)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let restore_requested = matches!(&request, xdg_session_v1::Request::RestoreToplevel { .. });
|
||||
match request {
|
||||
xdg_session_v1::Request::AddToplevel { id, toplevel, name }
|
||||
| xdg_session_v1::Request::RestoreToplevel { id, toplevel, name } => {
|
||||
let key = session_key(session_id, &name);
|
||||
if state
|
||||
.sessions
|
||||
.toplevels
|
||||
.values()
|
||||
.any(|w| w.session_id == *session_id && w.key == key)
|
||||
|| (!restore_requested && state.sessions.stored_keys.contains(&key))
|
||||
{
|
||||
resource.post_error(
|
||||
xdg_session_v1::Error::NameInUse,
|
||||
"toplevel name already in use",
|
||||
);
|
||||
return;
|
||||
}
|
||||
let toplevel_id = toplevel.id();
|
||||
if state.sessions.toplevels.contains_key(&toplevel_id) {
|
||||
resource.post_error(
|
||||
xdg_session_v1::Error::AlreadyAdded,
|
||||
"toplevel already belongs to a session",
|
||||
);
|
||||
return;
|
||||
}
|
||||
if restore_requested && state.sessions.committed_toplevels.contains(&toplevel_id) {
|
||||
resource.post_error(
|
||||
xdg_session_v1::Error::AlreadyMapped,
|
||||
"restore_toplevel after first commit",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let can_restore = restore_requested && state.sessions.stored_keys.contains(&key);
|
||||
let session_resource = data_init.init(id, ());
|
||||
state.sessions.toplevels.insert(
|
||||
toplevel_id,
|
||||
WindowSession {
|
||||
key,
|
||||
session_id: session_id.clone(),
|
||||
restore_requested: can_restore,
|
||||
restore_resource: Some(session_resource),
|
||||
},
|
||||
);
|
||||
if let Some(surface) = state.xdg_shell.get_toplevel(&toplevel) {
|
||||
state
|
||||
.wayvr_tasks
|
||||
.send(WayVRTask::WindowIdentityChanged(surface));
|
||||
}
|
||||
}
|
||||
xdg_session_v1::Request::RemoveToplevel { name } => {
|
||||
state.remove_session_toplevel(session_id, &name);
|
||||
}
|
||||
xdg_session_v1::Request::Destroy => {
|
||||
state.detach_session(session_id, false);
|
||||
state.sessions.sessions.insert(session_id.clone(), None);
|
||||
}
|
||||
xdg_session_v1::Request::Remove => {
|
||||
state.detach_session(session_id, true);
|
||||
let prefix = session_prefix(session_id);
|
||||
let keys: Vec<_> = state
|
||||
.sessions
|
||||
.stored_keys
|
||||
.iter()
|
||||
.filter(|key| key.starts_with(prefix.as_str()))
|
||||
.cloned()
|
||||
.collect();
|
||||
for key in keys {
|
||||
state.sessions.stored_keys.remove(&key);
|
||||
state.wayvr_tasks.send(WayVRTask::ForgetWindowState(key));
|
||||
}
|
||||
state.sessions.sessions.remove(session_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn destroyed(
|
||||
state: &mut Self,
|
||||
_client: smithay::reexports::wayland_server::backend::ClientId,
|
||||
resource: &XdgSessionV1,
|
||||
session_id: &String,
|
||||
) {
|
||||
if state
|
||||
.sessions
|
||||
.sessions
|
||||
.get(session_id)
|
||||
.and_then(Option::as_ref)
|
||||
== Some(resource)
|
||||
{
|
||||
state.detach_session(session_id, false);
|
||||
state.sessions.sessions.insert(session_id.clone(), None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<XdgToplevelSessionV1, ()> for Application {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_client: &Client,
|
||||
resource: &XdgToplevelSessionV1,
|
||||
request: xdg_toplevel_session_v1::Request,
|
||||
_data: &(),
|
||||
_dh: &DisplayHandle,
|
||||
_data_init: &mut DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
xdg_toplevel_session_v1::Request::Destroy => {
|
||||
for w in state.sessions.toplevels.values_mut() {
|
||||
if w.restore_resource.as_ref() == Some(resource) {
|
||||
w.restore_resource = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
xdg_toplevel_session_v1::Request::Rename { name } => {
|
||||
let entry = state
|
||||
.sessions
|
||||
.toplevels
|
||||
.iter()
|
||||
.find(|(_, w)| w.restore_resource.as_ref() == Some(resource))
|
||||
.map(|(id, w)| (id.clone(), w.session_id.clone(), w.key.clone()));
|
||||
let Some((id, session_id, old_key)) = entry else {
|
||||
return;
|
||||
};
|
||||
let new_key = session_key(&session_id, &name);
|
||||
if old_key == new_key {
|
||||
return;
|
||||
}
|
||||
if state.sessions.stored_keys.contains(&new_key)
|
||||
|| state
|
||||
.sessions
|
||||
.toplevels
|
||||
.values()
|
||||
.any(|w| w.session_id == session_id && w.key == new_key)
|
||||
{
|
||||
if let Some(Some(owner)) = state.sessions.sessions.get(&session_id) {
|
||||
owner.post_error(
|
||||
xdg_session_v1::Error::NameInUse,
|
||||
"toplevel name already in use",
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(w) = state.sessions.toplevels.get_mut(&id) {
|
||||
w.key = new_key.clone();
|
||||
}
|
||||
if state.sessions.stored_keys.remove(&old_key) {
|
||||
state.sessions.stored_keys.insert(new_key.clone());
|
||||
}
|
||||
state
|
||||
.wayvr_tasks
|
||||
.send(WayVRTask::RenameWindowState(old_key, new_key));
|
||||
if let Some(surface) = state
|
||||
.xdg_shell
|
||||
.toplevel_surfaces()
|
||||
.iter()
|
||||
.find(|s| s.xdg_toplevel().id() == id)
|
||||
.cloned()
|
||||
{
|
||||
state
|
||||
.wayvr_tasks
|
||||
.send(WayVRTask::WindowIdentityChanged(surface));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn destroyed(
|
||||
state: &mut Self,
|
||||
_client: smithay::reexports::wayland_server::backend::ClientId,
|
||||
resource: &XdgToplevelSessionV1,
|
||||
_data: &(),
|
||||
) {
|
||||
for w in state.sessions.toplevels.values_mut() {
|
||||
if w.restore_resource.as_ref() == Some(resource) {
|
||||
w.restore_resource = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#[allow(dead_code, non_camel_case_types, unused_unsafe, unused_variables)]
|
||||
#[allow(non_upper_case_globals, non_snake_case, unused_imports)]
|
||||
#[allow(clippy::all)]
|
||||
pub mod server {
|
||||
use smithay::reexports::wayland_server;
|
||||
use wayland_protocols::xdg::shell::server::xdg_toplevel;
|
||||
use wayland_server::protocol::*;
|
||||
|
||||
pub mod __interfaces {
|
||||
use super::wayland_server;
|
||||
use wayland_protocols::xdg::shell::server::__interfaces::{
|
||||
XDG_TOPLEVEL_INTERFACE, xdg_toplevel_interface,
|
||||
};
|
||||
use wayland_server::backend as wayland_backend;
|
||||
use wayland_server::protocol::__interfaces::*;
|
||||
wayland_scanner::generate_interfaces!("protocols/xdg-session-management-v1.xml");
|
||||
}
|
||||
use self::__interfaces::{
|
||||
XDG_SESSION_MANAGER_V1_INTERFACE, XDG_SESSION_V1_INTERFACE,
|
||||
XDG_TOPLEVEL_SESSION_V1_INTERFACE,
|
||||
};
|
||||
wayland_scanner::generate_server_code!("protocols/xdg-session-management-v1.xml");
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::rc::Rc;
|
||||
use std::{rc::Rc, sync::Arc};
|
||||
|
||||
use glam::DVec2;
|
||||
use slotmap::{DenseSlotMap, new_key_type};
|
||||
@@ -24,6 +24,10 @@ pub struct Window {
|
||||
pub visible: bool,
|
||||
pub toplevel: Rc<ToplevelSurface>,
|
||||
pub process: process::ProcessHandle,
|
||||
pub app_id: Option<Arc<str>>,
|
||||
pub parent: Option<WindowHandle>,
|
||||
pub name: Option<Arc<str>>,
|
||||
pub restore_on_spawn: bool,
|
||||
|
||||
pub pending_configure: Option<PendingConfigure>,
|
||||
}
|
||||
@@ -35,6 +39,10 @@ impl Window {
|
||||
bounds: Size<i32, Logical>,
|
||||
min_size: Size<i32, Logical>,
|
||||
max_size: Size<i32, Logical>,
|
||||
app_id: Option<Arc<str>>,
|
||||
parent: Option<WindowHandle>,
|
||||
name: Option<Arc<str>>,
|
||||
restore_on_spawn: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
bounds,
|
||||
@@ -45,6 +53,10 @@ impl Window {
|
||||
visible: true,
|
||||
toplevel,
|
||||
process,
|
||||
app_id,
|
||||
parent,
|
||||
name,
|
||||
restore_on_spawn,
|
||||
pending_configure: None,
|
||||
}
|
||||
}
|
||||
@@ -148,6 +160,10 @@ pub struct CreateWindowParams {
|
||||
pub max_size: Size<i32, Logical>,
|
||||
pub size_x: u32,
|
||||
pub size_y: u32,
|
||||
pub app_id: Option<Arc<str>>,
|
||||
pub parent: Option<WindowHandle>,
|
||||
pub name: Option<Arc<str>>,
|
||||
pub restore_on_spawn: bool,
|
||||
}
|
||||
|
||||
impl WindowManager {
|
||||
@@ -175,6 +191,10 @@ impl WindowManager {
|
||||
par.bounds,
|
||||
par.min_size,
|
||||
par.max_size,
|
||||
par.app_id,
|
||||
par.parent,
|
||||
par.name,
|
||||
par.restore_on_spawn,
|
||||
);
|
||||
window.remember_committed_size(Size::new(par.size_x as i32, par.size_y as i32), None);
|
||||
self.windows.insert(window)
|
||||
@@ -199,4 +219,8 @@ impl WindowHandle {
|
||||
user: self.0.as_ffi(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transient_name(self) -> Arc<str> {
|
||||
format!("wayvr/volatile/{}", self.as_packet().user).into()
|
||||
}
|
||||
}
|
||||
+9
-3
@@ -9,9 +9,9 @@ use wayvr_ipc::packet_client::WvrProcessLaunchParams;
|
||||
use wlx_common::{
|
||||
astr_containers::AStrMap,
|
||||
config::{
|
||||
AltModifier, CaptureMethod, ChromaKeyParams, DefaultPositioning, GeneralConfig,
|
||||
HandsfreeAltTab, HandsfreePointer, InputCaptureMethod, InputEmulationMethod, PinnedApp,
|
||||
SerializedWindowSet,
|
||||
AltModifier, CONFIG_VERSION, CaptureMethod, ChromaKeyParams, DefaultPositioning,
|
||||
GeneralConfig, HandsfreeAltTab, HandsfreePointer, InputCaptureMethod, InputEmulationMethod,
|
||||
PinnedApp, SerializedWindowSet,
|
||||
},
|
||||
config_io,
|
||||
locale::Language,
|
||||
@@ -189,6 +189,7 @@ pub struct AutoSettings {
|
||||
pub input_emulation_method: InputEmulationMethod,
|
||||
pub tutorial_graduated: bool,
|
||||
pub whisper_model: Arc<str>,
|
||||
pub whisper_gpu: u32,
|
||||
pub default_overlay_scale: f32,
|
||||
pub color_palette: Arc<str>,
|
||||
pub snap_angle_deg: f32,
|
||||
@@ -199,6 +200,7 @@ pub struct AutoSettings {
|
||||
pub default_opacity: f32,
|
||||
pub default_positioning: DefaultPositioning,
|
||||
pub middle_grabs_single: bool,
|
||||
pub config_version: u32,
|
||||
}
|
||||
|
||||
fn get_settings_path() -> PathBuf {
|
||||
@@ -265,6 +267,7 @@ pub fn save_settings(config: &GeneralConfig) -> anyhow::Result<()> {
|
||||
input_emulation_method: config.input_emulation_method,
|
||||
tutorial_graduated: config.tutorial_graduated,
|
||||
whisper_model: config.whisper_model.clone(),
|
||||
whisper_gpu: config.whisper_gpu,
|
||||
default_overlay_scale: config.default_overlay_scale,
|
||||
color_palette: config.color_palette.clone(),
|
||||
snap_angle_deg: config.snap_angle_deg,
|
||||
@@ -275,6 +278,7 @@ pub fn save_settings(config: &GeneralConfig) -> anyhow::Result<()> {
|
||||
default_opacity: config.default_opacity,
|
||||
default_positioning: config.default_positioning,
|
||||
middle_grabs_single: config.middle_grabs_single,
|
||||
config_version: CONFIG_VERSION,
|
||||
};
|
||||
|
||||
let json = serde_json::to_string_pretty(&conf).unwrap(); // want panic
|
||||
@@ -292,6 +296,7 @@ pub struct AutoState {
|
||||
pub spawn_overlays: Vec<Arc<str>>,
|
||||
pub last_set: u32,
|
||||
pub attribs: AStrMap<Vec<BackendAttribValue>>,
|
||||
pub state_version: u32,
|
||||
}
|
||||
|
||||
fn get_state_path() -> PathBuf {
|
||||
@@ -306,6 +311,7 @@ pub fn save_state(config: &GeneralConfig) -> anyhow::Result<()> {
|
||||
last_set: config.last_set,
|
||||
spawn_overlays: config.spawn_overlays.clone(),
|
||||
attribs: config.attribs.clone(),
|
||||
state_version: CONFIG_VERSION,
|
||||
};
|
||||
|
||||
let json = serde_json::to_string_pretty(&conf).unwrap(); // want panic
|
||||
|
||||
@@ -244,7 +244,7 @@ pub(super) fn setup_custom_button<S: 'static>(
|
||||
template_name: template_name.clone(),
|
||||
template_params: template_params.clone(),
|
||||
},
|
||||
position: data.metadata.get_mouse_pos_absolute().unwrap(), //want panic
|
||||
position: data.metadata.get_mouse_pos_absolute().unwrap().into(), // want panic
|
||||
});
|
||||
Ok(EventResult::Consumed)
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ impl OverlayList {
|
||||
}
|
||||
|
||||
params.insert_rc("idx", i.to_string().into());
|
||||
params.insert("name", meta.name.as_ref());
|
||||
params.insert("name", meta.title.as_deref().unwrap_or(&meta.name));
|
||||
parser_state
|
||||
.instantiate_template(doc_params, template, layout, root, params)?;
|
||||
let overlay_button = parser_state
|
||||
|
||||
+3
-27
@@ -1,38 +1,17 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::backend::wayvr::{self, WayVRTask, WvrServerState};
|
||||
use crate::backend::wayvr::{WayVRTask, WvrServerState};
|
||||
use wayvr_ipc::packet_client::{HandsfreeMode, HandsfreeParams};
|
||||
use wayvr_ipc::packet_server::PacketServer;
|
||||
use wlx_common::config::HandsfreePointer;
|
||||
|
||||
use crate::{
|
||||
backend::{
|
||||
self,
|
||||
task::{InputTask, OverlayTask, TaskType, ToggleMode},
|
||||
},
|
||||
backend::task::{InputTask, OverlayTask, TaskType, ToggleMode},
|
||||
ipc::{signal::WayVRSignal, window_attrib, window_state},
|
||||
state::AppState,
|
||||
windowing::{OverlaySelector, manager::OverlayWindowManager},
|
||||
};
|
||||
|
||||
fn process_tick_tasks(
|
||||
tick_tasks: Vec<backend::wayvr::TickTask>,
|
||||
server_state: &mut WvrServerState,
|
||||
) {
|
||||
for tick_task in tick_tasks {
|
||||
match tick_task {
|
||||
backend::wayvr::TickTask::NewExternalProcess(request) => {
|
||||
log::info!("Registering external process with PID {}", request.pid);
|
||||
server_state.add_external_process(request.pid);
|
||||
server_state.manager.add_client(wayvr::client::WayVRClient {
|
||||
client: request.client,
|
||||
pid: request.pid,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tick_events<O>(
|
||||
app: &mut AppState,
|
||||
overlays: &mut OverlayWindowManager<O>,
|
||||
@@ -135,10 +114,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
let tick_tasks = WvrServerState::tick_events(app)?;
|
||||
if let Some(wayvr_server) = app.wvr_server.as_mut() {
|
||||
process_tick_tasks(tick_tasks, wayvr_server);
|
||||
}
|
||||
WvrServerState::tick_events(app)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+22
-21
@@ -6,6 +6,7 @@ use crate::{
|
||||
backend::input::InputState,
|
||||
ipc::{event_queue::SyncEventQueue, signal::WayVRSignal},
|
||||
};
|
||||
use anyhow::Context;
|
||||
use bytes::BufMut;
|
||||
use glam::Vec3A;
|
||||
use interprocess::local_socket::{self, ToNsName, traits::Listener};
|
||||
@@ -88,17 +89,11 @@ fn read_payload(conn: &mut local_socket::Stream, size: u32) -> anyhow::Result<Op
|
||||
|
||||
pub struct TickParams<'a> {
|
||||
pub wvr_server: &'a mut WvrServerState,
|
||||
#[allow(dead_code)]
|
||||
pub tasks: &'a mut Vec<wayvr::TickTask>,
|
||||
pub signals: &'a SyncEventQueue<WayVRSignal>,
|
||||
pub input_state: &'a InputState,
|
||||
pub hid_wrapper: &'a mut HidWrapper,
|
||||
}
|
||||
|
||||
pub fn gen_args_vec(input: &str) -> Vec<&str> {
|
||||
input.split_whitespace().collect()
|
||||
}
|
||||
|
||||
pub fn gen_env_vec(input: &[String]) -> Vec<(&str, &str)> {
|
||||
input
|
||||
.iter()
|
||||
@@ -226,22 +221,28 @@ impl Connection {
|
||||
serial: ipc::Serial,
|
||||
packet_params: packet_client::WvrProcessLaunchParams,
|
||||
) -> anyhow::Result<()> {
|
||||
let args_vec = gen_args_vec(&packet_params.args);
|
||||
let env_vec = gen_env_vec(&packet_params.env);
|
||||
let res = {
|
||||
let mut argv_iter = packet_params.argv.iter().map(String::as_str);
|
||||
let exec_path = argv_iter
|
||||
.next()
|
||||
.context("No executable for process_launch")?;
|
||||
let exec_args = argv_iter.collect::<Box<_>>();
|
||||
let env_vec = gen_env_vec(&packet_params.env);
|
||||
|
||||
let res = params.wvr_server.spawn_process(
|
||||
&packet_params.name,
|
||||
&packet_params.exec,
|
||||
&args_vec,
|
||||
&env_vec,
|
||||
packet_params.resolution,
|
||||
packet_params.pos_mode,
|
||||
None,
|
||||
packet_params.icon.as_deref(),
|
||||
packet_params.userdata,
|
||||
);
|
||||
|
||||
let res = res.map(ProcessHandle::as_packet).map_err(|e| e.to_string());
|
||||
params.wvr_server.spawn_process(
|
||||
&packet_params.name,
|
||||
exec_path,
|
||||
&exec_args,
|
||||
&env_vec,
|
||||
packet_params.resolution,
|
||||
packet_params.pos_mode,
|
||||
None,
|
||||
packet_params.icon.as_deref(),
|
||||
packet_params.userdata,
|
||||
)
|
||||
}
|
||||
.map(ProcessHandle::as_packet)
|
||||
.map_err(|e| e.to_string());
|
||||
|
||||
send_packet(
|
||||
&mut self.conn,
|
||||
|
||||
+38
-19
@@ -180,43 +180,37 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
setup_signal_hooks()?;
|
||||
|
||||
let mut used_backend = None;
|
||||
auto_run(args, &mut used_backend);
|
||||
auto_run(&args, &mut used_backend);
|
||||
|
||||
if RESTART.load(Ordering::Relaxed) {
|
||||
log::warn!("Restarting...");
|
||||
let exe = std::env::current_exe()?;
|
||||
let mut args = vec!["--replace", "--show"];
|
||||
|
||||
match used_backend {
|
||||
Some(XrBackend::OpenXR) => args.push("--openxr"),
|
||||
Some(XrBackend::OpenVR) => args.push("--openvr"),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let _ = Command::new(exe).args(args).spawn();
|
||||
let _ = Command::new(exe)
|
||||
.args(restart_args(&args, used_backend))
|
||||
.spawn();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(unused_mut, clippy::similar_names)]
|
||||
fn auto_run(args: Args, used_backend: &mut Option<XrBackend>) {
|
||||
fn auto_run(args: &Args, out_backend: &mut Option<XrBackend>) {
|
||||
let mut tried_xr = false;
|
||||
let mut tried_vr = false;
|
||||
|
||||
loop {
|
||||
#[cfg(feature = "openxr")]
|
||||
if !args_get_openvr(&args) {
|
||||
if !args_get_openvr(args) {
|
||||
use crate::backend::{BackendError, openxr::openxr_run};
|
||||
tried_xr = true;
|
||||
match openxr_run(&args, init_run_params(args.no_audio)) {
|
||||
match openxr_run(args, init_run_params(args.no_audio)) {
|
||||
Ok(()) => {
|
||||
used_backend.replace(XrBackend::OpenXR);
|
||||
out_backend.replace(XrBackend::OpenXR);
|
||||
return;
|
||||
}
|
||||
Err(BackendError::NotSupported) => (),
|
||||
Err(e) => {
|
||||
used_backend.replace(XrBackend::OpenXR);
|
||||
out_backend.replace(XrBackend::OpenXR);
|
||||
log::error!("{e:?}");
|
||||
return;
|
||||
}
|
||||
@@ -224,17 +218,17 @@ fn auto_run(args: Args, used_backend: &mut Option<XrBackend>) {
|
||||
}
|
||||
|
||||
#[cfg(feature = "openvr")]
|
||||
if !args_get_openxr(&args) {
|
||||
if !args_get_openxr(args) {
|
||||
use crate::backend::{BackendError, openvr::openvr_run};
|
||||
tried_vr = true;
|
||||
match openvr_run(&args, init_run_params(args.no_audio)) {
|
||||
match openvr_run(args, init_run_params(args.no_audio)) {
|
||||
Ok(()) => {
|
||||
used_backend.replace(XrBackend::OpenVR);
|
||||
out_backend.replace(XrBackend::OpenVR);
|
||||
return;
|
||||
}
|
||||
Err(BackendError::NotSupported) => (),
|
||||
Err(e) => {
|
||||
used_backend.replace(XrBackend::OpenVR);
|
||||
out_backend.replace(XrBackend::OpenVR);
|
||||
log::error!("{e:?}");
|
||||
return;
|
||||
}
|
||||
@@ -303,6 +297,31 @@ const fn args_get_openxr(args: &Args) -> bool {
|
||||
ret
|
||||
}
|
||||
|
||||
fn restart_args(args: &Args, backend: Option<XrBackend>) -> Vec<&'static str> {
|
||||
let mut flags = vec!["--replace", "--show"];
|
||||
|
||||
match backend {
|
||||
Some(XrBackend::OpenXR) => flags.push("--openxr"),
|
||||
Some(XrBackend::OpenVR) => flags.push("--openvr"),
|
||||
None => {}
|
||||
}
|
||||
|
||||
if args.headless {
|
||||
flags.push("--headless");
|
||||
}
|
||||
if args.no_autostart {
|
||||
flags.push("--no-autostart");
|
||||
}
|
||||
if args.install {
|
||||
flags.push("--install");
|
||||
}
|
||||
if args.no_audio {
|
||||
flags.push("--no-audio");
|
||||
}
|
||||
|
||||
flags
|
||||
}
|
||||
|
||||
#[allow(clippy::single_match_else)]
|
||||
fn setup_signal_hooks() -> anyhow::Result<()> {
|
||||
let mut signals = Signals::new([SIGINT, SIGTERM, SIGUSR1])?;
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::{
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use dash_frontend::frontend::{self, FrontendTask, FrontendUpdateParams};
|
||||
use glam::{Affine2, Affine3A, Quat, Vec2, vec2, vec3};
|
||||
use wayvr_ipc::{
|
||||
@@ -20,7 +21,7 @@ use wgui::{
|
||||
widget::EventResult,
|
||||
};
|
||||
use wlx_common::{
|
||||
dash_interface::{self, ConfigChangeKind, DashInterface, DashPlayspaceTask},
|
||||
dash_interface::{self, ConfigChangeKind, DashBackendTask, DashInterface, DashPlayspaceTask},
|
||||
locale::WayVRLangProvider,
|
||||
overlays::{BackendAttrib, BackendAttribValue},
|
||||
};
|
||||
@@ -43,7 +44,7 @@ use crate::{
|
||||
},
|
||||
},
|
||||
config::{none_if_0, save_settings},
|
||||
ipc::ipc_server::{gen_args_vec, gen_env_vec},
|
||||
ipc::ipc_server::gen_env_vec,
|
||||
state::AppState,
|
||||
subsystem::hid::WheelDelta,
|
||||
windowing::{
|
||||
@@ -331,7 +332,7 @@ fn tutorial_spawn_effect(app: &mut AppState) {
|
||||
realign(&mut transform, hmd, 1.0, app.session.config.snap_angle_deg);
|
||||
|
||||
owc.active_state = Some(OverlayWindowState {
|
||||
saved_transform: Some(Affine3A::from_translation(vec3(0., -0.1, -0.9))),
|
||||
saved_transform: Some(Affine3A::from_translation(vec3(0., -0.1, -0.1))),
|
||||
transform,
|
||||
grabbable: true,
|
||||
interactable: true,
|
||||
@@ -369,7 +370,7 @@ pub fn create_dash_frontend(app: &mut AppState) -> anyhow::Result<OverlayWindowC
|
||||
Ok(OverlayWindowConfig {
|
||||
name: DASH_NAME.into(),
|
||||
default_state: OverlayWindowState {
|
||||
transform: Affine3A::from_translation(vec3(0., 0., -0.9)),
|
||||
transform: Affine3A::from_translation(vec3(0., 0., -0.1)),
|
||||
grabbable: true,
|
||||
interactable: true,
|
||||
positioning: Positioning::Floating,
|
||||
@@ -391,6 +392,10 @@ impl DashInterfaceLive {
|
||||
}
|
||||
|
||||
impl DashInterface<AppState> for DashInterfaceLive {
|
||||
fn enumerate_vulkan_devices(&mut self, data: &mut AppState) -> anyhow::Result<Vec<String>> {
|
||||
data.gfx.enumerate_devices()
|
||||
}
|
||||
|
||||
fn window_list(&mut self, app: &mut AppState) -> anyhow::Result<Vec<WvrWindow>> {
|
||||
let wvr_server = app.wvr_server.as_mut().unwrap();
|
||||
Ok(wvr_server
|
||||
@@ -436,7 +441,11 @@ impl DashInterface<AppState> for DashInterfaceLive {
|
||||
) -> anyhow::Result<WvrProcessHandle> {
|
||||
let wvr_server = app.wvr_server.as_mut().unwrap();
|
||||
|
||||
let args_vec = gen_args_vec(¶ms.args);
|
||||
let mut argv_iter = params.argv.iter().map(String::as_str);
|
||||
let exec_path = argv_iter
|
||||
.next()
|
||||
.context("No executable for process_launch")?;
|
||||
let exec_args = argv_iter.collect::<Box<_>>();
|
||||
let env_vec = gen_env_vec(¶ms.env);
|
||||
|
||||
if auto_start {
|
||||
@@ -447,8 +456,8 @@ impl DashInterface<AppState> for DashInterfaceLive {
|
||||
wvr_server
|
||||
.spawn_process(
|
||||
¶ms.name,
|
||||
¶ms.exec,
|
||||
&args_vec,
|
||||
exec_path,
|
||||
&exec_args,
|
||||
&env_vec,
|
||||
params.resolution,
|
||||
params.pos_mode,
|
||||
@@ -517,6 +526,20 @@ impl DashInterface<AppState> for DashInterfaceLive {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(unreachable_patterns)]
|
||||
fn backend_task(&mut self, app: &mut AppState, mode: DashBackendTask) -> anyhow::Result<()> {
|
||||
#[cfg(feature = "openvr")]
|
||||
if app.feats.xr_backend.is_open_vr() {
|
||||
use crate::backend::task::OpenVrTask;
|
||||
let task = match mode {
|
||||
DashBackendTask::ShowBindingsUI => OpenVrTask::ShowBindingsUI,
|
||||
_ => return Ok(()),
|
||||
};
|
||||
app.tasks.enqueue(TaskType::OpenVR(task));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn desktop_finder<'a>(
|
||||
&'a mut self,
|
||||
data: &'a mut AppState,
|
||||
@@ -564,6 +587,10 @@ impl DashInterface<AppState> for DashInterfaceLive {
|
||||
}
|
||||
}
|
||||
|
||||
fn close(&mut self, _data: &mut AppState) {
|
||||
RUNNING.store(false, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn restart(&mut self, _data: &mut AppState) {
|
||||
RUNNING.store(false, Ordering::Relaxed);
|
||||
RESTART.store(true, Ordering::Relaxed);
|
||||
|
||||
@@ -8,8 +8,8 @@ use glam::Affine3A;
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
use wgui::{
|
||||
gfx::{
|
||||
Buffer, BufferUsage, CommandBufferUsage, Filter, Format, Image, ImageView, Scissor,
|
||||
Vert2Uv, WGfx,
|
||||
AttachmentBlend, BlendFactor, BlendOp, Buffer, BufferUsage, CommandBufferUsage, Filter,
|
||||
Format, Image, ImageView, Scissor, Vert2Uv, WGfx,
|
||||
cmd::WGfxClearMode,
|
||||
pass::WGfxPass,
|
||||
pipeline::{WGfxPipeline, WPipelineCreateInfo},
|
||||
@@ -29,7 +29,10 @@ use crate::{
|
||||
windowing::backend::{FrameMeta, RenderResources},
|
||||
};
|
||||
|
||||
const CURSOR_SIZE: f32 = 16. / 1440.;
|
||||
const CURSOR_SIZE: f32 = 64. / 1440.;
|
||||
|
||||
// synchronize queue shared by the WlxCapture worker threads
|
||||
static CAPTURE_QUEUE_MUTEX: Mutex<()> = Mutex::new(());
|
||||
|
||||
struct BufPass {
|
||||
pass: WGfxPass<Vert2Uv>,
|
||||
@@ -39,6 +42,8 @@ struct BufPass {
|
||||
/// A render pipeline that supports mouse + stereo
|
||||
pub struct ScreenPipeline {
|
||||
mouse: BufPass,
|
||||
mouse_pipeline: Arc<WGfxPipeline<Vert2Uv>>,
|
||||
default_cursor_image: Arc<ImageView>,
|
||||
pass: SmallVec<[BufPass; 2]>,
|
||||
pipeline: Arc<WGfxPipeline<Vert2Uv>>,
|
||||
extentf: [f32; 2],
|
||||
@@ -107,6 +112,22 @@ impl ScreenPipeline {
|
||||
cmd_xfer.build_and_execute_now()?;
|
||||
let fallback_image = app.gfx.create_image_view(fallback_image)?;
|
||||
|
||||
let mouse_pipeline = app.gfx.create_pipeline(
|
||||
app.gfx_extras.shaders.get("vert_quad").unwrap(),
|
||||
app.gfx_extras.shaders.get("frag_wayland").unwrap(),
|
||||
WPipelineCreateInfo::new(app.gfx.surface_format())
|
||||
.use_updatable_descriptors([0])
|
||||
.use_blend(AttachmentBlend {
|
||||
src_color_blend_factor: BlendFactor::One,
|
||||
dst_color_blend_factor: BlendFactor::OneMinusSrcAlpha,
|
||||
color_blend_op: BlendOp::Add,
|
||||
src_alpha_blend_factor: BlendFactor::One,
|
||||
dst_alpha_blend_factor: BlendFactor::OneMinusSrcAlpha,
|
||||
alpha_blend_op: BlendOp::Add,
|
||||
}),
|
||||
)?;
|
||||
let (mouse, default_cursor_image) =
|
||||
Self::create_mouse_pass(app, mouse_pipeline.clone(), extentf, offsetf)?;
|
||||
let mut me = Self {
|
||||
pass: smallvec![Self::create_pass(
|
||||
app,
|
||||
@@ -115,7 +136,9 @@ impl ScreenPipeline {
|
||||
extentf,
|
||||
offsetf,
|
||||
)?],
|
||||
mouse: Self::create_mouse_pass(app, pipeline.clone(), extentf, offsetf)?,
|
||||
mouse,
|
||||
mouse_pipeline,
|
||||
default_cursor_image,
|
||||
pipeline,
|
||||
extentf,
|
||||
offsetf,
|
||||
@@ -144,6 +167,7 @@ impl ScreenPipeline {
|
||||
|
||||
/// Select normalized rect from backing image
|
||||
pub fn set_source_uv(&mut self, source_uv: [f32; 4]) {
|
||||
#[allow(clippy::float_cmp)]
|
||||
if self.source_uv != source_uv {
|
||||
self.source_uv = source_uv;
|
||||
self.pass.clear();
|
||||
@@ -193,7 +217,8 @@ impl ScreenPipeline {
|
||||
self.transform = transform;
|
||||
self.pass.clear();
|
||||
|
||||
self.mouse = Self::create_mouse_pass(app, self.pipeline.clone(), extentf, offsetf)?;
|
||||
(self.mouse, self.default_cursor_image) =
|
||||
Self::create_mouse_pass(app, self.mouse_pipeline.clone(), extentf, offsetf)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -227,7 +252,7 @@ impl ScreenPipeline {
|
||||
pipeline: Arc<WGfxPipeline<Vert2Uv>>,
|
||||
extentf: [f32; 2],
|
||||
offsetf: [f32; 2],
|
||||
) -> anyhow::Result<BufPass> {
|
||||
) -> anyhow::Result<(BufPass, Arc<ImageView>)> {
|
||||
#[rustfmt::skip]
|
||||
let mouse_bytes = [
|
||||
0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff,
|
||||
@@ -248,7 +273,7 @@ impl ScreenPipeline {
|
||||
.gfx
|
||||
.empty_buffer(BufferUsage::TRANSFER_DST | BufferUsage::VERTEX_BUFFER, 4)?;
|
||||
|
||||
let set0 = pipeline.uniform_sampler(0, view, Filter::Nearest)?;
|
||||
let set0 = pipeline.uniform_sampler(0, view.clone(), Filter::Nearest)?;
|
||||
let pass = pipeline.create_pass(
|
||||
extentf,
|
||||
offsetf,
|
||||
@@ -260,7 +285,7 @@ impl ScreenPipeline {
|
||||
)?;
|
||||
|
||||
cmd_xfer.build_and_execute_now()?;
|
||||
Ok(BufPass { pass, buf_vert })
|
||||
Ok((BufPass { pass, buf_vert }, view))
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
@@ -272,7 +297,7 @@ impl ScreenPipeline {
|
||||
) -> anyhow::Result<()> {
|
||||
self.render_screen(image, app, rdr)?;
|
||||
if let Some(mouse) = mouse {
|
||||
self.render_mouse(mouse, rdr)?;
|
||||
self.render_mouse(mouse, rdr, None)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -302,10 +327,26 @@ impl ScreenPipeline {
|
||||
&mut self,
|
||||
mouse: &MouseMeta,
|
||||
rdr: &mut RenderResources,
|
||||
cursor: Option<(&Arc<ImageView>, [f32; 2], [f32; 2])>,
|
||||
) -> anyhow::Result<()> {
|
||||
let image = cursor.map_or(&self.default_cursor_image, |(image, _, _)| image);
|
||||
self.mouse
|
||||
.pass
|
||||
.update_sampler(0, image.clone(), Filter::Nearest)?;
|
||||
for cmd_buf in &mut rdr.cmd_bufs {
|
||||
let size = CURSOR_SIZE * self.extentf[1];
|
||||
let half_size = size * 0.5;
|
||||
let (width, height, hotspot_x, hotspot_y) =
|
||||
if let Some((_, dimensions, hotspot)) = cursor {
|
||||
let scale = size / dimensions[0].max(dimensions[1]).max(1.0);
|
||||
(
|
||||
dimensions[0] * scale,
|
||||
dimensions[1] * scale,
|
||||
hotspot[0] * scale,
|
||||
hotspot[1] * scale,
|
||||
)
|
||||
} else {
|
||||
(size, size, size * 0.5, size * 0.5)
|
||||
};
|
||||
|
||||
let (x_scale, y_scale) = if self.stereo_adjust_mouse {
|
||||
match self.stereo {
|
||||
@@ -321,10 +362,10 @@ impl ScreenPipeline {
|
||||
&self.mouse.buf_vert,
|
||||
self.extentf[0],
|
||||
self.extentf[1],
|
||||
mouse.x.mul_add(self.extentf[0] * x_scale, -half_size),
|
||||
mouse.y.mul_add(self.extentf[1] * y_scale, -half_size),
|
||||
size,
|
||||
size,
|
||||
mouse.x.mul_add(self.extentf[0] * x_scale, -hotspot_x),
|
||||
mouse.y.mul_add(self.extentf[1] * y_scale, -hotspot_y),
|
||||
width,
|
||||
height,
|
||||
)?;
|
||||
|
||||
cmd_buf.run_ref(&self.mouse.pass)?;
|
||||
@@ -664,6 +705,17 @@ fn upload_image(
|
||||
height: u32,
|
||||
format: Format,
|
||||
data: &[u8],
|
||||
) -> Option<Arc<Image>> {
|
||||
upload_image_with_row_length(me, width, height, format, data, 0)
|
||||
}
|
||||
|
||||
fn upload_image_with_row_length(
|
||||
me: &WlxCaptureIn,
|
||||
width: u32,
|
||||
height: u32,
|
||||
format: Format,
|
||||
data: &[u8],
|
||||
buffer_row_length: u32,
|
||||
) -> Option<Arc<Image>> {
|
||||
let cmd_result = if me.use_capture_queue {
|
||||
me.gfx
|
||||
@@ -679,15 +731,23 @@ fn upload_image(
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let image = match cmd_xfer.upload_image(width, height, format, data) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
log::error!("{}: Could not create vkImage: {:?}", me.name, e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let image =
|
||||
match cmd_xfer.upload_image_with_row_length(width, height, format, data, buffer_row_length)
|
||||
{
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
log::error!("{}: Could not create vkImage: {:?}", me.name, e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = cmd_xfer.build_and_execute_now() {
|
||||
let result = if me.use_capture_queue {
|
||||
let _guard = CAPTURE_QUEUE_MUTEX.lock().unwrap(); // mutex poison only
|
||||
cmd_xfer.build_and_execute_now()
|
||||
} else {
|
||||
cmd_xfer.build_and_execute_now()
|
||||
};
|
||||
if let Err(e) = result {
|
||||
log::error!("{}: Could not execute upload: {:?}", me.name, e);
|
||||
return None;
|
||||
}
|
||||
@@ -757,8 +817,22 @@ pub(super) fn receive_callback(me: &WlxCaptureIn, frame: WlxFrame) -> Option<Wlx
|
||||
}
|
||||
};
|
||||
|
||||
let data = unsafe { std::slice::from_raw_parts(frame.ptr as *const u8, frame.size) };
|
||||
let image = upload_image(me, frame.format.width, frame.format.height, format, data)?;
|
||||
// SAFETY: the MemPtr params & allocation and must be valid and must outlive the callback
|
||||
let pixels = unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
(frame.ptr as *const u8).add(frame.offset),
|
||||
frame.size - frame.offset,
|
||||
)
|
||||
};
|
||||
let buffer_row_length = (frame.stride / 4) as u32;
|
||||
let image = upload_image_with_row_length(
|
||||
me,
|
||||
frame.format.width,
|
||||
frame.format.height,
|
||||
format,
|
||||
pixels,
|
||||
buffer_row_length,
|
||||
)?;
|
||||
|
||||
Some(WlxCaptureOut {
|
||||
image: me.gfx.create_image_view(image).ok()?,
|
||||
@@ -811,6 +885,8 @@ pub(super) enum MainThreadFrame {
|
||||
Cpu {
|
||||
format: FrameFormat,
|
||||
data: Vec<u8>,
|
||||
offset: usize,
|
||||
stride: usize,
|
||||
mouse: Option<MouseMeta>,
|
||||
},
|
||||
Dmabuf {
|
||||
@@ -870,12 +946,16 @@ where
|
||||
MainThreadFrame::Cpu {
|
||||
format,
|
||||
data,
|
||||
offset,
|
||||
stride,
|
||||
mouse,
|
||||
} => {
|
||||
let frame = wlx_frame::MemPtrFrame {
|
||||
format,
|
||||
ptr: data.as_ptr() as usize,
|
||||
size: data.len(),
|
||||
offset,
|
||||
stride,
|
||||
mouse,
|
||||
};
|
||||
receive_callback(
|
||||
@@ -910,22 +990,24 @@ fn receive_callback_dummy(_: &DmaExporterProxy, frame: WlxFrame) -> Option<MainT
|
||||
Some(MainThreadFrame::Cpu {
|
||||
format: frame.format,
|
||||
data: map.as_slice().to_vec(),
|
||||
offset: frame.plane.offset as _,
|
||||
stride: (frame.format.width as usize).checked_mul(4)?,
|
||||
mouse: frame.mouse,
|
||||
})
|
||||
}
|
||||
WlxFrame::MemPtr(frame) => {
|
||||
let data = if frame.size == 0 {
|
||||
Vec::new()
|
||||
} else {
|
||||
if frame.ptr == 0 {
|
||||
log::error!("Main-thread CPU capture received a null MemPtr frame");
|
||||
return None;
|
||||
}
|
||||
unsafe { std::slice::from_raw_parts(frame.ptr as *const u8, frame.size) }.to_vec()
|
||||
// SAFETY: Allocation must be valid & must outlive the callback
|
||||
let pixels = unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
(frame.ptr as *const u8).add(frame.offset),
|
||||
frame.size - frame.offset,
|
||||
)
|
||||
};
|
||||
Some(MainThreadFrame::Cpu {
|
||||
format: frame.format,
|
||||
data,
|
||||
data: pixels.to_vec(),
|
||||
offset: frame.offset,
|
||||
stride: frame.stride,
|
||||
mouse: frame.mouse,
|
||||
})
|
||||
}
|
||||
|
||||
+130
-38
@@ -3,13 +3,13 @@ use std::{mem, ops::RangeInclusive, rc::Rc, sync::Arc};
|
||||
use glam::{Affine2, Affine3A, Quat, Vec3, vec2, vec3};
|
||||
use slotmap::Key;
|
||||
use smithay::{
|
||||
desktop::PopupManager,
|
||||
input::pointer::CursorImageStatus,
|
||||
desktop::{PopupKind, PopupManager},
|
||||
input::pointer::{CursorImageStatus, CursorImageSurfaceData},
|
||||
reexports::wayland_server::Resource,
|
||||
utils::{Logical, Size},
|
||||
utils::{IsAlive, Logical, Size},
|
||||
wayland::{
|
||||
compositor::with_states,
|
||||
shell::xdg::{SurfaceCachedState, XdgPopupSurfaceData, XdgToplevelSurfaceData},
|
||||
shell::xdg::{SurfaceCachedState, XdgToplevelSurfaceData},
|
||||
},
|
||||
};
|
||||
use wayvr_ipc::packet_client::PositionMode;
|
||||
@@ -70,6 +70,7 @@ use crate::{
|
||||
#[derive(Clone)]
|
||||
pub enum WvrCommand {
|
||||
ReloadTitle,
|
||||
SetIcon(Arc<str>),
|
||||
CloseWindow,
|
||||
KillProcess(KillSignal),
|
||||
}
|
||||
@@ -88,6 +89,9 @@ const BLEND_WAYLAND_ALPHA: AttachmentBlend = AttachmentBlend {
|
||||
|
||||
pub fn create_wl_window_overlay(
|
||||
name: Arc<str>,
|
||||
title: Arc<str>,
|
||||
persistent: bool,
|
||||
restore_on_spawn: bool,
|
||||
app: &mut AppState,
|
||||
window: wayvr::window::WindowHandle,
|
||||
icon: Arc<str>,
|
||||
@@ -109,7 +113,10 @@ pub fn create_wl_window_overlay(
|
||||
.is_some_and(Window::resizable);
|
||||
|
||||
Ok(OverlayWindowConfig {
|
||||
name: name.clone(),
|
||||
name,
|
||||
title: Some(title.clone()),
|
||||
persistent,
|
||||
restore_on_spawn,
|
||||
default_state: OverlayWindowState {
|
||||
grabbable: true,
|
||||
interactable: true,
|
||||
@@ -131,7 +138,7 @@ pub fn create_wl_window_overlay(
|
||||
category: OverlayCategory::WayVR,
|
||||
show_on_spawn: true,
|
||||
..OverlayWindowConfig::from_backend(Box::new(WvrWindowBackend::new(
|
||||
name,
|
||||
title,
|
||||
app,
|
||||
window,
|
||||
icon,
|
||||
@@ -142,7 +149,7 @@ pub fn create_wl_window_overlay(
|
||||
}
|
||||
|
||||
pub struct WvrWindowBackend {
|
||||
name: Arc<str>,
|
||||
title: Arc<str>,
|
||||
icon: Arc<str>,
|
||||
pipeline: Option<ScreenPipeline>,
|
||||
subsurface_pipeline_opaque: Arc<WGfxPipeline<Vert2Uv>>,
|
||||
@@ -156,6 +163,8 @@ pub struct WvrWindowBackend {
|
||||
just_resumed: bool,
|
||||
meta: Option<FrameMeta>,
|
||||
mouse: Option<MouseMeta>,
|
||||
cursor_revision: Option<(Arc<ImageView>, [i32; 2], [i32; 2])>,
|
||||
cursor_status: CursorImageStatus,
|
||||
stereo: Option<StereoMode>,
|
||||
stereo_full_frame: bool,
|
||||
stereo_adjust_mouse: bool,
|
||||
@@ -176,7 +185,7 @@ pub struct WvrWindowBackend {
|
||||
|
||||
impl WvrWindowBackend {
|
||||
fn new(
|
||||
name: Arc<str>,
|
||||
title: Arc<str>,
|
||||
app: &mut AppState,
|
||||
window: wayvr::window::WindowHandle,
|
||||
icon: Arc<str>,
|
||||
@@ -243,7 +252,7 @@ impl WvrWindowBackend {
|
||||
NewGuiPanelParams {
|
||||
resize_to_parent: true,
|
||||
on_custom_attrib: Some(on_custom_attrib),
|
||||
extra_vars: [("title".into(), name.as_ref().into())].into(),
|
||||
extra_vars: [("title".into(), title.as_ref().into())].into(),
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
@@ -251,7 +260,7 @@ impl WvrWindowBackend {
|
||||
panel.update_layout(app)?;
|
||||
|
||||
Ok(Self {
|
||||
name,
|
||||
title,
|
||||
icon,
|
||||
pipeline: None,
|
||||
window,
|
||||
@@ -265,6 +274,8 @@ impl WvrWindowBackend {
|
||||
just_resumed: false,
|
||||
meta: None,
|
||||
mouse: None,
|
||||
cursor_revision: None,
|
||||
cursor_status: CursorImageStatus::default_named(),
|
||||
stereo: app
|
||||
.feats
|
||||
.xr_backend
|
||||
@@ -390,6 +401,7 @@ impl WvrWindowBackend {
|
||||
}
|
||||
|
||||
fn update_title(&mut self, new_title: Rc<str>) {
|
||||
self.title = new_title.as_ref().into();
|
||||
let mut common = CallbackDataCommon {
|
||||
state: &self.panel.layout.state,
|
||||
alterables: &mut self.panel.layout.alterables,
|
||||
@@ -527,7 +539,11 @@ impl WvrWindowBackend {
|
||||
states
|
||||
.data_map
|
||||
.get::<XdgToplevelSurfaceData>()
|
||||
.and_then(|data| data.lock().ok().and_then(|data| data.current_serial))
|
||||
.and_then(|data| {
|
||||
data.lock()
|
||||
.ok()
|
||||
.and_then(|data| data.last_acked.as_ref().map(|c| c.serial))
|
||||
})
|
||||
});
|
||||
|
||||
if committed == clamped {
|
||||
@@ -568,7 +584,7 @@ impl OverlayBackend for WvrWindowBackend {
|
||||
else {
|
||||
log::debug!(
|
||||
"{:?}: WayVR overlay without matching window entry",
|
||||
self.name
|
||||
self.title
|
||||
);
|
||||
return Ok(ShouldRender::Unable);
|
||||
};
|
||||
@@ -585,20 +601,12 @@ impl OverlayBackend for WvrWindowBackend {
|
||||
let surfaces = collect_rendered_surface_tree(toplevel.wl_surface());
|
||||
|
||||
let mut popup_roots = Vec::new();
|
||||
let mut popups = Vec::new();
|
||||
let mut popup_trees = Vec::new();
|
||||
|
||||
for (popup, point) in PopupManager::popups_for_surface(toplevel.wl_surface()) {
|
||||
let configured = with_states(popup.wl_surface(), |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<XdgPopupSurfaceData>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.configured
|
||||
});
|
||||
|
||||
if !configured {
|
||||
if let PopupKind::Xdg(popup) = &popup
|
||||
&& !popup.is_initial_configure_sent()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -609,13 +617,15 @@ impl OverlayBackend for WvrWindowBackend {
|
||||
surface_origin: vec2(popup_origin.x as f32, popup_origin.y as f32),
|
||||
});
|
||||
|
||||
popups.extend(collect_rendered_surface_tree_at(
|
||||
popup_trees.push(collect_rendered_surface_tree_at(
|
||||
popup.wl_surface(),
|
||||
popup_origin,
|
||||
true,
|
||||
));
|
||||
}
|
||||
|
||||
let popups: Vec<RenderedSurface> = popup_trees.into_iter().rev().flatten().collect();
|
||||
|
||||
let mut tree_dirty = false;
|
||||
|
||||
if let Some(wvr_server) = app.wvr_server.as_mut() {
|
||||
@@ -653,10 +663,11 @@ impl OverlayBackend for WvrWindowBackend {
|
||||
let source_uv = surf.source_uv(states);
|
||||
let mut xdg_state = states.cached_state.get::<SurfaceCachedState>();
|
||||
let window_geometry = xdg_state.current().geometry;
|
||||
drop(xdg_state);
|
||||
Some((surf, surface_size, source_uv, window_geometry))
|
||||
})
|
||||
else {
|
||||
log::trace!("{}: no buffer for wl_surface", self.name);
|
||||
log::trace!("{}: no buffer for wl_surface", self.title);
|
||||
return Ok(ShouldRender::Unable);
|
||||
};
|
||||
|
||||
@@ -691,9 +702,9 @@ impl OverlayBackend for WvrWindowBackend {
|
||||
}
|
||||
|
||||
let inner_extent = meta.extent;
|
||||
let committed_window_extent = window_geometry
|
||||
.map(|geometry| [geometry.size.w.max(1) as u32, geometry.size.h.max(1) as u32])
|
||||
.unwrap_or(inner_extent);
|
||||
let committed_window_extent = window_geometry.map_or(inner_extent, |geometry| {
|
||||
[geometry.size.w.max(1) as u32, geometry.size.h.max(1) as u32]
|
||||
});
|
||||
self.sync_committed_toplevel_size(app, committed_window_extent);
|
||||
|
||||
if self.server_side_decorations {
|
||||
@@ -752,7 +763,48 @@ impl OverlayBackend for WvrWindowBackend {
|
||||
y: (m.pos.y as f32) / (inner_extent[1] as f32),
|
||||
});
|
||||
|
||||
let dirty = self.mouse != mouse || rendered_surfaces_dirty(&self.popups, &popups);
|
||||
let cursor_status = {
|
||||
let state = &mut app.wvr_server.as_mut().unwrap().manager.state;
|
||||
if matches!(
|
||||
&state.cursor_image,
|
||||
CursorImageStatus::Surface(surface) if !surface.alive()
|
||||
) {
|
||||
state.cursor_image = CursorImageStatus::default_named();
|
||||
}
|
||||
if mouse.is_some() {
|
||||
state.cursor_image.clone()
|
||||
} else {
|
||||
CursorImageStatus::default_named()
|
||||
}
|
||||
};
|
||||
let cursor_revision = match &cursor_status {
|
||||
CursorImageStatus::Surface(surface) if surface.alive() => {
|
||||
with_states(surface, |states| {
|
||||
let image = SurfaceBufWithImage::get_from_surface(states)?;
|
||||
let size = image.logical_size(states);
|
||||
let hotspot = states
|
||||
.data_map
|
||||
.get::<CursorImageSurfaceData>()?
|
||||
.lock()
|
||||
.ok()?
|
||||
.hotspot;
|
||||
Some((image.image, [size.w, size.h], [hotspot.x, hotspot.y]))
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let cursor_dirty = self.cursor_status != cursor_status
|
||||
|| match (&self.cursor_revision, &cursor_revision) {
|
||||
(Some((a, asize, ah)), Some((b, bsize, bh))) => {
|
||||
!Arc::ptr_eq(a, b) || asize != bsize || ah != bh
|
||||
}
|
||||
(None, None) => false,
|
||||
_ => true,
|
||||
};
|
||||
self.cursor_status = cursor_status;
|
||||
self.cursor_revision = cursor_revision;
|
||||
let dirty =
|
||||
cursor_dirty || self.mouse != mouse || rendered_surfaces_dirty(&self.popups, &popups);
|
||||
if !self.scrolling {
|
||||
self.mouse = mouse;
|
||||
}
|
||||
@@ -771,7 +823,7 @@ impl OverlayBackend for WvrWindowBackend {
|
||||
{
|
||||
log::trace!(
|
||||
"{}: new {} image",
|
||||
self.name,
|
||||
self.title,
|
||||
if surf.dmabuf { "DMA-buf" } else { "SHM" }
|
||||
);
|
||||
self.cur_image = Some(surf.image);
|
||||
@@ -817,12 +869,40 @@ impl OverlayBackend for WvrWindowBackend {
|
||||
if let Some(wvr_server) = app.wvr_server.as_mut() {
|
||||
let state = &mut wvr_server.manager.state;
|
||||
|
||||
match state.cursor_image {
|
||||
CursorImageStatus::Hidden => {}
|
||||
CursorImageStatus::Named(_) | CursorImageStatus::Surface(_) => {
|
||||
// TODO: properly render surface?
|
||||
if let Some(mouse) = self.mouse.as_ref() {
|
||||
self.pipeline.as_mut().unwrap().render_mouse(mouse, rdr)?;
|
||||
if let Some(mouse) = self.mouse.as_ref() {
|
||||
match state.cursor_image.clone() {
|
||||
CursorImageStatus::Hidden => {}
|
||||
CursorImageStatus::Surface(surface) if surface.alive() => {
|
||||
let cursor = with_states(&surface, |states| {
|
||||
let image = SurfaceBufWithImage::get_from_surface(states)?;
|
||||
let size = image.logical_size(states);
|
||||
let hotspot = states
|
||||
.data_map
|
||||
.get::<CursorImageSurfaceData>()?
|
||||
.lock()
|
||||
.ok()?
|
||||
.hotspot;
|
||||
Some((
|
||||
image.image,
|
||||
[size.w as f32, size.h as f32],
|
||||
[hotspot.x as f32, hotspot.y as f32],
|
||||
))
|
||||
});
|
||||
if let Some((image, size, hotspot)) = cursor {
|
||||
self.pipeline.as_mut().unwrap().render_mouse(
|
||||
mouse,
|
||||
rdr,
|
||||
Some((&image, size, hotspot)),
|
||||
)?;
|
||||
state.send_frame_callbacks_for_surface_id(&surface.id());
|
||||
}
|
||||
// a live cursor surface with no buffer makes an invisible cursor
|
||||
}
|
||||
CursorImageStatus::Named(_) | CursorImageStatus::Surface(_) => {
|
||||
self.pipeline
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.render_mouse(mouse, rdr, None)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -857,6 +937,9 @@ impl OverlayBackend for WvrWindowBackend {
|
||||
let wvr_server = app.wvr_server.as_mut().unwrap(); //never None
|
||||
wvr_server.overlay_added(oid, self.window);
|
||||
}
|
||||
OverlayEventData::WvrCommand(WvrCommand::SetIcon(icon)) => {
|
||||
self.icon = icon;
|
||||
}
|
||||
OverlayEventData::WvrCommand(WvrCommand::ReloadTitle) => {
|
||||
let wvr_server = app.wvr_server.as_mut().unwrap(); //never None
|
||||
if let Some(window) = wvr_server.wm.windows.get(self.window) {
|
||||
@@ -926,6 +1009,12 @@ impl OverlayBackend for WvrWindowBackend {
|
||||
Some(WvrHitTarget::Panel(hit2)) => {
|
||||
if let Some(wvr_server) = app.wvr_server.as_mut() {
|
||||
wvr_server.clear_dnd_focus(hit.pointer);
|
||||
wvr_server.send_mouse_move(
|
||||
PointerFocusTarget::None,
|
||||
ctx.unclamped_client_pos_from_hit(hit),
|
||||
self.window,
|
||||
false,
|
||||
);
|
||||
}
|
||||
self.panel_hovered = true;
|
||||
self.panel.on_hover(app, &hit2)
|
||||
@@ -996,6 +1085,9 @@ impl OverlayBackend for WvrWindowBackend {
|
||||
self.panel.on_left(app, pointer);
|
||||
self.panel_hovered = false;
|
||||
}
|
||||
if let Some(wvr_server) = app.wvr_server.as_mut() {
|
||||
wvr_server.leave_overlay(self.window, pointer);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_pointer(&mut self, app: &mut state::AppState, hit: &input::PointerHit, pressed: bool) {
|
||||
@@ -1161,7 +1253,7 @@ impl OverlayBackend for WvrWindowBackend {
|
||||
match value {
|
||||
BackendAttribValue::Stereo(new) => {
|
||||
if let Some(stereo) = self.stereo.as_mut() {
|
||||
log::debug!("{}: stereo: {stereo:?} → {new:?}", self.name);
|
||||
log::debug!("{}: stereo: {stereo:?} → {new:?}", self.title);
|
||||
*stereo = new;
|
||||
if let Some(meta) = self.meta {
|
||||
let _ = self.apply_extent(app, &meta);
|
||||
|
||||
@@ -107,6 +107,8 @@ fn start_progress_state(
|
||||
let mut st = st.borrow_mut();
|
||||
let mut update_text = false;
|
||||
|
||||
common.alterables.mark_redraw();
|
||||
|
||||
while let Ok(msg) = progress_rx.try_recv() {
|
||||
match msg {
|
||||
PttProgress::VuVolume(volume) => {
|
||||
@@ -230,8 +232,11 @@ pub fn create_whisper(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig>
|
||||
let model_path = data_dir::get_path("whisper")
|
||||
.join(app.session.config.whisper_model.as_ref());
|
||||
if model_path.is_file() {
|
||||
app.whisper_sst = match WhisperStt::new(model_path)
|
||||
.log_err("Error while starting Whisper engine")
|
||||
app.whisper_sst = match WhisperStt::new(
|
||||
model_path,
|
||||
app.session.config.whisper_gpu,
|
||||
)
|
||||
.log_err("Error while starting Whisper engine")
|
||||
{
|
||||
Ok(x) => Some(x),
|
||||
Err(e) => {
|
||||
|
||||
@@ -223,26 +223,22 @@
|
||||
},
|
||||
{
|
||||
"inputs" : {
|
||||
"scroll" : {
|
||||
"position" : {
|
||||
"output" : "/actions/default/in/scroll"
|
||||
}
|
||||
},
|
||||
"mode" : "scroll",
|
||||
"parameters" : {
|
||||
"scroll_mode" : "smooth"
|
||||
},
|
||||
"mode" : "joystick",
|
||||
"parameters" : {},
|
||||
"path" : "/user/hand/left/input/thumbstick"
|
||||
},
|
||||
{
|
||||
"inputs" : {
|
||||
"scroll" : {
|
||||
"position" : {
|
||||
"output" : "/actions/default/in/scroll"
|
||||
}
|
||||
},
|
||||
"mode" : "scroll",
|
||||
"parameters" : {
|
||||
"scroll_mode" : "smooth"
|
||||
},
|
||||
"mode" : "joystick",
|
||||
"parameters" : {},
|
||||
"path" : "/user/hand/right/input/thumbstick"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -141,26 +141,22 @@
|
||||
},
|
||||
{
|
||||
"inputs" : {
|
||||
"scroll" : {
|
||||
"position" : {
|
||||
"output" : "/actions/default/in/scroll"
|
||||
}
|
||||
},
|
||||
"mode" : "scroll",
|
||||
"parameters" : {
|
||||
"scroll_mode" : "smooth"
|
||||
},
|
||||
"mode" : "joystick",
|
||||
"parameters" : {},
|
||||
"path" : "/user/hand/left/input/thumbstick"
|
||||
},
|
||||
{
|
||||
"inputs" : {
|
||||
"scroll" : {
|
||||
"position" : {
|
||||
"output" : "/actions/default/in/scroll"
|
||||
}
|
||||
},
|
||||
"mode" : "scroll",
|
||||
"parameters" : {
|
||||
"scroll_mode" : "smooth"
|
||||
},
|
||||
"mode" : "joystick",
|
||||
"parameters" : {},
|
||||
"path" : "/user/hand/right/input/thumbstick"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -107,26 +107,22 @@
|
||||
},
|
||||
{
|
||||
"inputs" : {
|
||||
"scroll" : {
|
||||
"position" : {
|
||||
"output" : "/actions/default/in/scroll"
|
||||
}
|
||||
},
|
||||
"mode" : "scroll",
|
||||
"parameters" : {
|
||||
"scroll_mode" : "smooth"
|
||||
},
|
||||
"mode" : "joystick",
|
||||
"parameters" : {},
|
||||
"path" : "/user/hand/left/input/joystick"
|
||||
},
|
||||
{
|
||||
"inputs" : {
|
||||
"scroll" : {
|
||||
"position" : {
|
||||
"output" : "/actions/default/in/scroll"
|
||||
}
|
||||
},
|
||||
"mode" : "scroll",
|
||||
"parameters" : {
|
||||
"scroll_mode" : "smooth"
|
||||
},
|
||||
"mode" : "joystick",
|
||||
"parameters" : {},
|
||||
"path" : "/user/hand/right/input/joystick"
|
||||
}
|
||||
]
|
||||
|
||||
+3
-3
@@ -105,7 +105,7 @@ impl AppState {
|
||||
let wvr_signals = SyncEventQueue::new();
|
||||
|
||||
let wvr_server = {
|
||||
let mut maybe_wvr = WvrServerState::new(gfx.clone(), &gfx_extras)
|
||||
let mut maybe_wvr = WvrServerState::new(gfx.clone(), &gfx_extras, &session.config)
|
||||
.log_err("Could not initialize WayVR Server")
|
||||
.ok();
|
||||
if let Some(wvr) = maybe_wvr.as_mut() {
|
||||
@@ -172,14 +172,14 @@ impl AppState {
|
||||
let mut theme = WguiTheme {
|
||||
animation_mult: 1. / session.config.ui_animation_speed,
|
||||
rounding_mult: session.config.ui_round_multiplier,
|
||||
..Default::default()
|
||||
gradient_intensity: session.config.ui_gradient_intensity,
|
||||
};
|
||||
|
||||
let dbus = DbusConnector::default();
|
||||
|
||||
let ipc_server = ipc_server::WayVRServer::new()?;
|
||||
|
||||
let mut desktop_finder = DesktopFinder::new();
|
||||
let mut desktop_finder = DesktopFinder::new(params.wgui_globals.i18n().get_locale());
|
||||
desktop_finder.refresh();
|
||||
|
||||
#[cfg(feature = "pipewire")]
|
||||
|
||||
@@ -116,8 +116,10 @@ pub enum PttProgress {
|
||||
}
|
||||
|
||||
impl WhisperStt {
|
||||
pub fn new(model_path: impl AsRef<Path>) -> Result<Self, WhisperSttError> {
|
||||
Self::init(WhisperSttConfig::new(model_path))
|
||||
pub fn new(model_path: impl AsRef<Path>, gpu_idx: u32) -> Result<Self, WhisperSttError> {
|
||||
let mut conf = WhisperSttConfig::new(model_path);
|
||||
conf.gpu_device = gpu_idx as i32;
|
||||
Self::init(conf)
|
||||
}
|
||||
|
||||
pub fn init(config: WhisperSttConfig) -> Result<Self, WhisperSttError> {
|
||||
|
||||
@@ -127,6 +127,7 @@ macro_rules! attrib_value {
|
||||
pub struct OverlayMeta {
|
||||
pub id: OverlayID,
|
||||
pub name: Arc<str>,
|
||||
pub title: Option<Arc<str>>,
|
||||
pub category: OverlayCategory,
|
||||
pub visible: bool,
|
||||
pub icon: Option<Arc<str>>,
|
||||
|
||||
+309
-44
@@ -1,6 +1,7 @@
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
sync::atomic::Ordering,
|
||||
};
|
||||
|
||||
@@ -9,9 +10,11 @@ use glam::{Affine3A, Vec3, Vec3A};
|
||||
use slotmap::{Key, SecondaryMap, SlotMap};
|
||||
use wgui::{i18n::Translation, log::LogErr};
|
||||
use wlx_common::{
|
||||
DesktopBackend,
|
||||
astr_containers::{AStrMap, AStrMapExt},
|
||||
config::SerializedWindowSet,
|
||||
overlays::{BackendAttrib, BackendAttribValue, ToastTopic},
|
||||
windowing::{OverlayWindowState, Positioning},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -84,6 +87,8 @@ where
|
||||
};
|
||||
|
||||
if headless {
|
||||
app.screens.clear();
|
||||
app.feats.desktop_backend = DesktopBackend::Headless;
|
||||
log::info!("Running in headless mode; keyboard will be en-US");
|
||||
} else {
|
||||
// create one window set for each screen.
|
||||
@@ -92,12 +97,12 @@ where
|
||||
match create_screens(app) {
|
||||
Ok((data, backend)) => {
|
||||
app.feats.desktop_backend = backend;
|
||||
let last_idx = data.screens.len() - 1;
|
||||
let screen_count = data.screens.len();
|
||||
for (idx, (meta, mut config)) in data.screens.into_iter().enumerate() {
|
||||
config.show_on_spawn = true;
|
||||
me.add(OverlayWindowData::from_config(config), app);
|
||||
|
||||
if idx < last_idx {
|
||||
if idx + 1 < screen_count {
|
||||
me.sets.push(OverlayWindowSet::default());
|
||||
me.switch_to_set(app, Some(me.current_set.unwrap() + 1), false);
|
||||
}
|
||||
@@ -243,6 +248,7 @@ where
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let key = o.config.name.clone();
|
||||
let parent_set = if o.config.global {
|
||||
&mut self.global_set
|
||||
} else {
|
||||
@@ -254,8 +260,8 @@ where
|
||||
|
||||
parent_set
|
||||
.hidden_overlays
|
||||
.arc_set(o.config.name.clone(), active_state);
|
||||
} else if let Some(state) = parent_set.hidden_overlays.arc_rm(&o.config.name) {
|
||||
.arc_set(key.clone(), active_state);
|
||||
} else if let Some(state) = parent_set.hidden_overlays.arc_rm(&key) {
|
||||
let o = &mut self.overlays[id];
|
||||
log::debug!("{}: toggle on", o.config.name);
|
||||
o.config.dirty = true;
|
||||
@@ -387,9 +393,23 @@ where
|
||||
OverlayTask::Modify(sel, f) => {
|
||||
if let Some(o) = self.mut_by_selector(&sel) {
|
||||
let was_visible = o.config.is_active();
|
||||
let old_title = o.config.title.clone();
|
||||
let old_icon = match o.config.backend.get_attrib(BackendAttrib::Icon) {
|
||||
Some(BackendAttribValue::Icon(icon)) => Some(icon),
|
||||
_ => None,
|
||||
};
|
||||
f(app, &mut o.config);
|
||||
let new_icon = match o.config.backend.get_attrib(BackendAttrib::Icon) {
|
||||
Some(BackendAttribValue::Icon(icon)) => Some(icon),
|
||||
_ => None,
|
||||
};
|
||||
let title_changed = old_title != o.config.title || old_icon != new_icon;
|
||||
let visibility_changed = was_visible != o.config.is_active();
|
||||
|
||||
if was_visible != o.config.is_active() {
|
||||
if title_changed {
|
||||
self.overlays_changed(app)?;
|
||||
}
|
||||
if visibility_changed {
|
||||
let _ = self.visible_overlays_changed(app);
|
||||
}
|
||||
} else {
|
||||
@@ -400,65 +420,52 @@ where
|
||||
self.spawn_overlay(app, sel, spawn_pos, f);
|
||||
}
|
||||
OverlayTask::Drop(sel) => {
|
||||
let (id, name) = match &sel {
|
||||
OverlaySelector::Id(id) => {
|
||||
let id = *id;
|
||||
let name = self.overlays.get(id).map(|o| o.config.name.clone());
|
||||
(Some(id), name)
|
||||
}
|
||||
OverlaySelector::Name(name) => {
|
||||
let id = self.lookup(name);
|
||||
(id, Some(name.clone()))
|
||||
}
|
||||
_ => (None, None),
|
||||
};
|
||||
|
||||
let id = self.id_by_selector(&sel);
|
||||
if let Some(o) = self.mut_by_selector(&sel)
|
||||
&& o.birthframe < FRAME_COUNTER.load(Ordering::Relaxed)
|
||||
&& let Some(o) = self.remove_by_selector(&sel, app)
|
||||
{
|
||||
log::debug!("Dropping overlay {}", o.config.name);
|
||||
if let (Some(id), Some(name)) = (id, name) {
|
||||
self.remove_saved_state(id, &name);
|
||||
if let Some(id) = id {
|
||||
self.on_overlay_dropped(id, &o);
|
||||
}
|
||||
self.dropped_overlays.push_back(o);
|
||||
}
|
||||
}
|
||||
OverlayTask::DropImmediate(sel) => {
|
||||
let (id, name) = match &sel {
|
||||
OverlaySelector::Id(id) => {
|
||||
let id = *id;
|
||||
let name = self.overlays.get(id).map(|o| o.config.name.clone());
|
||||
(Some(id), name)
|
||||
}
|
||||
OverlaySelector::Name(name) => {
|
||||
let id = self.lookup(name);
|
||||
(id, Some(name.clone()))
|
||||
}
|
||||
_ => (None, None),
|
||||
};
|
||||
|
||||
let id = self.id_by_selector(&sel);
|
||||
if let Some(o) = self.remove_by_selector(&sel, app) {
|
||||
log::debug!("Dropping overlay {} immediately", o.config.name);
|
||||
if let (Some(id), Some(name)) = (id, name) {
|
||||
self.remove_saved_state(id, &name);
|
||||
if let Some(id) = id {
|
||||
self.on_overlay_dropped(id, &o);
|
||||
}
|
||||
self.dropped_overlays.push_back(o);
|
||||
}
|
||||
}
|
||||
OverlayTask::SetOverlayName(id, name, persistent, restore_on_spawn) => {
|
||||
self.set_window_name(app, id, name, persistent, restore_on_spawn);
|
||||
}
|
||||
OverlayTask::ForgetWindowState(id, key) => {
|
||||
self.freeze_window_state(id, &key);
|
||||
}
|
||||
OverlayTask::RenameWindowState(old, new) => {
|
||||
self.rename_window_state(app, &old, &new);
|
||||
}
|
||||
OverlayTask::ClearWindowState(key) => {
|
||||
self.clear_window_state(app, &key);
|
||||
}
|
||||
OverlayTask::DropExpired(id) => {
|
||||
let should_drop = self
|
||||
.overlays
|
||||
.get(id)
|
||||
.and_then(|overlay| overlay.config.lifetime.as_ref())
|
||||
.is_some_and(|lifetime| lifetime.is_expired());
|
||||
.is_some_and(super::window::OverlayLifetime::is_expired);
|
||||
|
||||
if should_drop
|
||||
&& let Some(o) = self.remove_by_selector(&OverlaySelector::Id(id), app)
|
||||
{
|
||||
log::debug!("Dropping expired overlay {}", o.config.name);
|
||||
let name = o.config.name.clone();
|
||||
self.remove_saved_state(id, &name);
|
||||
self.on_overlay_dropped(id, &o);
|
||||
self.dropped_overlays.push_back(o);
|
||||
}
|
||||
}
|
||||
@@ -532,14 +539,195 @@ impl<T> OverlayWindowManager<T> {
|
||||
self.dropped_overlays.pop_front()
|
||||
}
|
||||
|
||||
fn remove_saved_state(&mut self, id: OverlayID, name: &str) {
|
||||
fn on_overlay_dropped(&mut self, id: OverlayID, overlay: &OverlayWindowData<T>) {
|
||||
let name = &overlay.config.name;
|
||||
let remember_position =
|
||||
overlay.config.category == OverlayCategory::WayVR && overlay.config.can_persist();
|
||||
|
||||
// keep removed overlays by name
|
||||
for set in &mut self.sets {
|
||||
let state = set.overlays.remove(id);
|
||||
let hidden_state = set.hidden_overlays.arc_rm(name);
|
||||
if remember_position {
|
||||
if let Some(state) = state.or(hidden_state) {
|
||||
set.inactive_overlays.arc_set(name.clone(), state);
|
||||
}
|
||||
} else {
|
||||
set.inactive_overlays.arc_rm(name);
|
||||
}
|
||||
}
|
||||
|
||||
let state = self.global_set.overlays.remove(id);
|
||||
let hidden_state = self.global_set.hidden_overlays.arc_rm(name);
|
||||
if remember_position {
|
||||
if let Some(state) = state.or(hidden_state) {
|
||||
self.global_set
|
||||
.inactive_overlays
|
||||
.arc_set(name.clone(), state);
|
||||
}
|
||||
|
||||
if let Some(state) = &overlay.config.active_state {
|
||||
let set = if overlay.config.global {
|
||||
&mut self.global_set
|
||||
} else {
|
||||
&mut self.sets[self.current_set.unwrap_or(self.restore_set)]
|
||||
};
|
||||
set.inactive_overlays.arc_set(name.clone(), state.clone());
|
||||
}
|
||||
} else {
|
||||
// transient overlays (toasts, etc)
|
||||
self.global_set.inactive_overlays.arc_rm(name);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the name of an existing window, restoring the state associated with the new name
|
||||
fn set_window_name(
|
||||
&mut self,
|
||||
app: &mut AppState,
|
||||
id: OverlayID,
|
||||
name: Arc<str>,
|
||||
persistent: bool,
|
||||
restore_on_spawn: bool,
|
||||
) {
|
||||
let Some(overlay) = self.overlays.get(id) else {
|
||||
return;
|
||||
};
|
||||
if overlay.config.category != OverlayCategory::WayVR
|
||||
|| (overlay.config.name == name
|
||||
&& overlay.config.persistent == persistent
|
||||
&& overlay.config.restore_on_spawn == restore_on_spawn)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if self
|
||||
.overlays
|
||||
.iter()
|
||||
.any(|(other_id, other)| other_id != id && other.config.name == name)
|
||||
{
|
||||
log::debug!("Ignoring duplicate WayVR window name {name}");
|
||||
return;
|
||||
}
|
||||
|
||||
let old_name = overlay.config.name.clone();
|
||||
let global = overlay.config.global;
|
||||
if old_name != name {
|
||||
for set in &mut self.sets {
|
||||
if let Some(state) = set.hidden_overlays.arc_rm(&old_name) {
|
||||
set.hidden_overlays.arc_set(name.clone(), state);
|
||||
}
|
||||
}
|
||||
if let Some(state) = self.global_set.hidden_overlays.arc_rm(&old_name) {
|
||||
self.global_set.hidden_overlays.arc_set(name.clone(), state);
|
||||
}
|
||||
}
|
||||
{
|
||||
let config = &mut self.overlays[id].config;
|
||||
config.name = name.clone();
|
||||
config.persistent = persistent;
|
||||
config.restore_on_spawn = restore_on_spawn;
|
||||
}
|
||||
if let Err(err) = self.overlays_changed(app) {
|
||||
log::error!("Unable to update overlay name: {err:?}");
|
||||
}
|
||||
if !persistent || !restore_on_spawn {
|
||||
return;
|
||||
}
|
||||
|
||||
// apply to all saved states
|
||||
if global {
|
||||
if let Some(state) = self.global_set.inactive_overlays.arc_rm(&name) {
|
||||
let o = &mut self.overlays[id];
|
||||
o.config.active_state = Some(state);
|
||||
o.config.reset(app, false);
|
||||
o.config.dirty = true;
|
||||
}
|
||||
} else {
|
||||
for (i, set) in self.sets.iter_mut().enumerate() {
|
||||
let Some(state) = set.inactive_overlays.arc_rm(&name) else {
|
||||
continue;
|
||||
};
|
||||
if self.current_set == Some(i) {
|
||||
let o = &mut self.overlays[id];
|
||||
o.config.active_state = Some(state);
|
||||
o.config.reset(app, false);
|
||||
o.config.dirty = true;
|
||||
} else {
|
||||
set.overlays.insert(id, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn freeze_window_state(&mut self, id: OverlayID, name: &Arc<str>) {
|
||||
let Some(overlay) = self.overlays.get(id) else {
|
||||
return;
|
||||
};
|
||||
if let Some(state) = overlay.config.active_state.clone() {
|
||||
let set = if overlay.config.global {
|
||||
&mut self.global_set
|
||||
} else {
|
||||
&mut self.sets[self.current_set.unwrap_or(self.restore_set)]
|
||||
};
|
||||
set.inactive_overlays.arc_set(name.clone(), state);
|
||||
} else {
|
||||
for set in &mut self.sets {
|
||||
if let Some(state) = set.overlays.get(id) {
|
||||
set.inactive_overlays.arc_set(name.clone(), state.clone());
|
||||
}
|
||||
}
|
||||
if let Some(state) = self.global_set.overlays.get(id) {
|
||||
self.global_set
|
||||
.inactive_overlays
|
||||
.arc_set(name.clone(), state.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rename_window_state(&mut self, app: &mut AppState, old: &Arc<str>, new: &Arc<str>) {
|
||||
if old == new {
|
||||
return;
|
||||
}
|
||||
for set in &mut self.sets {
|
||||
if let Some(state) = set.inactive_overlays.arc_rm(old) {
|
||||
set.inactive_overlays.arc_set(new.clone(), state);
|
||||
}
|
||||
if let Some(state) = set.hidden_overlays.arc_rm(old) {
|
||||
set.hidden_overlays.arc_set(new.clone(), state);
|
||||
}
|
||||
}
|
||||
if let Some(state) = self.global_set.inactive_overlays.arc_rm(old) {
|
||||
self.global_set
|
||||
.inactive_overlays
|
||||
.arc_set(new.clone(), state);
|
||||
}
|
||||
if let Some(state) = self.global_set.hidden_overlays.arc_rm(old) {
|
||||
self.global_set.hidden_overlays.arc_set(new.clone(), state);
|
||||
}
|
||||
if let Some(attrs) = app.session.config.attribs.arc_rm(old) {
|
||||
app.session.config.attribs.arc_set(new.clone(), attrs);
|
||||
}
|
||||
for set in &mut app.session.config.sets {
|
||||
if let Some(state) = set.overlays.remove(old.as_ref()) {
|
||||
set.overlays.insert(new.clone(), state);
|
||||
}
|
||||
if let Some(state) = set.hidden_overlays.remove(old.as_ref()) {
|
||||
set.hidden_overlays.insert(new.clone(), state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_window_state(&mut self, app: &mut AppState, name: &str) {
|
||||
for set in &mut self.sets {
|
||||
set.overlays.retain(|k, _| k != id);
|
||||
set.inactive_overlays.arc_rm(name);
|
||||
set.hidden_overlays.arc_rm(name);
|
||||
}
|
||||
self.global_set.inactive_overlays.arc_rm(name);
|
||||
self.global_set.hidden_overlays.arc_rm(name);
|
||||
app.session.config.attribs.arc_rm(name);
|
||||
for set in &mut app.session.config.sets {
|
||||
set.overlays.remove(name);
|
||||
set.hidden_overlays.remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn persist_layout(&mut self, app: &mut AppState) {
|
||||
@@ -561,7 +749,11 @@ impl<T> OverlayWindowManager<T> {
|
||||
.overlays
|
||||
.iter()
|
||||
.filter_map(|(k, v)| {
|
||||
let n = self.overlays.get(k).map(|o| o.config.name.clone())?;
|
||||
let o = self.overlays.get(k)?;
|
||||
if !o.config.can_persist() {
|
||||
return None;
|
||||
}
|
||||
let n = o.config.name.clone();
|
||||
Some((n, v.clone()))
|
||||
})
|
||||
.collect();
|
||||
@@ -604,6 +796,7 @@ impl<T> OverlayWindowManager<T> {
|
||||
for o in self.overlays.values() {
|
||||
if o.config.global
|
||||
&& let Some(state) = &o.config.active_state
|
||||
&& o.config.can_persist()
|
||||
{
|
||||
global_overlays.insert(o.config.name.clone(), state.clone());
|
||||
}
|
||||
@@ -628,7 +821,7 @@ impl<T> OverlayWindowManager<T> {
|
||||
.filter_map(|a| o.config.backend.get_attrib(*a))
|
||||
.filter(|val| !val.is_default())
|
||||
.collect();
|
||||
if !attrs.is_empty() {
|
||||
if !attrs.is_empty() && o.config.can_persist() {
|
||||
app.session
|
||||
.config
|
||||
.attribs
|
||||
@@ -647,6 +840,29 @@ impl<T> OverlayWindowManager<T> {
|
||||
return;
|
||||
}
|
||||
|
||||
// floating transforms in older states are relative to hmd, not anchor
|
||||
if app.session.config.state_version < 1 {
|
||||
let clear_legacy_floating = |state: &mut OverlayWindowState| {
|
||||
if matches!(state.positioning, Positioning::Floating) {
|
||||
state.saved_transform = None;
|
||||
}
|
||||
};
|
||||
|
||||
for set in &mut app.session.config.sets {
|
||||
for state in set
|
||||
.overlays
|
||||
.values_mut()
|
||||
.chain(set.hidden_overlays.values_mut())
|
||||
{
|
||||
clear_legacy_floating(state);
|
||||
}
|
||||
}
|
||||
for state in app.session.config.global_set.values_mut() {
|
||||
clear_legacy_floating(state);
|
||||
}
|
||||
app.session.config.state_version = 1;
|
||||
}
|
||||
|
||||
// only safe to load when current_set is None
|
||||
if self.current_set.is_some() {
|
||||
self.switch_to_set(app, None, false);
|
||||
@@ -973,9 +1189,13 @@ impl<T> OverlayWindowManager<T> {
|
||||
overlay.config.name
|
||||
);
|
||||
overlay.config.name = format!("{}_2", overlay.config.name).into();
|
||||
if overlay.config.category == OverlayCategory::WayVR {
|
||||
overlay.config.persistent = false;
|
||||
}
|
||||
}
|
||||
|
||||
let name = overlay.config.name.clone();
|
||||
let restore = overlay.config.can_persist() && overlay.config.restore_on_spawn;
|
||||
let global = overlay.config.global;
|
||||
let internal = matches!(overlay.config.category, OverlayCategory::Internal);
|
||||
let show_on_spawn = overlay.config.show_on_spawn;
|
||||
@@ -985,6 +1205,14 @@ impl<T> OverlayWindowManager<T> {
|
||||
|
||||
if !global {
|
||||
for (i, set) in self.sets.iter_mut().enumerate() {
|
||||
if !restore {
|
||||
// Explicitly new session window: replace any old slot instead of
|
||||
// accidentally restoring it on a later relaunch.
|
||||
if self.overlays[oid].config.can_persist() {
|
||||
set.inactive_overlays.arc_rm(&name);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let Some(state) = set.inactive_overlays.arc_rm(&name) else {
|
||||
continue;
|
||||
};
|
||||
@@ -999,7 +1227,7 @@ impl<T> OverlayWindowManager<T> {
|
||||
log::debug!("loaded state for {name} to set {i}");
|
||||
}
|
||||
}
|
||||
} else if let Some(state) = self.global_set.inactive_overlays.arc_rm(&name) {
|
||||
} else if restore && let Some(state) = self.global_set.inactive_overlays.arc_rm(&name) {
|
||||
let o = &mut self.overlays[oid];
|
||||
o.config.active_state = Some(state);
|
||||
o.config.reset(app, false);
|
||||
@@ -1007,7 +1235,11 @@ impl<T> OverlayWindowManager<T> {
|
||||
log::debug!("loaded state for {name} from global_set!");
|
||||
}
|
||||
|
||||
let saved_attribs = app.session.config.attribs.arc_get(&name).cloned();
|
||||
let saved_attribs = if restore {
|
||||
app.session.config.attribs.arc_get(&name).cloned()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(attribs) = saved_attribs {
|
||||
for value in attribs {
|
||||
self.overlays[oid].config.backend.set_attrib(app, value);
|
||||
@@ -1049,6 +1281,38 @@ impl<T> OverlayWindowManager<T> {
|
||||
SpawnPos::Parent(parent) => {
|
||||
let _ = self.offset_spawn_from_parent(app, oid, &parent);
|
||||
}
|
||||
SpawnPos::WlDefault(window, fallback_pid) => {
|
||||
let Some((parent, has_app_id)) = app.wvr_server.as_ref().and_then(|wvr| {
|
||||
wvr.wm.windows.get(window).map(|w| {
|
||||
(
|
||||
w.parent.and_then(|p| wvr.get_overlay_id(p)),
|
||||
w.app_id.is_some(),
|
||||
)
|
||||
})
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let parent_id = parent.or_else(|| {
|
||||
if has_app_id {
|
||||
None
|
||||
} else {
|
||||
// legacy pid-based matching
|
||||
fallback_pid.filter(|candidate| *candidate != oid)
|
||||
}
|
||||
});
|
||||
if let Some(parent_id) = parent_id
|
||||
&& self
|
||||
.offset_spawn_from_parent(app, oid, &OverlaySelector::Id(parent_id))
|
||||
.is_some()
|
||||
{
|
||||
return;
|
||||
}
|
||||
if let Some(parent_id) = self.spread_parent_for(oid) {
|
||||
let _ =
|
||||
self.offset_spawn_from_parent(app, oid, &OverlaySelector::Id(parent_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1213,6 +1477,7 @@ impl<T> OverlayWindowManager<T> {
|
||||
meta.push(OverlayMeta {
|
||||
id,
|
||||
name: data.config.name.clone(),
|
||||
title: data.config.title.clone(),
|
||||
category: data.config.category,
|
||||
visible: data.config.is_active(),
|
||||
icon,
|
||||
|
||||
@@ -108,6 +108,10 @@ impl OverlayLifetime {
|
||||
|
||||
pub struct OverlayWindowConfig {
|
||||
pub name: Arc<str>,
|
||||
/// Optional presentation title
|
||||
pub title: Option<Arc<str>>,
|
||||
pub persistent: bool,
|
||||
pub restore_on_spawn: bool,
|
||||
pub backend: Box<dyn OverlayBackend>,
|
||||
/// The default state to show when the overlay is newly spawned.
|
||||
pub default_state: OverlayWindowState,
|
||||
@@ -136,9 +140,16 @@ pub struct OverlayWindowConfig {
|
||||
}
|
||||
|
||||
impl OverlayWindowConfig {
|
||||
pub const fn can_persist(&self) -> bool {
|
||||
self.persistent
|
||||
}
|
||||
|
||||
pub fn from_backend(backend: Box<dyn OverlayBackend>) -> Self {
|
||||
Self {
|
||||
name: "".into(),
|
||||
title: None,
|
||||
persistent: true,
|
||||
restore_on_spawn: true,
|
||||
backend,
|
||||
default_state: OverlayWindowState {
|
||||
transform: Affine3A::from_translation(Vec3::NEG_Z),
|
||||
@@ -275,8 +286,8 @@ impl OverlayWindowConfig {
|
||||
app.input_state.pointers[hand as usize].pose * cur_transform,
|
||||
lerp,
|
||||
),
|
||||
Positioning::Anchored => (app.anchor * cur_transform, 1.0),
|
||||
Positioning::Static | Positioning::Floating => (state.transform, 1.0), // STAGE space
|
||||
Positioning::Anchored | Positioning::Floating => (app.anchor * cur_transform, 1.0),
|
||||
Positioning::Static => (state.transform, 1.0), // STAGE space
|
||||
};
|
||||
|
||||
state.transform = match lerp {
|
||||
@@ -320,8 +331,12 @@ impl OverlayWindowConfig {
|
||||
return;
|
||||
}
|
||||
|
||||
let to_hmd = (state.transform.translation - app.input_state.hmd.translation).normalize();
|
||||
let watch_normal = state.transform.transform_vector3a(Vec3A::NEG_Z).normalize();
|
||||
let to_hmd =
|
||||
(state.transform.translation - app.input_state.hmd.translation).normalize_or_zero();
|
||||
let watch_normal = state
|
||||
.transform
|
||||
.transform_vector3a(Vec3A::NEG_Z)
|
||||
.normalize_or_zero();
|
||||
let dot = to_hmd.dot(watch_normal);
|
||||
|
||||
let range = (app.session.config.watch_view_angle_max
|
||||
@@ -343,9 +358,9 @@ impl OverlayWindowConfig {
|
||||
.unwrap_or(self.default_state.transform);
|
||||
|
||||
let parent_transform = match state.positioning {
|
||||
Positioning::Floating | Positioning::FollowHead { .. } => app.input_state.hmd,
|
||||
Positioning::FollowHead { .. } => app.input_state.hmd,
|
||||
Positioning::FollowHand { hand, .. } => app.input_state.pointers[hand as usize].pose,
|
||||
Positioning::Anchored => app.anchor,
|
||||
Positioning::Anchored | Positioning::Floating => app.anchor,
|
||||
Positioning::Static => {
|
||||
if hard_reset {
|
||||
app.input_state.hmd
|
||||
@@ -433,7 +448,7 @@ pub fn scalar_scale(a: &Affine3A) -> f32 {
|
||||
|
||||
pub fn save_transform(state: &mut OverlayWindowState, app: &mut AppState) {
|
||||
let parent_transform = match state.positioning {
|
||||
Positioning::Floating => snap_upright(app.input_state.hmd, Vec3A::Y),
|
||||
Positioning::Floating => app.anchor,
|
||||
Positioning::FollowHead { .. } => app.input_state.hmd,
|
||||
Positioning::FollowHand { hand, .. } => app.input_state.pointers[hand as usize].pose,
|
||||
Positioning::Anchored => snap_upright(app.anchor, Vec3A::Y),
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=WayVR (Headless)
|
||||
Comment=Your way to enjoy VR on Linux
|
||||
Exec=wayvr --headless
|
||||
Icon=wayvr
|
||||
Terminal=false
|
||||
Categories=Utility;X-WiVRn-VR;
|
||||
X-XR-Plugin=true
|
||||
X-XR-Plugin-Exec=wayvr --openxr --headless
|
||||
+1
-1
@@ -15,7 +15,7 @@ clap.workspace = true
|
||||
log.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
shell-words.workspace = true
|
||||
smol.workspace = true
|
||||
|
||||
env_logger = "0.11.11"
|
||||
shell-words = "1.1.1"
|
||||
@@ -107,13 +107,12 @@ pub async fn wvr_process_terminate(
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn wvr_process_launch(
|
||||
state: &mut WayVRClientState,
|
||||
exec: String,
|
||||
argv: Vec<String>,
|
||||
name: String,
|
||||
env: Vec<String>,
|
||||
resolution: [u32; 2],
|
||||
pos_mode: PositionMode,
|
||||
icon: Option<String>,
|
||||
args: String,
|
||||
userdata: HashMap<String, String>,
|
||||
) {
|
||||
handle_result(
|
||||
@@ -123,9 +122,8 @@ pub async fn wvr_process_launch(
|
||||
state.serial_generator.increment_get(),
|
||||
packet_client::WvrProcessLaunchParams {
|
||||
env,
|
||||
exec,
|
||||
argv,
|
||||
name,
|
||||
args,
|
||||
resolution,
|
||||
pos_mode,
|
||||
icon,
|
||||
|
||||
+8
-10
@@ -157,13 +157,12 @@ async fn run_once(state: &mut WayVRClientState, args: Args) -> anyhow::Result<()
|
||||
wvr_process_terminate(state, handle).await;
|
||||
}
|
||||
Subcommands::ProcessLaunch {
|
||||
exec,
|
||||
name,
|
||||
env,
|
||||
resolution,
|
||||
pos,
|
||||
icon,
|
||||
args,
|
||||
command,
|
||||
} => {
|
||||
let env = env.split(",").map(|s| s.to_string()).collect::<Vec<_>>();
|
||||
let resolution = resolution
|
||||
@@ -181,13 +180,12 @@ async fn run_once(state: &mut WayVRClientState, args: Args) -> anyhow::Result<()
|
||||
|
||||
wvr_process_launch(
|
||||
state,
|
||||
exec,
|
||||
command,
|
||||
name,
|
||||
env,
|
||||
resolution,
|
||||
pos_mode,
|
||||
icon,
|
||||
args,
|
||||
HashMap::new(),
|
||||
)
|
||||
.await;
|
||||
@@ -316,17 +314,17 @@ enum Subcommands {
|
||||
/// Environment variables, separated by comma
|
||||
#[arg(short, long, default_value = "")]
|
||||
env: String,
|
||||
/// Executable to run
|
||||
exec: String,
|
||||
#[arg(default_value = "1920x1080")]
|
||||
#[arg(short, long, default_value = "1920x1080")]
|
||||
resolution: String,
|
||||
/// Default positioning
|
||||
#[arg(short, long, default_value = "anchored")]
|
||||
pos: PosModeEnum,
|
||||
/// Absolute path to the app icon
|
||||
#[arg(short, long, default_value = "")]
|
||||
icon: Option<String>,
|
||||
/// Arguments to pass to executable
|
||||
#[arg(default_value = "")]
|
||||
args: String,
|
||||
/// Executable and optional arguments
|
||||
#[arg(required = true, num_args = 1..)]
|
||||
command: Vec<String>,
|
||||
},
|
||||
/// Trigger haptics on the user's controller
|
||||
Haptics {
|
||||
|
||||
+6
-11
@@ -24,17 +24,12 @@ raw-window-handle = "0.6"
|
||||
cosmic-text = "0.19.0"
|
||||
etagere = "0.3.0"
|
||||
flate2 = "1.1.9"
|
||||
image = {
|
||||
version = "0.25.10",
|
||||
default-features = false,
|
||||
features = [
|
||||
"gif",
|
||||
"jpeg",
|
||||
"png",
|
||||
"rayon",
|
||||
"webp",
|
||||
]
|
||||
}
|
||||
image = { workspace = true, features = [
|
||||
"gif",
|
||||
"jpeg",
|
||||
"rayon",
|
||||
"webp",
|
||||
] }
|
||||
lru = "0.18.1"
|
||||
ouroboros = "0.18.5"
|
||||
parking_lot = "0.12.5"
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
<layout version="2">
|
||||
<!-- text: str -->
|
||||
<template name="Cell">
|
||||
<Button id="button" text="${text}" tooltip_str="${tooltip_str}" weight="bold" border="0" padding="4" color="background(transparent)" height="24" justify_content="start" />
|
||||
<macro name="ContextMenuCell" tooltip_str="${tooltip_str}" weight="bold" border="0" padding_left="4" padding_right="4" color="background(transparent)" justify_content="start" min_height="28"/>
|
||||
|
||||
<!--
|
||||
text
|
||||
-->
|
||||
<template name="CellButton">
|
||||
<Button id="button" text="${text}" macro="ContextMenuCell"/>
|
||||
</template>
|
||||
|
||||
<!--
|
||||
text, sprite_src_builtin
|
||||
-->
|
||||
<template name="CellButtonSprite">
|
||||
<Button id="button" macro="ContextMenuCell" gap="8">
|
||||
<sprite src_builtin="${sprite_src_builtin}" width="24" height="24"/>
|
||||
<label text="${text}" weight="bold"/>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<template name="Separator">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{cell::RefCell, f32, rc::Rc};
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use glam::{Mat4, Vec2, Vec3};
|
||||
use taffy::prelude::{length, percent};
|
||||
|
||||
+18
-3
@@ -139,6 +139,17 @@ impl CommandBufferBuilder<CmdBufGfx> {
|
||||
|
||||
impl CommandBufferBuilder<CmdBufXfer> {
|
||||
pub fn upload_image(&mut self, width: u32, height: u32, format: Format, data: &[u8]) -> anyhow::Result<Arc<Image>> {
|
||||
self.upload_image_with_row_length(width, height, format, data, 0)
|
||||
}
|
||||
|
||||
pub fn upload_image_with_row_length(
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
format: Format,
|
||||
data: &[u8],
|
||||
buffer_row_length: u32,
|
||||
) -> anyhow::Result<Arc<Image>> {
|
||||
let image = self.graphics.create_image(ImageCreateInfo::new_2d(
|
||||
width,
|
||||
height,
|
||||
@@ -149,9 +160,13 @@ impl CommandBufferBuilder<CmdBufXfer> {
|
||||
.graphics
|
||||
.empty_buffer::<u8>(BufferUsage::TRANSFER_SRC, data.len() as u64)?;
|
||||
staging.write()?.copy_from_slice(data);
|
||||
self
|
||||
.raw
|
||||
.copy_buffer_to_image(&staging.raw, &image.raw, [0, 0, 0], [width, height, 1]);
|
||||
self.raw.copy_buffer_to_image_with_row_length(
|
||||
&staging.raw,
|
||||
&image.raw,
|
||||
[0, 0, 0],
|
||||
[width, height, 1],
|
||||
buffer_row_length,
|
||||
);
|
||||
self.keepalive.push(Box::new(staging));
|
||||
self.keepalive.push(Box::new(image.clone()));
|
||||
Ok(image)
|
||||
|
||||
+5
-1
@@ -218,7 +218,7 @@ impl WGfx {
|
||||
|
||||
/// Submit a non-blocking completion marker to the queue
|
||||
///
|
||||
/// It uses an ALL_COMMANDS to ALL_COMMANDS barrier, so its fence doesn't
|
||||
/// It uses an `ALL_COMMANDS` to `ALL_COMMANDS` barrier, so its fence doesn't
|
||||
/// complete until all earlier work is finished.
|
||||
pub fn submit_graphics_completion_marker(&self) -> anyhow::Result<GpuCompletionMarker> {
|
||||
Ok(GpuCompletionMarker {
|
||||
@@ -462,4 +462,8 @@ impl WGfx {
|
||||
}
|
||||
cmd::CommandBufferBuilder::new(self.clone(), QueueType::Transfer, usage, PhantomData)
|
||||
}
|
||||
|
||||
pub fn enumerate_devices(&self) -> anyhow::Result<Vec<String>> {
|
||||
self.raw.enumerate_devices()
|
||||
}
|
||||
}
|
||||
+31
-1
@@ -1687,6 +1687,25 @@ impl Context {
|
||||
external_images: BTreeMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn enumerate_devices(&self) -> anyhow::Result<Vec<String>> {
|
||||
let devices = unsafe { self.instance.enumerate_physical_devices() }
|
||||
.map_err(|e| anyhow!("vkEnumeratePhysicalDevices failed: {e:?}"))?;
|
||||
|
||||
let devices: Vec<String> = devices
|
||||
.iter()
|
||||
.map(|dev| unsafe {
|
||||
let mut id = vk::PhysicalDeviceIDProperties::default();
|
||||
let mut props2 = vk::PhysicalDeviceProperties2::default().push_next(&mut id);
|
||||
self.instance.get_physical_device_properties2(*dev, &mut props2);
|
||||
CStr::from_ptr(props2.properties.device_name.as_ptr())
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(devices)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Context {
|
||||
@@ -2475,11 +2494,22 @@ impl RawCommandBuffer {
|
||||
image: &Arc<RawImage>,
|
||||
offset: [u32; 3],
|
||||
extent: [u32; 3],
|
||||
) {
|
||||
self.copy_buffer_to_image_with_row_length(buffer, image, offset, extent, 0);
|
||||
}
|
||||
|
||||
pub(super) fn copy_buffer_to_image_with_row_length(
|
||||
&mut self,
|
||||
buffer: &RawBuffer,
|
||||
image: &Arc<RawImage>,
|
||||
offset: [u32; 3],
|
||||
extent: [u32; 3],
|
||||
buffer_row_length: u32,
|
||||
) {
|
||||
self.transition_image(image, ImageLayout::TransferDst);
|
||||
let regions = [vk::BufferImageCopy::default()
|
||||
.buffer_offset(0)
|
||||
.buffer_row_length(0)
|
||||
.buffer_row_length(buffer_row_length)
|
||||
.buffer_image_height(0)
|
||||
.image_subresource(vk::ImageSubresourceLayers {
|
||||
aspect_mask: vk::ImageAspectFlags::COLOR,
|
||||
|
||||
@@ -69,6 +69,16 @@ pub trait LangsList {
|
||||
}
|
||||
|
||||
impl Locale {
|
||||
#[must_use]
|
||||
/// Returns a clone of `self` without `region`
|
||||
pub fn without_region(&self) -> Locale {
|
||||
Locale {
|
||||
lang: self.lang.clone(),
|
||||
region: None,
|
||||
matched: self.matched.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn match_locale<'o>(
|
||||
default_lang: &'static str,
|
||||
lang: &str,
|
||||
|
||||
+1
-1
@@ -660,7 +660,7 @@ impl Layout {
|
||||
pub fn recompute_layout(&mut self, new_size: Option<Vec2>) -> anyhow::Result<()> {
|
||||
let size = match new_size {
|
||||
Some(size) => Vec2::new(size.x.round(), size.y.round()),
|
||||
None => self.content_size,
|
||||
None => self.prev_size,
|
||||
};
|
||||
|
||||
if !self.state.tree.dirty(self.tree_root_node)? && self.prev_size == size {
|
||||
|
||||
@@ -374,11 +374,15 @@ impl ParserState {
|
||||
'children: for child in el_context_menu.children() {
|
||||
match child.tag_name().name() {
|
||||
"" => {}
|
||||
"separator" => {
|
||||
cells.push(context_menu::Cell::Separator);
|
||||
}
|
||||
"cell" => {
|
||||
let mut title: Option<Translation> = None;
|
||||
let mut tooltip: Option<Translation> = None;
|
||||
let mut action_name: Option<Rc<str>> = None;
|
||||
let mut attribs = Vec::<AttribPair>::new();
|
||||
let mut sprite_src_builtin: Option<Rc<str>> = None;
|
||||
|
||||
for attrib in child.attributes() {
|
||||
let (key, value) = (attrib.name(), attrib.value());
|
||||
@@ -389,6 +393,7 @@ impl ParserState {
|
||||
"tooltip" => tooltip = Some(Translation::from_translation_key(value)),
|
||||
"tooltip_str" => tooltip = Some(Translation::from_raw_text(value)),
|
||||
"action" => action_name = Some(value.into()),
|
||||
"sprite_src_builtin" => sprite_src_builtin = Some(value.into()),
|
||||
"skip" => {
|
||||
let resolved = replace_vars(value, template_params);
|
||||
//FIXME: this is always empty
|
||||
@@ -406,12 +411,13 @@ impl ParserState {
|
||||
}
|
||||
|
||||
let title = title.context("No text/translation provided")?;
|
||||
cells.push(context_menu::Cell {
|
||||
cells.push(context_menu::Cell::Button(context_menu::Button {
|
||||
title,
|
||||
tooltip,
|
||||
action_name,
|
||||
sprite_src_builtin,
|
||||
attribs,
|
||||
});
|
||||
}));
|
||||
}
|
||||
other => {
|
||||
anyhow::bail!("{:?}: unexpected <{other}> tag", self.xml_path.get_path().display());
|
||||
|
||||
Loaded 100 of 116 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user