mirror of
https://github.com/wayvr-org/wayvr.git
synced 2026-10-28 17:06:46 +01:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab7ced2014 |
No files matched your search
@@ -1 +0,0 @@
|
||||
open_collective: wlx-team
|
||||
@@ -36,9 +36,10 @@ jobs:
|
||||
cargo build --release
|
||||
- name: Make tarball
|
||||
run: |
|
||||
cargo vendor > vendor-config.toml
|
||||
mv vendor-config.toml vendor
|
||||
tar --xz -cf vendor.tar.xz vendor
|
||||
pip install portage pycargoebuild
|
||||
wget https://github.com/gentoo/gentoo/raw/refs/heads/master/metadata/license-mapping.conf
|
||||
mkdir dist
|
||||
pycargoebuild --distdir dist --license-mapping license-mapping.conf --crate-tarball --crate-tarball-path wayvr-crates.tar.xz
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
@@ -80,12 +81,12 @@ jobs:
|
||||
asset_name: WayVR-${{ github.ref_name }}-x86_64.AppImage
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload vendor tarball
|
||||
- name: Upload crates tarball
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_KEY }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./wayvr/vendor.tar.xz
|
||||
asset_name: vendor.tar.xz
|
||||
asset_path: ./wayvr/wayvr-crates.tar.xz
|
||||
asset_name: WayVR-${{ github.ref_name }}-crates.tar.xz
|
||||
asset_content_type: application/x-gtar
|
||||
Generated
+122
-38
@@ -143,6 +143,16 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "annotate-snippets"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccaf7e9dfbb6ab22c82e473cd1a8a7bd313c19a5b7e40970f3d89ef5a5c9e81e"
|
||||
dependencies = [
|
||||
"unicode-width 0.1.14",
|
||||
"yansi-term",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.21"
|
||||
@@ -673,20 +683,26 @@ checksum = "f09c37bc0e9f0924b7dae9988265ef3c76c88538f41a3b06caf4bed07cee5226"
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.72.1"
|
||||
version = "0.69.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
|
||||
checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
|
||||
dependencies = [
|
||||
"annotate-snippets",
|
||||
"bitflags 2.10.0",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.10.5",
|
||||
"itertools 0.12.1",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"log",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash 1.1.0",
|
||||
"shlex",
|
||||
"syn 2.0.113",
|
||||
"which",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -796,9 +812,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.11.1"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
||||
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
|
||||
|
||||
[[package]]
|
||||
name = "calloop"
|
||||
@@ -919,7 +935,6 @@ dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"pure-rust-locales",
|
||||
"wasm-bindgen",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
@@ -2262,6 +2277,15 @@ version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hound"
|
||||
version = "3.5.1"
|
||||
@@ -2653,6 +2677,15 @@ dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
@@ -2795,6 +2828,12 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "lazycell"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "lebe"
|
||||
version = "0.5.3"
|
||||
@@ -2854,10 +2893,12 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
|
||||
|
||||
[[package]]
|
||||
name = "libmonado"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0df3183760954d877b894d42f5c6954837d8a7d7bd5f042409c4175afeaf24ea"
|
||||
version = "1.3.2"
|
||||
source = "git+https://github.com/technobaboo/libmonado-rs.git?rev=26292e5b14663ee2f089f66f0851438a0c00ee67#26292e5b14663ee2f089f66f0851438a0c00ee67"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cmake",
|
||||
"convert_case 0.6.0",
|
||||
"dlopen2",
|
||||
"flagset",
|
||||
"libc",
|
||||
@@ -2882,7 +2923,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "libspa"
|
||||
version = "0.8.0"
|
||||
source = "git+https://gitlab.freedesktop.org/galister/pipewire-rs.git?rev=6ef2cd1af41e5e8844c63c37ea9a01f0758079b0#6ef2cd1af41e5e8844c63c37ea9a01f0758079b0"
|
||||
source = "git+https://gitlab.freedesktop.org/galister/pipewire-rs.git?rev=ba32202c3c391004c3bb533b58fa75a50e47ff57#ba32202c3c391004c3bb533b58fa75a50e47ff57"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"cc",
|
||||
@@ -2898,7 +2939,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "libspa-sys"
|
||||
version = "0.8.0"
|
||||
source = "git+https://gitlab.freedesktop.org/galister/pipewire-rs.git?rev=6ef2cd1af41e5e8844c63c37ea9a01f0758079b0#6ef2cd1af41e5e8844c63c37ea9a01f0758079b0"
|
||||
source = "git+https://gitlab.freedesktop.org/galister/pipewire-rs.git?rev=ba32202c3c391004c3bb533b58fa75a50e47ff57#ba32202c3c391004c3bb533b58fa75a50e47ff57"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
@@ -3261,9 +3302,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.2.0"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num-derive"
|
||||
@@ -3776,7 +3817,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ovr_overlay"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/galister/ovr_overlay_oyasumi?rev=e477bd2a9e04293ea68c1e7529ef2cb131f32acc#e477bd2a9e04293ea68c1e7529ef2cb131f32acc"
|
||||
source = "git+https://github.com/galister/ovr_overlay_oyasumi?rev=8d62c73d5f17e4210d6d0cd52e7f3953eb9b481a#8d62c73d5f17e4210d6d0cd52e7f3953eb9b481a"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"derive_more",
|
||||
@@ -3791,7 +3832,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ovr_overlay_sys"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/galister/ovr_overlay_oyasumi?rev=e477bd2a9e04293ea68c1e7529ef2cb131f32acc#e477bd2a9e04293ea68c1e7529ef2cb131f32acc"
|
||||
source = "git+https://github.com/galister/ovr_overlay_oyasumi?rev=8d62c73d5f17e4210d6d0cd52e7f3953eb9b481a#8d62c73d5f17e4210d6d0cd52e7f3953eb9b481a"
|
||||
dependencies = [
|
||||
"autocxx",
|
||||
"autocxx-build",
|
||||
@@ -3975,7 +4016,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pipewire"
|
||||
version = "0.8.0"
|
||||
source = "git+https://gitlab.freedesktop.org/galister/pipewire-rs.git?rev=6ef2cd1af41e5e8844c63c37ea9a01f0758079b0#6ef2cd1af41e5e8844c63c37ea9a01f0758079b0"
|
||||
source = "git+https://gitlab.freedesktop.org/galister/pipewire-rs.git?rev=ba32202c3c391004c3bb533b58fa75a50e47ff57#ba32202c3c391004c3bb533b58fa75a50e47ff57"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.10.0",
|
||||
@@ -3991,7 +4032,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pipewire-sys"
|
||||
version = "0.8.0"
|
||||
source = "git+https://gitlab.freedesktop.org/galister/pipewire-rs.git?rev=6ef2cd1af41e5e8844c63c37ea9a01f0758079b0#6ef2cd1af41e5e8844c63c37ea9a01f0758079b0"
|
||||
source = "git+https://gitlab.freedesktop.org/galister/pipewire-rs.git?rev=ba32202c3c391004c3bb533b58fa75a50e47ff57#ba32202c3c391004c3bb533b58fa75a50e47ff57"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"libspa-sys",
|
||||
@@ -4167,12 +4208,6 @@ dependencies = [
|
||||
"syn 2.0.113",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pure-rust-locales"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "869675ad2d7541aea90c6d88c81f46a7f4ea9af8cd0395d38f11a95126998a0d"
|
||||
|
||||
[[package]]
|
||||
name = "pxfm"
|
||||
version = "0.1.27"
|
||||
@@ -5481,30 +5516,30 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.47"
|
||||
version = "0.3.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
|
||||
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"num-conv",
|
||||
"powerfmt",
|
||||
"serde_core",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.8"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
|
||||
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.27"
|
||||
version = "0.2.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
|
||||
checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
@@ -5802,7 +5837,7 @@ dependencies = [
|
||||
"log",
|
||||
"rust-embed",
|
||||
"tracing-subscriber",
|
||||
"vulkano",
|
||||
"vulkano 0.35.2 (git+https://github.com/galister/vulkano.git?rev=cf7f92867928a56ce16b376037c1120f2b167678)",
|
||||
"vulkano-shaders",
|
||||
"wgui",
|
||||
"winit",
|
||||
@@ -5960,6 +5995,35 @@ dependencies = [
|
||||
"xml-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vulkano"
|
||||
version = "0.35.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee487adf7c13e5aa46d4d7fac6ffb8e172eb7e6472aaf815e9ade4992f8f2414"
|
||||
dependencies = [
|
||||
"ash",
|
||||
"bytemuck",
|
||||
"crossbeam-queue",
|
||||
"foldhash 0.1.5",
|
||||
"half",
|
||||
"heck 0.4.1",
|
||||
"indexmap 2.12.1",
|
||||
"libloading 0.8.9",
|
||||
"nom 7.1.3",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"raw-window-handle",
|
||||
"raw-window-metal",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"slabbin",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"vk-parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vulkano"
|
||||
version = "0.35.2"
|
||||
@@ -6012,7 +6076,7 @@ dependencies = [
|
||||
"quote",
|
||||
"shaderc",
|
||||
"syn 2.0.113",
|
||||
"vulkano",
|
||||
"vulkano 0.35.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6266,7 +6330,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wayvr"
|
||||
version = "26.2.1"
|
||||
version = "26.1.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"ash",
|
||||
@@ -6293,7 +6357,6 @@ dependencies = [
|
||||
"mint",
|
||||
"openxr",
|
||||
"ovr_overlay",
|
||||
"pure-rust-locales",
|
||||
"regex",
|
||||
"rosc",
|
||||
"rust-embed",
|
||||
@@ -6311,7 +6374,7 @@ dependencies = [
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"uuid",
|
||||
"vulkano",
|
||||
"vulkano 0.35.2 (git+https://github.com/galister/vulkano.git?rev=cf7f92867928a56ce16b376037c1120f2b167678)",
|
||||
"vulkano-shaders",
|
||||
"wayland-client",
|
||||
"wayvr-ipc",
|
||||
@@ -6341,7 +6404,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wayvrctl"
|
||||
version = "26.2.1"
|
||||
version = "26.1.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -6403,10 +6466,22 @@ dependencies = [
|
||||
"slotmap",
|
||||
"smallvec",
|
||||
"taffy",
|
||||
"vulkano",
|
||||
"vulkano 0.35.2 (git+https://github.com/galister/vulkano.git?rev=cf7f92867928a56ce16b376037c1120f2b167678)",
|
||||
"vulkano-shaders",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "4.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
|
||||
dependencies = [
|
||||
"either",
|
||||
"home",
|
||||
"once_cell",
|
||||
"rustix 0.38.44",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "widestring"
|
||||
version = "1.2.1"
|
||||
@@ -7102,6 +7177,15 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
||||
|
||||
[[package]]
|
||||
name = "yansi-term"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yazi"
|
||||
version = "0.2.1"
|
||||
|
||||
+2
-4
@@ -33,9 +33,6 @@ members = [
|
||||
]
|
||||
resolver = "3"
|
||||
|
||||
[patch.crates-io]
|
||||
vulkano = { git = "https://github.com/galister/vulkano.git", rev = "cf7f92867928a56ce16b376037c1120f2b167678" }
|
||||
|
||||
[workspace.dependencies]
|
||||
anyhow = "1.0.100"
|
||||
glam = { version = "0.30.9", features = ["mint", "serde"] }
|
||||
@@ -50,7 +47,8 @@ serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1.0.145"
|
||||
slotmap = "1.1.1"
|
||||
strum = { version = "0.27.2", features = ["derive"] }
|
||||
vulkano = { version = "0.35.2", default-features = false, features = [
|
||||
#vulkano = { version = "0.35.2", default-features = false, features = [
|
||||
vulkano = { git = "https://github.com/galister/vulkano.git", rev = "cf7f92867928a56ce16b376037c1120f2b167678", default-features = false, features = [
|
||||
"macros",
|
||||
] }
|
||||
vulkano-shaders = "0.35.0"
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||

|
||||

|
||||
|
||||
# WayVR (previously WlxOverlay-S)
|
||||
|
||||
A lightweight OpenXR/OpenVR overlay for Wayland and X11 desktops.
|
||||
|
||||
WayVR lets you access your desktop screens while in VR, and even launch apps directly in VR.
|
||||
WayVR lets you to access your desktop screens while in VR, and even launch apps directly in VR.
|
||||
|
||||
In comparison to similar overlays, WayVR aims to run alongside VR games and experiences while having as little performance impact as possible. The UI appearance and rendering techniques are kept as simple and efficient as possible, while still allowing a high degree of customizability.
|
||||
|
||||

|
||||

|
||||
|
||||
## Join the Linux VR Community
|
||||
|
||||
We are available on either **Discord** or **Matrix space**:
|
||||
|
||||
[](https://discord.gg/EHAYe3tTYa) [](https://matrix.to/#/#linux-vr-adventures:matrix.org)
|
||||
[](https://discord.gg/EHAYe3tTYa) [](https://matrix.to/#/#linux-vr-adventures:matrix.org)
|
||||
|
||||
Questions/issues specific to WayVR will be handled in the `wayvr` chat room. Feel free to ask anything.
|
||||
|
||||
@@ -24,10 +24,14 @@ Questions/issues specific to WayVR will be handled in the `wayvr` chat room. Fee
|
||||
|
||||
There are multiple ways to install WayVR:
|
||||
|
||||
1. AppImage: Download from [Releases](https://github.com/wayvr-org/wayvr/releases)
|
||||
1. AUR package: [wayvr](https://aur.archlinux.org/packages/wayvr) or [wayvr-git](https://aur.archlinux.org/packages/wayvr-git)
|
||||
1. Nix package: [wayvr](https://search.nixos.org/packages?channel=unstable&show=wayvr&query=wayvr) or [unstable package from nixpkgs-xr](https://github.com/nix-community/nixpkgs-xr)
|
||||
1. [Docs: Building from source](https://wayvr.org/docs/basics/building-from-source/).
|
||||
1. AppImage: Download from [Releases](https://github.com/wlx-team/wayvr/releases)
|
||||
1. AUR package: [wayvr](https://aur.archlinux.org/packages/wayvr) or [wayvr-git](https://aur.archlinux.org/packages/wayvr-git)
|
||||
1. Homebrew:
|
||||
|
||||
- Add AtomicXR tap: `brew tap matrixfurry.com/atomicxr https://tangled.sh/@matrixfurry.com/homebrew-atomicxr`
|
||||
- Install WayVR: `brew install wlx-overlay-s`
|
||||
|
||||
1. [Building from source](https://github.com/wlx-team/wayvr/wiki/Building-from-Source).
|
||||
|
||||
### General Setup
|
||||
|
||||
@@ -40,7 +44,7 @@ There are multiple ways to install WayVR:
|
||||
|
||||
For users specifically running **SteamVR via Steam Flatpak**, follow these steps:
|
||||
|
||||
1. Grab the latest AppImage from [Releases](https://github.com/wayvr-org/wayvr/releases).
|
||||
1. Grab the latest AppImage from [Releases](https://github.com/wlx-team/wayvr/releases).
|
||||
1. `WayVR-*.AppImage --appimage-extract`
|
||||
1. `chmod +x squashfs-root/AppRun`
|
||||
1. Move the newly created `squashfs-root` folder to a location accessible by the Steam Flatpak.
|
||||
@@ -56,15 +60,15 @@ For users specifically running **SteamVR via Steam Flatpak**, follow these steps
|
||||
In case screens were selected in the wrong order:
|
||||
|
||||
- Go to Settings and press `Clear PipeWire tokens` and then `Restart software`
|
||||
- Pay attention to your notifications, which tell you in which order to pick the screens.
|
||||
- If notifications don't show, try starting WayVR from the terminal and look for instructions in there.
|
||||
- Pay attention to your notifications, it tells you in which order to pick the screens.
|
||||
- If notifications don't show, try start WayVR from the terminal and look for instructions in there.
|
||||
|
||||
**WiVRn users**: Select WayVR from the `Application` drop-down. If there's no such entry, select `Custom` and browse to your WayVR executable or AppImage.
|
||||
|
||||
**Envision users**: Go to the Plugins menu and select the WayVR plugin. This will download and run the AppImage version of the overlay.
|
||||
To run a standalone installation (for instance, from the AUR), create a bash script containing `wayvr --openxr --show` and then set this bash script as a custom Envision plugin.
|
||||
In order to run a standalone installation (for instance from the AUR), create a bash script containing `wayvr --openxr --show` and then set this bash script as a custom Envision plugin.
|
||||
|
||||
This will show a home environment with headset passthrough enabled by default or a [customizable background](https://wayvr.org/docs/various/openxr-skybox/)!
|
||||
This will show a home environment with headset passthrough by default or a [customizable background](https://github.com/wlx-team/wayvr/wiki/OpenXR-Skybox)!
|
||||
|
||||
**SteamVR users**: WayVR will register itself for auto-start, so there is no need to start it every time. Disclaimer: SteamVR will sometimes disregard this and not start WayVR anyway.
|
||||
|
||||
@@ -74,7 +78,7 @@ This will show a home environment with headset passthrough enabled by default or
|
||||
|
||||
### Working Set
|
||||
|
||||
The working set consists of all currently selected overlays: screens, mirrors, keyboard, etc.
|
||||
The working set consists of all currently selected overlays; screens, mirrors, keyboard, etc.
|
||||
|
||||
The working set appears in front of the headset when shown, and can be re-centered by hiding and showing again.
|
||||
|
||||
@@ -104,17 +108,17 @@ Check your left wrist for the watch. The watch is the primary tool for controlli
|
||||
|
||||
The top of the watch shows device batteries, and the bottom shows your overlay controls.
|
||||
|
||||
Enter edit mode (the leftmost button at the bottom) to edit your overlay sets.
|
||||
Enter edit mode (leftmost button on bottom) to edit your overlay sets.
|
||||
|
||||
While in edit mode, the watch can also be grabbed and passed between your hands.
|
||||
While in edit mode, the watch can also be grabbed, and passed between your hands.
|
||||
|
||||
After grabbing, the watch will automatically attach to the hand that's opposite from the one that held it.
|
||||
|
||||
In edit mode, try hovering over other overlays to see their advanced options!
|
||||
In edit mode, try hovering other overlays to see their advanced options!
|
||||
|
||||
### The screens
|
||||
|
||||
Hovering a pointer over a screen will move the mouse. If more than one pointer is hovering over a screen, the pointer that was last used to click will take precedence.
|
||||
Hovering a pointer over a screen will move the mouse. If there are more than one pointers hovering a screen, the pointer that was last used to click will take precedence.
|
||||
|
||||
The click type depends on the laser color:
|
||||
|
||||
@@ -128,45 +132,29 @@ The click type depends on the laser color:
|
||||
Typing
|
||||
|
||||
- Use the BLUE laser when typing regularly.
|
||||
- While using the ORANGE laser, all keystrokes will have SHIFT applied.
|
||||
- While using ORANGE laser, all keystrokes will have SHIFT applied.
|
||||
- Purple laser is customizable via the settings, no modifier by default.
|
||||
|
||||
**Modifier Keys are sticky**. They will remain pressed until either:
|
||||
|
||||
- a non-modifier key is pressed
|
||||
- the modifier is toggled off by clicking again
|
||||
- the keyboard is hidden (including via show-hide)
|
||||
**Modifier Keys** are sticky. They will remain pressed until a non-modifier key is pressed, the modifier gets toggled off, or the keyboard gets hidden.
|
||||
|
||||
### Default Bindings
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
### Changing Bindings
|
||||
To customize bindings on OpenXR, refer to the [OpenXR Bindings wiki page](https://github.com/wlx-team/wayvr/wiki/OpenXR-Bindings).
|
||||
|
||||
SteamVR: Simply change the bindings from the SteamVR bindings section. If WayVR doesn't show up on the list, select any other title and then press back on the top left. (SteamVR is weird like that sometimes)
|
||||
|
||||
OpenXR (Monado/WiVRn): See [Docs: OpenXR Bindings](https://wayvr.org/docs/various/openxr-bindings/)
|
||||
|
||||
If your controllers are not supported, please reach out. \
|
||||
If your bindings are not supported, please reach out. \
|
||||
We would like to work with you and include additional bindings.
|
||||
|
||||
## Customization
|
||||
|
||||
See these relevant wiki pages:
|
||||
|
||||
- For all available config options, check [Docs: Configuration](https://wayvr.org/docs/basics/configuration/)
|
||||
- Looking to customize look & feel, or add functionality? See [Docs: Customization](https://wayvr.org/docs/basics/customization/)
|
||||
- Looking to change the OpenXR background? See [Docs: OpenXR Skybox](https://wayvr.org/docs/various/openxr-skybox/)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
When an error is detected, we often print tips for fixing it into the log file.
|
||||
When an error is detected, we often print tips for fixing into the log file.
|
||||
|
||||
Logs will be at `/tmp/wayvr.log` for most distros.
|
||||
|
||||
Check [here](https://wayvr.org/docs/various/troubleshooting/) for tips.
|
||||
Check [here](https://github.com/wlx-team/wayvr/wiki/Troubleshooting) for tips.
|
||||
|
||||
## Known Issues
|
||||
|
||||
@@ -175,8 +163,8 @@ Check [here](https://wayvr.org/docs/various/troubleshooting/) for tips.
|
||||
If the mouse is moving on a completely different screen, the screens were likely selected in the wrong order:
|
||||
|
||||
- Go to Settings and press `Clear PipeWire tokens` and then `Restart software`
|
||||
- Pay attention to your notifications, which tell you in which order to pick the screens.
|
||||
- If notifications don't show, try starting WayVR from the terminal and look for instructions in there.
|
||||
- Pay attention to your notifications, it tells you in which order to pick the screens.
|
||||
- If notifications don't show, try start WayVR from the terminal and look for instructions in there.
|
||||
|
||||
COSMIC desktop:
|
||||
|
||||
@@ -188,23 +176,23 @@ X11 users:
|
||||
- DPI scaling is not supported and will mess with the mouse.
|
||||
- Upright screens are not supported and will mess with the mouse.
|
||||
|
||||
### Screens are blank or black or frozen on SteamVR 2.14+
|
||||
### Screens are blank or black or frozen on Steam Link
|
||||
|
||||
As of SteamVR version 2.14.x, PipeWire capture no longer works when using Steam Link.
|
||||
|
||||
We're unable to completely troubleshoot how and why SteamVR interferes with PipeWire, so consider the following workarounds for the time being:
|
||||
We're unable to completely troubleshoot how and why Steam Link interferes with PipeWire, so consider the following workarounds for the time being:
|
||||
|
||||
- Use another streamer, such as WiVRn or ALVR. Note that SteamVR on Linux is considered experimental by Valve themselves [disclaimer](https://github.com/ValveSoftware/SteamVR-for-Linux?tab=readme-ov-file#steamvr-release-notes-and-known-issues).
|
||||
- Use another streamer, such as WiVRn or ALVR
|
||||
- If your desktop [supports ScreenCopy](https://wayland.app/protocols/wlr-screencopy-unstable-v1#compositor-support), go to Settings and set `Wayland capture method` to `ScreenCopy`
|
||||
- If your desktop has an X11 mode, try using that
|
||||
|
||||
### Modifiers get stuck
|
||||
|
||||
Hiding the keyboard will unpress all of its buttons. Alternatively, go to Settings and use the `Restart software` button.
|
||||
Hiding the keyboard will un-press all of its buttons. Alternatively, go to Settings and use the `Restart software` button.
|
||||
|
||||
### X11 limitations
|
||||
|
||||
- X11 capture can generally seem slow. This is because zero-copy GPU capture is not supported on the general X11 desktop. Consider trying Wayland.
|
||||
- DPI scaling is not supported and may cause the mouse to not follow the laser properly.
|
||||
- Upright screens are not supported and can cause the mouse to not follow the laser properly.
|
||||
- Screen changes (connecting/disconnecting a display, resolution changes, etc) are not handled at runtime. Restart the overlay for these to take effect.
|
||||
- Screen changes (connecting / disconnecting a display, resolution changes, etc) are not handled at runtime. Restart the overlay for these to take effect.
|
||||
@@ -1,3 +0,0 @@
|
||||
<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="m10 18l-6-6l6-6l1.4 1.45L7.85 11H20v2H7.85l3.55 3.55z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 294 B |
@@ -1,3 +0,0 @@
|
||||
<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="m14 18l-1.4-1.45L16.15 13H4v-2h12.15L12.6 7.45L14 6l6 6z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 297 B |
Binary file not shown.
|
Before Width: | Height: | Size: 22 KiB |
@@ -0,0 +1,110 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="30"
|
||||
height="30"
|
||||
viewBox="0 0 7.9375004 7.9375004"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
sodipodi:docname="dashboard_logo.svg"
|
||||
inkscape:export-filename="dashboard_logo.png"
|
||||
inkscape:export-xdpi="409.60001"
|
||||
inkscape:export-ydpi="409.60001"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="true"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="16"
|
||||
inkscape:cx="11.8125"
|
||||
inkscape:cy="16.78125"
|
||||
inkscape:window-width="1836"
|
||||
inkscape:window-height="1185"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
showguides="false">
|
||||
<inkscape:grid
|
||||
id="grid2"
|
||||
units="mm"
|
||||
originx="0"
|
||||
originy="0"
|
||||
spacingx="0.26458333"
|
||||
spacingy="0.26458333"
|
||||
empcolor="#0099e5"
|
||||
empopacity="0.30196078"
|
||||
color="#0099e5"
|
||||
opacity="0.14901961"
|
||||
empspacing="5"
|
||||
enabled="true"
|
||||
visible="true" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs1">
|
||||
<linearGradient
|
||||
id="linearGradient1"
|
||||
inkscape:collect="always">
|
||||
<stop
|
||||
style="stop-color:#ad70ff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1" />
|
||||
<stop
|
||||
style="stop-color:#00ffff;stop-opacity:1;"
|
||||
offset="0.99844205"
|
||||
id="stop2" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient1"
|
||||
id="linearGradient2"
|
||||
x1="0"
|
||||
y1="8.4666653"
|
||||
x2="8.4664993"
|
||||
y2="0"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="scale(0.93751843,0.93750002)" />
|
||||
</defs>
|
||||
<g
|
||||
inkscape:label="back"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<rect
|
||||
style="font-variation-settings:'wght' 700;fill:url(#linearGradient2);stroke-width:0.468754;stroke-linecap:round;stroke-linejoin:round"
|
||||
id="rect1"
|
||||
width="7.9380002"
|
||||
height="7.9375"
|
||||
x="0"
|
||||
y="0"
|
||||
rx="1.5874999"
|
||||
ry="1.5874999" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:1.05833;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1.5874998,3.7041664 v 1.3229167 h 1.3229166 v 1.3229165 l 1.3229165,-1e-7"
|
||||
id="path3"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:1.05833;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 1.5874998,2.6458332 V 1.5875 c 3.1749997,-2e-7 4.7624995,1.5874997 4.7624995,4.7624995 H 5.2916661"
|
||||
id="path4"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer2"
|
||||
inkscape:label="front"
|
||||
style="stroke-width:2.11667;stroke-dasharray:none" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
@@ -3,7 +3,6 @@
|
||||
|
||||
<theme>
|
||||
<var key="side_size" value="48" />
|
||||
<var key="top_size" value="40" />
|
||||
<var key="side_sprite_size" value="26" />
|
||||
<var key="side_button_size" value="48" />
|
||||
</theme>
|
||||
@@ -26,6 +25,13 @@
|
||||
</template>
|
||||
|
||||
<elements>
|
||||
<div position="absolute" width="100%" justify_content="space_between">
|
||||
<div />
|
||||
<div padding="6">
|
||||
<Button id="btn_close" sprite_src_builtin="dashboard/close.svg" color="#000000" border="2" border_color="~color_faded" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- left/right separator (menu and rest) -->
|
||||
<div flex_direction="row" gap="8" width="100%" height="100%" padding="4" interactable="0">
|
||||
<!-- LEFT MENU -->
|
||||
@@ -49,34 +55,21 @@
|
||||
<SideButton id="btn_side_apps" src_builtin="dashboard/apps.svg" tooltip="APPLICATIONS" tooltip_side="right" />
|
||||
<SideButton id="btn_side_games" src_builtin="dashboard/games.svg" tooltip="GAMES" tooltip_side="right" />
|
||||
<SideButton id="btn_side_monado" src_builtin="dashboard/monado.svg" tooltip="MONADO_RUNTIME" tooltip_side="right" />
|
||||
<!-- SideButton id="btn_side_processes" src_builtin="dashboard/window.svg" tooltip="PROCESSES" tooltip_side="right" /-->
|
||||
<rectangle height="2" color="#FFFFFF33" width="~side_sprite_size" />
|
||||
<SideButton id="btn_side_settings" src_builtin="dashboard/settings.svg" tooltip="SETTINGS" tooltip_side="right" />
|
||||
</rectangle>
|
||||
</div>
|
||||
<!-- REST -->
|
||||
<!-- top/content/bottom panel separator -->
|
||||
<!-- content/bottom panel separator -->
|
||||
<div
|
||||
flex_direction="column"
|
||||
gap="8"
|
||||
width="100%"
|
||||
height="100%"
|
||||
overflow_x="scroll">
|
||||
<!-- TOP PANEL -->
|
||||
<div position="relative" width="100%" height="~top_size" min_height="~top_size" max_height="~top_size" justify_content="end">
|
||||
<!-- Title bar -->
|
||||
<div width="100%" align_items="center" justify_content="center">
|
||||
<rectangle min_width="300" height="100%" consume_mouse_events="1" round="100%" color="~color_top_panel" align_items="center" justify_content="center" gap="8">
|
||||
<sprite id="sprite_titlebar_icon" width="24" height="24" />
|
||||
<label id="label_titlebar_title" weight="bold" size="16" text="Title" />
|
||||
</rectangle>
|
||||
</div>
|
||||
<!-- Close button -->
|
||||
<Button position="absolute" id="btn_close" color="~color_top_panel" round="100%" width="~top_size" height="~top_size" tooltip="CLOSE_WINDOW" tooltip_side="left">
|
||||
<sprite src_builtin="dashboard/close.svg" width="32" height="32" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- CONTENT -->
|
||||
|
||||
<!-- color and color2 alpha will be modified at runtime -->
|
||||
<rectangle
|
||||
id="rect_content"
|
||||
@@ -93,18 +86,22 @@
|
||||
position="absolute" width="100%" height="100%"
|
||||
gradient="radial" color="#44BBFF11" color2="#00000000" />
|
||||
|
||||
<div overflow_x="scroll" overflow_y="scroll" width="100%" height="100%">
|
||||
<div
|
||||
id="content"
|
||||
flex_direction="column"
|
||||
padding="16"
|
||||
gap="8"
|
||||
width="100%"
|
||||
height="100%"
|
||||
>
|
||||
<!-- filled-in at runtime -->
|
||||
</div>
|
||||
<div
|
||||
id="content"
|
||||
flex_direction="column"
|
||||
overflow_x="scroll"
|
||||
overflow_y="scroll"
|
||||
padding_top="8"
|
||||
padding_bottom="8"
|
||||
padding_left="16"
|
||||
padding_right="16"
|
||||
gap="8"
|
||||
width="100%"
|
||||
min_height="100%"
|
||||
>
|
||||
<!-- filled-in at runtime -->
|
||||
</div>
|
||||
|
||||
<div position="absolute" id="popup_manager" width="100%" height="100%" />
|
||||
</rectangle>
|
||||
<!-- BOTTOM PANEL -->
|
||||
@@ -154,4 +151,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</elements>
|
||||
</layout>
|
||||
</layout>
|
||||
@@ -1,4 +1,5 @@
|
||||
<layout>
|
||||
<include src="t_tab_title.xml" />
|
||||
<include src="../theme.xml" />
|
||||
|
||||
<template name="AppEntry">
|
||||
@@ -24,6 +25,7 @@
|
||||
</template>
|
||||
|
||||
<elements>
|
||||
<TabTitle translation="APPLICATIONS" icon="dashboard/apps.svg" />
|
||||
<!-- placeholders for now -->
|
||||
<!--
|
||||
<div gap="4" align_items="center">
|
||||
@@ -44,6 +46,7 @@
|
||||
flex_direction="row"
|
||||
flex_wrap="wrap"
|
||||
gap="4"
|
||||
overflow_y="scroll"
|
||||
/>
|
||||
</elements>
|
||||
</layout>
|
||||
@@ -1,6 +1,8 @@
|
||||
<layout>
|
||||
<include src="t_tab_title.xml" />
|
||||
|
||||
<elements>
|
||||
<div id="running_games_list_parent" align_self="center" />
|
||||
<div id="game_list_parent" align_items="center" flex_direction="column" gap="8" />
|
||||
<TabTitle translation="GAMES" icon="dashboard/games.svg" />
|
||||
<div id="game_list_parent" align_items="center" />
|
||||
</elements>
|
||||
</layout>
|
||||
@@ -8,7 +8,7 @@
|
||||
align_items="center"
|
||||
flex_grow="1"
|
||||
gap="24">
|
||||
<sprite src_builtin="dashboard/wayvr_dashboard.png" width="96" height="96" />
|
||||
<sprite src_builtin="dashboard/wayvr_dashboard.svg" width="96" height="96" />
|
||||
<label id="label_hello" size="32" weight="bold" />
|
||||
|
||||
<!-- main button list -->
|
||||
@@ -16,8 +16,9 @@
|
||||
<MenuButton id="btn_apps" icon="dashboard/apps.svg" translation="APPLICATIONS" />
|
||||
<MenuButton id="btn_games" icon="dashboard/games.svg" translation="GAMES" />
|
||||
<MenuButton id="btn_monado" icon="dashboard/monado.svg" text="Monado" />
|
||||
<!--MenuButton id="btn_processes" icon="dashboard/window.svg" translation="PROCESSES" /-->
|
||||
<MenuButton id="btn_settings" icon="dashboard/settings.svg" translation="SETTINGS" />
|
||||
</div>
|
||||
</div>
|
||||
</elements>
|
||||
</layout>
|
||||
</layout>
|
||||
@@ -1,6 +1,8 @@
|
||||
<layout>
|
||||
<include src="t_tab_title.xml" />
|
||||
<include src="../t_group_box.xml" />
|
||||
|
||||
|
||||
<!-- key: str, value: str -->
|
||||
<template name="BoolFlag">
|
||||
<div flex_direction="row" gap="4">
|
||||
@@ -25,10 +27,11 @@
|
||||
</template>
|
||||
|
||||
<elements>
|
||||
<TabTitle translation="MONADO_RUNTIME" icon="dashboard/monado.svg" />
|
||||
<label translation="DISPLAY_BRIGHTNESS" />
|
||||
<Slider id="slider_brightness" width="300" height="24" min_value="0" max_value="140" />
|
||||
|
||||
<label translation="PROCESS_LIST" />
|
||||
<label translation="LIST_OF_PROCESSES" />
|
||||
<div id="list_parent" flex_direction="column" gap="8">
|
||||
<!-- filled at runtime -->
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<layout>
|
||||
<include src="t_tab_title.xml" />
|
||||
|
||||
<elements>
|
||||
<TabTitle translation="LIST_OF_WINDOWS" icon="dashboard/window.svg" />
|
||||
<div id="window_list_parent" />
|
||||
<TabTitle translation="LIST_OF_PROCESSES" icon="dashboard/cpu.svg" />
|
||||
<div id="process_list_parent" />
|
||||
</elements>
|
||||
</layout>
|
||||
@@ -1,4 +1,5 @@
|
||||
<layout>
|
||||
<include src="t_tab_title.xml" />
|
||||
<include src="../t_group_box.xml" />
|
||||
<include src="../t_dropdown_button.xml" />
|
||||
|
||||
@@ -27,7 +28,7 @@
|
||||
</template>
|
||||
|
||||
<template name="DangerButton">
|
||||
<Button id="${id}" color="#AA3333" height="32" tooltip="${translation}_HELP" padding="4" gap="8">
|
||||
<Button id="${id}" color="#AA3333" height="32" tooltip="${translation}_HELP" padding="4" gap="8" >
|
||||
<sprite src_builtin="${icon}" height="24" width="24" />
|
||||
<label align="left" translation="${translation}" weight="bold" min_width="200" />
|
||||
</Button>
|
||||
@@ -35,26 +36,17 @@
|
||||
|
||||
<template name="AutostartApp">
|
||||
<div id="${id}_root" flex_direction="row">
|
||||
<Button id="${id}" color="#AA3333" height="24" padding="4" margin_top="-2" margin_bottom="-2">
|
||||
<Button id="${id}" color="#AA3333" height="24" padding="4" margin_top="-2" margin_bottom="-2" >
|
||||
<sprite src_builtin="dashboard/close.svg" height="20" width="20" />
|
||||
</Button>
|
||||
<div padding_left="8">
|
||||
<label align="left" text="${text}" weight="bold" overflow="hidden" />
|
||||
<div padding_left="8" >
|
||||
<label align="left" text="${text}" weight="bold" overflow="hidden"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<elements>
|
||||
<div gap="4">
|
||||
<Tabs id="tabs">
|
||||
<Tab name="look_and_feel" translation="APP_SETTINGS.LOOK_AND_FEEL" sprite_src_builtin="dashboard/palette.svg" />
|
||||
<Tab name="features" translation="APP_SETTINGS.FEATURES" sprite_src_builtin="dashboard/options.svg" />
|
||||
<Tab name="controls" translation="APP_SETTINGS.CONTROLS" sprite_src_builtin="dashboard/controller.svg" />
|
||||
<Tab name="misc" translation="APP_SETTINGS.MISC" sprite_src_builtin="dashboard/blocks.svg" />
|
||||
<Tab name="autostart_apps" translation="APP_SETTINGS.AUTOSTART_APPS" sprite_src_builtin="dashboard/apps.svg" />
|
||||
<Tab name="troubleshooting" translation="APP_SETTINGS.TROUBLESHOOTING" sprite_src_builtin="dashboard/cpu.svg" />
|
||||
</Tabs>
|
||||
<div flex_wrap="wrap" justify_content="stretch" gap="4" id="settings_root" width="100%" />
|
||||
</div>
|
||||
<TabTitle translation="SETTINGS" icon="dashboard/settings.svg" />
|
||||
<div flex_wrap="wrap" justify_content="stretch" gap="4" id="settings_root" />
|
||||
</elements>
|
||||
</layout>
|
||||
</layout>
|
||||
@@ -0,0 +1,9 @@
|
||||
<layout>
|
||||
<!-- translation, icon -->
|
||||
<template name="TabTitle">
|
||||
<div gap="8" align_items="center">
|
||||
<sprite src_builtin="${icon}" width="24" height="24" />
|
||||
<label translation="${translation}" size="18" weight="bold" />
|
||||
</div>
|
||||
</template>
|
||||
</layout>
|
||||
@@ -2,7 +2,6 @@
|
||||
<theme>
|
||||
<var key="color_menu_dark" value="#0A0A0ACC" />
|
||||
<var key="color_accent" value="#008cff" />
|
||||
<var key="color_top_panel" value="#00000099" />
|
||||
<var key="color_bottom_panel" value="#141e28" />
|
||||
</theme>
|
||||
</layout>
|
||||
@@ -37,18 +37,18 @@
|
||||
<Separator />
|
||||
<label translation="APP_LAUNCHER.ASPECT_TITLE" />
|
||||
<RadioGroup id="radio_orientation" flex_direction="row" gap="16">
|
||||
<RadioBox translation="APP_LAUNCHER.ASPECT.WIDE" value="Wide" tooltip_str="16:9" checked="1" />
|
||||
<RadioBox translation="APP_LAUNCHER.ASPECT.SEMI_WIDE" value="SemiWide" tooltip_str="3:2" />
|
||||
<RadioBox translation="APP_LAUNCHER.ASPECT.SQUARE" value="Square" tooltip_str="1:1" />
|
||||
<RadioBox translation="APP_LAUNCHER.ASPECT.SEMI_TALL" value="SemiTall" tooltip_str="2:3" />
|
||||
<RadioBox translation="APP_LAUNCHER.ASPECT.TALL" value="Tall" tooltip_str="9:16" />
|
||||
<RadioBox translation="APP_LAUNCHER.ASPECT.WIDE" value="Wide" tooltip="16:9" checked="1" />
|
||||
<RadioBox translation="APP_LAUNCHER.ASPECT.SEMI_WIDE" value="SemiWide" tooltip="3:2" />
|
||||
<RadioBox translation="APP_LAUNCHER.ASPECT.SQUARE" value="Square" tooltip="1:1" />
|
||||
<RadioBox translation="APP_LAUNCHER.ASPECT.SEMI_TALL" value="SemiTall" tooltip="2:3" />
|
||||
<RadioBox translation="APP_LAUNCHER.ASPECT.TALL" value="Tall" tooltip="9:16" />
|
||||
</RadioGroup>
|
||||
<!-- Separator /> // saved settings override this, so let's hide it for now
|
||||
<label translation="APP_LAUNCHER.POS_TITLE" />
|
||||
<RadioGroup id="radio_pos" flex_direction="row" gap="16">
|
||||
<RadioBox translation="APP_LAUNCHER.POS.FLOATING" value="Floating" tooltip_str="APP_LAUNCHER.POS.FLOATING_HELP" />
|
||||
<RadioBox translation="APP_LAUNCHER.POS.ANCHORED" value="Anchored" tooltip_str="APP_LAUNCHER.POS.ANCHORED_HELP" checked="1" />
|
||||
<RadioBox translation="APP_LAUNCHER.POS.STATIC" value="Static" tooltip_str="APP_LAUNCHER.POS.STATIC_HELP" />
|
||||
<RadioBox translation="APP_LAUNCHER.POS.FLOATING" value="Floating" tooltip="APP_LAUNCHER.POS.FLOATING_HELP" />
|
||||
<RadioBox translation="APP_LAUNCHER.POS.ANCHORED" value="Anchored" tooltip="APP_LAUNCHER.POS.ANCHORED_HELP" checked="1" />
|
||||
<RadioBox translation="APP_LAUNCHER.POS.STATIC" value="Static" tooltip="APP_LAUNCHER.POS.STATIC_HELP" />
|
||||
</RadioGroup -->
|
||||
<Separator />
|
||||
<div flex_direction="row" justify_content="space_between" gap="16">
|
||||
@@ -61,4 +61,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</elements>
|
||||
</layout>
|
||||
</layout>
|
||||
@@ -46,25 +46,23 @@
|
||||
</template>
|
||||
|
||||
<elements>
|
||||
<div flex_direction="column" gap="4">
|
||||
<div id="devices" flex_direction="column" gap="4" max_height="400" overflow_y="scroll">
|
||||
<div id="devices" flex_direction="column" gap="4">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- bottom buttons -->
|
||||
<div flex_direction="row" gap="4">
|
||||
<Button
|
||||
id="btn_auto"
|
||||
sprite_src_builtin="dashboard/magic_wand.svg"
|
||||
min_width="32"
|
||||
tooltip="AUDIO.AUTO_SWITCH_TO_VR_AUDIO"
|
||||
color="~color_accent"
|
||||
tooltip_side="right" />
|
||||
<!-- bottom buttons -->
|
||||
<div flex_direction="row" gap="4">
|
||||
<Button
|
||||
id="btn_auto"
|
||||
sprite_src_builtin="dashboard/magic_wand.svg"
|
||||
min_width="32"
|
||||
tooltip="AUDIO.AUTO_SWITCH_TO_VR_AUDIO"
|
||||
color="~color_accent"
|
||||
tooltip_side="right" />
|
||||
|
||||
<BottomButton id="btn_sinks" src_builtin="dashboard/volume.svg" translation="AUDIO.SPEAKERS" />
|
||||
<BottomButton id="btn_sources" src_builtin="dashboard/microphone.svg" translation="AUDIO.MICROPHONES" />
|
||||
<BottomButton id="btn_cards" src_builtin="dashboard/cpu.svg" translation="AUDIO.CARDS" />
|
||||
</div>
|
||||
<BottomButton id="btn_sinks" src_builtin="dashboard/volume.svg" translation="AUDIO.SPEAKERS" />
|
||||
<BottomButton id="btn_sources" src_builtin="dashboard/microphone.svg" translation="AUDIO.MICROPHONES" />
|
||||
<BottomButton id="btn_cards" src_builtin="dashboard/cpu.svg" translation="AUDIO.CARDS" />
|
||||
</div>
|
||||
</elements>
|
||||
</layout>
|
||||
@@ -1,12 +1,7 @@
|
||||
<layout>
|
||||
<include src="../t_group_box.xml" />
|
||||
|
||||
<elements>
|
||||
<div flex_direction="column" padding_top="8" padding_bottom="8">
|
||||
<div id="list_parent" gap="8" flex_direction="row" flex_wrap="wrap" justify_content="center" />
|
||||
</div>
|
||||
<div align_items="center" justify_content="center" gap="16">
|
||||
<Button id="btn_prev" sprite_src_builtin="dashboard/arrow_left.svg" width="32" height="32" />
|
||||
<label id="label_page" text="Page X" weight="bold" />
|
||||
<Button id="btn_next" sprite_src_builtin="dashboard/arrow_right.svg" width="32" height="32" />
|
||||
</div>
|
||||
<div id="list_parent" gap="8" flex_direction="row" flex_wrap="wrap" justify_content="center" />
|
||||
</elements>
|
||||
</layout>
|
||||
@@ -0,0 +1,9 @@
|
||||
<layout>
|
||||
<include src="../t_group_box.xml" />
|
||||
|
||||
<elements>
|
||||
<rectangle macro="group_box" flex_direction="row" align_items="center">
|
||||
<div id="list_parent" gap="8" flex_direction="column" flex_wrap="wrap" flex_grow="1" />
|
||||
</rectangle>
|
||||
</elements>
|
||||
</layout>
|
||||
@@ -1,20 +0,0 @@
|
||||
<layout>
|
||||
<include src="../t_group_box.xml" />
|
||||
|
||||
<template name="RunningGameCell">
|
||||
<rectangle macro="group_box" flex_direction="row">
|
||||
<Button id="btn_stop" sprite_src_builtin="dashboard/remove_circle.svg" tooltip="PROCESS.STOP" />
|
||||
<Button id="btn_kill" sprite_src_builtin="dashboard/knife.svg" tooltip="PROCESS.FORCE_KILL" />
|
||||
<label id="label_name" weight="bold" />
|
||||
</rectangle>
|
||||
</template>
|
||||
|
||||
<elements>
|
||||
<div align_items="center" gap="8">
|
||||
<Button id="btn_refresh" tooltip="REFRESH" width="32" height="32" sprite_src_builtin="dashboard/refresh.svg" />
|
||||
<sprite src_builtin="dashboard/cpu.svg" width="24" height="24" />
|
||||
<label translation="GAME_LIST.RUNNING_GAMES_LIST" />
|
||||
</div>
|
||||
<div id="list_parent" gap="8" />
|
||||
</elements>
|
||||
</layout>
|
||||
@@ -4,12 +4,14 @@
|
||||
"APPLICATIONS": "Anwendungen",
|
||||
"GAMES": "Spiele",
|
||||
"SETTINGS": "Einstellungen",
|
||||
"PROCESSES": "Prozesse",
|
||||
"HELLO_USER": "Hallo, {USER}!",
|
||||
"GENERAL_SETTINGS": "Allgemeine Einstellungen",
|
||||
"APPLICATION_LAUNCHER": "Anwendung Launcher",
|
||||
"APP_SETTINGS": {
|
||||
"HIDE_USERNAME": "Benutzernamen ausblenden",
|
||||
"OPAQUE_BACKGROUND": "Undurchsichtiger Hintergrund",
|
||||
"WLX": {},
|
||||
"LOOK_AND_FEEL": "Aussehen und Verhalten",
|
||||
"HIDE_GRAB_HELP": "Greif-Hilfe ausblenden",
|
||||
"ANIMATION_SPEED": "UI-Animationsgeschwindigkeit",
|
||||
@@ -74,25 +76,9 @@
|
||||
"PIPEWIRE_HELP": "Schnelle GPU-Erfassung,\nStandard auf allen Desktops.",
|
||||
"PW_FALLBACK_HELP": "Langsame Methode mit hoher CPU-Auslastung.\nVersuchen Sie es, falls PipeWire GPU nicht funktioniert.",
|
||||
"SCREENCOPY_GPU_HELP": "Schnell, keine Bildschirmfreigabe-Popups.\nFunktioniert mit: Hyprland, Niri, River, Sway",
|
||||
"SCREENCOPY_HELP": "Langsam, keine Bildschirmfreigabe-Popups.\nFunktioniert mit: Hyprland, Niri, River, Sway",
|
||||
"NONE": "Keine",
|
||||
"HMD_PINCH": "HMD + Kneifen",
|
||||
"EYE_PINCH": "Auge + Kneifen",
|
||||
"EYE_ONLY": "Nur Auge",
|
||||
"HMD_ONLY": "Nur HMD"
|
||||
"SCREENCOPY_HELP": "Langsam, keine Bildschirmfreigabe-Popups.\nFunktioniert mit: Hyprland, Niri, River, Sway"
|
||||
},
|
||||
"AUTOSTART_APPS": "Anwendungen, die beim Start ausgeführt werden sollen",
|
||||
"HANDSFREE_POINTER": "Freihändige Modus",
|
||||
"HANDSFREE_POINTER_HELP": "Eingabe, die bei Bewegung\nder Controller verwendet wird, wenn diese nicht verfügbar sind.\nLinkes Kneifen greift, rechtes klickt.",
|
||||
"UI_GRADIENT_INTENSITY": "UI-Verlaufsintensität",
|
||||
"RESET_PLAYSPACE": "Spielbereich zurücksetzen",
|
||||
"RESET_PLAYSPACE_HELP": "Den Abstand des Spielbereichs zurücksetzen.",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION": "Posen beim Interagieren mit der Tastatur blockieren",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Verhindert, dass das Spiel Posen empfängt, wenn die Tastatur angefahren wird und „Spieleingabe blockieren“ aktiviert ist",
|
||||
"LANGUAGE": "Sprache",
|
||||
"REQUIRES_RESTART": "Erfordert Neustart",
|
||||
"GRID_OPACITY": "Bodenraster-Undurchsichtigkeit",
|
||||
"GRID_OPACITY_HELP": "Undurchsichtigkeit des Bodenrasters, wenn der Skybox aktiviert ist"
|
||||
"AUTOSTART_APPS": "Anwendungen, die beim Start ausgeführt werden sollen"
|
||||
},
|
||||
"HELLO": "Hallo!",
|
||||
"AUDIO": {
|
||||
@@ -112,6 +98,7 @@
|
||||
"ACTIONS": {
|
||||
"RECENTER_PLAYSPACE": "Playspace neu zentrieren"
|
||||
},
|
||||
"LIST_OF_PROCESSES": "Prozessliste",
|
||||
"POPUP_ADD_DISPLAY": {
|
||||
"RESOLUTION": "Auflösung"
|
||||
},
|
||||
@@ -120,12 +107,19 @@
|
||||
"HIDE": "Verbergen",
|
||||
"REMOVE": "Entfernen",
|
||||
"SHOW": "Anzeigen",
|
||||
"PROCESS_LIST": {
|
||||
"NO_PROCESSES_FOUND": "Keine Prozesse gefunden",
|
||||
"LOCATED_ON": "auf",
|
||||
"TERMINATE_PROCESS_NAMED_X": "Prozess \"{PROCESS_NAME}\" beenden"
|
||||
},
|
||||
"FAILED_TO_LAUNCH_APPLICATION": "Fehler beim Starten der Anwendung:",
|
||||
"NO_WINDOWS_FOUND": "Keine Fenster gefunden",
|
||||
"WINDOW_OPTIONS": "Fensteroptionen",
|
||||
"APPLICATION_STARTED": "Anwendung gestartet",
|
||||
"LIST_OF_WINDOWS": "Fensterliste",
|
||||
"CLOSE_WINDOW": "Fenster schließen",
|
||||
"GAME_LIST": {
|
||||
"NO_GAMES_FOUND": "Keine Spiele gefunden",
|
||||
"RUNNING_GAMES_LIST": "Liste der laufenden Spiele"
|
||||
"NO_GAMES_FOUND": "Keine Spiele gefunden"
|
||||
},
|
||||
"TERMINATE_PROCESS": "Prozess beenden",
|
||||
"GAME_LAUNCHED": "Spiel gestartet",
|
||||
@@ -155,11 +149,5 @@
|
||||
"AUTOSTART": "Automatisch beim Start ausführen",
|
||||
"LAUNCH": "Starten"
|
||||
},
|
||||
"DISPLAY_BRIGHTNESS": "Bildschirmhelligkeit",
|
||||
"PROCESS_LIST": "Prozessliste",
|
||||
"REFRESH": "Aktualisieren",
|
||||
"PROCESS": {
|
||||
"STOP": "Stopp",
|
||||
"FORCE_KILL": "Erzwinge Beenden"
|
||||
}
|
||||
"DISPLAY_BRIGHTNESS": "Bildschirmhelligkeit"
|
||||
}
|
||||
@@ -31,13 +31,10 @@
|
||||
"APP_SETTINGS": {
|
||||
"ALLOW_SLIDING": "Stick interaction during grab",
|
||||
"ANIMATION_SPEED": "UI Animation speed",
|
||||
"AUTOSTART_APPS": "Apps to run on startup",
|
||||
"BLOCK_GAME_INPUT": "Block game input",
|
||||
"BLOCK_GAME_INPUT_HELP": "Blocks all input when an overlay is hovered",
|
||||
"BLOCK_GAME_INPUT_IGNORE_WATCH": "Ignore watch when blocking input",
|
||||
"BLOCK_GAME_INPUT_IGNORE_WATCH_HELP": "Do not block input when watch is hovered",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION": "Block poses when interacting with keyboard",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Blocks the game from receiving poses when the keyboard is hovered and 'Block game input' is enabled",
|
||||
"CAPTURE_METHOD": "Wayland screen capture",
|
||||
"CAPTURE_METHOD_HELP": "Try changing this if you are\nexperiencing black or glitchy screens",
|
||||
"CLEAR_PIPEWIRE_TOKENS": "Clear PipeWire tokens",
|
||||
@@ -54,8 +51,6 @@
|
||||
"DOUBLE_CURSOR_FIX_HELP": "Enable this if you see 2 cursors",
|
||||
"FEATURES": "Features",
|
||||
"FOCUS_FOLLOWS_MOUSE_MODE": "Mouse move on trigger touch",
|
||||
"HANDSFREE_POINTER": "Handsfree mode",
|
||||
"HANDSFREE_POINTER_HELP": "Input to use when motion\ncontrollers are unavailable.\nLeft pinch is grab, right is click.",
|
||||
"HIDE_GRAB_HELP": "Hide grab help",
|
||||
"HIDE_USERNAME": "Hide username",
|
||||
"INVERT_SCROLL_DIRECTION_X": "Invert horizontal scroll direction",
|
||||
@@ -63,7 +58,6 @@
|
||||
"KEYBOARD_MIDDLE_CLICK": "Keyboard middle click",
|
||||
"KEYBOARD_MIDDLE_CLICK_HELP": "Modifier to use when typing\nwith purple laser",
|
||||
"KEYBOARD_SOUND_ENABLED": "Keyboard sounds",
|
||||
"LANGUAGE": "Language",
|
||||
"LEFT_HANDED_MOUSE": "Left-handed mouse",
|
||||
"LEFT_HANDED_MOUSE_HELP": "Use this if mouse buttons are swapped",
|
||||
"LONG_PRESS_DURATION": "Long press duration",
|
||||
@@ -75,20 +69,12 @@
|
||||
"OPTION": {
|
||||
"AUTO": "Automatic",
|
||||
"AUTO_HELP": "ScreenCopy GPU if supported,\notherwise PipeWire GPU.",
|
||||
"EYE_ONLY": "Eye only",
|
||||
"EYE_PINCH": "Eye + pinch",
|
||||
"HMD_ONLY": "HMD only",
|
||||
"HMD_PINCH": "HMD + pinch",
|
||||
"NONE": "None",
|
||||
"PIPEWIRE_HELP": "Fast GPU capture,\nstandard on all desktops.",
|
||||
"PW_FALLBACK_HELP": "Slow method with high CPU usage.\nTry in case PipeWire GPU doesn't work",
|
||||
"SCREENCOPY_GPU_HELP": "Fast, no screen share popups.\nWorks on: Hyprland, Niri, River, Sway",
|
||||
"SCREENCOPY_HELP": "Slow, no screen share popups.\nWorks on: Hyprland, Niri, River, Sway"
|
||||
},
|
||||
"POINTER_LERP_FACTOR": "Pointer smoothing",
|
||||
"REQUIRES_RESTART": "Requires restart",
|
||||
"RESET_PLAYSPACE": "Reset playspace",
|
||||
"RESET_PLAYSPACE_HELP": "Clear the stage space offset.",
|
||||
"RESTART_SOFTWARE": "Restart software",
|
||||
"RESTART_SOFTWARE_HELP": "Apply settings that require a restart",
|
||||
"ROUND_MULTIPLIER": "UI Edge roundness",
|
||||
@@ -100,20 +86,18 @@
|
||||
"SPACE_DRAG_UNLOCKED": "Allow space drag on all axes",
|
||||
"SPACE_ROTATE_UNLOCKED": "Allow space rotate on all axes",
|
||||
"TROUBLESHOOTING": "Troubleshooting",
|
||||
"UI_GRADIENT_INTENSITY": "UI Gradient intensity",
|
||||
"UPRIGHT_SCREEN_FIX": "Upright screen fix",
|
||||
"UPRIGHT_SCREEN_FIX_HELP": "Fixes upright screens on some desktops",
|
||||
"USE_PASSTHROUGH": "Enable passthrough",
|
||||
"USE_PASSTHROUGH_HELP": "Allow passthrough if the XR runtime supports it",
|
||||
"USE_SKYBOX": "Enable skybox",
|
||||
"USE_SKYBOX_HELP": "Show a skybox if there's no scene app or passthrough",
|
||||
"GRID_OPACITY": "Floor grid opacity",
|
||||
"GRID_OPACITY_HELP": "Opacity of the floor grid when the skybox is enabled",
|
||||
"XR_CLICK_SENSITIVITY": "XR click sensitivity",
|
||||
"XR_CLICK_SENSITIVITY_HELP": "Analog trigger sensitivity",
|
||||
"XR_CLICK_SENSITIVITY_RELEASE": "XR release sensitivity",
|
||||
"XR_CLICK_SENSITIVITY_RELEASE_HELP": "Must be lower than click",
|
||||
"XWAYLAND_BY_DEFAULT": "Run apps in Compatibility mode by default"
|
||||
"XWAYLAND_BY_DEFAULT": "Run apps in Compatibility mode by default",
|
||||
"AUTOSTART_APPS": "Apps to run on startup"
|
||||
},
|
||||
"APPLICATION_LAUNCHER": "Application launcher",
|
||||
"APPLICATION_STARTED": "Application started",
|
||||
@@ -137,8 +121,7 @@
|
||||
"FAILED_TO_LAUNCH_APPLICATION": "Failed to launch a application:",
|
||||
"GAME_LAUNCHED": "Game launched",
|
||||
"GAME_LIST": {
|
||||
"NO_GAMES_FOUND": "No games found",
|
||||
"RUNNING_GAMES_LIST": "List of running games"
|
||||
"NO_GAMES_FOUND": "No games found"
|
||||
},
|
||||
"GAMES": "Games",
|
||||
"GENERAL_SETTINGS": "General settings",
|
||||
@@ -147,19 +130,23 @@
|
||||
"HELLO_USER": "Hello, {USER}!",
|
||||
"HIDE": "Hide",
|
||||
"HOME_SCREEN": "Home",
|
||||
"LIST_OF_PROCESSES": "Process list",
|
||||
"LIST_OF_WINDOWS": "Window list",
|
||||
"MONADO_RUNTIME": "Monado runtime",
|
||||
"NO_WINDOWS_FOUND": "No windows found",
|
||||
"POPUP_ADD_DISPLAY": {
|
||||
"RESOLUTION": "Resolution"
|
||||
},
|
||||
"PROCESS": {
|
||||
"FORCE_KILL": "Force-kill",
|
||||
"STOP": "Stop"
|
||||
"PROCESS_LIST": {
|
||||
"LOCATED_ON": "on",
|
||||
"NO_PROCESSES_FOUND": "No processes found",
|
||||
"TERMINATE_PROCESS_NAMED_X": "Terminate process \"{PROCESS_NAME}\""
|
||||
},
|
||||
"PROCESS_LIST": "Process list",
|
||||
"REFRESH": "Refresh",
|
||||
"PROCESSES": "Processes",
|
||||
"REMOVE": "Remove",
|
||||
"SETTINGS": "Settings",
|
||||
"SHOW": "Show",
|
||||
"TERMINATE_PROCESS": "Terminate process",
|
||||
"WIDTH": "Width"
|
||||
"WIDTH": "Width",
|
||||
"WINDOW_OPTIONS": "Window options"
|
||||
}
|
||||
@@ -4,12 +4,14 @@
|
||||
"APPLICATIONS": "Aplicaciones",
|
||||
"GAMES": "Juegos",
|
||||
"SETTINGS": "Ajustes",
|
||||
"PROCESSES": "Procesos",
|
||||
"HELLO_USER": "¡Hola, {USER}!",
|
||||
"GENERAL_SETTINGS": "Ajustes generales",
|
||||
"APPLICATION_LAUNCHER": "Lanzador de aplicaciones",
|
||||
"APP_SETTINGS": {
|
||||
"HIDE_USERNAME": "Ocultar nombre de usuario",
|
||||
"OPAQUE_BACKGROUND": "Fondo opaco",
|
||||
"WLX": {},
|
||||
"LOOK_AND_FEEL": "Apariencia y estilo",
|
||||
"HIDE_GRAB_HELP": "Ocultar ayuda para agarrar",
|
||||
"ANIMATION_SPEED": "Velocidad de animación de la IU",
|
||||
@@ -74,25 +76,9 @@
|
||||
"PIPEWIRE_HELP": "Captura de GPU rápida,\nestándar en todos los escritorios.",
|
||||
"PW_FALLBACK_HELP": "Método lento con alto uso de CPU.\nPruébalo si PipeWire GPU no funciona",
|
||||
"SCREENCOPY_GPU_HELP": "Rápido, sin ventanas emergentes de uso compartido de pantalla.\nFunciona en: Hyprland, Niri, River, Sway",
|
||||
"SCREENCOPY_HELP": "Lento, sin ventanas emergentes de uso compartido de pantalla.\nFunciona en: Hyprland, Niri, River, Sway",
|
||||
"NONE": "Ninguno",
|
||||
"HMD_PINCH": "HMD + pellizco",
|
||||
"EYE_PINCH": "Ojo + pellizco",
|
||||
"EYE_ONLY": "Solo ojo",
|
||||
"HMD_ONLY": "Solo HMD"
|
||||
"SCREENCOPY_HELP": "Lento, sin ventanas emergentes de uso compartido de pantalla.\nFunciona en: Hyprland, Niri, River, Sway"
|
||||
},
|
||||
"AUTOSTART_APPS": "Aplicaciones a ejecutar al inicio",
|
||||
"HANDSFREE_POINTER": "Modo manos libres",
|
||||
"HANDSFREE_POINTER_HELP": "Entrada a utilizar cuando no\nestén disponibles los mandos de movimiento.\nPellizco con la izquierda para agarrar, con la derecha para hacer clic.",
|
||||
"UI_GRADIENT_INTENSITY": "Intensidad del degradado de la IU",
|
||||
"RESET_PLAYSPACE": "Restablecer espacio de juego",
|
||||
"RESET_PLAYSPACE_HELP": "Borrar el desplazamiento del espacio de juego.",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION": "Bloquear poses al interactuar con el teclado",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Bloquea que el juego reciba poses cuando el teclado está sobre él y 'Bloquear entrada del juego' está habilitado",
|
||||
"LANGUAGE": "Idioma",
|
||||
"REQUIRES_RESTART": "Requiere reinicio",
|
||||
"GRID_OPACITY": "Opacidad de la cuadrícula del suelo",
|
||||
"GRID_OPACITY_HELP": "Opacidad de la cuadrícula del suelo cuando el skybox está habilitado"
|
||||
"AUTOSTART_APPS": "Aplicaciones a ejecutar al inicio"
|
||||
},
|
||||
"HELLO": "¡Hola!",
|
||||
"AUDIO": {
|
||||
@@ -112,6 +98,7 @@
|
||||
"ACTIONS": {
|
||||
"RECENTER_PLAYSPACE": "Re-centrar espacio de juego"
|
||||
},
|
||||
"LIST_OF_PROCESSES": "Lista de procesos",
|
||||
"POPUP_ADD_DISPLAY": {
|
||||
"RESOLUTION": "Resolución"
|
||||
},
|
||||
@@ -120,12 +107,19 @@
|
||||
"HIDE": "Ocultar",
|
||||
"REMOVE": "Eliminar",
|
||||
"SHOW": "Mostrar",
|
||||
"PROCESS_LIST": {
|
||||
"NO_PROCESSES_FOUND": "No se encontraron procesos",
|
||||
"LOCATED_ON": "en",
|
||||
"TERMINATE_PROCESS_NAMED_X": "Terminar proceso \"{PROCESS_NAME}\""
|
||||
},
|
||||
"FAILED_TO_LAUNCH_APPLICATION": "No se pudo iniciar la aplicación:",
|
||||
"NO_WINDOWS_FOUND": "No se encontraron ventanas",
|
||||
"WINDOW_OPTIONS": "Opciones de ventana",
|
||||
"APPLICATION_STARTED": "Aplicación iniciada",
|
||||
"LIST_OF_WINDOWS": "Lista de ventanas",
|
||||
"CLOSE_WINDOW": "Cerrar ventana",
|
||||
"GAME_LIST": {
|
||||
"NO_GAMES_FOUND": "No se encontraron juegos",
|
||||
"RUNNING_GAMES_LIST": "Lista de juegos en ejecución"
|
||||
"NO_GAMES_FOUND": "No se encontraron juegos"
|
||||
},
|
||||
"TERMINATE_PROCESS": "Finalizar proceso",
|
||||
"GAME_LAUNCHED": "Juego lanzado",
|
||||
@@ -155,11 +149,5 @@
|
||||
"AUTOSTART": "Ejecutar automáticamente al inicio",
|
||||
"LAUNCH": "Iniciar"
|
||||
},
|
||||
"DISPLAY_BRIGHTNESS": "Brillo de la pantalla",
|
||||
"PROCESS_LIST": "Lista de procesos",
|
||||
"REFRESH": "Actualizar",
|
||||
"PROCESS": {
|
||||
"STOP": "Detener",
|
||||
"FORCE_KILL": "Forzar cierre"
|
||||
}
|
||||
"DISPLAY_BRIGHTNESS": "Brillo de la pantalla"
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
{
|
||||
"ACTIONS": {
|
||||
"RECENTER_PLAYSPACE": "Recentra lo spazio di gioco"
|
||||
},
|
||||
"APP_LAUNCHER": {
|
||||
"ASPECT": {
|
||||
"SEMI_TALL": "Semi-alto",
|
||||
"SEMI_WIDE": "Semi-ampio",
|
||||
"SQUARE": "Quadrato",
|
||||
"TALL": "Alto",
|
||||
"WIDE": "Ampio"
|
||||
},
|
||||
"ASPECT_TITLE": "Proporzioni",
|
||||
"AUTOSTART": "Avvia automaticamente all'avvio",
|
||||
"LAUNCH": "Avvia",
|
||||
"MODE": {
|
||||
"CAGE": "Modalità compatibilità (Cage)",
|
||||
"NATIVE": "Modalità nativa"
|
||||
},
|
||||
"POS": {
|
||||
"ANCHORED": "Ancorato",
|
||||
"ANCHORED_HELP": "Rimane fermo rispetto al marcatore centrale.",
|
||||
"FLOATING": "Fluttuante",
|
||||
"FLOATING_HELP": "Si muove in modo indipendente, si recentra quando viene mostrato.",
|
||||
"STATIC": "Statico",
|
||||
"STATIC_HELP": "Non fa parte di alcun set. Non recentra."
|
||||
},
|
||||
"POS_TITLE": "Posizionamento",
|
||||
"RES_TITLE": "Risoluzione"
|
||||
},
|
||||
"APP_SETTINGS": {
|
||||
"ALLOW_SLIDING": "Interazione tramite stick durante l'afferrare",
|
||||
"ANIMATION_SPEED": "Velocità dell'animazione dell'interfaccia utente",
|
||||
"BLOCK_GAME_INPUT": "Blocca l'input di gioco",
|
||||
"BLOCK_GAME_INPUT_HELP": "Blocca tutti gli input quando il cursore è sopra un overlay",
|
||||
"BLOCK_GAME_INPUT_IGNORE_WATCH": "Ignora il Watch quando si blocca l'input",
|
||||
"BLOCK_GAME_INPUT_IGNORE_WATCH_HELP": "Non bloccare l'input quando il watch è evidenziato",
|
||||
"CAPTURE_METHOD": "Acquisizione dello schermo Wayland",
|
||||
"CAPTURE_METHOD_HELP": "Prova a modificare questa impostazione se riscontri schermate nere o con artefatti.",
|
||||
"CLEAR_PIPEWIRE_TOKENS": "Cancella i token PipeWire",
|
||||
"CLEAR_PIPEWIRE_TOKENS_HELP": "Richiedi la selezione dello schermo all'avvio successivo",
|
||||
"CLEAR_SAVED_STATE": "Cancella stato salvato",
|
||||
"CLEAR_SAVED_STATE_HELP": "Ripristina set e posizioni degli overlay",
|
||||
"CLICK_FREEZE_TIME_MS": "Tempo di congelamento del click (ms)",
|
||||
"CLICK_FREEZE_TIME_MS_HELP": "Aiuta con la precisione dei doppi clic",
|
||||
"CLOCK_12H": "Orologio a 12 ore",
|
||||
"CONTROLS": "Controlli",
|
||||
"DELETE_ALL_CONFIGS": "Elimina configurazione",
|
||||
"DELETE_ALL_CONFIGS_HELP": "Rimuovi tutti i file di configurazione da conf.d",
|
||||
"DOUBLE_CURSOR_FIX": "Correzione doppio cursore",
|
||||
"DOUBLE_CURSOR_FIX_HELP": "Abilita questa opzione se vedi 2 cursori",
|
||||
"FEATURES": "Funzionalità",
|
||||
"FOCUS_FOLLOWS_MOUSE_MODE": "Movimento del mouse al tocco del grilletto",
|
||||
"HIDE_GRAB_HELP": "Nascondi l'aiuto per l'afferrare",
|
||||
"HIDE_USERNAME": "Nascondi nome utente",
|
||||
"INVERT_SCROLL_DIRECTION_X": "Inverti direzione dello scorrimento orizzontale",
|
||||
"INVERT_SCROLL_DIRECTION_Y": "Inverti direzione dello scorrimento verticale",
|
||||
"KEYBOARD_MIDDLE_CLICK": "Click centrale del tasto",
|
||||
"KEYBOARD_MIDDLE_CLICK_HELP": "Modificatore da usare durante la digitazione\ncon laser viola",
|
||||
"KEYBOARD_SOUND_ENABLED": "Suoni della tastiera",
|
||||
"LEFT_HANDED_MOUSE": "Mouse per mancini",
|
||||
"LEFT_HANDED_MOUSE_HELP": "Usalo se i pulsanti del mouse sono invertiti",
|
||||
"LONG_PRESS_DURATION": "Durata pressione prolungata",
|
||||
"LOOK_AND_FEEL": "Aspetto",
|
||||
"MISC": "Varie",
|
||||
"NOTIFICATIONS_ENABLED": "Abilita notifiche",
|
||||
"NOTIFICATIONS_SOUND_ENABLED": "Suoni di notifica",
|
||||
"OPAQUE_BACKGROUND": "Sfondo opaco",
|
||||
"OPTION": {
|
||||
"AUTO": "Automatico",
|
||||
"AUTO_HELP": "Copia dello schermo tramite GPU se supportata,\naltrimenti tramite GPU PipeWire.",
|
||||
"PIPEWIRE_HELP": "Acquisizione GPU veloce,\nstandard su tutti i desktop.",
|
||||
"PW_FALLBACK_HELP": "Metodo lento con elevato utilizzo della CPU.\nProva in caso PipeWire GPU non funzioni",
|
||||
"SCREENCOPY_GPU_HELP": "Veloce, senza popup di condivisione schermo.\nFunziona su: Hyprland, Niri, River, Sway",
|
||||
"SCREENCOPY_HELP": "Lento, nessuna finestra pop-up per la condivisione dello schermo.\nFunziona su: Hyprland, Niri, River, Sway",
|
||||
"NONE": "Nessuno",
|
||||
"HMD_PINCH": "HMD + pizzico",
|
||||
"EYE_PINCH": "Occhio + pizzico",
|
||||
"EYE_ONLY": "Solo occhio",
|
||||
"HMD_ONLY": "Solo HMD"
|
||||
},
|
||||
"POINTER_LERP_FACTOR": "Smussamento puntatore",
|
||||
"RESTART_SOFTWARE": "Riavvia il software",
|
||||
"RESTART_SOFTWARE_HELP": "Applica impostazioni che richiedono un riavvio",
|
||||
"ROUND_MULTIPLIER": "Arrotondamento bordi UI",
|
||||
"SCREEN_RENDER_DOWN": "Rendering dello schermo a risoluzione inferiore",
|
||||
"SCREEN_RENDER_DOWN_HELP": "Aiuta a ridurre l'aliasing su schermi ad alta risoluzione",
|
||||
"SCROLL_SPEED": "Velocità di scorrimento",
|
||||
"SETS_ON_WATCH": "Set sul Watch",
|
||||
"SPACE_DRAG_MULTIPLIER": "Moltiplicatore space-drag",
|
||||
"SPACE_DRAG_UNLOCKED": "Consenti lo space-drag su tutti gli assi",
|
||||
"SPACE_ROTATE_UNLOCKED": "Consenti la rotazione dello spazio su tutti gli assi",
|
||||
"TROUBLESHOOTING": "Risoluzione dei problemi",
|
||||
"UPRIGHT_SCREEN_FIX": "Correzione schermo verticale",
|
||||
"UPRIGHT_SCREEN_FIX_HELP": "Corregge schermi verticali su alcuni desktop",
|
||||
"USE_PASSTHROUGH": "Abilita passthrough",
|
||||
"USE_PASSTHROUGH_HELP": "Consenti il passthrough se supportato dal runtime XR",
|
||||
"USE_SKYBOX": "Abilita skybox",
|
||||
"USE_SKYBOX_HELP": "Mostra uno skybox se non c'è un'app di scena o passthrough",
|
||||
"XR_CLICK_SENSITIVITY": "Sensibilità del click XR",
|
||||
"XR_CLICK_SENSITIVITY_HELP": "Sensibilità del trigger analogico",
|
||||
"XR_CLICK_SENSITIVITY_RELEASE": "Sensibilità di rilascio XR",
|
||||
"XR_CLICK_SENSITIVITY_RELEASE_HELP": "Deve essere inferiore al clic",
|
||||
"XWAYLAND_BY_DEFAULT": "Esegui le app in modalità Compatibilità per impostazione predefinita",
|
||||
"AUTOSTART_APPS": "App da avviare all'avvio",
|
||||
"HANDSFREE_POINTER": "Modalità a mani libere",
|
||||
"HANDSFREE_POINTER_HELP": "Input da usare quando i\ncontroller di movimento non sono disponibili.\nPizzico sinistro per afferrare, destro per cliccare.",
|
||||
"UI_GRADIENT_INTENSITY": "Intensità gradiente dell'interfaccia utente",
|
||||
"RESET_PLAYSPACE": "Ripristina playspace",
|
||||
"RESET_PLAYSPACE_HELP": "Cancella l'offset dello spazio di gioco.",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION": "Blocca le pose durante l'interazione con la tastiera",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Impedisce al gioco di ricevere pose quando la tastiera è evidenziata e 'Blocca input di gioco' è abilitato",
|
||||
"LANGUAGE": "Lingua",
|
||||
"REQUIRES_RESTART": "Richiede riavvio",
|
||||
"GRID_OPACITY": "Opacità della griglia del pavimento",
|
||||
"GRID_OPACITY_HELP": "Opacità della griglia del pavimento quando lo skybox è abilitato"
|
||||
},
|
||||
"APPLICATION_LAUNCHER": "Lanciatore applicazioni",
|
||||
"APPLICATION_STARTED": "Applicazione avviata",
|
||||
"APPLICATIONS": "Applicazioni",
|
||||
"AUDIO": {
|
||||
"AUTO_SWITCH_TO_VR_AUDIO": "Passa automaticamente all'audio VR",
|
||||
"CARDS": "Schede",
|
||||
"FAILED_TO_SWITCH_MICROPHONE": "Impossibile cambiare microfono",
|
||||
"MICROPHONE_SET_SUCCESSFULLY": "Microfono impostato con successo",
|
||||
"MICROPHONES": "Microfoni",
|
||||
"NO_VR_MICROPHONE_SWITCH_MANUALLY": "Nessun microfono VR trovato. Attivalo manualmente.",
|
||||
"NO_VR_SPEAKERS_FOUND_SWITCH_MANUALLY": "Nessun altoparlante VR trovato. Selezionali manualmente.",
|
||||
"SELECT_AUDIO_CARD_PROFILE": "Seleziona profilo scheda audio",
|
||||
"SETTINGS": "Impostazioni audio",
|
||||
"SPEAKERS": "Altoparlanti",
|
||||
"SPEAKERS_SET_SUCCESSFULLY": "Altoparlanti impostati correttamente",
|
||||
"VOLUME": "Volume"
|
||||
},
|
||||
"CLOSE_WINDOW": "Chiudi finestra",
|
||||
"DISPLAY_BRIGHTNESS": "Luminosità display",
|
||||
"FAILED_TO_LAUNCH_APPLICATION": "Impossibile avviare l'applicazione:",
|
||||
"GAME_LAUNCHED": "Gioco lanciato",
|
||||
"GAME_LIST": {
|
||||
"NO_GAMES_FOUND": "Nessun gioco trovato",
|
||||
"RUNNING_GAMES_LIST": "Lista dei giochi in esecuzione"
|
||||
},
|
||||
"GAMES": "Giochi",
|
||||
"GENERAL_SETTINGS": "Impostazioni generali",
|
||||
"HEIGHT": "Altezza",
|
||||
"HELLO": "Ciao!",
|
||||
"HELLO_USER": "Ciao, {USER}!",
|
||||
"HIDE": "Nascondi",
|
||||
"HOME_SCREEN": "Home",
|
||||
"MONADO_RUNTIME": "Runtime Monado",
|
||||
"POPUP_ADD_DISPLAY": {
|
||||
"RESOLUTION": "Risoluzione"
|
||||
},
|
||||
"REMOVE": "Rimuovi",
|
||||
"SETTINGS": "Impostazioni",
|
||||
"SHOW": "Mostra",
|
||||
"TERMINATE_PROCESS": "Termina processo",
|
||||
"WIDTH": "Larghezza",
|
||||
"PROCESS_LIST": "Elenco processi",
|
||||
"REFRESH": "Aggiorna",
|
||||
"PROCESS": {
|
||||
"STOP": "Interrompi",
|
||||
"FORCE_KILL": "Uccidi forzatamente"
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,14 @@
|
||||
"APPLICATIONS": "アプリ",
|
||||
"GAMES": "ゲーム",
|
||||
"SETTINGS": "設定",
|
||||
"PROCESSES": "プロセス",
|
||||
"HELLO_USER": "こんにちは、{USER}!",
|
||||
"GENERAL_SETTINGS": "全般設定",
|
||||
"APPLICATION_LAUNCHER": "アプリケーションランチャー",
|
||||
"APP_SETTINGS": {
|
||||
"HIDE_USERNAME": "ユーザー名を非表示",
|
||||
"OPAQUE_BACKGROUND": "不透明な背景",
|
||||
"WLX": {},
|
||||
"LOOK_AND_FEEL": "外観",
|
||||
"HIDE_GRAB_HELP": "グリップ動作中にのヘルプを非表示",
|
||||
"ANIMATION_SPEED": "UIアニメーション速度",
|
||||
@@ -74,25 +76,9 @@
|
||||
"PIPEWIRE_HELP": "GPU高速キャプチャ。\nすべてのデスクトップで標準です。",
|
||||
"PW_FALLBACK_HELP": "CPU使用量が多い低速な方法です。\nPipeWire GPUが動作しない場合に試してください。",
|
||||
"SCREENCOPY_GPU_HELP": "高速で、スクリーン共有ポップアップはありません。\n動作する環境: Hyprland, Niri, River, Sway",
|
||||
"SCREENCOPY_HELP": "遅延あり、画面共有ポップアップなし。\n動作する環境: Hyprland, Niri, River, Sway",
|
||||
"NONE": "なし",
|
||||
"HMD_PINCH": "HMD + ピンチ",
|
||||
"EYE_PINCH": "つまんで目を合わせる",
|
||||
"EYE_ONLY": "視野のみ",
|
||||
"HMD_ONLY": "HMDのみ"
|
||||
"SCREENCOPY_HELP": "遅延あり、画面共有ポップアップなし。\n動作する環境: Hyprland, Niri, River, Sway"
|
||||
},
|
||||
"AUTOSTART_APPS": "起動時に実行するアプリ",
|
||||
"HANDSFREE_POINTER": "ハンズフリーモード",
|
||||
"HANDSFREE_POINTER_HELP": "モーションコントローラーが利用できない場合の入力方法。\n左手のピンチは掴み、右手のピンチはクリックです。",
|
||||
"UI_GRADIENT_INTENSITY": "UIグラデーションの強さ",
|
||||
"RESET_PLAYSPACE": "プレイエリアをリセット",
|
||||
"RESET_PLAYSPACE_HELP": "プレイエリアのオフセットをクリアします。",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION": "キーボード操作時のポーズをブロック",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "キーボードがホバーされ、「ゲーム入力をブロック」が有効になっている場合、ゲームがポーズを受信することをブロックします",
|
||||
"LANGUAGE": "言語",
|
||||
"REQUIRES_RESTART": "再起動が必要です",
|
||||
"GRID_OPACITY": "フロアグリッドの不透明度",
|
||||
"GRID_OPACITY_HELP": "スカイボックスが有効なときの床グリッドの不透明度"
|
||||
"AUTOSTART_APPS": "起動時に実行するアプリ"
|
||||
},
|
||||
"HELLO": "こんにちは!",
|
||||
"AUDIO": {
|
||||
@@ -112,6 +98,7 @@
|
||||
"ACTIONS": {
|
||||
"RECENTER_PLAYSPACE": "プレイスペースを再中央"
|
||||
},
|
||||
"LIST_OF_PROCESSES": "プロセスのリスト",
|
||||
"POPUP_ADD_DISPLAY": {
|
||||
"RESOLUTION": "解像度"
|
||||
},
|
||||
@@ -120,12 +107,19 @@
|
||||
"HIDE": "隠す",
|
||||
"REMOVE": "削除",
|
||||
"SHOW": "表示",
|
||||
"PROCESS_LIST": {
|
||||
"NO_PROCESSES_FOUND": "プロセスが見つかりませんでした",
|
||||
"LOCATED_ON": "に",
|
||||
"TERMINATE_PROCESS_NAMED_X": "プロセス \"{PROCESS_NAME}\" を終了します"
|
||||
},
|
||||
"FAILED_TO_LAUNCH_APPLICATION": "アプリケーションの起動に失敗しました:",
|
||||
"NO_WINDOWS_FOUND": "ウィンドウが見つかりませんでした",
|
||||
"WINDOW_OPTIONS": "ウィンドウオプション",
|
||||
"APPLICATION_STARTED": "アプリケーションが起動しました",
|
||||
"LIST_OF_WINDOWS": "ウィンドウ一覧",
|
||||
"CLOSE_WINDOW": "ウィンドウを閉じる",
|
||||
"GAME_LIST": {
|
||||
"NO_GAMES_FOUND": "ゲームが見つかりませんでした",
|
||||
"RUNNING_GAMES_LIST": "実行中のゲーム一覧"
|
||||
"NO_GAMES_FOUND": "ゲームが見つかりませんでした"
|
||||
},
|
||||
"TERMINATE_PROCESS": "プロセスを終了する",
|
||||
"GAME_LAUNCHED": "ゲームが起動しました",
|
||||
@@ -155,11 +149,5 @@
|
||||
"AUTOSTART": "起動時に自動実行",
|
||||
"LAUNCH": "起動"
|
||||
},
|
||||
"DISPLAY_BRIGHTNESS": "ディスプレイの明るさ",
|
||||
"PROCESS_LIST": "プロセスリスト",
|
||||
"REFRESH": "更新",
|
||||
"PROCESS": {
|
||||
"STOP": "停止",
|
||||
"FORCE_KILL": "強制終了"
|
||||
}
|
||||
"DISPLAY_BRIGHTNESS": "ディスプレイの明るさ"
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
"APP_SETTINGS": {
|
||||
"HIDE_USERNAME": "Ukryj nazwę użytkownika",
|
||||
"OPAQUE_BACKGROUND": "Nieprzezroczyste tło",
|
||||
"WLX": {},
|
||||
"LOOK_AND_FEEL": "Wygląd i działanie",
|
||||
"HIDE_GRAB_HELP": "Ukryj pomoc dotyczącą chwytania",
|
||||
"ANIMATION_SPEED": "Prędkość animacji UI",
|
||||
@@ -33,6 +34,7 @@
|
||||
"XR_CLICK_SENSITIVITY": "Czułość kliknięć XR",
|
||||
"XR_CLICK_SENSITIVITY_RELEASE": "Czułość zwalniania XR",
|
||||
"CLICK_FREEZE_TIME_MS": "Czas zamrożenia po kliknięciu (ms)",
|
||||
"MISC": "Różne",
|
||||
"XWAYLAND_BY_DEFAULT": "Uruchamiaj aplikacje domyślnie w trybie kompatybilności",
|
||||
"UPRIGHT_SCREEN_FIX": "Naprawa pozycji ekranu",
|
||||
"DOUBLE_CURSOR_FIX": "Naprawa podwójnego kursora",
|
||||
@@ -53,7 +55,7 @@
|
||||
"CLEAR_SAVED_STATE": "Wyczyść zapisany stan",
|
||||
"CLEAR_PIPEWIRE_TOKENS": "Wyczyść tokeny PipeWire",
|
||||
"DELETE_ALL_CONFIGS": "Wyczyść konfigurację",
|
||||
"RESTART_SOFTWARE": "Restartuj WayVR",
|
||||
"RESTART_SOFTWARE": "Uruchom ponownie oprogramowanie",
|
||||
"CLEAR_SAVED_STATE_HELP": "Zresetuj zestawy i pozycje nakładek",
|
||||
"CLEAR_PIPEWIRE_TOKENS_HELP": "Zapytaj o wybór ekranu przy następnym uruchomieniu",
|
||||
"DELETE_ALL_CONFIGS_HELP": "Usuń wszystkie pliki konfiguracyjne z katalogu conf.d",
|
||||
@@ -68,26 +70,9 @@
|
||||
"PIPEWIRE_HELP": "Szybkie przechwytywanie GPU,\nstandard na wszystkich komputerach.",
|
||||
"PW_FALLBACK_HELP": "Powolna metoda z wysokim użyciem procesora.\nWypróbuj w przypadku, gdy PipeWire GPU nie działa",
|
||||
"SCREENCOPY_GPU_HELP": "Szybkie działanie, brak wyskakujących okien z informacją o udostępnianiu ekranu.\nDziała na: Hyprland, Niri, River, Sway",
|
||||
"SCREENCOPY_HELP": "Wolne, bez wyskakujących okienek udostępniania ekranu.\nDziała na: Hyprland, Niri, River, Sway",
|
||||
"NONE": "Brak",
|
||||
"HMD_PINCH": "HMD + ściśnięcie placami",
|
||||
"EYE_PINCH": "Ściśnięcie palcami + oko",
|
||||
"EYE_ONLY": "Tylko oko",
|
||||
"HMD_ONLY": "Tylko HMD"
|
||||
"SCREENCOPY_HELP": "Wolne, bez wyskakujących okienek udostępniania ekranu.\nDziała na: Hyprland, Niri, River, Sway"
|
||||
},
|
||||
"AUTOSTART_APPS": "Aplikacje auto-start",
|
||||
"HANDSFREE_POINTER": "Tryb bez użycia rąk",
|
||||
"HANDSFREE_POINTER_HELP": "Wejście do użycia, gdy kontrolery ruchu\nsą niedostępne. Lewy szczyptak to chwyt,\nprawy to kliknięcie.",
|
||||
"UI_GRADIENT_INTENSITY": "Intensywność gradientu UI",
|
||||
"RESET_PLAYSPACE": "Zresetuj przestrzeń gry",
|
||||
"RESET_PLAYSPACE_HELP": "Wyczyść przesunięcie przestrzeni gry.",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION": "Blokuj pozy podczas interakcji z klawiaturą",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Blokuje odbieranie póz przez grę, gdy kursor myszy znajduje się nad klawiaturą i włączona jest opcja 'Blokuj dane wejściowe z gry'",
|
||||
"LANGUAGE": "Język",
|
||||
"REQUIRES_RESTART": "Wymaga restartu",
|
||||
"MISC": "Różne",
|
||||
"GRID_OPACITY": "Przezroczystość siatki podłogowej",
|
||||
"GRID_OPACITY_HELP": "Przezroczystość siatki podłogowej, gdy włączony jest skybox"
|
||||
"AUTOSTART_APPS": "Aplikacje do uruchomienia przy starcie"
|
||||
},
|
||||
"APPLICATION_LAUNCHER": "Uruchamiacz aplikacji",
|
||||
"APPLICATIONS": "Aplikacje",
|
||||
@@ -112,6 +97,7 @@
|
||||
"HELLO_USER": "Witaj, {USER}!",
|
||||
"HIDE": "Ukryj",
|
||||
"HOME_SCREEN": "Ekran główny",
|
||||
"LIST_OF_PROCESSES": "Lista procesów",
|
||||
"MONADO_RUNTIME": "Środowisko Monado",
|
||||
"POPUP_ADD_DISPLAY": {
|
||||
"RESOLUTION": "Rozdzielczość"
|
||||
@@ -120,12 +106,20 @@
|
||||
"SETTINGS": "Ustawienia",
|
||||
"SHOW": "Pokaż",
|
||||
"WIDTH": "Szerokość",
|
||||
"PROCESSES": "Procesy",
|
||||
"PROCESS_LIST": {
|
||||
"NO_PROCESSES_FOUND": "Nie znaleziono procesów",
|
||||
"LOCATED_ON": "na",
|
||||
"TERMINATE_PROCESS_NAMED_X": "Zakończ proces \"{PROCESS_NAME}\""
|
||||
},
|
||||
"FAILED_TO_LAUNCH_APPLICATION": "Nie udało się uruchomić aplikacji:",
|
||||
"NO_WINDOWS_FOUND": "Nie znaleziono okien",
|
||||
"WINDOW_OPTIONS": "Opcje okna",
|
||||
"APPLICATION_STARTED": "Aplikacja uruchomiona",
|
||||
"LIST_OF_WINDOWS": "Lista okien",
|
||||
"CLOSE_WINDOW": "Zamknij okno",
|
||||
"GAME_LIST": {
|
||||
"NO_GAMES_FOUND": "Nie znaleziono gier",
|
||||
"RUNNING_GAMES_LIST": "Lista uruchomionych gier"
|
||||
"NO_GAMES_FOUND": "Nie znaleziono gier"
|
||||
},
|
||||
"TERMINATE_PROCESS": "Zakończ proces",
|
||||
"GAME_LAUNCHED": "Gra uruchomiona",
|
||||
@@ -155,11 +149,5 @@
|
||||
"AUTOSTART": "Uruchom automatycznie przy starcie",
|
||||
"LAUNCH": "Uruchom"
|
||||
},
|
||||
"DISPLAY_BRIGHTNESS": "Jasność wyświetlacza",
|
||||
"PROCESS_LIST": "Lista procesów",
|
||||
"REFRESH": "Odśwież",
|
||||
"PROCESS": {
|
||||
"STOP": "Zatrzymaj",
|
||||
"FORCE_KILL": "Wymuś zakończenie"
|
||||
}
|
||||
"DISPLAY_BRIGHTNESS": "Jasność wyświetlacza"
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
{
|
||||
"ACTIONS": {
|
||||
"RECENTER_PLAYSPACE": "重置游玩区中心"
|
||||
},
|
||||
"APP_LAUNCHER": {
|
||||
"ASPECT": {
|
||||
"SEMI_TALL": "略高",
|
||||
"SEMI_WIDE": "略宽",
|
||||
"SQUARE": "正方形",
|
||||
"TALL": "高",
|
||||
"WIDE": "宽"
|
||||
},
|
||||
"ASPECT_TITLE": "比例",
|
||||
"AUTOSTART": "启动时自动运行",
|
||||
"LAUNCH": "启动",
|
||||
"MODE": {
|
||||
"CAGE": "兼容模式 (Cage)",
|
||||
"NATIVE": "原生模式"
|
||||
},
|
||||
"POS": {
|
||||
"ANCHORED": "锚定",
|
||||
"ANCHORED_HELP": "相对于中心标记保持固定。",
|
||||
"FLOATING": "悬浮",
|
||||
"FLOATING_HELP": "独立移动,显示时自动重置中心。",
|
||||
"STATIC": "静态",
|
||||
"STATIC_HELP": "不属于任何集合。不会重置中心。"
|
||||
},
|
||||
"POS_TITLE": "定位",
|
||||
"RES_TITLE": "分辨率"
|
||||
},
|
||||
"APP_SETTINGS": {
|
||||
"ALLOW_SLIDING": "抓取时允许摇杆交互",
|
||||
"ANIMATION_SPEED": "UI 动画速度",
|
||||
"BLOCK_GAME_INPUT": "屏蔽游戏输入",
|
||||
"BLOCK_GAME_INPUT_HELP": "悬停在覆盖层上时屏蔽所有输入",
|
||||
"BLOCK_GAME_INPUT_IGNORE_WATCH": "屏蔽输入时忽略手表",
|
||||
"BLOCK_GAME_INPUT_IGNORE_WATCH_HELP": "悬停在手表上时不屏蔽输入",
|
||||
"CAPTURE_METHOD": "Wayland 屏幕采集",
|
||||
"CAPTURE_METHOD_HELP": "如果您遇到黑屏或花屏,请尝试更改此项",
|
||||
"CLEAR_PIPEWIRE_TOKENS": "清除 PipeWire 令牌",
|
||||
"CLEAR_PIPEWIRE_TOKENS_HELP": "下次启动时提示选择屏幕",
|
||||
"CLEAR_SAVED_STATE": "清除保存的状态",
|
||||
"CLEAR_SAVED_STATE_HELP": "重置集合与覆盖层位置",
|
||||
"CLICK_FREEZE_TIME_MS": "点击冻结时间 (毫秒)",
|
||||
"CLICK_FREEZE_TIME_MS_HELP": "有助于提高双击精度",
|
||||
"CLOCK_12H": "12 小时制时钟",
|
||||
"CONTROLS": "控制",
|
||||
"DELETE_ALL_CONFIGS": "擦除配置",
|
||||
"DELETE_ALL_CONFIGS_HELP": "从 conf.d 中删除所有配置文件",
|
||||
"DOUBLE_CURSOR_FIX": "双光标修复",
|
||||
"DOUBLE_CURSOR_FIX_HELP": "如果您看到两个光标,请启用此项",
|
||||
"FEATURES": "功能",
|
||||
"FOCUS_FOLLOWS_MOUSE_MODE": "触摸扳机时移动鼠标",
|
||||
"HIDE_GRAB_HELP": "隐藏抓取帮助",
|
||||
"HIDE_USERNAME": "隐藏用户名",
|
||||
"INVERT_SCROLL_DIRECTION_X": "反转水平滚动方向",
|
||||
"INVERT_SCROLL_DIRECTION_Y": "反转垂直滚动方向",
|
||||
"KEYBOARD_MIDDLE_CLICK": "键盘中键点击",
|
||||
"KEYBOARD_MIDDLE_CLICK_HELP": "使用紫色激光打字时的修饰键",
|
||||
"KEYBOARD_SOUND_ENABLED": "键盘音效",
|
||||
"LEFT_HANDED_MOUSE": "左手鼠标",
|
||||
"LEFT_HANDED_MOUSE_HELP": "如果鼠标按钮反置,请使用此项",
|
||||
"LONG_PRESS_DURATION": "长按持续时间",
|
||||
"LOOK_AND_FEEL": "外观与体验",
|
||||
"MISC": "杂项",
|
||||
"NOTIFICATIONS_ENABLED": "启用通知",
|
||||
"NOTIFICATIONS_SOUND_ENABLED": "通知音效",
|
||||
"OPAQUE_BACKGROUND": "不透明背景",
|
||||
"OPTION": {
|
||||
"AUTO": "自动",
|
||||
"AUTO_HELP": "如果支持,则使用 ScreenCopy GPU,\n否则使用 PipeWire GPU。",
|
||||
"PIPEWIRE_HELP": "快速 GPU 采集,\n所有桌面环境的标准方式。",
|
||||
"PW_FALLBACK_HELP": "高 CPU 占用的慢速方法。\n在 PipeWire GPU 不起作用时尝试。",
|
||||
"SCREENCOPY_GPU_HELP": "快速,无屏幕共享弹窗。\n支持:Hyprland, Niri, River, Sway",
|
||||
"SCREENCOPY_HELP": "慢速,无屏幕共享弹窗。\n支持:Hyprland, Niri, River, Sway",
|
||||
"NONE": "无",
|
||||
"HMD_PINCH": "HMD + 捏合",
|
||||
"EYE_PINCH": "眼睛 + 捏合",
|
||||
"EYE_ONLY": "仅眼球",
|
||||
"HMD_ONLY": "仅限头显"
|
||||
},
|
||||
"POINTER_LERP_FACTOR": "指针平滑",
|
||||
"RESTART_SOFTWARE": "重启软件",
|
||||
"RESTART_SOFTWARE_HELP": "应用需要重启的设置",
|
||||
"ROUND_MULTIPLIER": "UI 边缘圆角",
|
||||
"SCREEN_RENDER_DOWN": "以较低分辨率渲染屏幕",
|
||||
"SCREEN_RENDER_DOWN_HELP": "有助于解决高分辨率屏幕的锯齿问题",
|
||||
"SCROLL_SPEED": "滚动速度",
|
||||
"SETS_ON_WATCH": "在手表上显示集合",
|
||||
"SPACE_DRAG_MULTIPLIER": "空间拖拽倍率",
|
||||
"SPACE_DRAG_UNLOCKED": "允许在所有轴上进行空间拖拽",
|
||||
"SPACE_ROTATE_UNLOCKED": "允许在所有轴上进行空间旋转",
|
||||
"TROUBLESHOOTING": "故障排除",
|
||||
"UPRIGHT_SCREEN_FIX": "垂直屏幕修复",
|
||||
"UPRIGHT_SCREEN_FIX_HELP": "修复某些桌面上的垂直屏幕问题",
|
||||
"USE_PASSTHROUGH": "启用穿透",
|
||||
"USE_PASSTHROUGH_HELP": "如果 XR 运行时支持,则允许穿透",
|
||||
"USE_SKYBOX": "启用天空盒",
|
||||
"USE_SKYBOX_HELP": "如果没有场景应用或穿透,则显示天空盒",
|
||||
"XR_CLICK_SENSITIVITY": "XR 点击灵敏度",
|
||||
"XR_CLICK_SENSITIVITY_HELP": "模拟扳机灵敏度",
|
||||
"XR_CLICK_SENSITIVITY_RELEASE": "XR 释放灵敏度",
|
||||
"XR_CLICK_SENSITIVITY_RELEASE_HELP": "必须低于点击灵敏度",
|
||||
"XWAYLAND_BY_DEFAULT": "默认以兼容模式运行应用",
|
||||
"AUTOSTART_APPS": "开机启动应用",
|
||||
"HANDSFREE_POINTER": "免提模式",
|
||||
"HANDSFREE_POINTER_HELP": "当运动控制器不可用时使用的输入。\n左手捏合为抓取,右手为点击。",
|
||||
"UI_GRADIENT_INTENSITY": "UI 渐变强度",
|
||||
"RESET_PLAYSPACE": "重置游戏空间",
|
||||
"RESET_PLAYSPACE_HELP": "清除舞台空间偏移。",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION": "与键盘交互时阻止姿势",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "当键盘悬停且启用“阻止游戏输入”时,阻止游戏接收姿势",
|
||||
"LANGUAGE": "语言",
|
||||
"REQUIRES_RESTART": "需要重启",
|
||||
"GRID_OPACITY": "地面网格不透明度",
|
||||
"GRID_OPACITY_HELP": "启用天空盒时地板网格的不透明度"
|
||||
},
|
||||
"APPLICATION_LAUNCHER": "应用启动器",
|
||||
"APPLICATION_STARTED": "应用已启动",
|
||||
"APPLICATIONS": "应用",
|
||||
"AUDIO": {
|
||||
"AUTO_SWITCH_TO_VR_AUDIO": "自动切换到 VR 音频",
|
||||
"CARDS": "声卡",
|
||||
"FAILED_TO_SWITCH_MICROPHONE": "切换麦克风失败",
|
||||
"MICROPHONE_SET_SUCCESSFULLY": "麦克风设置成功",
|
||||
"MICROPHONES": "麦克风",
|
||||
"NO_VR_MICROPHONE_SWITCH_MANUALLY": "未找到 VR 麦克风。请手动切换。",
|
||||
"NO_VR_SPEAKERS_FOUND_SWITCH_MANUALLY": "未找到 VR 扬声器。请手动切换。",
|
||||
"SELECT_AUDIO_CARD_PROFILE": "选择声卡配置",
|
||||
"SETTINGS": "音频设置",
|
||||
"SPEAKERS": "扬声器",
|
||||
"SPEAKERS_SET_SUCCESSFULLY": "扬声器设置成功",
|
||||
"VOLUME": "音量"
|
||||
},
|
||||
"CLOSE_WINDOW": "关闭窗口",
|
||||
"DISPLAY_BRIGHTNESS": "显示亮度",
|
||||
"FAILED_TO_LAUNCH_APPLICATION": "启动应用失败:",
|
||||
"GAME_LAUNCHED": "游戏已启动",
|
||||
"GAME_LIST": {
|
||||
"NO_GAMES_FOUND": "未找到游戏",
|
||||
"RUNNING_GAMES_LIST": "正在运行的游戏列表"
|
||||
},
|
||||
"GAMES": "游戏",
|
||||
"GENERAL_SETTINGS": "通用设置",
|
||||
"HEIGHT": "高度",
|
||||
"HELLO": "你好!",
|
||||
"HELLO_USER": "你好,{USER}!",
|
||||
"HIDE": "隐藏",
|
||||
"HOME_SCREEN": "主页",
|
||||
"MONADO_RUNTIME": "Monado 运行时",
|
||||
"POPUP_ADD_DISPLAY": {
|
||||
"RESOLUTION": "分辨率"
|
||||
},
|
||||
"REMOVE": "移除",
|
||||
"SETTINGS": "设置",
|
||||
"SHOW": "显示",
|
||||
"TERMINATE_PROCESS": "终止进程",
|
||||
"WIDTH": "宽度",
|
||||
"PROCESS_LIST": "进程列表",
|
||||
"REFRESH": "刷新",
|
||||
"PROCESS": {
|
||||
"STOP": "停止",
|
||||
"FORCE_KILL": "强制关闭"
|
||||
}
|
||||
}
|
||||
@@ -10,22 +10,18 @@ use wgui::{
|
||||
i18n::Translation,
|
||||
layout::{Layout, LayoutParams, LayoutUpdateParams, LayoutUpdateResult, WidgetID},
|
||||
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||
renderer_vk::text::custom_glyph::CustomGlyphData,
|
||||
task::Tasks,
|
||||
theme::WguiTheme,
|
||||
widget::{label::WidgetLabel, rectangle::WidgetRectangle, sprite::WidgetSprite},
|
||||
widget::{label::WidgetLabel, rectangle::WidgetRectangle},
|
||||
windowing::window::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra, WguiWindowPlacement},
|
||||
};
|
||||
use wlx_common::{
|
||||
audio,
|
||||
dash_interface::{BoxDashInterface, RecenterMode},
|
||||
locale::WayVRLangProvider,
|
||||
timestep::{self, Timestep},
|
||||
};
|
||||
use wlx_common::{audio, dash_interface::BoxDashInterface, timestep::Timestep};
|
||||
|
||||
use crate::{
|
||||
assets,
|
||||
tab::{Tab, TabType, apps::TabApps, games::TabGames, home::TabHome, monado::TabMonado, settings::TabSettings},
|
||||
tab::{
|
||||
apps::TabApps, games::TabGames, home::TabHome, monado::TabMonado, processes::TabProcesses, settings::TabSettings,
|
||||
Tab, TabType,
|
||||
},
|
||||
util::{
|
||||
popup_manager::{MountPopupParams, PopupManager, PopupManagerParams},
|
||||
toast_manager::ToastManager,
|
||||
@@ -35,10 +31,8 @@ use crate::{
|
||||
};
|
||||
|
||||
pub struct FrontendWidgets {
|
||||
id_label_time: WidgetID,
|
||||
id_rect_content: WidgetID,
|
||||
id_sprite_titlebar_icon: WidgetID,
|
||||
id_label_titlebar_title: WidgetID,
|
||||
pub id_label_time: WidgetID,
|
||||
pub id_rect_content: WidgetID,
|
||||
}
|
||||
|
||||
pub type FrontendTasks = Tasks<FrontendTask>;
|
||||
@@ -83,11 +77,9 @@ pub struct FrontendUpdateResult {
|
||||
pub sounds_to_play: Vec<SoundType>,
|
||||
}
|
||||
|
||||
pub struct InitParams<'a, T> {
|
||||
pub struct InitParams<T> {
|
||||
pub interface: BoxDashInterface<T>,
|
||||
pub lang_provider: &'a WayVRLangProvider,
|
||||
pub has_monado: bool,
|
||||
pub theme: Rc<WguiTheme>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -112,7 +104,7 @@ pub enum FrontendTask {
|
||||
}
|
||||
|
||||
impl<T: 'static> Frontend<T> {
|
||||
pub fn new(params: InitParams<T>) -> anyhow::Result<Frontend<T>> {
|
||||
pub fn new(params: InitParams<T>, data: &mut T) -> anyhow::Result<Frontend<T>> {
|
||||
let mut assets = Box::new(assets::Asset {});
|
||||
|
||||
let font_binary_bold = assets.load_from_path_gzip("Quicksand-Bold.ttf.gz")?;
|
||||
@@ -121,7 +113,7 @@ impl<T: 'static> Frontend<T> {
|
||||
|
||||
let globals = WguiGlobals::new(
|
||||
assets,
|
||||
params.lang_provider,
|
||||
wgui::globals::Defaults::default(),
|
||||
&WguiFontConfig {
|
||||
binaries: vec![&font_binary_regular, &font_binary_bold, &font_binary_light],
|
||||
family_name_sans_serif: "Quicksand",
|
||||
@@ -137,10 +129,7 @@ impl<T: 'static> Frontend<T> {
|
||||
path: AssetPath::BuiltIn("gui/dashboard.xml"),
|
||||
extra: Default::default(),
|
||||
},
|
||||
LayoutParams {
|
||||
resize_to_parent: true,
|
||||
theme: params.theme,
|
||||
},
|
||||
&LayoutParams { resize_to_parent: true },
|
||||
)?;
|
||||
|
||||
let id_popup_manager = state.get_widget_id("popup_manager")?;
|
||||
@@ -155,8 +144,6 @@ impl<T: 'static> Frontend<T> {
|
||||
|
||||
let id_label_time = state.get_widget_id("label_time")?;
|
||||
let id_rect_content = state.get_widget_id("rect_content")?;
|
||||
let id_sprite_titlebar_icon = state.get_widget_id("sprite_titlebar_icon")?;
|
||||
let id_label_titlebar_title = state.get_widget_id("label_titlebar_title")?;
|
||||
|
||||
let timestep = Timestep::new(60.0);
|
||||
|
||||
@@ -170,8 +157,6 @@ impl<T: 'static> Frontend<T> {
|
||||
widgets: FrontendWidgets {
|
||||
id_label_time,
|
||||
id_rect_content,
|
||||
id_sprite_titlebar_icon,
|
||||
id_label_titlebar_title,
|
||||
},
|
||||
timestep,
|
||||
interface: params.interface,
|
||||
@@ -184,8 +169,8 @@ impl<T: 'static> Frontend<T> {
|
||||
};
|
||||
|
||||
// init some things first
|
||||
frontend.tasks.push(FrontendTask::RefreshBackground);
|
||||
frontend.tasks.push(FrontendTask::RefreshClock);
|
||||
frontend.update_background(data)?;
|
||||
frontend.update_time(data)?;
|
||||
|
||||
Frontend::register_widgets(&mut frontend)?;
|
||||
|
||||
@@ -198,19 +183,10 @@ impl<T: 'static> Frontend<T> {
|
||||
|
||||
fn play_sound(&mut self, audio_system: &mut audio::AudioSystem, sound_type: SoundType) -> anyhow::Result<()> {
|
||||
let mut assets = self.globals.assets_builtin();
|
||||
|
||||
let path = match sound_type {
|
||||
let sample = audio::AudioSample::from_mp3(&assets.load_from_path(match sound_type {
|
||||
SoundType::Startup => "sound/startup.mp3",
|
||||
SoundType::Launch => "sound/app_start.mp3",
|
||||
};
|
||||
|
||||
// try loading a custom sound; if one doesn't exist (or it failed to load), use the built-in asset
|
||||
let sound_bytes = match audio::AudioSample::try_bytes_from_config(path) {
|
||||
Ok(bytes) => bytes,
|
||||
Err(_) => assets.load_from_path(path)?.into(),
|
||||
};
|
||||
|
||||
let sample = audio::AudioSample::from_mp3(&sound_bytes)?;
|
||||
})?)?;
|
||||
audio_system.play_sample(&sample);
|
||||
Ok(())
|
||||
}
|
||||
@@ -222,10 +198,8 @@ impl<T: 'static> Frontend<T> {
|
||||
self.process_task(&mut params, task)?;
|
||||
}
|
||||
|
||||
let time_ms = timestep::get_micros() / 1000;
|
||||
|
||||
if let Some(mut tab) = self.current_tab.take() {
|
||||
tab.update(self, time_ms as u32, params.data)?;
|
||||
tab.update(self, params.data)?;
|
||||
|
||||
self.current_tab = Some(tab);
|
||||
}
|
||||
@@ -263,7 +237,7 @@ impl<T: 'static> Frontend<T> {
|
||||
{
|
||||
// always 30 times per second
|
||||
while self.timestep.on_tick() {
|
||||
self.toast_manager.tick(&mut self.layout)?;
|
||||
self.toast_manager.tick(&self.globals, &mut self.layout)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,10 +257,9 @@ impl<T: 'static> Frontend<T> {
|
||||
let mut common = c.common();
|
||||
|
||||
{
|
||||
let mut label = common
|
||||
.state
|
||||
.widgets
|
||||
.cast_as::<WidgetLabel>(self.widgets.id_label_time)?;
|
||||
let Some(mut label) = common.state.widgets.get_as::<WidgetLabel>(self.widgets.id_label_time) else {
|
||||
anyhow::bail!("");
|
||||
};
|
||||
|
||||
let now = chrono::Local::now();
|
||||
let hours = now.hour();
|
||||
@@ -328,8 +301,6 @@ impl<T: 'static> Frontend<T> {
|
||||
}
|
||||
|
||||
fn update_background(&mut self, data: &mut T) -> anyhow::Result<()> {
|
||||
self.layout.mark_redraw();
|
||||
|
||||
let Some(mut rect) = self
|
||||
.layout
|
||||
.state
|
||||
@@ -368,53 +339,17 @@ impl<T: 'static> Frontend<T> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_tab_title(&mut self, translation: &str, icon: &str) -> anyhow::Result<()> {
|
||||
let mut c = self.layout.start_common();
|
||||
let mut common = c.common();
|
||||
|
||||
{
|
||||
let mut label = common
|
||||
.state
|
||||
.widgets
|
||||
.cast_as::<WidgetLabel>(self.widgets.id_label_titlebar_title)?;
|
||||
label.set_text(&mut common, Translation::from_translation_key(translation));
|
||||
}
|
||||
|
||||
{
|
||||
let mut sprite = common
|
||||
.state
|
||||
.widgets
|
||||
.cast_as::<WidgetSprite>(self.widgets.id_sprite_titlebar_icon)?;
|
||||
sprite.set_content(
|
||||
&mut common,
|
||||
Some(CustomGlyphData::from_assets(&self.globals, AssetPath::BuiltIn(icon))?),
|
||||
);
|
||||
}
|
||||
|
||||
c.finish()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_tab(&mut self, data: &mut T, tab_type: TabType) -> anyhow::Result<()> {
|
||||
log::info!("Setting tab to {tab_type:?}");
|
||||
let widget_content = self.state.fetch_widget(&self.layout.state, "content")?;
|
||||
self.layout.remove_children(widget_content.id);
|
||||
|
||||
let (tab_translation, icon_path) = match tab_type {
|
||||
TabType::Home => ("HOME_SCREEN", "dashboard/home.svg"),
|
||||
TabType::Apps => ("APPLICATIONS", "dashboard/apps.svg"),
|
||||
TabType::Games => ("GAMES", "dashboard/games.svg"),
|
||||
TabType::Monado => ("MONADO_RUNTIME", "dashboard/monado.svg"),
|
||||
TabType::Settings => ("SETTINGS", "dashboard/settings.svg"),
|
||||
};
|
||||
|
||||
self.set_tab_title(tab_translation, icon_path)?;
|
||||
|
||||
let tab: Box<dyn Tab<T>> = match tab_type {
|
||||
TabType::Home => Box::new(TabHome::new(self, widget_content.id, data)?),
|
||||
TabType::Apps => Box::new(TabApps::new(self, widget_content.id, data)?),
|
||||
TabType::Games => Box::new(TabGames::new(self, widget_content.id)?),
|
||||
TabType::Monado => Box::new(TabMonado::new(self, widget_content.id)?),
|
||||
TabType::Processes => Box::new(TabProcesses::new(self, widget_content.id)?),
|
||||
TabType::Settings => Box::new(TabSettings::new(self, widget_content.id, data)?),
|
||||
};
|
||||
|
||||
@@ -458,6 +393,12 @@ impl<T: 'static> Frontend<T> {
|
||||
FrontendTask::SetTab(TabType::Monado),
|
||||
);
|
||||
|
||||
// "Processes" side button
|
||||
// self.tasks.handle_button(
|
||||
// &self.state.fetch_component_as::<ComponentButton>("btn_side_processes")?,
|
||||
// FrontendTask::SetTab(TabType::Processes),
|
||||
// );
|
||||
|
||||
// "Settings" side button
|
||||
self.tasks.handle_button(
|
||||
&self.state.fetch_component_as::<ComponentButton>("btn_side_settings")?,
|
||||
@@ -525,7 +466,7 @@ impl<T: 'static> Frontend<T> {
|
||||
}
|
||||
|
||||
fn action_recenter_playspace(&mut self, data: &mut T) -> anyhow::Result<()> {
|
||||
self.interface.recenter_playspace(data, RecenterMode::Recenter)?;
|
||||
self.interface.recenter_playspace(data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ impl<T> Tab<T> for TabApps<T> {
|
||||
TabType::Apps
|
||||
}
|
||||
|
||||
fn update(&mut self, frontend: &mut Frontend<T>, _time_ms: u32, data: &mut T) -> anyhow::Result<()> {
|
||||
fn update(&mut self, frontend: &mut Frontend<T>, data: &mut T) -> anyhow::Result<()> {
|
||||
let mut state = self.state.borrow_mut();
|
||||
|
||||
for task in self.tasks.drain() {
|
||||
@@ -81,7 +81,7 @@ fn on_app_click(
|
||||
state: Rc<RefCell<State>>,
|
||||
tasks: Tasks<Task>,
|
||||
) -> ButtonClickCallback {
|
||||
Rc::new(move |_common, _evt| {
|
||||
Box::new(move |_common, _evt| {
|
||||
frontend_tasks.push(FrontendTask::MountPopup(MountPopupParams {
|
||||
title: Translation::from_raw_text(&entry.app_name),
|
||||
on_content: {
|
||||
|
||||
@@ -9,8 +9,7 @@ use wgui::{
|
||||
use crate::{
|
||||
frontend::Frontend,
|
||||
tab::{Tab, TabType},
|
||||
util::steam_utils::SteamUtils,
|
||||
views::{game_list, running_games_list},
|
||||
views::game_list,
|
||||
};
|
||||
|
||||
pub struct TabGames<T> {
|
||||
@@ -18,8 +17,6 @@ pub struct TabGames<T> {
|
||||
pub state: ParserState,
|
||||
|
||||
view_game_list: game_list::View,
|
||||
view_running_games_list: running_games_list::View,
|
||||
steam_utils: SteamUtils,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
@@ -28,22 +25,17 @@ impl<T> Tab<T> for TabGames<T> {
|
||||
TabType::Games
|
||||
}
|
||||
|
||||
fn update(&mut self, frontend: &mut Frontend<T>, time_ms: u32, _data: &mut T) -> anyhow::Result<()> {
|
||||
self
|
||||
.view_game_list
|
||||
.update(&mut frontend.layout, &mut self.steam_utils, &frontend.executor)?;
|
||||
self.view_running_games_list.update(&mut frontend.layout, time_ms)?;
|
||||
fn update(&mut self, frontend: &mut Frontend<T>, _data: &mut T) -> anyhow::Result<()> {
|
||||
self.view_game_list.update(&mut frontend.layout, &frontend.executor)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TabGames<T> {
|
||||
pub fn new(frontend: &mut Frontend<T>, parent_id: WidgetID) -> anyhow::Result<Self> {
|
||||
let globals = frontend.layout.state.globals.clone();
|
||||
|
||||
let state = wgui::parser::parse_from_assets(
|
||||
&ParseDocumentParams {
|
||||
globals: globals.clone(),
|
||||
globals: frontend.layout.state.globals.clone(),
|
||||
path: AssetPath::BuiltIn("gui/tab/games.xml"),
|
||||
extra: Default::default(),
|
||||
},
|
||||
@@ -52,32 +44,19 @@ impl<T> TabGames<T> {
|
||||
)?;
|
||||
|
||||
let game_list_parent = state.get_widget_id("game_list_parent")?;
|
||||
let id_running_games_list_parent = state.get_widget_id("running_games_list_parent")?;
|
||||
|
||||
let view_game_list = game_list::View::new(game_list::Params {
|
||||
executor: frontend.executor.clone(),
|
||||
frontend_tasks: frontend.tasks.clone(),
|
||||
globals: globals.clone(),
|
||||
globals: frontend.layout.state.globals.clone(),
|
||||
layout: &mut frontend.layout,
|
||||
parent_id: game_list_parent,
|
||||
})?;
|
||||
|
||||
let mut steam_utils = SteamUtils::new()?;
|
||||
|
||||
let view_running_games_list = running_games_list::View::new(running_games_list::Params {
|
||||
globals: globals.clone(),
|
||||
layout: &mut frontend.layout,
|
||||
parent_id: id_running_games_list_parent,
|
||||
steam_utils: &mut steam_utils,
|
||||
frontend_tasks: frontend.tasks.clone(),
|
||||
})?;
|
||||
|
||||
Ok(Self {
|
||||
state,
|
||||
view_game_list,
|
||||
view_running_games_list,
|
||||
marker: PhantomData,
|
||||
steam_utils,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ pub mod apps;
|
||||
pub mod games;
|
||||
pub mod home;
|
||||
pub mod monado;
|
||||
pub mod processes;
|
||||
pub mod settings;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -12,6 +13,7 @@ pub enum TabType {
|
||||
Apps,
|
||||
Games,
|
||||
Monado,
|
||||
Processes,
|
||||
Settings,
|
||||
}
|
||||
|
||||
@@ -19,7 +21,7 @@ pub trait Tab<T> {
|
||||
#[allow(dead_code)]
|
||||
fn get_type(&self) -> TabType;
|
||||
|
||||
fn update(&mut self, _frontend: &mut Frontend<T>, _time_ms: u32, _user_data: &mut T) -> anyhow::Result<()> {
|
||||
fn update(&mut self, _: &mut Frontend<T>, _: &mut T) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ impl<T> Tab<T> for TabMonado<T> {
|
||||
TabType::Games
|
||||
}
|
||||
|
||||
fn update(&mut self, frontend: &mut Frontend<T>, _time_ms: u32, data: &mut T) -> anyhow::Result<()> {
|
||||
fn update(&mut self, frontend: &mut Frontend<T>, data: &mut T) -> anyhow::Result<()> {
|
||||
for task in self.tasks.drain() {
|
||||
match task {
|
||||
Task::Refresh => self.refresh(frontend, data)?,
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use wgui::{
|
||||
assets::AssetPath,
|
||||
layout::WidgetID,
|
||||
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
frontend::Frontend,
|
||||
tab::{Tab, TabType},
|
||||
views::{process_list, window_list},
|
||||
};
|
||||
|
||||
pub struct TabProcesses<T> {
|
||||
#[allow(dead_code)]
|
||||
pub state: ParserState,
|
||||
|
||||
view_window_list: window_list::View,
|
||||
view_process_list: process_list::View,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> Tab<T> for TabProcesses<T> {
|
||||
fn get_type(&self) -> TabType {
|
||||
TabType::Games
|
||||
}
|
||||
|
||||
fn update(&mut self, frontend: &mut Frontend<T>, data: &mut T) -> anyhow::Result<()> {
|
||||
self
|
||||
.view_window_list
|
||||
.update(&mut frontend.layout, &mut frontend.interface, data)?;
|
||||
self
|
||||
.view_process_list
|
||||
.update(&mut frontend.layout, &mut frontend.interface, data)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TabProcesses<T> {
|
||||
pub fn new(frontend: &mut Frontend<T>, parent_id: WidgetID) -> anyhow::Result<Self> {
|
||||
let globals = frontend.layout.state.globals.clone();
|
||||
let state = wgui::parser::parse_from_assets(
|
||||
&ParseDocumentParams {
|
||||
globals: globals.clone(),
|
||||
path: AssetPath::BuiltIn("gui/tab/processes.xml"),
|
||||
extra: Default::default(),
|
||||
},
|
||||
&mut frontend.layout,
|
||||
parent_id,
|
||||
)?;
|
||||
|
||||
Ok(Self {
|
||||
view_window_list: window_list::View::new(window_list::Params {
|
||||
layout: &mut frontend.layout,
|
||||
parent_id: state.get_widget_id("window_list_parent")?,
|
||||
globals: globals.clone(),
|
||||
frontend_tasks: frontend.tasks.clone(),
|
||||
on_click: None,
|
||||
})?,
|
||||
view_process_list: process_list::View::new(process_list::Params {
|
||||
layout: &mut frontend.layout,
|
||||
parent_id: state.get_widget_id("process_list_parent")?,
|
||||
globals,
|
||||
})?,
|
||||
state,
|
||||
marker: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
+223
-429
@@ -4,72 +4,33 @@ use glam::Vec2;
|
||||
use strum::{AsRefStr, EnumProperty, EnumString, VariantArray};
|
||||
use wgui::{
|
||||
assets::AssetPath,
|
||||
components::{
|
||||
button::{ButtonClickEvent, ComponentButton},
|
||||
checkbox::ComponentCheckbox,
|
||||
slider::ComponentSlider,
|
||||
tabs::ComponentTabs,
|
||||
},
|
||||
drawing,
|
||||
components::{button::ComponentButton, checkbox::ComponentCheckbox, slider::ComponentSlider},
|
||||
event::{CallbackDataCommon, EventAlterables},
|
||||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
layout::{Layout, WidgetID},
|
||||
log::LogErr,
|
||||
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||
renderer_vk::text::{FontWeight, TextStyle},
|
||||
taffy::{self, prelude::length},
|
||||
task::Tasks,
|
||||
widget::{
|
||||
div::WidgetDiv,
|
||||
label::{WidgetLabel, WidgetLabelParams},
|
||||
},
|
||||
widget::label::WidgetLabel,
|
||||
windowing::context_menu::{self, Blueprint, ContextMenu, TickResult},
|
||||
};
|
||||
use wlx_common::{config::GeneralConfig, config_io::ConfigRoot, dash_interface::RecenterMode};
|
||||
use wlx_common::{config::GeneralConfig, config_io::ConfigRoot};
|
||||
|
||||
use crate::{
|
||||
frontend::{Frontend, FrontendTask},
|
||||
tab::{Tab, TabType},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
enum TabNameEnum {
|
||||
LookAndFeel,
|
||||
Features,
|
||||
Controls,
|
||||
Misc,
|
||||
AutostartApps,
|
||||
Troubleshooting,
|
||||
}
|
||||
|
||||
impl TabNameEnum {
|
||||
fn from_string(s: &str) -> Option<Self> {
|
||||
match s {
|
||||
"look_and_feel" => Some(TabNameEnum::LookAndFeel),
|
||||
"features" => Some(TabNameEnum::Features),
|
||||
"controls" => Some(TabNameEnum::Controls),
|
||||
"misc" => Some(TabNameEnum::Misc),
|
||||
"autostart_apps" => Some(TabNameEnum::AutostartApps),
|
||||
"troubleshooting" => Some(TabNameEnum::Troubleshooting),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Task {
|
||||
UpdateBool(SettingType, bool),
|
||||
UpdateFloat(SettingType, f32),
|
||||
UpdateInt(SettingType, i32),
|
||||
SettingUpdated(SettingType),
|
||||
OpenContextMenu(Vec2, Vec<context_menu::Cell>),
|
||||
ClearPipewireTokens,
|
||||
ClearSavedState,
|
||||
DeleteAllConfigs,
|
||||
ResetPlayspace,
|
||||
RestartSoftware,
|
||||
RemoveAutostartApp(Rc<str>),
|
||||
SetTab(TabNameEnum),
|
||||
}
|
||||
|
||||
pub struct TabSettings<T> {
|
||||
@@ -87,37 +48,23 @@ impl<T> Tab<T> for TabSettings<T> {
|
||||
TabType::Settings
|
||||
}
|
||||
|
||||
fn update(&mut self, frontend: &mut Frontend<T>, _time_ms: u32, data: &mut T) -> anyhow::Result<()> {
|
||||
fn update(&mut self, frontend: &mut Frontend<T>, data: &mut T) -> anyhow::Result<()> {
|
||||
let config = frontend.interface.general_config(data);
|
||||
let mut changed = false;
|
||||
for task in self.tasks.drain() {
|
||||
match task {
|
||||
Task::SetTab(tab) => {
|
||||
self.set_tab(frontend, data, tab)?;
|
||||
}
|
||||
Task::UpdateBool(setting, n) => {
|
||||
self.tasks.push(Task::SettingUpdated(setting));
|
||||
if let Some(task) = setting.get_frontend_task() {
|
||||
frontend.tasks.push(task)
|
||||
}
|
||||
let config = frontend.interface.general_config(data);
|
||||
setting.get_frontend_task().map(|task| frontend.tasks.push(task));
|
||||
*setting.mut_bool(config) = n;
|
||||
changed = true;
|
||||
}
|
||||
Task::UpdateFloat(setting, n) => {
|
||||
self.tasks.push(Task::SettingUpdated(setting));
|
||||
if let Some(task) = setting.get_frontend_task() {
|
||||
frontend.tasks.push(task)
|
||||
}
|
||||
let config = frontend.interface.general_config(data);
|
||||
setting.get_frontend_task().map(|task| frontend.tasks.push(task));
|
||||
*setting.mut_f32(config) = n;
|
||||
changed = true;
|
||||
}
|
||||
Task::UpdateInt(setting, n) => {
|
||||
self.tasks.push(Task::SettingUpdated(setting));
|
||||
if let Some(task) = setting.get_frontend_task() {
|
||||
frontend.tasks.push(task)
|
||||
}
|
||||
let config = frontend.interface.general_config(data);
|
||||
setting.get_frontend_task().map(|task| frontend.tasks.push(task));
|
||||
*setting.mut_i32(config) = n;
|
||||
changed = true;
|
||||
}
|
||||
@@ -134,10 +81,6 @@ impl<T> Tab<T> for TabSettings<T> {
|
||||
std::fs::remove_dir_all(&path)?;
|
||||
std::fs::create_dir(&path)?;
|
||||
}
|
||||
Task::ResetPlayspace => {
|
||||
frontend.interface.recenter_playspace(data, RecenterMode::Reset)?;
|
||||
return Ok(());
|
||||
}
|
||||
Task::RestartSoftware => {
|
||||
frontend.interface.restart(data);
|
||||
return Ok(());
|
||||
@@ -155,48 +98,41 @@ impl<T> Tab<T> for TabSettings<T> {
|
||||
self.state.get_widget_id(&format!("{button_id}_root")),
|
||||
) {
|
||||
self.app_button_ids.remove(idx);
|
||||
let config = frontend.interface.general_config(data);
|
||||
config.autostart_apps.remove(idx);
|
||||
frontend.layout.remove_widget(widget);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
Task::SettingUpdated(setting) => match setting {
|
||||
SettingType::UiAnimationSpeed | SettingType::UiGradientIntensity | SettingType::UiRoundMultiplier => {
|
||||
// todo: currently, wayvr restart is required to apply these changes (WguiTheme is Rc)
|
||||
}
|
||||
_ => { /* do nothing */ }
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Dropdown handling
|
||||
if let TickResult::Action(name) = self.context_menu.tick(&mut frontend.layout, &mut self.state)?
|
||||
&& let (Some(setting), Some(id), Some(value), Some(text), Some(translated)) = {
|
||||
if let TickResult::Action(name) = self.context_menu.tick(&mut frontend.layout, &mut self.state)? {
|
||||
if let (Some(setting), Some(id), Some(value), Some(text), Some(translated)) = {
|
||||
let mut s = name.splitn(5, ';');
|
||||
(s.next(), s.next(), s.next(), s.next(), s.next())
|
||||
} {
|
||||
let mut label = self
|
||||
.state
|
||||
.fetch_widget_as::<WidgetLabel>(&frontend.layout.state, &format!("{id}_value"))?;
|
||||
let mut label = self
|
||||
.state
|
||||
.fetch_widget_as::<WidgetLabel>(&frontend.layout.state, &format!("{id}_value"))?;
|
||||
|
||||
let mut alterables = EventAlterables::default();
|
||||
let mut common = CallbackDataCommon {
|
||||
alterables: &mut alterables,
|
||||
state: &frontend.layout.state,
|
||||
};
|
||||
let mut alterables = EventAlterables::default();
|
||||
let mut common = CallbackDataCommon {
|
||||
alterables: &mut alterables,
|
||||
state: &frontend.layout.state,
|
||||
};
|
||||
|
||||
let translation = Translation {
|
||||
text: text.into(),
|
||||
translated: translated == "1",
|
||||
};
|
||||
let translation = Translation {
|
||||
text: text.into(),
|
||||
translated: translated == "1",
|
||||
};
|
||||
|
||||
label.set_text(&mut common, translation);
|
||||
label.set_text(&mut common, translation);
|
||||
|
||||
let setting = SettingType::from_str(setting).expect("Invalid Enum string");
|
||||
let config = frontend.interface.general_config(data);
|
||||
setting.set_enum(config, value);
|
||||
changed = true;
|
||||
let setting = SettingType::from_str(setting).expect("Invalid Enum string");
|
||||
setting.set_enum(config, value);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Notify overlays of the change
|
||||
@@ -208,53 +144,47 @@ impl<T> Tab<T> for TabSettings<T> {
|
||||
}
|
||||
}
|
||||
|
||||
// Sorted alphabetically
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
#[derive(Clone, Copy, AsRefStr, EnumString)]
|
||||
enum SettingType {
|
||||
AllowSliding,
|
||||
BlockGameInput,
|
||||
BlockGameInputIgnoreWatch,
|
||||
BlockPosesOnKbdInteraction,
|
||||
CaptureMethod,
|
||||
ClickFreezeTimeMs,
|
||||
Clock12h,
|
||||
DoubleCursorFix,
|
||||
FocusFollowsMouseMode,
|
||||
HandsfreePointer,
|
||||
HideGrabHelp,
|
||||
HideUsername,
|
||||
AnimationSpeed,
|
||||
RoundMultiplier,
|
||||
InvertScrollDirectionX,
|
||||
InvertScrollDirectionY,
|
||||
KeyboardMiddleClick,
|
||||
KeyboardSoundEnabled,
|
||||
Language,
|
||||
LeftHandedMouse,
|
||||
ScrollSpeed,
|
||||
LongPressDuration,
|
||||
NotificationsEnabled,
|
||||
NotificationsSoundEnabled,
|
||||
OpaqueBackground,
|
||||
PointerLerpFactor,
|
||||
ScreenRenderDown,
|
||||
ScrollSpeed,
|
||||
SetsOnWatch,
|
||||
SpaceDragMultiplier,
|
||||
SpaceDragUnlocked,
|
||||
SpaceRotateUnlocked,
|
||||
UiAnimationSpeed,
|
||||
UiGradientIntensity,
|
||||
UiRoundMultiplier,
|
||||
KeyboardSoundEnabled,
|
||||
UprightScreenFix,
|
||||
UsePassthrough,
|
||||
UseSkybox,
|
||||
GridOpacity,
|
||||
DoubleCursorFix,
|
||||
SetsOnWatch,
|
||||
HideGrabHelp,
|
||||
XrClickSensitivity,
|
||||
XrClickSensitivityRelease,
|
||||
AllowSliding,
|
||||
ClickFreezeTimeMs,
|
||||
FocusFollowsMouseMode,
|
||||
LeftHandedMouse,
|
||||
BlockGameInput,
|
||||
BlockGameInputIgnoreWatch,
|
||||
SpaceDragMultiplier,
|
||||
UseSkybox,
|
||||
UsePassthrough,
|
||||
ScreenRenderDown,
|
||||
PointerLerpFactor,
|
||||
SpaceDragUnlocked,
|
||||
SpaceRotateUnlocked,
|
||||
Clock12h,
|
||||
HideUsername,
|
||||
OpaqueBackground,
|
||||
XwaylandByDefault,
|
||||
CaptureMethod,
|
||||
KeyboardMiddleClick,
|
||||
}
|
||||
|
||||
impl SettingType {
|
||||
pub fn mut_bool(self, config: &mut GeneralConfig) -> &mut bool {
|
||||
pub fn mut_bool<'a>(self, config: &'a mut GeneralConfig) -> &'a mut bool {
|
||||
match self {
|
||||
Self::InvertScrollDirectionX => &mut config.invert_scroll_direction_x,
|
||||
Self::InvertScrollDirectionY => &mut config.invert_scroll_direction_y,
|
||||
@@ -270,7 +200,6 @@ impl SettingType {
|
||||
Self::LeftHandedMouse => &mut config.left_handed_mouse,
|
||||
Self::BlockGameInput => &mut config.block_game_input,
|
||||
Self::BlockGameInputIgnoreWatch => &mut config.block_game_input_ignore_watch,
|
||||
Self::BlockPosesOnKbdInteraction => &mut config.block_poses_on_kbd_interaction,
|
||||
Self::UseSkybox => &mut config.use_skybox,
|
||||
Self::UsePassthrough => &mut config.use_passthrough,
|
||||
Self::ScreenRenderDown => &mut config.screen_render_down,
|
||||
@@ -284,30 +213,28 @@ impl SettingType {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mut_f32(self, config: &mut GeneralConfig) -> &mut f32 {
|
||||
pub fn mut_f32<'a>(self, config: &'a mut GeneralConfig) -> &'a mut f32 {
|
||||
match self {
|
||||
Self::UiAnimationSpeed => &mut config.ui_animation_speed,
|
||||
Self::UiGradientIntensity => &mut config.ui_gradient_intensity,
|
||||
Self::UiRoundMultiplier => &mut config.ui_round_multiplier,
|
||||
Self::AnimationSpeed => &mut config.animation_speed,
|
||||
Self::RoundMultiplier => &mut config.round_multiplier,
|
||||
Self::ScrollSpeed => &mut config.scroll_speed,
|
||||
Self::LongPressDuration => &mut config.long_press_duration,
|
||||
Self::XrClickSensitivity => &mut config.xr_click_sensitivity,
|
||||
Self::XrClickSensitivityRelease => &mut config.xr_click_sensitivity_release,
|
||||
Self::SpaceDragMultiplier => &mut config.space_drag_multiplier,
|
||||
Self::PointerLerpFactor => &mut config.pointer_lerp_factor,
|
||||
Self::GridOpacity => &mut config.grid_opacity,
|
||||
_ => panic!("Requested f32 for non-f32 SettingType"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mut_i32(self, config: &mut GeneralConfig) -> &mut i32 {
|
||||
pub fn mut_i32<'a>(self, config: &'a mut GeneralConfig) -> &'a mut i32 {
|
||||
match self {
|
||||
Self::ClickFreezeTimeMs => &mut config.click_freeze_time_ms,
|
||||
_ => panic!("Requested i32 for non-i32 SettingType"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_enum(self, config: &mut GeneralConfig, value: &str) {
|
||||
pub fn set_enum<'a>(self, config: &'a mut GeneralConfig, value: &str) {
|
||||
match self {
|
||||
Self::CaptureMethod => {
|
||||
config.capture_method = wlx_common::config::CaptureMethod::from_str(value).expect("Invalid enum value!")
|
||||
@@ -316,25 +243,14 @@ impl SettingType {
|
||||
config.keyboard_middle_click_mode =
|
||||
wlx_common::config::AltModifier::from_str(value).expect("Invalid enum value!")
|
||||
}
|
||||
Self::HandsfreePointer => {
|
||||
config.handsfree_pointer = wlx_common::config::HandsfreePointer::from_str(value).expect("Invalid enum value!")
|
||||
}
|
||||
Self::Language => {
|
||||
config.language = Some(wlx_common::locale::Language::from_str(value).expect("Invalid enum value!"))
|
||||
}
|
||||
_ => panic!("Requested enum for non-enum SettingType"),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_enum_title(self, config: &mut GeneralConfig) -> Translation {
|
||||
fn get_enum_title<'a>(self, config: &'a mut GeneralConfig) -> Translation {
|
||||
match self {
|
||||
Self::CaptureMethod => Self::get_enum_title_inner(config.capture_method),
|
||||
Self::KeyboardMiddleClick => Self::get_enum_title_inner(config.keyboard_middle_click_mode),
|
||||
Self::HandsfreePointer => Self::get_enum_title_inner(config.handsfree_pointer),
|
||||
Self::Language => match &config.language {
|
||||
Some(lang) => Self::get_enum_title_inner(*lang),
|
||||
None => Translation::from_translation_key("APP_SETTINGS.OPTION.AUTO"),
|
||||
},
|
||||
_ => panic!("Requested enum for non-enum SettingType"),
|
||||
}
|
||||
}
|
||||
@@ -345,8 +261,8 @@ impl SettingType {
|
||||
{
|
||||
value
|
||||
.get_str("Translation")
|
||||
.map(Translation::from_translation_key)
|
||||
.or_else(|| value.get_str("Text").map(Translation::from_raw_text))
|
||||
.map(|x| Translation::from_translation_key(x))
|
||||
.or_else(|| value.get_str("Text").map(|x| Translation::from_raw_text(x)))
|
||||
.unwrap_or_else(|| Translation::from_raw_text(value.as_ref()))
|
||||
}
|
||||
|
||||
@@ -354,89 +270,81 @@ impl SettingType {
|
||||
where
|
||||
E: EnumProperty + AsRef<str>,
|
||||
{
|
||||
value.get_str("Tooltip").map(Translation::from_translation_key)
|
||||
value.get_str("Tooltip").map(|x| Translation::from_translation_key(x))
|
||||
}
|
||||
|
||||
/// Ok is translation, Err is raw text
|
||||
/// `match` sorted alphabetically
|
||||
fn get_translation(self) -> Result<&'static str, &'static str> {
|
||||
match self {
|
||||
Self::AllowSliding => Ok("APP_SETTINGS.ALLOW_SLIDING"),
|
||||
Self::BlockGameInput => Ok("APP_SETTINGS.BLOCK_GAME_INPUT"),
|
||||
Self::BlockGameInputIgnoreWatch => Ok("APP_SETTINGS.BLOCK_GAME_INPUT_IGNORE_WATCH"),
|
||||
Self::BlockPosesOnKbdInteraction => Ok("APP_SETTINGS.BLOCK_POSES_ON_KBD_INTERACTION"),
|
||||
Self::CaptureMethod => Ok("APP_SETTINGS.CAPTURE_METHOD"),
|
||||
Self::ClickFreezeTimeMs => Ok("APP_SETTINGS.CLICK_FREEZE_TIME_MS"),
|
||||
Self::Clock12h => Ok("APP_SETTINGS.CLOCK_12H"),
|
||||
Self::DoubleCursorFix => Ok("APP_SETTINGS.DOUBLE_CURSOR_FIX"),
|
||||
Self::FocusFollowsMouseMode => Ok("APP_SETTINGS.FOCUS_FOLLOWS_MOUSE_MODE"),
|
||||
Self::GridOpacity => Ok("APP_SETTINGS.GRID_OPACITY"),
|
||||
Self::HandsfreePointer => Ok("APP_SETTINGS.HANDSFREE_POINTER"),
|
||||
Self::HideGrabHelp => Ok("APP_SETTINGS.HIDE_GRAB_HELP"),
|
||||
Self::HideUsername => Ok("APP_SETTINGS.HIDE_USERNAME"),
|
||||
Self::AnimationSpeed => Ok("APP_SETTINGS.ANIMATION_SPEED"),
|
||||
Self::RoundMultiplier => Ok("APP_SETTINGS.ROUND_MULTIPLIER"),
|
||||
Self::InvertScrollDirectionX => Ok("APP_SETTINGS.INVERT_SCROLL_DIRECTION_X"),
|
||||
Self::InvertScrollDirectionY => Ok("APP_SETTINGS.INVERT_SCROLL_DIRECTION_Y"),
|
||||
Self::KeyboardMiddleClick => Ok("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK"),
|
||||
Self::KeyboardSoundEnabled => Ok("APP_SETTINGS.KEYBOARD_SOUND_ENABLED"),
|
||||
Self::Language => Ok("APP_SETTINGS.LANGUAGE"),
|
||||
Self::LeftHandedMouse => Ok("APP_SETTINGS.LEFT_HANDED_MOUSE"),
|
||||
Self::ScrollSpeed => Ok("APP_SETTINGS.SCROLL_SPEED"),
|
||||
Self::LongPressDuration => Ok("APP_SETTINGS.LONG_PRESS_DURATION"),
|
||||
Self::NotificationsEnabled => Ok("APP_SETTINGS.NOTIFICATIONS_ENABLED"),
|
||||
Self::NotificationsSoundEnabled => Ok("APP_SETTINGS.NOTIFICATIONS_SOUND_ENABLED"),
|
||||
Self::OpaqueBackground => Ok("APP_SETTINGS.OPAQUE_BACKGROUND"),
|
||||
Self::PointerLerpFactor => Ok("APP_SETTINGS.POINTER_LERP_FACTOR"),
|
||||
Self::ScreenRenderDown => Ok("APP_SETTINGS.SCREEN_RENDER_DOWN"),
|
||||
Self::ScrollSpeed => Ok("APP_SETTINGS.SCROLL_SPEED"),
|
||||
Self::SetsOnWatch => Ok("APP_SETTINGS.SETS_ON_WATCH"),
|
||||
Self::SpaceDragMultiplier => Ok("APP_SETTINGS.SPACE_DRAG_MULTIPLIER"),
|
||||
Self::SpaceDragUnlocked => Ok("APP_SETTINGS.SPACE_DRAG_UNLOCKED"),
|
||||
Self::SpaceRotateUnlocked => Ok("APP_SETTINGS.SPACE_ROTATE_UNLOCKED"),
|
||||
Self::UiAnimationSpeed => Ok("APP_SETTINGS.ANIMATION_SPEED"),
|
||||
Self::UiGradientIntensity => Ok("APP_SETTINGS.UI_GRADIENT_INTENSITY"),
|
||||
Self::UiRoundMultiplier => Ok("APP_SETTINGS.ROUND_MULTIPLIER"),
|
||||
Self::KeyboardSoundEnabled => Ok("APP_SETTINGS.KEYBOARD_SOUND_ENABLED"),
|
||||
Self::UprightScreenFix => Ok("APP_SETTINGS.UPRIGHT_SCREEN_FIX"),
|
||||
Self::UsePassthrough => Ok("APP_SETTINGS.USE_PASSTHROUGH"),
|
||||
Self::UseSkybox => Ok("APP_SETTINGS.USE_SKYBOX"),
|
||||
Self::DoubleCursorFix => Ok("APP_SETTINGS.DOUBLE_CURSOR_FIX"),
|
||||
Self::SetsOnWatch => Ok("APP_SETTINGS.SETS_ON_WATCH"),
|
||||
Self::HideGrabHelp => Ok("APP_SETTINGS.HIDE_GRAB_HELP"),
|
||||
Self::XrClickSensitivity => Ok("APP_SETTINGS.XR_CLICK_SENSITIVITY"),
|
||||
Self::XrClickSensitivityRelease => Ok("APP_SETTINGS.XR_CLICK_SENSITIVITY_RELEASE"),
|
||||
Self::AllowSliding => Ok("APP_SETTINGS.ALLOW_SLIDING"),
|
||||
Self::ClickFreezeTimeMs => Ok("APP_SETTINGS.CLICK_FREEZE_TIME_MS"),
|
||||
Self::FocusFollowsMouseMode => Ok("APP_SETTINGS.FOCUS_FOLLOWS_MOUSE_MODE"),
|
||||
Self::LeftHandedMouse => Ok("APP_SETTINGS.LEFT_HANDED_MOUSE"),
|
||||
Self::BlockGameInput => Ok("APP_SETTINGS.BLOCK_GAME_INPUT"),
|
||||
Self::BlockGameInputIgnoreWatch => Ok("APP_SETTINGS.BLOCK_GAME_INPUT_IGNORE_WATCH"),
|
||||
Self::SpaceDragMultiplier => Ok("APP_SETTINGS.SPACE_DRAG_MULTIPLIER"),
|
||||
Self::UseSkybox => Ok("APP_SETTINGS.USE_SKYBOX"),
|
||||
Self::UsePassthrough => Ok("APP_SETTINGS.USE_PASSTHROUGH"),
|
||||
Self::ScreenRenderDown => Ok("APP_SETTINGS.SCREEN_RENDER_DOWN"),
|
||||
Self::PointerLerpFactor => Ok("APP_SETTINGS.POINTER_LERP_FACTOR"),
|
||||
Self::SpaceDragUnlocked => Ok("APP_SETTINGS.SPACE_DRAG_UNLOCKED"),
|
||||
Self::SpaceRotateUnlocked => Ok("APP_SETTINGS.SPACE_ROTATE_UNLOCKED"),
|
||||
Self::Clock12h => Ok("APP_SETTINGS.CLOCK_12H"),
|
||||
Self::HideUsername => Ok("APP_SETTINGS.HIDE_USERNAME"),
|
||||
Self::OpaqueBackground => Ok("APP_SETTINGS.OPAQUE_BACKGROUND"),
|
||||
Self::XwaylandByDefault => Ok("APP_SETTINGS.XWAYLAND_BY_DEFAULT"),
|
||||
Self::CaptureMethod => Ok("APP_SETTINGS.CAPTURE_METHOD"),
|
||||
Self::KeyboardMiddleClick => Ok("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK"),
|
||||
}
|
||||
}
|
||||
|
||||
/// `match` sorted alphabetically
|
||||
fn get_tooltip(self) -> Option<&'static str> {
|
||||
match self {
|
||||
Self::BlockGameInput => Some("APP_SETTINGS.BLOCK_GAME_INPUT_HELP"),
|
||||
Self::BlockGameInputIgnoreWatch => Some("APP_SETTINGS.BLOCK_GAME_INPUT_IGNORE_WATCH_HELP"),
|
||||
Self::BlockPosesOnKbdInteraction => Some("APP_SETTINGS.BLOCK_POSES_ON_KBD_INTERACTION_HELP"),
|
||||
Self::CaptureMethod => Some("APP_SETTINGS.CAPTURE_METHOD_HELP"),
|
||||
Self::DoubleCursorFix => Some("APP_SETTINGS.DOUBLE_CURSOR_FIX_HELP"),
|
||||
Self::GridOpacity => Some("APP_SETTINGS.GRID_OPACITY_HELP"),
|
||||
Self::HandsfreePointer => Some("APP_SETTINGS.HANDSFREE_POINTER_HELP"),
|
||||
Self::KeyboardMiddleClick => Some("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK_HELP"),
|
||||
Self::LeftHandedMouse => Some("APP_SETTINGS.LEFT_HANDED_MOUSE_HELP"),
|
||||
Self::ScreenRenderDown => Some("APP_SETTINGS.SCREEN_RENDER_DOWN_HELP"),
|
||||
Self::UprightScreenFix => Some("APP_SETTINGS.UPRIGHT_SCREEN_FIX_HELP"),
|
||||
Self::UsePassthrough => Some("APP_SETTINGS.USE_PASSTHROUGH_HELP"),
|
||||
Self::UseSkybox => Some("APP_SETTINGS.USE_SKYBOX_HELP"),
|
||||
Self::DoubleCursorFix => Some("APP_SETTINGS.DOUBLE_CURSOR_FIX_HELP"),
|
||||
Self::XrClickSensitivity => Some("APP_SETTINGS.XR_CLICK_SENSITIVITY_HELP"),
|
||||
Self::XrClickSensitivityRelease => Some("APP_SETTINGS.XR_CLICK_SENSITIVITY_RELEASE_HELP"),
|
||||
Self::FocusFollowsMouseMode => Some("APP_SETTINGS.FOCUS_FOLLOWS_MOUSE_MODE_HELP"),
|
||||
Self::LeftHandedMouse => Some("APP_SETTINGS.LEFT_HANDED_MOUSE_HELP"),
|
||||
Self::BlockGameInput => Some("APP_SETTINGS.BLOCK_GAME_INPUT_HELP"),
|
||||
Self::BlockGameInputIgnoreWatch => Some("APP_SETTINGS.BLOCK_GAME_INPUT_IGNORE_WATCH_HELP"),
|
||||
Self::UseSkybox => Some("APP_SETTINGS.USE_SKYBOX_HELP"),
|
||||
Self::UsePassthrough => Some("APP_SETTINGS.USE_PASSTHROUGH_HELP"),
|
||||
Self::ScreenRenderDown => Some("APP_SETTINGS.SCREEN_RENDER_DOWN_HELP"),
|
||||
Self::CaptureMethod => Some("APP_SETTINGS.CAPTURE_METHOD_HELP"),
|
||||
Self::KeyboardMiddleClick => Some("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK_HELP"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: incorporate this
|
||||
fn requires_restart(self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::UiAnimationSpeed
|
||||
| Self::UiRoundMultiplier
|
||||
| Self::UiGradientIntensity
|
||||
| Self::UprightScreenFix
|
||||
| Self::DoubleCursorFix
|
||||
| Self::ScreenRenderDown
|
||||
| Self::Language
|
||||
| Self::CaptureMethod
|
||||
)
|
||||
match self {
|
||||
Self::AnimationSpeed
|
||||
| Self::RoundMultiplier
|
||||
| Self::UprightScreenFix
|
||||
| Self::DoubleCursorFix
|
||||
| Self::SetsOnWatch
|
||||
| Self::UseSkybox
|
||||
| Self::UsePassthrough
|
||||
| Self::ScreenRenderDown => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_frontend_task(self) -> Option<FrontendTask> {
|
||||
@@ -448,42 +356,6 @@ impl SettingType {
|
||||
}
|
||||
}
|
||||
|
||||
// creates a simple div with horizontal, centered flow
|
||||
fn horiz_cell(layout: &mut Layout, parent: WidgetID) -> anyhow::Result<WidgetID> {
|
||||
let (pair, _) = layout.add_child(
|
||||
parent,
|
||||
WidgetDiv::create(),
|
||||
taffy::Style {
|
||||
flex_direction: taffy::FlexDirection::Row,
|
||||
align_items: Some(taffy::AlignItems::Center),
|
||||
gap: length(8.0),
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(pair.id)
|
||||
}
|
||||
|
||||
fn mount_requires_restart(layout: &mut Layout, parent: WidgetID) -> anyhow::Result<()> {
|
||||
let content = Translation::from_translation_key("APP_SETTINGS.REQUIRES_RESTART");
|
||||
let label = WidgetLabel::create(
|
||||
&mut layout.state,
|
||||
WidgetLabelParams {
|
||||
content,
|
||||
style: TextStyle {
|
||||
wrap: false,
|
||||
color: Some(drawing::Color::new(1.0, 0.5, 0.5, 1.0)),
|
||||
weight: Some(FontWeight::Bold),
|
||||
size: Some(10.0),
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
layout.add_child(parent, label, Default::default())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
macro_rules! category {
|
||||
($pe:expr, $root:expr, $translation:expr, $icon:expr) => {{
|
||||
let id = $pe.idx.to_string();
|
||||
@@ -522,15 +394,9 @@ macro_rules! checkbox {
|
||||
let checked = if *$setting.mut_bool($mp.config) { "1" } else { "0" };
|
||||
params.insert(Rc::from("checked"), Rc::from(checked));
|
||||
|
||||
let id_cell = horiz_cell($mp.layout, $root)?;
|
||||
|
||||
$mp
|
||||
.parser_state
|
||||
.instantiate_template($mp.doc_params, "CheckBoxSetting", $mp.layout, id_cell, params)?;
|
||||
|
||||
if $setting.requires_restart() {
|
||||
mount_requires_restart($mp.layout, id_cell)?;
|
||||
}
|
||||
.instantiate_template($mp.doc_params, "CheckBoxSetting", $mp.layout, $root, params)?;
|
||||
|
||||
let checkbox = $mp.parser_state.fetch_component_as::<ComponentCheckbox>(&id)?;
|
||||
checkbox.on_toggle(Box::new({
|
||||
@@ -566,15 +432,9 @@ macro_rules! slider_f32 {
|
||||
params.insert(Rc::from("max"), Rc::from($max.to_string()));
|
||||
params.insert(Rc::from("step"), Rc::from($step.to_string()));
|
||||
|
||||
let id_cell = horiz_cell($mp.layout, $root)?;
|
||||
|
||||
$mp
|
||||
.parser_state
|
||||
.instantiate_template($mp.doc_params, "SliderSetting", $mp.layout, id_cell, params)?;
|
||||
|
||||
if $setting.requires_restart() {
|
||||
mount_requires_restart($mp.layout, id_cell)?;
|
||||
}
|
||||
.instantiate_template($mp.doc_params, "SliderSetting", $mp.layout, $root, params)?;
|
||||
|
||||
let slider = $mp.parser_state.fetch_component_as::<ComponentSlider>(&id)?;
|
||||
slider.on_value_changed(Box::new({
|
||||
@@ -604,8 +464,6 @@ macro_rules! slider_i32 {
|
||||
params.insert(Rc::from("tooltip"), Rc::from(tooltip));
|
||||
}
|
||||
|
||||
let id_cell = horiz_cell($mp.layout, $root)?;
|
||||
|
||||
let value = $setting.mut_i32($mp.config).to_string();
|
||||
params.insert(Rc::from("value"), Rc::from(value));
|
||||
params.insert(Rc::from("min"), Rc::from($min.to_string()));
|
||||
@@ -614,11 +472,7 @@ macro_rules! slider_i32 {
|
||||
|
||||
$mp
|
||||
.parser_state
|
||||
.instantiate_template($mp.doc_params, "SliderSetting", $mp.layout, id_cell, params)?;
|
||||
|
||||
if $setting.requires_restart() {
|
||||
mount_requires_restart($mp.layout, id_cell)?;
|
||||
}
|
||||
.instantiate_template($mp.doc_params, "SliderSetting", $mp.layout, $root, params)?;
|
||||
|
||||
let slider = $mp.parser_state.fetch_component_as::<ComponentSlider>(&id)?;
|
||||
slider.on_value_changed(Box::new({
|
||||
@@ -632,7 +486,7 @@ macro_rules! slider_i32 {
|
||||
}
|
||||
|
||||
macro_rules! dropdown {
|
||||
($mp:expr /* `MacroParams` struct */, $root:expr, $setting:expr, $options:expr) => {
|
||||
($mp:expr, $root:expr, $setting:expr, $options:expr) => {
|
||||
let id = $mp.idx.to_string();
|
||||
$mp.idx += 1;
|
||||
|
||||
@@ -648,15 +502,9 @@ macro_rules! dropdown {
|
||||
params.insert(Rc::from("tooltip"), Rc::from(tooltip));
|
||||
}
|
||||
|
||||
let id_cell = horiz_cell($mp.layout, $root)?;
|
||||
|
||||
$mp
|
||||
.parser_state
|
||||
.instantiate_template($mp.doc_params, "DropdownButton", $mp.layout, id_cell, params)?;
|
||||
|
||||
if $setting.requires_restart() {
|
||||
mount_requires_restart($mp.layout, id_cell)?;
|
||||
}
|
||||
.instantiate_template($mp.doc_params, "DropdownButton", $mp.layout, $root, params)?;
|
||||
|
||||
let setting_str = $setting.as_ref();
|
||||
let title = $setting.get_enum_title($mp.config);
|
||||
@@ -669,9 +517,9 @@ macro_rules! dropdown {
|
||||
}
|
||||
|
||||
let btn = $mp.parser_state.fetch_component_as::<ComponentButton>(&id)?;
|
||||
btn.on_click(Rc::new({
|
||||
btn.on_click(Box::new({
|
||||
let tasks = $mp.tasks.clone();
|
||||
move |_common, e: ButtonClickEvent| {
|
||||
move |_common, e| {
|
||||
tasks.push(Task::OpenContextMenu(
|
||||
e.mouse_pos_absolute.unwrap_or_default(),
|
||||
$options
|
||||
@@ -718,7 +566,7 @@ macro_rules! danger_button {
|
||||
.instantiate_template($mp.doc_params, "DangerButton", $mp.layout, $root, params)?;
|
||||
|
||||
let btn = $mp.parser_state.fetch_component_as::<ComponentButton>(&id)?;
|
||||
btn.on_click(Rc::new({
|
||||
btn.on_click(Box::new({
|
||||
let tasks = $mp.tasks.clone();
|
||||
move |_common, _e| {
|
||||
tasks.push($task);
|
||||
@@ -746,7 +594,7 @@ macro_rules! autostart_app {
|
||||
|
||||
$ids.push(id.clone());
|
||||
|
||||
btn.on_click(Rc::new({
|
||||
btn.on_click(Box::new({
|
||||
let tasks = $mp.tasks.clone();
|
||||
move |_common, _e| {
|
||||
tasks.push(Task::RemoveAutostartApp(id.clone()));
|
||||
@@ -765,175 +613,121 @@ struct MacroParams<'a> {
|
||||
idx: usize,
|
||||
}
|
||||
|
||||
fn doc_params(globals: &'_ WguiGlobals) -> ParseDocumentParams<'_> {
|
||||
ParseDocumentParams {
|
||||
globals: globals.clone(),
|
||||
path: AssetPath::BuiltIn("gui/tab/settings.xml"),
|
||||
extra: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
let mut mp = MacroParams {
|
||||
layout: &mut frontend.layout,
|
||||
parser_state: &mut self.state,
|
||||
doc_params: &doc_params(&globals),
|
||||
config: frontend.interface.general_config(data),
|
||||
tasks: self.tasks.clone(),
|
||||
idx: 9001,
|
||||
};
|
||||
|
||||
match name {
|
||||
TabNameEnum::LookAndFeel => {
|
||||
let c = category!(mp, root, "APP_SETTINGS.LOOK_AND_FEEL", "dashboard/palette.svg")?;
|
||||
dropdown!(mp, c, SettingType::Language, wlx_common::locale::Language::VARIANTS);
|
||||
checkbox!(mp, c, SettingType::OpaqueBackground);
|
||||
checkbox!(mp, c, SettingType::HideUsername);
|
||||
checkbox!(mp, c, SettingType::HideGrabHelp);
|
||||
slider_f32!(mp, c, SettingType::UiAnimationSpeed, 0.5, 5.0, 0.1); // min, max, step
|
||||
slider_f32!(mp, c, SettingType::UiGradientIntensity, 0.0, 1.0, 0.05); // min, max, step
|
||||
slider_f32!(mp, c, SettingType::UiRoundMultiplier, 0.5, 5.0, 0.1);
|
||||
checkbox!(mp, c, SettingType::SetsOnWatch);
|
||||
checkbox!(mp, c, SettingType::UseSkybox);
|
||||
slider_f32!(mp, c, SettingType::GridOpacity, 0.0, 1.0, 0.05); // min, max, step
|
||||
checkbox!(mp, c, SettingType::UsePassthrough);
|
||||
checkbox!(mp, c, SettingType::Clock12h);
|
||||
}
|
||||
TabNameEnum::Features => {
|
||||
let c = category!(mp, root, "APP_SETTINGS.FEATURES", "dashboard/options.svg")?;
|
||||
checkbox!(mp, c, SettingType::NotificationsEnabled);
|
||||
checkbox!(mp, c, SettingType::NotificationsSoundEnabled);
|
||||
checkbox!(mp, c, SettingType::KeyboardSoundEnabled);
|
||||
checkbox!(mp, c, SettingType::SpaceDragUnlocked);
|
||||
checkbox!(mp, c, SettingType::SpaceRotateUnlocked);
|
||||
slider_f32!(mp, c, SettingType::SpaceDragMultiplier, -10.0, 10.0, 0.5);
|
||||
checkbox!(mp, c, SettingType::BlockGameInput);
|
||||
checkbox!(mp, c, SettingType::BlockGameInputIgnoreWatch);
|
||||
checkbox!(mp, c, SettingType::BlockPosesOnKbdInteraction);
|
||||
}
|
||||
TabNameEnum::Controls => {
|
||||
let c = category!(mp, root, "APP_SETTINGS.CONTROLS", "dashboard/controller.svg")?;
|
||||
dropdown!(
|
||||
mp,
|
||||
c,
|
||||
SettingType::KeyboardMiddleClick,
|
||||
wlx_common::config::AltModifier::VARIANTS
|
||||
);
|
||||
dropdown!(
|
||||
mp,
|
||||
c,
|
||||
SettingType::HandsfreePointer,
|
||||
wlx_common::config::HandsfreePointer::VARIANTS
|
||||
);
|
||||
checkbox!(mp, c, SettingType::FocusFollowsMouseMode);
|
||||
checkbox!(mp, c, SettingType::LeftHandedMouse);
|
||||
checkbox!(mp, c, SettingType::AllowSliding);
|
||||
checkbox!(mp, c, SettingType::InvertScrollDirectionX);
|
||||
checkbox!(mp, c, SettingType::InvertScrollDirectionY);
|
||||
slider_f32!(mp, c, SettingType::ScrollSpeed, 0.1, 5.0, 0.1);
|
||||
slider_f32!(mp, c, SettingType::LongPressDuration, 0.1, 2.0, 0.1);
|
||||
slider_f32!(mp, c, SettingType::PointerLerpFactor, 0.1, 1.0, 0.1);
|
||||
slider_f32!(mp, c, SettingType::XrClickSensitivity, 0.1, 1.0, 0.1);
|
||||
slider_f32!(mp, c, SettingType::XrClickSensitivityRelease, 0.1, 1.0, 0.1);
|
||||
slider_i32!(mp, c, SettingType::ClickFreezeTimeMs, 0, 500, 50);
|
||||
}
|
||||
TabNameEnum::Misc => {
|
||||
let c = category!(mp, root, "APP_SETTINGS.MISC", "dashboard/blocks.svg")?;
|
||||
dropdown!(
|
||||
mp,
|
||||
c,
|
||||
SettingType::CaptureMethod,
|
||||
wlx_common::config::CaptureMethod::VARIANTS
|
||||
);
|
||||
checkbox!(mp, c, SettingType::XwaylandByDefault);
|
||||
checkbox!(mp, c, SettingType::UprightScreenFix);
|
||||
checkbox!(mp, c, SettingType::DoubleCursorFix);
|
||||
checkbox!(mp, c, SettingType::ScreenRenderDown);
|
||||
}
|
||||
TabNameEnum::AutostartApps => {
|
||||
self.app_button_ids = vec![];
|
||||
|
||||
if !mp.config.autostart_apps.is_empty() {
|
||||
let c = category!(mp, root, "APP_SETTINGS.AUTOSTART_APPS", "dashboard/apps.svg")?;
|
||||
|
||||
for app in &mp.config.autostart_apps {
|
||||
autostart_app!(mp, c, app.name, self.app_button_ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
TabNameEnum::Troubleshooting => {
|
||||
let c = category!(mp, root, "APP_SETTINGS.TROUBLESHOOTING", "dashboard/cpu.svg")?;
|
||||
danger_button!(
|
||||
mp,
|
||||
c,
|
||||
"APP_SETTINGS.RESET_PLAYSPACE",
|
||||
"dashboard/recenter.svg",
|
||||
Task::ResetPlayspace
|
||||
);
|
||||
danger_button!(
|
||||
mp,
|
||||
c,
|
||||
"APP_SETTINGS.CLEAR_PIPEWIRE_TOKENS",
|
||||
"dashboard/display.svg",
|
||||
Task::ClearPipewireTokens
|
||||
);
|
||||
danger_button!(
|
||||
mp,
|
||||
c,
|
||||
"APP_SETTINGS.CLEAR_SAVED_STATE",
|
||||
"dashboard/binary.svg",
|
||||
Task::ClearSavedState
|
||||
);
|
||||
danger_button!(
|
||||
mp,
|
||||
c,
|
||||
"APP_SETTINGS.DELETE_ALL_CONFIGS",
|
||||
"dashboard/circle.svg",
|
||||
Task::DeleteAllConfigs
|
||||
);
|
||||
danger_button!(
|
||||
mp,
|
||||
c,
|
||||
"APP_SETTINGS.RESTART_SOFTWARE",
|
||||
"dashboard/refresh.svg",
|
||||
Task::RestartSoftware
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn new(frontend: &mut Frontend<T>, parent_id: WidgetID, _data: &mut T) -> anyhow::Result<Self> {
|
||||
pub fn new(frontend: &mut Frontend<T>, parent_id: WidgetID, data: &mut T) -> anyhow::Result<Self> {
|
||||
let doc_params = ParseDocumentParams {
|
||||
globals: frontend.layout.state.globals.clone(),
|
||||
path: AssetPath::BuiltIn("gui/tab/settings.xml"),
|
||||
extra: Default::default(),
|
||||
};
|
||||
let mut parser_state = wgui::parser::parse_from_assets(&doc_params, &mut frontend.layout, parent_id)?;
|
||||
|
||||
let parser_state = wgui::parser::parse_from_assets(&doc_params, &mut frontend.layout, parent_id)?;
|
||||
let tasks = Tasks::default();
|
||||
let tabs = parser_state.fetch_component_as::<ComponentTabs>("tabs")?;
|
||||
tabs.on_select({
|
||||
let tasks = tasks.clone();
|
||||
Rc::new(move |_common, evt| {
|
||||
if let Some(tab) = TabNameEnum::from_string(&evt.name) {
|
||||
tasks.push(Task::SetTab(tab));
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
});
|
||||
let root = parser_state.get_widget_id("settings_root")?;
|
||||
|
||||
tasks.push(Task::SetTab(TabNameEnum::LookAndFeel));
|
||||
let mut mp = MacroParams {
|
||||
layout: &mut frontend.layout,
|
||||
parser_state: &mut parser_state,
|
||||
doc_params: &doc_params,
|
||||
config: frontend.interface.general_config(data),
|
||||
tasks: Tasks::default(),
|
||||
idx: 9001,
|
||||
};
|
||||
|
||||
let c = category!(mp, root, "APP_SETTINGS.LOOK_AND_FEEL", "dashboard/palette.svg")?;
|
||||
checkbox!(mp, c, SettingType::OpaqueBackground);
|
||||
checkbox!(mp, c, SettingType::HideUsername);
|
||||
checkbox!(mp, c, SettingType::HideGrabHelp);
|
||||
slider_f32!(mp, c, SettingType::AnimationSpeed, 0.5, 5.0, 0.1); // min, max, step
|
||||
slider_f32!(mp, c, SettingType::RoundMultiplier, 0.5, 5.0, 0.1);
|
||||
checkbox!(mp, c, SettingType::SetsOnWatch);
|
||||
checkbox!(mp, c, SettingType::UseSkybox);
|
||||
checkbox!(mp, c, SettingType::UsePassthrough);
|
||||
checkbox!(mp, c, SettingType::Clock12h);
|
||||
|
||||
let c = category!(mp, root, "APP_SETTINGS.FEATURES", "dashboard/options.svg")?;
|
||||
checkbox!(mp, c, SettingType::NotificationsEnabled);
|
||||
checkbox!(mp, c, SettingType::NotificationsSoundEnabled);
|
||||
checkbox!(mp, c, SettingType::KeyboardSoundEnabled);
|
||||
checkbox!(mp, c, SettingType::SpaceDragUnlocked);
|
||||
checkbox!(mp, c, SettingType::SpaceRotateUnlocked);
|
||||
slider_f32!(mp, c, SettingType::SpaceDragMultiplier, -10.0, 10.0, 0.5);
|
||||
checkbox!(mp, c, SettingType::BlockGameInput);
|
||||
checkbox!(mp, c, SettingType::BlockGameInputIgnoreWatch);
|
||||
|
||||
let c = category!(mp, root, "APP_SETTINGS.CONTROLS", "dashboard/controller.svg")?;
|
||||
dropdown!(
|
||||
mp,
|
||||
c,
|
||||
SettingType::KeyboardMiddleClick,
|
||||
wlx_common::config::AltModifier::VARIANTS
|
||||
);
|
||||
checkbox!(mp, c, SettingType::FocusFollowsMouseMode);
|
||||
checkbox!(mp, c, SettingType::LeftHandedMouse);
|
||||
checkbox!(mp, c, SettingType::AllowSliding);
|
||||
checkbox!(mp, c, SettingType::InvertScrollDirectionX);
|
||||
checkbox!(mp, c, SettingType::InvertScrollDirectionY);
|
||||
slider_f32!(mp, c, SettingType::ScrollSpeed, 0.1, 5.0, 0.1);
|
||||
slider_f32!(mp, c, SettingType::LongPressDuration, 0.1, 2.0, 0.1);
|
||||
slider_f32!(mp, c, SettingType::PointerLerpFactor, 0.1, 1.0, 0.1);
|
||||
slider_f32!(mp, c, SettingType::XrClickSensitivity, 0.1, 1.0, 0.1);
|
||||
slider_f32!(mp, c, SettingType::XrClickSensitivityRelease, 0.1, 1.0, 0.1);
|
||||
slider_i32!(mp, c, SettingType::ClickFreezeTimeMs, 0, 500, 50);
|
||||
|
||||
let c = category!(mp, root, "APP_SETTINGS.MISC", "dashboard/blocks.svg")?;
|
||||
dropdown!(
|
||||
mp,
|
||||
c,
|
||||
SettingType::CaptureMethod,
|
||||
wlx_common::config::CaptureMethod::VARIANTS
|
||||
);
|
||||
checkbox!(mp, c, SettingType::XwaylandByDefault);
|
||||
checkbox!(mp, c, SettingType::UprightScreenFix);
|
||||
checkbox!(mp, c, SettingType::DoubleCursorFix);
|
||||
checkbox!(mp, c, SettingType::ScreenRenderDown);
|
||||
|
||||
let mut app_button_ids = vec![];
|
||||
|
||||
if !mp.config.autostart_apps.is_empty() {
|
||||
let c = category!(mp, root, "APP_SETTINGS.AUTOSTART_APPS", "dashboard/apps.svg")?;
|
||||
|
||||
for app in &mp.config.autostart_apps {
|
||||
autostart_app!(mp, c, app.name, app_button_ids);
|
||||
}
|
||||
}
|
||||
|
||||
let c = category!(mp, root, "APP_SETTINGS.TROUBLESHOOTING", "dashboard/cpu.svg")?;
|
||||
danger_button!(
|
||||
mp,
|
||||
c,
|
||||
"APP_SETTINGS.CLEAR_PIPEWIRE_TOKENS",
|
||||
"dashboard/display.svg",
|
||||
Task::ClearPipewireTokens
|
||||
);
|
||||
danger_button!(
|
||||
mp,
|
||||
c,
|
||||
"APP_SETTINGS.CLEAR_SAVED_STATE",
|
||||
"dashboard/binary.svg",
|
||||
Task::ClearSavedState
|
||||
);
|
||||
danger_button!(
|
||||
mp,
|
||||
c,
|
||||
"APP_SETTINGS.DELETE_ALL_CONFIGS",
|
||||
"dashboard/circle.svg",
|
||||
Task::DeleteAllConfigs
|
||||
);
|
||||
danger_button!(
|
||||
mp,
|
||||
c,
|
||||
"APP_SETTINGS.RESTART_SOFTWARE",
|
||||
"dashboard/refresh.svg",
|
||||
Task::RestartSoftware
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
app_button_ids: Vec::new(),
|
||||
tasks,
|
||||
app_button_ids,
|
||||
tasks: mp.tasks,
|
||||
state: parser_state,
|
||||
marker: PhantomData,
|
||||
context_menu: ContextMenu::default(),
|
||||
|
||||
@@ -5,4 +5,3 @@ pub mod popup_manager;
|
||||
pub mod steam_utils;
|
||||
pub mod toast_manager;
|
||||
pub mod various;
|
||||
pub mod wgui_simple;
|
||||
@@ -163,7 +163,7 @@ impl PopupManager {
|
||||
|
||||
but_back.on_click({
|
||||
let popup_handle = Rc::downgrade(&popup_handle.state);
|
||||
Rc::new(move |_common, _evt| {
|
||||
Box::new(move |_common, _evt| {
|
||||
if let Some(popup_handle) = popup_handle.upgrade() {
|
||||
popup_handle.borrow_mut().mounted_popup = None; // will call Drop
|
||||
}
|
||||
|
||||
@@ -32,10 +32,7 @@ pub struct AppManifest {
|
||||
pub last_played: Option<u64>, // unix timestamp
|
||||
}
|
||||
|
||||
// TODO @oo8dev: game sort methods
|
||||
#[allow(dead_code)]
|
||||
pub enum GameSortMethod {
|
||||
None,
|
||||
NameAsc,
|
||||
NameDesc,
|
||||
PlayDateDesc,
|
||||
@@ -122,12 +119,6 @@ struct AppEntry {
|
||||
pub app_id: AppID,
|
||||
}
|
||||
|
||||
pub fn launch(app_id: &AppID) -> anyhow::Result<()> {
|
||||
log::info!("Launching Steam game with AppID {}", app_id);
|
||||
call_steam(&format!("steam://rungameid/{}", app_id))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn stop(app_id: AppID, force_kill: bool) -> anyhow::Result<()> {
|
||||
log::info!("Stopping Steam game with AppID {}", app_id);
|
||||
|
||||
@@ -146,6 +137,12 @@ pub fn stop(app_id: AppID, force_kill: bool) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn launch(app_id: &AppID) -> anyhow::Result<()> {
|
||||
log::info!("Launching Steam game with AppID {}", app_id);
|
||||
call_steam(&format!("steam://rungameid/{}", app_id))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct RunningGame {
|
||||
pub app_id: AppID,
|
||||
@@ -284,7 +281,6 @@ impl SteamUtils {
|
||||
.collect();
|
||||
|
||||
match sort_method {
|
||||
GameSortMethod::None => {}
|
||||
GameSortMethod::NameAsc => {
|
||||
games.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use wgui::{
|
||||
animation::{Animation, AnimationEasing},
|
||||
components::tooltip::{TOOLTIP_BORDER_COLOR, TOOLTIP_COLOR},
|
||||
drawing::Color,
|
||||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
layout::{Layout, LayoutTask, LayoutTasks, WidgetID},
|
||||
renderer_vk::{
|
||||
@@ -60,7 +61,15 @@ impl ToastManager {
|
||||
}
|
||||
}
|
||||
|
||||
fn mount_toast(&self, layout: &mut Layout, state: &mut State, content: Translation) -> anyhow::Result<()> {
|
||||
fn mount_toast(
|
||||
&self,
|
||||
globals: &WguiGlobals,
|
||||
layout: &mut Layout,
|
||||
state: &mut State,
|
||||
content: Translation,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut globals = globals.get();
|
||||
|
||||
let (root, _) = layout.add_topmost_child(
|
||||
WidgetDiv::create(),
|
||||
taffy::Style {
|
||||
@@ -101,24 +110,27 @@ impl ToastManager {
|
||||
},
|
||||
)?;
|
||||
|
||||
let label = WidgetLabel::create(
|
||||
&mut layout.state,
|
||||
WidgetLabelParams {
|
||||
content,
|
||||
style: TextStyle {
|
||||
weight: Some(FontWeight::Bold),
|
||||
align: Some(HorizontalAlign::Center),
|
||||
wrap: true,
|
||||
..Default::default()
|
||||
let (label, _) = layout.add_child(
|
||||
rect.id,
|
||||
WidgetLabel::create(
|
||||
&mut globals,
|
||||
WidgetLabelParams {
|
||||
content,
|
||||
style: TextStyle {
|
||||
weight: Some(FontWeight::Bold),
|
||||
align: Some(HorizontalAlign::Center),
|
||||
wrap: true,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
let (label, _) = layout.add_child(rect.id, label, taffy::Style { ..Default::default() })?;
|
||||
),
|
||||
taffy::Style { ..Default::default() },
|
||||
)?;
|
||||
|
||||
// show-up animation
|
||||
layout.animations.add(Animation::new(
|
||||
rect.id,
|
||||
(TOAST_DURATION_TICKS as f32 * layout.state.theme.animation_mult) as u32,
|
||||
(TOAST_DURATION_TICKS as f32 * globals.defaults.animation_mult) as u32,
|
||||
AnimationEasing::Linear,
|
||||
Box::new(move |common, data| {
|
||||
let pos_showup = AnimationEasing::OutQuint.interpolate((data.pos * 4.0).min(1.0));
|
||||
@@ -149,7 +161,7 @@ impl ToastManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn tick(&mut self, layout: &mut Layout) -> anyhow::Result<()> {
|
||||
pub fn tick(&mut self, globals: &WguiGlobals, layout: &mut Layout) -> anyhow::Result<()> {
|
||||
let mut state = self.state.borrow_mut();
|
||||
if state.timeout > 0 {
|
||||
state.timeout -= 1;
|
||||
@@ -164,7 +176,7 @@ impl ToastManager {
|
||||
state.timeout = TOAST_DURATION_TICKS;
|
||||
// mount next
|
||||
if let Some(content) = state.queue.pop_front() {
|
||||
self.mount_toast(layout, &mut state, content)?;
|
||||
self.mount_toast(globals, layout, &mut state, content)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,77 @@
|
||||
use std::rc::Rc;
|
||||
use std::{path::PathBuf, rc::Rc, str::FromStr};
|
||||
use wgui::{
|
||||
assets::{AssetPath, AssetPathOwned},
|
||||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
layout::{Layout, WidgetID},
|
||||
renderer_vk::text::custom_glyph::CustomGlyphData,
|
||||
taffy::{self, prelude::length},
|
||||
widget::{
|
||||
label::{WidgetLabel, WidgetLabelParams},
|
||||
sprite::{WidgetSprite, WidgetSpriteParams},
|
||||
},
|
||||
};
|
||||
use wlx_common::desktop_finder;
|
||||
|
||||
pub type AsyncExecutor = Rc<smol::LocalExecutor<'static>>;
|
||||
|
||||
// the compiler wants to scream
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
pub fn get_desktop_file_icon_path(desktop_file: &desktop_finder::DesktopEntry) -> AssetPathOwned {
|
||||
/*
|
||||
FIXME: why is the compiler complaining about trailing irrefutable patterns there?!?!
|
||||
looking at the PathBuf::from_str implementation, it always returns Ok() and it's inline, maybe that's why.
|
||||
*/
|
||||
if let Some(icon) = &desktop_file.icon_path
|
||||
&& let Ok(path) = PathBuf::from_str(icon)
|
||||
{
|
||||
return AssetPathOwned::File(path);
|
||||
}
|
||||
|
||||
AssetPathOwned::BuiltIn(PathBuf::from_str("dashboard/terminal.svg").unwrap())
|
||||
}
|
||||
|
||||
pub fn mount_simple_label(
|
||||
globals: &WguiGlobals,
|
||||
layout: &mut Layout,
|
||||
parent_id: WidgetID,
|
||||
translation: Translation,
|
||||
) -> anyhow::Result<()> {
|
||||
layout.add_child(
|
||||
parent_id,
|
||||
WidgetLabel::create(
|
||||
&mut globals.get(),
|
||||
WidgetLabelParams {
|
||||
content: translation,
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
taffy::Style::default(),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn mount_simple_sprite_square(
|
||||
globals: &WguiGlobals,
|
||||
layout: &mut Layout,
|
||||
parent_id: WidgetID,
|
||||
size_px: f32,
|
||||
path: AssetPath,
|
||||
) -> anyhow::Result<()> {
|
||||
layout.add_child(
|
||||
parent_id,
|
||||
WidgetSprite::create(WidgetSpriteParams {
|
||||
glyph_data: Some(CustomGlyphData::from_assets(globals, path)?),
|
||||
..Default::default()
|
||||
}),
|
||||
taffy::Style {
|
||||
size: taffy::Size {
|
||||
width: length(size_px),
|
||||
height: length(size_px),
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
use wgui::{
|
||||
i18n::Translation,
|
||||
layout::{Layout, WidgetID},
|
||||
renderer_vk::text::TextStyle,
|
||||
widget::label::{WidgetLabel, WidgetLabelParams},
|
||||
};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn create_label(layout: &mut Layout, parent: WidgetID, content: Translation) -> anyhow::Result<()> {
|
||||
let label = WidgetLabel::create(
|
||||
&mut layout.state,
|
||||
WidgetLabelParams {
|
||||
content,
|
||||
style: TextStyle {
|
||||
wrap: true,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
layout.add_child(parent, label, Default::default())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -50,6 +50,7 @@ enum CompositorMode {
|
||||
enum Task {
|
||||
SetCompositor(CompositorMode),
|
||||
SetRes(ResMode),
|
||||
SetPos(PosMode),
|
||||
SetOrientation(OrientationMode),
|
||||
SetAutoStart(bool),
|
||||
Launch,
|
||||
@@ -155,7 +156,7 @@ impl View {
|
||||
} else {
|
||||
CompositorMode::Native
|
||||
};
|
||||
radio_compositor.set_value_simple(compositor_mode.as_ref())?;
|
||||
radio_compositor.set_value(compositor_mode.as_ref())?;
|
||||
tasks.push(Task::SetCompositor(compositor_mode));
|
||||
|
||||
let res_mode = ResMode::Res1080;
|
||||
@@ -179,7 +180,7 @@ impl View {
|
||||
let tasks = tasks.clone();
|
||||
Box::new(move |_, ev| {
|
||||
if let Some(mode) = ev.value.and_then(|v| {
|
||||
CompositorMode::from_str(&v)
|
||||
CompositorMode::from_str(&*v)
|
||||
.inspect_err(|_| {
|
||||
log::error!(
|
||||
"Invalid value for compositor: '{v}'. Valid values are: {:?}",
|
||||
@@ -198,7 +199,7 @@ impl View {
|
||||
let tasks = tasks.clone();
|
||||
Box::new(move |_, ev| {
|
||||
if let Some(mode) = ev.value.and_then(|v| {
|
||||
ResMode::from_str(&v)
|
||||
ResMode::from_str(&*v)
|
||||
.inspect_err(|_| {
|
||||
log::error!(
|
||||
"Invalid value for resolution: '{v}'. Valid values are: {:?}",
|
||||
@@ -236,7 +237,7 @@ impl View {
|
||||
let tasks = tasks.clone();
|
||||
Box::new(move |_, ev| {
|
||||
if let Some(mode) = ev.value.and_then(|v| {
|
||||
OrientationMode::from_str(&v)
|
||||
OrientationMode::from_str(&*v)
|
||||
.inspect_err(|_| {
|
||||
log::error!(
|
||||
"Invalid value for orientation: '{v}'. Valid values are: {:?}",
|
||||
@@ -294,6 +295,7 @@ impl View {
|
||||
match task {
|
||||
Task::SetCompositor(mode) => self.compositor_mode = mode,
|
||||
Task::SetRes(mode) => self.res_mode = mode,
|
||||
Task::SetPos(mode) => self.pos_mode = mode,
|
||||
Task::SetOrientation(mode) => self.orientation_mode = mode,
|
||||
Task::SetAutoStart(auto_start) => self.auto_start = auto_start,
|
||||
Task::Launch => self.action_launch(interface, data),
|
||||
|
||||
@@ -110,7 +110,8 @@ struct MultiSelectorParams<'a> {
|
||||
}
|
||||
|
||||
fn mount_multi_selector(params: MultiSelectorParams) -> anyhow::Result<()> {
|
||||
let accent_color = params.ess.layout.state.theme.accent_color;
|
||||
let globals = params.ess.layout.state.globals.clone();
|
||||
let accent_color = globals.get().defaults.accent_color;
|
||||
|
||||
for cell in params.cells {
|
||||
let highlighted = cell.key == params.def_cell;
|
||||
@@ -130,7 +131,7 @@ fn mount_multi_selector(params: MultiSelectorParams) -> anyhow::Result<()> {
|
||||
button.on_click({
|
||||
let on_click = params.on_click.clone();
|
||||
let key = cell.key.clone();
|
||||
Rc::new(move |_, _| {
|
||||
Box::new(move |_, _| {
|
||||
(*on_click)(key.as_str());
|
||||
Ok(())
|
||||
})
|
||||
@@ -609,7 +610,7 @@ impl View {
|
||||
fn handle_func_button_click(&self, task: ViewTask) -> ButtonClickCallback {
|
||||
let tasks = self.tasks.clone();
|
||||
let on_update = self.on_update.clone();
|
||||
Rc::new(move |_common, _evt| {
|
||||
Box::new(move |_common, _evt| {
|
||||
tasks.push(task.clone());
|
||||
(*on_update)();
|
||||
Ok(())
|
||||
@@ -653,6 +654,8 @@ impl View {
|
||||
}
|
||||
|
||||
fn update_button_highlights(&self, layout: &mut Layout) -> anyhow::Result<()> {
|
||||
let defaults = self.globals.defaults();
|
||||
|
||||
let mut c = layout.start_common();
|
||||
let mut common = c.common();
|
||||
|
||||
@@ -664,12 +667,14 @@ impl View {
|
||||
};
|
||||
|
||||
let mut perform = |btn_num: u8, btn: &Rc<ComponentButton>| {
|
||||
let color = if num == btn_num {
|
||||
common.state.theme.accent_color
|
||||
} else {
|
||||
common.state.theme.button_color
|
||||
};
|
||||
btn.set_color(&mut common, color);
|
||||
btn.set_color(
|
||||
&mut common,
|
||||
if num == btn_num {
|
||||
defaults.accent_color
|
||||
} else {
|
||||
defaults.button_color
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
perform(0, &self.btn_sinks);
|
||||
@@ -757,7 +762,7 @@ impl View {
|
||||
let tasks = self.tasks.clone();
|
||||
let card = params.card.clone();
|
||||
let on_update = self.on_update.clone();
|
||||
Rc::new(move |_common, _evt| {
|
||||
Box::new(move |_common, _evt| {
|
||||
tasks.push(ViewTask::SetMode(CurrentMode::CardProfileSelector(card.clone())));
|
||||
(*on_update)();
|
||||
Ok(())
|
||||
@@ -831,7 +836,7 @@ impl View {
|
||||
|
||||
btn_mute.on_click({
|
||||
let control = params.control.clone();
|
||||
Rc::new(move |_common, _event| {
|
||||
Box::new(move |_common, _event| {
|
||||
control.on_mute_toggle()?;
|
||||
Ok(())
|
||||
})
|
||||
@@ -952,7 +957,7 @@ impl View {
|
||||
btn_back.on_click({
|
||||
let tasks = self.tasks.clone();
|
||||
let on_update = self.on_update.clone();
|
||||
Rc::new(move |_, _| {
|
||||
Box::new(move |_, _| {
|
||||
tasks.push(ViewTask::SetMode(CurrentMode::Cards));
|
||||
(*on_update)();
|
||||
Ok(())
|
||||
|
||||
@@ -10,7 +10,7 @@ use wgui::{
|
||||
drawing::{self, GradientMode},
|
||||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
layout::{Layout, WidgetID},
|
||||
layout::{Layout, WidgetID, WidgetPair},
|
||||
renderer_vk::text::{FontWeight, HorizontalAlign, TextShadow, TextStyle, custom_glyph::CustomGlyphData},
|
||||
taffy::{
|
||||
self, AlignItems, AlignSelf, JustifyContent, JustifySelf,
|
||||
@@ -47,6 +47,7 @@ pub struct Params<'a, 'b> {
|
||||
|
||||
pub struct View {
|
||||
pub button: Rc<ComponentButton>,
|
||||
pair: WidgetPair,
|
||||
id_image_parent: WidgetID,
|
||||
app_name: String,
|
||||
app_id: AppID,
|
||||
@@ -98,9 +99,15 @@ impl View {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mount_placeholder_text(&self, layout: &mut Layout, parent: WidgetID, text: &str) -> anyhow::Result<()> {
|
||||
fn mount_placeholder_text(
|
||||
&self,
|
||||
globals: &WguiGlobals,
|
||||
layout: &mut Layout,
|
||||
parent: WidgetID,
|
||||
text: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
let label = WidgetLabel::create(
|
||||
&mut layout.state,
|
||||
&mut globals.get(),
|
||||
WidgetLabelParams {
|
||||
content: Translation::from_raw_text(text),
|
||||
style: TextStyle {
|
||||
@@ -147,7 +154,7 @@ impl View {
|
||||
// mount placeholder
|
||||
let img = view_common.get_placeholder_image()?.clone();
|
||||
self.mount_image(layout, &img)?;
|
||||
self.mount_placeholder_text(layout, self.id_image_parent, &self.app_name)?;
|
||||
self.mount_placeholder_text(&view_common.globals, layout, self.id_image_parent, &self.app_name)?;
|
||||
} else {
|
||||
// mount image
|
||||
let path = format!("app:{:?}", self.app_id);
|
||||
@@ -282,6 +289,7 @@ impl View {
|
||||
.detach();
|
||||
|
||||
Ok(View {
|
||||
pair: widget_button,
|
||||
button,
|
||||
id_image_parent: image_parent.id,
|
||||
app_name: params.manifest.name.clone(),
|
||||
|
||||
@@ -2,7 +2,6 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||
|
||||
use wgui::{
|
||||
assets::AssetPath,
|
||||
components::button::ComponentButton,
|
||||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
layout::{Layout, WidgetID},
|
||||
@@ -19,7 +18,7 @@ use crate::{
|
||||
util::{
|
||||
cached_fetcher::CoverArt,
|
||||
popup_manager::{MountPopupParams, PopupHandle},
|
||||
steam_utils::{self, AppID, SteamUtils},
|
||||
steam_utils::{self, AppID, AppManifest, SteamUtils},
|
||||
various::AsyncExecutor,
|
||||
},
|
||||
views::{self, game_cover, game_launcher},
|
||||
@@ -30,10 +29,7 @@ enum Task {
|
||||
AppManifestClicked(steam_utils::AppManifest),
|
||||
SetCoverArt(AppID, Rc<CoverArt>),
|
||||
CloseLauncher,
|
||||
LoadManifests,
|
||||
FillPage(u32),
|
||||
PrevPage,
|
||||
NextPage,
|
||||
Refresh,
|
||||
}
|
||||
|
||||
pub struct Params<'a> {
|
||||
@@ -44,10 +40,9 @@ pub struct Params<'a> {
|
||||
pub parent_id: WidgetID,
|
||||
}
|
||||
|
||||
const MAX_GAMES_PER_PAGE: u32 = 30;
|
||||
|
||||
pub struct GameCoverCell {
|
||||
pub struct Cell {
|
||||
view_cover: game_cover::View,
|
||||
manifest: AppManifest,
|
||||
}
|
||||
|
||||
struct State {
|
||||
@@ -61,14 +56,11 @@ pub struct View {
|
||||
frontend_tasks: FrontendTasks,
|
||||
globals: WguiGlobals,
|
||||
id_list_parent: WidgetID,
|
||||
steam_utils: steam_utils::SteamUtils,
|
||||
cells: HashMap<AppID, Cell>,
|
||||
game_cover_view_common: game_cover::ViewCommon,
|
||||
executor: AsyncExecutor,
|
||||
state: Rc<RefCell<State>>,
|
||||
mounted_game_covers: HashMap<AppID, GameCoverCell>,
|
||||
all_manifests: Vec<steam_utils::AppManifest>,
|
||||
cur_page: u32,
|
||||
page_count: u32,
|
||||
id_label_page: WidgetID,
|
||||
}
|
||||
|
||||
impl View {
|
||||
@@ -81,21 +73,12 @@ impl View {
|
||||
|
||||
let parser_state = wgui::parser::parse_from_assets(doc_params, params.layout, params.parent_id)?;
|
||||
let list_parent = parser_state.fetch_widget(¶ms.layout.state, "list_parent")?;
|
||||
let id_label_page = parser_state.get_widget_id("label_page")?;
|
||||
|
||||
let tasks = Tasks::new();
|
||||
|
||||
tasks.handle_button(
|
||||
&parser_state.fetch_component_as::<ComponentButton>("btn_prev")?,
|
||||
Task::PrevPage,
|
||||
);
|
||||
let steam_utils = SteamUtils::new()?;
|
||||
|
||||
tasks.handle_button(
|
||||
&parser_state.fetch_component_as::<ComponentButton>("btn_next")?,
|
||||
Task::NextPage,
|
||||
);
|
||||
|
||||
tasks.push(Task::LoadManifests);
|
||||
tasks.push(Task::Refresh);
|
||||
|
||||
Ok(Self {
|
||||
parser_state,
|
||||
@@ -103,23 +86,15 @@ impl View {
|
||||
frontend_tasks: params.frontend_tasks,
|
||||
globals: params.globals.clone(),
|
||||
id_list_parent: list_parent.id,
|
||||
mounted_game_covers: HashMap::new(),
|
||||
steam_utils,
|
||||
cells: HashMap::new(),
|
||||
game_cover_view_common: game_cover::ViewCommon::new(params.globals.clone()),
|
||||
state: Rc::new(RefCell::new(State { view_launcher: None })),
|
||||
executor: params.executor,
|
||||
all_manifests: Vec::new(),
|
||||
cur_page: 0,
|
||||
page_count: 0,
|
||||
id_label_page,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
layout: &mut Layout,
|
||||
steam_utils: &mut SteamUtils,
|
||||
executor: &AsyncExecutor,
|
||||
) -> anyhow::Result<()> {
|
||||
pub fn update(&mut self, layout: &mut Layout, executor: &AsyncExecutor) -> anyhow::Result<()> {
|
||||
loop {
|
||||
let tasks = self.tasks.drain();
|
||||
if tasks.is_empty() {
|
||||
@@ -127,13 +102,10 @@ impl View {
|
||||
}
|
||||
for task in tasks {
|
||||
match task {
|
||||
Task::LoadManifests => self.load_manifests(steam_utils),
|
||||
Task::FillPage(page_idx) => self.fill_page(layout, executor, page_idx)?,
|
||||
Task::Refresh => self.refresh(layout, executor)?,
|
||||
Task::AppManifestClicked(manifest) => self.action_app_manifest_clicked(manifest)?,
|
||||
Task::SetCoverArt(app_id, cover_art) => self.set_cover_art(layout, app_id, cover_art),
|
||||
Task::CloseLauncher => self.state.borrow_mut().view_launcher = None,
|
||||
Task::PrevPage => self.page_prev(),
|
||||
Task::NextPage => self.page_next(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -147,14 +119,18 @@ impl View {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Games {
|
||||
manifests: Vec<steam_utils::AppManifest>,
|
||||
}
|
||||
|
||||
fn fill_game_list(
|
||||
ess: &mut ConstructEssentials,
|
||||
executor: &AsyncExecutor,
|
||||
mounted_game_covers: &mut HashMap<AppID, GameCoverCell>,
|
||||
manifests: &[steam_utils::AppManifest],
|
||||
cells: &mut HashMap<AppID, Cell>,
|
||||
games: &Games,
|
||||
tasks: &Tasks<Task>,
|
||||
) -> anyhow::Result<()> {
|
||||
for manifest in manifests {
|
||||
for manifest in &games.manifests {
|
||||
let on_loaded = {
|
||||
let app_id = manifest.app_id.clone();
|
||||
let tasks = tasks.clone();
|
||||
@@ -174,107 +150,81 @@ fn fill_game_list(
|
||||
view_cover.button.on_click({
|
||||
let tasks = tasks.clone();
|
||||
let manifest = manifest.clone();
|
||||
Rc::new(move |_, _| {
|
||||
Box::new(move |_, _| {
|
||||
tasks.push(Task::AppManifestClicked(manifest.clone()));
|
||||
Ok(())
|
||||
})
|
||||
});
|
||||
|
||||
mounted_game_covers.insert(manifest.app_id.clone(), GameCoverCell { view_cover });
|
||||
cells.insert(
|
||||
manifest.app_id.clone(),
|
||||
Cell {
|
||||
view_cover,
|
||||
manifest: manifest.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl View {
|
||||
fn load_manifests(&mut self, steam_utils: &mut SteamUtils) {
|
||||
match steam_utils.list_installed_games(steam_utils::GameSortMethod::PlayDateDesc) {
|
||||
Ok(manifests) => {
|
||||
self.page_count = (manifests.len() as u32 + MAX_GAMES_PER_PAGE) / MAX_GAMES_PER_PAGE;
|
||||
self.all_manifests = manifests;
|
||||
self.tasks.push(Task::FillPage(0));
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to list installed games: {e:?}");
|
||||
}
|
||||
}
|
||||
fn game_list(&self) -> anyhow::Result<Games> {
|
||||
let manifests = self
|
||||
.steam_utils
|
||||
.list_installed_games(steam_utils::GameSortMethod::PlayDateDesc)?;
|
||||
|
||||
Ok(Games { manifests })
|
||||
}
|
||||
|
||||
fn page_prev(&mut self) {
|
||||
if self.cur_page == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
self.cur_page -= 1;
|
||||
self.tasks.push(Task::FillPage(self.cur_page));
|
||||
}
|
||||
|
||||
fn page_next(&mut self) {
|
||||
if self.cur_page >= self.page_count - 1 {
|
||||
return;
|
||||
}
|
||||
self.cur_page += 1;
|
||||
self.tasks.push(Task::FillPage(self.cur_page));
|
||||
}
|
||||
|
||||
fn fill_page(&mut self, layout: &mut Layout, executor: &AsyncExecutor, page_idx: u32) -> anyhow::Result<()> {
|
||||
fn refresh(&mut self, layout: &mut Layout, executor: &AsyncExecutor) -> anyhow::Result<()> {
|
||||
layout.remove_children(self.id_list_parent);
|
||||
self.mounted_game_covers.clear();
|
||||
|
||||
let idx_from = (page_idx * MAX_GAMES_PER_PAGE).min(self.all_manifests.len() as u32);
|
||||
let idx_to = ((page_idx + 1) * MAX_GAMES_PER_PAGE).min(self.all_manifests.len() as u32);
|
||||
|
||||
let page_manifests = &self.all_manifests[idx_from as usize..idx_to as usize];
|
||||
self.cells.clear();
|
||||
|
||||
let mut text: Option<Translation> = None;
|
||||
|
||||
if page_manifests.is_empty() {
|
||||
text = Some(Translation::from_translation_key("GAME_LIST.NO_GAMES_FOUND"))
|
||||
match self.game_list() {
|
||||
Ok(list) => {
|
||||
if list.manifests.is_empty() {
|
||||
text = Some(Translation::from_translation_key("GAME_LIST.NO_GAMES_FOUND"))
|
||||
} else {
|
||||
fill_game_list(
|
||||
&mut ConstructEssentials {
|
||||
layout,
|
||||
parent: self.id_list_parent,
|
||||
},
|
||||
executor,
|
||||
&mut self.cells,
|
||||
&list,
|
||||
&self.tasks,
|
||||
)?
|
||||
}
|
||||
}
|
||||
Err(e) => text = Some(Translation::from_raw_text(&format!("Error: {:?}", e))),
|
||||
}
|
||||
|
||||
// set page text
|
||||
let mut c = layout.start_common();
|
||||
{
|
||||
let mut common = c.common();
|
||||
let mut widget = common.state.widgets.cast_as::<WidgetLabel>(self.id_label_page)?;
|
||||
widget.set_text(
|
||||
&mut common,
|
||||
Translation::from_raw_text_string(format!("{}/{}", self.cur_page + 1, self.page_count)),
|
||||
);
|
||||
}
|
||||
c.finish()?;
|
||||
|
||||
fill_game_list(
|
||||
&mut ConstructEssentials {
|
||||
layout,
|
||||
parent: self.id_list_parent,
|
||||
},
|
||||
executor,
|
||||
&mut self.mounted_game_covers,
|
||||
page_manifests,
|
||||
&self.tasks,
|
||||
)?;
|
||||
|
||||
if let Some(text) = text.take() {
|
||||
let label = WidgetLabel::create(
|
||||
&mut layout.state,
|
||||
WidgetLabelParams {
|
||||
content: text,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
layout.add_child(self.id_list_parent, label, Default::default())?;
|
||||
layout.add_child(
|
||||
self.id_list_parent,
|
||||
WidgetLabel::create(
|
||||
&mut self.globals.get(),
|
||||
WidgetLabelParams {
|
||||
content: text,
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
Default::default(),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_cover_art(&mut self, layout: &mut Layout, app_id: AppID, cover_art: Rc<CoverArt>) {
|
||||
let Some(cover) = &mut self.mounted_game_covers.get_mut(&app_id) else {
|
||||
let Some(cell) = &mut self.cells.get_mut(&app_id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Err(e) = cover
|
||||
if let Err(e) = cell
|
||||
.view_cover
|
||||
.set_cover_art(&mut self.game_cover_view_common, layout, &cover_art)
|
||||
{
|
||||
|
||||
@@ -3,4 +3,6 @@ pub mod audio_settings;
|
||||
pub mod game_cover;
|
||||
pub mod game_launcher;
|
||||
pub mod game_list;
|
||||
pub mod running_games_list;
|
||||
pub mod process_list;
|
||||
pub mod window_list;
|
||||
pub mod window_options;
|
||||
@@ -0,0 +1,255 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use wayvr_ipc::packet_server::{self};
|
||||
use wgui::{
|
||||
assets::AssetPath,
|
||||
components::{
|
||||
self,
|
||||
button::ComponentButton,
|
||||
tooltip::{TooltipInfo, TooltipSide},
|
||||
},
|
||||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
layout::{Layout, WidgetID},
|
||||
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||
taffy::{self, prelude::length},
|
||||
task::Tasks,
|
||||
widget::{
|
||||
ConstructEssentials,
|
||||
div::WidgetDiv,
|
||||
label::{WidgetLabel, WidgetLabelParams},
|
||||
},
|
||||
};
|
||||
use wlx_common::{dash_interface::BoxDashInterface, desktop_finder::DesktopEntry};
|
||||
|
||||
use crate::util::{self, various::get_desktop_file_icon_path};
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Task {
|
||||
Refresh,
|
||||
TerminateProcess(packet_server::WvrProcess),
|
||||
}
|
||||
|
||||
pub struct Params<'a> {
|
||||
pub globals: WguiGlobals,
|
||||
pub layout: &'a mut Layout,
|
||||
pub parent_id: WidgetID,
|
||||
}
|
||||
|
||||
pub struct View {
|
||||
#[allow(dead_code)]
|
||||
pub parser_state: ParserState,
|
||||
tasks: Tasks<Task>,
|
||||
globals: WguiGlobals,
|
||||
id_list_parent: WidgetID,
|
||||
}
|
||||
|
||||
impl View {
|
||||
pub fn new(params: Params) -> anyhow::Result<Self> {
|
||||
let doc_params = &ParseDocumentParams {
|
||||
globals: params.globals.clone(),
|
||||
path: AssetPath::BuiltIn("gui/view/process_list.xml"),
|
||||
extra: Default::default(),
|
||||
};
|
||||
|
||||
let parser_state = wgui::parser::parse_from_assets(doc_params, params.layout, params.parent_id)?;
|
||||
let list_parent = parser_state.fetch_widget(¶ms.layout.state, "list_parent")?;
|
||||
|
||||
let tasks = Tasks::new();
|
||||
|
||||
tasks.push(Task::Refresh);
|
||||
|
||||
Ok(Self {
|
||||
parser_state,
|
||||
tasks,
|
||||
globals: params.globals,
|
||||
id_list_parent: list_parent.id,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update<T>(
|
||||
&mut self,
|
||||
layout: &mut Layout,
|
||||
interface: &mut BoxDashInterface<T>,
|
||||
data: &mut T,
|
||||
) -> anyhow::Result<()> {
|
||||
loop {
|
||||
let tasks = self.tasks.drain();
|
||||
if tasks.is_empty() {
|
||||
break;
|
||||
}
|
||||
for task in tasks {
|
||||
match task {
|
||||
Task::Refresh => self.refresh(layout, interface, data)?,
|
||||
Task::TerminateProcess(process) => self.action_terminate_process(interface, data, process)?,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_desktop_entry_from_process(process: &packet_server::WvrProcess) -> Option<DesktopEntry> {
|
||||
// TODO: refactor this after we ditch old wayvr-dashboard completely
|
||||
let Some(dfile_str) = process.userdata.get("desktop-entry") else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let Ok(desktop_file) = serde_json::from_str::<DesktopEntry>(dfile_str) else {
|
||||
debug_assert!(false); // invalid json???
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(desktop_file)
|
||||
}
|
||||
|
||||
struct ProcessEntryResult {
|
||||
btn_terminate: Rc<ComponentButton>,
|
||||
}
|
||||
|
||||
fn construct_process_entry(
|
||||
ess: &mut ConstructEssentials,
|
||||
globals: &WguiGlobals,
|
||||
process: &packet_server::WvrProcess,
|
||||
) -> anyhow::Result<ProcessEntryResult> {
|
||||
let (cell, _) = ess.layout.add_child(
|
||||
ess.parent,
|
||||
WidgetDiv::create(),
|
||||
taffy::Style {
|
||||
flex_direction: taffy::FlexDirection::Row,
|
||||
align_items: Some(taffy::AlignItems::Center),
|
||||
gap: length(8.0),
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let text_terminate_process = Translation::from_raw_text_string(globals.i18n().translate_and_replace(
|
||||
"PROCESS_LIST.TERMINATE_PROCESS_NAMED_X",
|
||||
("{PROCESS_NAME}", &process.name),
|
||||
));
|
||||
|
||||
//"Terminate process" button
|
||||
let (_, btn_terminate) = components::button::construct(
|
||||
&mut ConstructEssentials {
|
||||
layout: ess.layout,
|
||||
parent: cell.id,
|
||||
},
|
||||
components::button::Params {
|
||||
sprite_src: Some(AssetPath::BuiltIn("dashboard/remove_circle.svg")),
|
||||
tooltip: Some(TooltipInfo {
|
||||
text: text_terminate_process,
|
||||
side: TooltipSide::Right,
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
if let Some(desktop_file) = get_desktop_entry_from_process(process) {
|
||||
// desktop file icon and process name
|
||||
util::various::mount_simple_sprite_square(
|
||||
globals,
|
||||
ess.layout,
|
||||
cell.id,
|
||||
24.0,
|
||||
get_desktop_file_icon_path(&desktop_file).as_ref(),
|
||||
)?;
|
||||
|
||||
util::various::mount_simple_label(
|
||||
globals,
|
||||
ess.layout,
|
||||
cell.id,
|
||||
Translation::from_raw_text_rc(desktop_file.app_name.clone()),
|
||||
)?;
|
||||
} else {
|
||||
// just show a process name
|
||||
util::various::mount_simple_label(
|
||||
globals,
|
||||
ess.layout,
|
||||
cell.id,
|
||||
Translation::from_raw_text_string(process.name.clone()),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(ProcessEntryResult { btn_terminate })
|
||||
}
|
||||
|
||||
fn fill_process_list(
|
||||
globals: &WguiGlobals,
|
||||
ess: &mut ConstructEssentials,
|
||||
tasks: &Tasks<Task>,
|
||||
list: &Vec<packet_server::WvrProcess>,
|
||||
) -> anyhow::Result<()> {
|
||||
for process_entry in list {
|
||||
let entry_res = construct_process_entry(ess, globals, process_entry)?;
|
||||
|
||||
entry_res.btn_terminate.on_click({
|
||||
let tasks = tasks.clone();
|
||||
let entry = process_entry.clone();
|
||||
Box::new(move |_, _| {
|
||||
tasks.push(Task::TerminateProcess(entry.clone()));
|
||||
Ok(())
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl View {
|
||||
fn refresh<T>(
|
||||
&mut self,
|
||||
layout: &mut Layout,
|
||||
interface: &mut BoxDashInterface<T>,
|
||||
data: &mut T,
|
||||
) -> anyhow::Result<()> {
|
||||
layout.remove_children(self.id_list_parent);
|
||||
|
||||
let mut text: Option<Translation> = None;
|
||||
match interface.process_list(data) {
|
||||
Ok(list) => {
|
||||
if list.is_empty() {
|
||||
text = Some(Translation::from_translation_key("PROCESS_LIST.NO_PROCESSES_FOUND"))
|
||||
} else {
|
||||
fill_process_list(
|
||||
&self.globals,
|
||||
&mut ConstructEssentials {
|
||||
layout,
|
||||
parent: self.id_list_parent,
|
||||
},
|
||||
&self.tasks,
|
||||
&list,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Err(e) => text = Some(Translation::from_raw_text(&format!("Error: {:?}", e))),
|
||||
}
|
||||
|
||||
if let Some(text) = text.take() {
|
||||
layout.add_child(
|
||||
self.id_list_parent,
|
||||
WidgetLabel::create(
|
||||
&mut self.globals.get(),
|
||||
WidgetLabelParams {
|
||||
content: text,
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
Default::default(),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn action_terminate_process<T>(
|
||||
&mut self,
|
||||
interface: &mut BoxDashInterface<T>,
|
||||
data: &mut T,
|
||||
process: packet_server::WvrProcess,
|
||||
) -> anyhow::Result<()> {
|
||||
interface.process_terminate(data, process.handle)?;
|
||||
self.tasks.push(Task::Refresh);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
use wgui::{
|
||||
assets::AssetPath,
|
||||
components::button::ComponentButton,
|
||||
event::StyleSetRequest,
|
||||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
layout::{Layout, LayoutTask, WidgetID},
|
||||
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||
taffy::Display,
|
||||
task::Tasks,
|
||||
widget::label::WidgetLabel,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
frontend::{FrontendTask, FrontendTasks},
|
||||
util::steam_utils::{self, AppID, AppManifest, GameSortMethod, SteamUtils},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Task {
|
||||
Refresh,
|
||||
StopGame(AppID, bool /* kill */),
|
||||
}
|
||||
|
||||
pub struct Params<'a> {
|
||||
pub globals: WguiGlobals,
|
||||
pub layout: &'a mut Layout,
|
||||
pub parent_id: WidgetID,
|
||||
pub steam_utils: &'a mut SteamUtils,
|
||||
pub frontend_tasks: FrontendTasks,
|
||||
}
|
||||
|
||||
pub struct View {
|
||||
#[allow(dead_code)]
|
||||
state: ParserState,
|
||||
tasks: Tasks<Task>,
|
||||
last_update_ms: u32,
|
||||
id_list_parent: WidgetID,
|
||||
installed_games: Vec<AppManifest>,
|
||||
frontend_tasks: FrontendTasks,
|
||||
parent_id: WidgetID,
|
||||
}
|
||||
|
||||
fn doc_params(globals: WguiGlobals) -> ParseDocumentParams<'static> {
|
||||
ParseDocumentParams {
|
||||
globals,
|
||||
path: AssetPath::BuiltIn("gui/view/running_games_list.xml"),
|
||||
extra: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
impl View {
|
||||
pub fn new(params: Params) -> anyhow::Result<Self> {
|
||||
let state = wgui::parser::parse_from_assets(&doc_params(params.globals.clone()), params.layout, params.parent_id)?;
|
||||
let btn_refresh = state.fetch_component_as::<ComponentButton>("btn_refresh")?;
|
||||
let id_list_parent = state.get_widget_id("list_parent")?;
|
||||
|
||||
let installed_games = params
|
||||
.steam_utils
|
||||
.list_installed_games(GameSortMethod::None)
|
||||
.unwrap_or_default();
|
||||
|
||||
let tasks = Tasks::<Task>::new();
|
||||
|
||||
tasks.handle_button(&btn_refresh, Task::Refresh);
|
||||
tasks.push(Task::Refresh);
|
||||
|
||||
Ok(Self {
|
||||
state,
|
||||
tasks,
|
||||
last_update_ms: 0,
|
||||
id_list_parent,
|
||||
installed_games,
|
||||
frontend_tasks: params.frontend_tasks,
|
||||
parent_id: params.parent_id,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update(&mut self, layout: &mut Layout, time_ms: u32) -> anyhow::Result<()> {
|
||||
loop {
|
||||
let tasks = self.tasks.drain();
|
||||
if tasks.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
for task in tasks {
|
||||
match task {
|
||||
Task::Refresh => self.refresh(layout)?,
|
||||
Task::StopGame(app_id, kill) => self.stop_game(app_id, kill),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.last_update_ms + 5000 < time_ms {
|
||||
self.last_update_ms = time_ms;
|
||||
self.tasks.push(Task::Refresh);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn extract_name_from_appid(app_id: &AppID, manifests: &[AppManifest]) -> String {
|
||||
for manifest in manifests {
|
||||
if manifest.app_id == *app_id {
|
||||
return manifest.name.clone();
|
||||
}
|
||||
}
|
||||
|
||||
format!("Unknown AppID {}", app_id)
|
||||
}
|
||||
|
||||
fn fill_list(&mut self, layout: &mut Layout, games: Vec<steam_utils::RunningGame>) -> anyhow::Result<()> {
|
||||
if games.is_empty() {
|
||||
// hide self
|
||||
layout.tasks.push(LayoutTask::SetWidgetStyle(
|
||||
self.parent_id,
|
||||
StyleSetRequest::Display(Display::None),
|
||||
));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
layout.tasks.push(LayoutTask::SetWidgetStyle(
|
||||
self.parent_id,
|
||||
StyleSetRequest::Display(Display::DEFAULT),
|
||||
));
|
||||
|
||||
for game in games {
|
||||
let game_name = View::extract_name_from_appid(&game.app_id, &self.installed_games);
|
||||
|
||||
let t = self.state.parse_template(
|
||||
&doc_params(layout.state.globals.clone()),
|
||||
"RunningGameCell",
|
||||
layout,
|
||||
self.id_list_parent,
|
||||
Default::default(),
|
||||
)?;
|
||||
|
||||
let mut label_name = t.fetch_widget_as::<WidgetLabel>(&layout.state, "label_name")?;
|
||||
|
||||
self.tasks.handle_button(
|
||||
&t.fetch_component_as::<ComponentButton>("btn_stop")?,
|
||||
Task::StopGame(game.app_id.clone(), false),
|
||||
);
|
||||
|
||||
self.tasks.handle_button(
|
||||
&t.fetch_component_as::<ComponentButton>("btn_kill")?,
|
||||
Task::StopGame(game.app_id, true),
|
||||
);
|
||||
|
||||
label_name.set_text_simple(
|
||||
&mut layout.state.globals.get(),
|
||||
Translation::from_raw_text_string(game_name),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn refresh(&mut self, layout: &mut Layout) -> anyhow::Result<()> {
|
||||
log::debug!("refreshing running games list");
|
||||
|
||||
layout.remove_children(self.id_list_parent);
|
||||
|
||||
match steam_utils::list_running_games() {
|
||||
Ok(games) => self.fill_list(layout, games)?,
|
||||
Err(e) => {
|
||||
log::error!("failed to list games: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stop_game(&mut self, app_id: AppID, kill: bool) {
|
||||
if let Err(e) = steam_utils::stop(app_id, kill) {
|
||||
self
|
||||
.frontend_tasks
|
||||
.push(FrontendTask::PushToast(Translation::from_raw_text_string(format!(
|
||||
"Error: {}",
|
||||
e
|
||||
))));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use wayvr_ipc::packet_server::{self, WvrWindowHandle};
|
||||
use wgui::{
|
||||
assets::AssetPath,
|
||||
components::{self, button::ComponentButton},
|
||||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
layout::{Layout, WidgetID, WidgetPair},
|
||||
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||
renderer_vk::text::{FontWeight, HorizontalAlign, TextStyle},
|
||||
taffy::{self, prelude::length},
|
||||
task::Tasks,
|
||||
widget::{
|
||||
ConstructEssentials,
|
||||
label::{WidgetLabel, WidgetLabelParams},
|
||||
},
|
||||
};
|
||||
use wlx_common::dash_interface::BoxDashInterface;
|
||||
|
||||
use crate::{
|
||||
frontend::{FrontendTask, FrontendTasks},
|
||||
util::popup_manager::{MountPopupParams, PopupHandle},
|
||||
views::window_options,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Task {
|
||||
WindowClicked(packet_server::WvrWindow),
|
||||
WindowOptionsFinish,
|
||||
Refresh,
|
||||
}
|
||||
|
||||
pub struct Params<'a> {
|
||||
pub globals: WguiGlobals,
|
||||
pub frontend_tasks: FrontendTasks,
|
||||
pub layout: &'a mut Layout,
|
||||
pub parent_id: WidgetID,
|
||||
pub on_click: Option<Box<dyn Fn(WvrWindowHandle)>>,
|
||||
}
|
||||
|
||||
struct State {
|
||||
view_window_options: Option<(PopupHandle, window_options::View)>,
|
||||
}
|
||||
|
||||
pub struct View {
|
||||
#[allow(dead_code)]
|
||||
pub parser_state: ParserState,
|
||||
tasks: Tasks<Task>,
|
||||
frontend_tasks: FrontendTasks,
|
||||
globals: WguiGlobals,
|
||||
state: Rc<RefCell<State>>,
|
||||
id_list_parent: WidgetID,
|
||||
on_click: Option<Box<dyn Fn(WvrWindowHandle)>>,
|
||||
}
|
||||
|
||||
impl View {
|
||||
pub fn new(params: Params) -> anyhow::Result<Self> {
|
||||
let doc_params = &ParseDocumentParams {
|
||||
globals: params.globals.clone(),
|
||||
path: AssetPath::BuiltIn("gui/view/window_list.xml"),
|
||||
extra: Default::default(),
|
||||
};
|
||||
|
||||
let parser_state = wgui::parser::parse_from_assets(doc_params, params.layout, params.parent_id)?;
|
||||
let list_parent = parser_state.fetch_widget(¶ms.layout.state, "list_parent")?;
|
||||
|
||||
let tasks = Tasks::new();
|
||||
|
||||
tasks.push(Task::Refresh);
|
||||
|
||||
let state = Rc::new(RefCell::new(State {
|
||||
view_window_options: None,
|
||||
}));
|
||||
|
||||
Ok(Self {
|
||||
parser_state,
|
||||
tasks,
|
||||
frontend_tasks: params.frontend_tasks,
|
||||
globals: params.globals.clone(),
|
||||
state,
|
||||
id_list_parent: list_parent.id,
|
||||
on_click: params.on_click,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update<T>(
|
||||
&mut self,
|
||||
layout: &mut Layout,
|
||||
interface: &mut BoxDashInterface<T>,
|
||||
data: &mut T,
|
||||
) -> anyhow::Result<()> {
|
||||
loop {
|
||||
let tasks = self.tasks.drain();
|
||||
if tasks.is_empty() {
|
||||
break;
|
||||
}
|
||||
for task in tasks {
|
||||
match task {
|
||||
Task::WindowClicked(display) => self.action_window_clicked(display)?,
|
||||
Task::WindowOptionsFinish => self.action_window_options_finish(),
|
||||
Task::Refresh => self.refresh(layout, interface, data)?,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut state = self.state.borrow_mut();
|
||||
if let Some((_, view)) = &mut state.view_window_options {
|
||||
view.update(layout, interface, data)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn construct_window_button<T>(
|
||||
ess: &mut ConstructEssentials,
|
||||
interface: &mut BoxDashInterface<T>,
|
||||
data: &mut T,
|
||||
globals: &WguiGlobals,
|
||||
window: &packet_server::WvrWindow,
|
||||
) -> anyhow::Result<(WidgetPair, Rc<ComponentButton>)> {
|
||||
let aspect = window.size_x as f32 / window.size_y as f32;
|
||||
let height = 96.0;
|
||||
let width = height * aspect;
|
||||
let accent_color = globals.defaults().accent_color;
|
||||
|
||||
let (widget_button, button) = components::button::construct(
|
||||
ess,
|
||||
components::button::Params {
|
||||
color: Some(accent_color.with_alpha(0.2)),
|
||||
border_color: Some(accent_color),
|
||||
style: taffy::Style {
|
||||
align_items: Some(taffy::AlignItems::Center),
|
||||
justify_content: Some(taffy::JustifyContent::Center),
|
||||
size: taffy::Size {
|
||||
width: length(width),
|
||||
height: length(height),
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let process_name = match interface.process_get(data, window.process_handle.clone()) {
|
||||
Some(process) => process.name.clone(),
|
||||
None => String::from("Unknown"),
|
||||
};
|
||||
|
||||
let label_name = WidgetLabel::create(
|
||||
&mut globals.get(),
|
||||
WidgetLabelParams {
|
||||
content: Translation::from_raw_text(&process_name),
|
||||
style: TextStyle {
|
||||
weight: Some(FontWeight::Bold),
|
||||
wrap: true,
|
||||
align: Some(HorizontalAlign::Center),
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
let label_resolution = WidgetLabel::create(
|
||||
&mut globals.get(),
|
||||
WidgetLabelParams {
|
||||
content: Translation::from_raw_text(""),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
ess.layout.add_child(widget_button.id, label_name, Default::default())?;
|
||||
ess
|
||||
.layout
|
||||
.add_child(widget_button.id, label_resolution, Default::default())?;
|
||||
|
||||
Ok((widget_button, button))
|
||||
}
|
||||
|
||||
fn fill_window_list<T>(
|
||||
globals: &WguiGlobals,
|
||||
ess: &mut ConstructEssentials,
|
||||
interface: &mut BoxDashInterface<T>,
|
||||
data: &mut T,
|
||||
list: Vec<packet_server::WvrWindow>,
|
||||
tasks: &Tasks<Task>,
|
||||
) -> anyhow::Result<()> {
|
||||
for entry in list {
|
||||
let (_, button) = construct_window_button(ess, interface, data, globals, &entry)?;
|
||||
|
||||
button.on_click({
|
||||
let tasks = tasks.clone();
|
||||
Box::new(move |_, _| {
|
||||
tasks.push(Task::WindowClicked(entry.clone()));
|
||||
Ok(())
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl View {
|
||||
fn action_window_options_finish(&mut self) {
|
||||
self.state.borrow_mut().view_window_options = None;
|
||||
self.tasks.push(Task::Refresh);
|
||||
}
|
||||
|
||||
fn refresh<T>(
|
||||
&mut self,
|
||||
layout: &mut Layout,
|
||||
interface: &mut BoxDashInterface<T>,
|
||||
data: &mut T,
|
||||
) -> anyhow::Result<()> {
|
||||
layout.remove_children(self.id_list_parent);
|
||||
|
||||
let mut text: Option<Translation> = None;
|
||||
match interface.window_list(data) {
|
||||
Ok(list) => {
|
||||
if list.is_empty() {
|
||||
text = Some(Translation::from_translation_key("NO_WINDOWS_FOUND"))
|
||||
} else {
|
||||
fill_window_list(
|
||||
&self.globals,
|
||||
&mut ConstructEssentials {
|
||||
layout,
|
||||
parent: self.id_list_parent,
|
||||
},
|
||||
interface,
|
||||
data,
|
||||
list,
|
||||
&self.tasks,
|
||||
)?
|
||||
}
|
||||
}
|
||||
Err(e) => text = Some(Translation::from_raw_text(&format!("Error: {:?}", e))),
|
||||
}
|
||||
|
||||
if let Some(text) = text.take() {
|
||||
layout.add_child(
|
||||
self.id_list_parent,
|
||||
WidgetLabel::create(
|
||||
&mut self.globals.get(),
|
||||
WidgetLabelParams {
|
||||
content: text,
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
Default::default(),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn action_window_clicked(&mut self, window: packet_server::WvrWindow) -> anyhow::Result<()> {
|
||||
if let Some(on_click) = &mut self.on_click {
|
||||
(*on_click)(window.handle);
|
||||
} else {
|
||||
self.frontend_tasks.push(FrontendTask::MountPopup(MountPopupParams {
|
||||
title: Translation::from_translation_key("WINDOW_OPTIONS"),
|
||||
on_content: {
|
||||
let frontend_tasks = self.frontend_tasks.clone();
|
||||
let globals = self.globals.clone();
|
||||
let state = self.state.clone();
|
||||
let tasks = self.tasks.clone();
|
||||
|
||||
//TODO
|
||||
|
||||
Rc::new(move |data| {
|
||||
// state.borrow_mut().view_window_options = Some((
|
||||
// data.handle,
|
||||
// window_options::View::new(window_options::Params {
|
||||
// globals: globals.clone(),
|
||||
// layout: data.layout,
|
||||
// parent_id: data.id_content,
|
||||
// on_submit: tasks.make_callback(Task::WindowOptionsFinish),
|
||||
// window: window.clone(),
|
||||
// frontend_tasks: frontend_tasks.clone(),
|
||||
// })?,
|
||||
// ));
|
||||
Ok(())
|
||||
})
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
use anyhow::Context;
|
||||
use std::rc::Rc;
|
||||
use wayvr_ipc::packet_server;
|
||||
use wgui::{
|
||||
assets::AssetPath,
|
||||
components::button::ComponentButton,
|
||||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
layout::{Layout, WidgetID},
|
||||
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||
task::Tasks,
|
||||
widget::ConstructEssentials,
|
||||
};
|
||||
use wlx_common::dash_interface::BoxDashInterface;
|
||||
|
||||
use crate::{
|
||||
frontend::{FrontendTask, FrontendTasks},
|
||||
views::window_list::construct_window_button,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Task {
|
||||
SetVisible(bool),
|
||||
Kill,
|
||||
Close,
|
||||
}
|
||||
|
||||
pub struct View {
|
||||
#[allow(dead_code)]
|
||||
pub state: ParserState,
|
||||
tasks: Tasks<Task>,
|
||||
frontend_tasks: FrontendTasks,
|
||||
window: packet_server::WvrWindow,
|
||||
on_submit: Rc<dyn Fn()>,
|
||||
}
|
||||
|
||||
pub struct Params<'a, T> {
|
||||
pub globals: WguiGlobals,
|
||||
pub frontend_tasks: FrontendTasks,
|
||||
pub layout: &'a mut Layout,
|
||||
pub parent_id: WidgetID,
|
||||
pub on_submit: Rc<dyn Fn()>,
|
||||
pub window: packet_server::WvrWindow,
|
||||
pub interface: &'a mut BoxDashInterface<T>,
|
||||
pub data: &'a mut T,
|
||||
}
|
||||
|
||||
impl View {
|
||||
pub fn new<T>(params: Params<T>) -> anyhow::Result<Self> {
|
||||
let doc_params = &ParseDocumentParams {
|
||||
globals: params.globals.clone(),
|
||||
path: AssetPath::BuiltIn("gui/view/window_options.xml"),
|
||||
extra: Default::default(),
|
||||
};
|
||||
|
||||
let state = wgui::parser::parse_from_assets(doc_params, params.layout, params.parent_id)?;
|
||||
|
||||
let tasks = Tasks::new();
|
||||
|
||||
let window_parent = state.get_widget_id("window_parent")?;
|
||||
let btn_close = state.fetch_component_as::<ComponentButton>("btn_close")?;
|
||||
let btn_kill = state.fetch_component_as::<ComponentButton>("btn_kill")?;
|
||||
let btn_show_hide = state.fetch_component_as::<ComponentButton>("btn_show_hide")?;
|
||||
|
||||
construct_window_button(
|
||||
&mut ConstructEssentials {
|
||||
layout: params.layout,
|
||||
parent: window_parent,
|
||||
},
|
||||
params.interface,
|
||||
params.data,
|
||||
¶ms.globals,
|
||||
¶ms.window,
|
||||
)?;
|
||||
|
||||
{
|
||||
let mut c = params.layout.start_common();
|
||||
btn_show_hide.set_text(
|
||||
&mut c.common(),
|
||||
Translation::from_translation_key(if params.window.visible { "HIDE" } else { "SHOW" }),
|
||||
);
|
||||
c.finish()?;
|
||||
}
|
||||
|
||||
tasks.handle_button(&btn_close, Task::Close);
|
||||
tasks.handle_button(&btn_kill, Task::Kill);
|
||||
tasks.handle_button(&btn_show_hide, Task::SetVisible(!params.window.visible));
|
||||
|
||||
Ok(Self {
|
||||
state,
|
||||
tasks,
|
||||
window: params.window,
|
||||
frontend_tasks: params.frontend_tasks,
|
||||
on_submit: params.on_submit,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update<T>(
|
||||
&mut self,
|
||||
_layout: &mut Layout,
|
||||
interface: &mut BoxDashInterface<T>,
|
||||
data: &mut T,
|
||||
) -> anyhow::Result<()> {
|
||||
for task in self.tasks.drain() {
|
||||
match task {
|
||||
Task::SetVisible(v) => self.action_set_visible(interface, data, v),
|
||||
Task::Close => self.action_close(interface, data),
|
||||
Task::Kill => self.action_kill(interface, data),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl View {
|
||||
fn action_set_visible<T>(&mut self, interface: &mut BoxDashInterface<T>, data: &mut T, visible: bool) {
|
||||
if let Err(e) = interface.window_set_visible(data, self.window.handle.clone(), visible) {
|
||||
self
|
||||
.frontend_tasks
|
||||
.push(FrontendTask::PushToast(Translation::from_raw_text_string(format!(
|
||||
"Failed to set window visibility: {:?}",
|
||||
e
|
||||
))));
|
||||
};
|
||||
|
||||
(*self.on_submit)();
|
||||
}
|
||||
|
||||
fn action_close<T>(&mut self, interface: &mut BoxDashInterface<T>, data: &mut T) {
|
||||
if let Err(e) = interface.window_request_close(data, self.window.handle.clone()) {
|
||||
self
|
||||
.frontend_tasks
|
||||
.push(FrontendTask::PushToast(Translation::from_raw_text_string(format!(
|
||||
"Failed to close window: {:?}",
|
||||
e
|
||||
))));
|
||||
};
|
||||
|
||||
(*self.on_submit)();
|
||||
}
|
||||
|
||||
fn action_kill_process<T>(&mut self, interface: &mut BoxDashInterface<T>, data: &mut T) -> anyhow::Result<()> {
|
||||
let process = interface
|
||||
.process_get(data, self.window.process_handle.clone())
|
||||
.context("Process not found")?;
|
||||
interface.process_terminate(data, process.handle)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn action_kill<T>(&mut self, interface: &mut BoxDashInterface<T>, data: &mut T) {
|
||||
if let Err(e) = self.action_kill_process(interface, data) {
|
||||
self
|
||||
.frontend_tasks
|
||||
.push(FrontendTask::PushToast(Translation::from_raw_text_string(format!(
|
||||
"Failed to kill process: {:?}",
|
||||
e
|
||||
))));
|
||||
};
|
||||
|
||||
(*self.on_submit)();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 0,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "llm_translator",
|
||||
|
||||
+121
-116
@@ -9,181 +9,186 @@ const template_name = process.env["TEMPLATE"] as string;
|
||||
let lang_path = process.env["LANG_PATH"] as string;
|
||||
|
||||
if (model_name === undefined) {
|
||||
console.log("MODEL not set");
|
||||
exit(-1);
|
||||
console.log("MODEL not set");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if (template_name === undefined) {
|
||||
console.log("TEMPLATE not set");
|
||||
exit(-1);
|
||||
console.log("TEMPLATE not set");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if (lang_path === undefined) {
|
||||
console.log(
|
||||
`LANG_PATH is not set.
|
||||
console.log(
|
||||
`LANG_PATH is not set.
|
||||
Try one of these:
|
||||
LANG_PATH=../../uidev/assets/lang/ ./run.sh
|
||||
LANG_PATH=../../dash-frontend/assets/lang/ ./run.sh
|
||||
LANG_PATH=../../wayvr/src/assets/lang/ ./run.sh`);
|
||||
exit(-1);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
lang_path = path.resolve(__dirname + "/" + lang_path);
|
||||
if (lang_path === undefined || !fs.existsSync(lang_path)) {
|
||||
console.log("Invalid or non-existent LANG_PATH");
|
||||
exit(-1);
|
||||
console.log("Invalid or non-existent LANG_PATH");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
const current_path = path.resolve(__dirname);
|
||||
const templates_path = path.resolve(__dirname + "/templates");
|
||||
|
||||
async function loop_object(obj: any, initial_str: string, callback: (key: string, value: string) => Promise<void>) {
|
||||
for (var key in obj) {
|
||||
let full_key = initial_str + key;
|
||||
if (typeof obj[key] === "object" && obj[key] !== null) {
|
||||
await loop_object(obj[key], full_key + ".", callback)
|
||||
} else if (obj.hasOwnProperty(key)) {
|
||||
await callback(full_key, obj[key])
|
||||
}
|
||||
}
|
||||
for (var key in obj) {
|
||||
let full_key = initial_str + key;
|
||||
if (typeof obj[key] === "object" && obj[key] !== null) {
|
||||
await loop_object(obj[key], full_key + ".", callback)
|
||||
} else if (obj.hasOwnProperty(key)) {
|
||||
await callback(full_key, obj[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function extract_backticks(str: string) {
|
||||
const regex = /`([^`]+)`/g;
|
||||
return str.match(regex)?.map(match => match.slice(1, -1).trim());
|
||||
const regex = /`([^`]+)`/g;
|
||||
return str.match(regex)?.map(match => match.slice(1, -1).trim());
|
||||
}
|
||||
|
||||
function set_i18n_key(obj: any, key: string, value: string | undefined) {
|
||||
const parts = key.split('.');
|
||||
let cur_level = obj;
|
||||
for (let i = 0; i < parts.length - 1; i++) {
|
||||
const part = parts[i]!;
|
||||
if (!cur_level[part]) {
|
||||
cur_level[part] = {};
|
||||
}
|
||||
cur_level = cur_level[part];
|
||||
}
|
||||
cur_level[parts[parts.length - 1]!] = value;
|
||||
const parts = key.split('.');
|
||||
let cur_level = obj;
|
||||
for (let i = 0; i < parts.length - 1; i++) {
|
||||
const part = parts[i]!;
|
||||
if (!cur_level[part]) {
|
||||
cur_level[part] = {};
|
||||
}
|
||||
cur_level = cur_level[part];
|
||||
}
|
||||
cur_level[parts[parts.length - 1]!] = value;
|
||||
}
|
||||
|
||||
function key_exists(obj: any, key: string) {
|
||||
const parts = key.split('.');
|
||||
let level = obj;
|
||||
const parts = key.split('.');
|
||||
let level = obj;
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const part = parts[i]!;
|
||||
if (!level || !level[part]) {
|
||||
return false;
|
||||
}
|
||||
level = level[part];
|
||||
}
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const part = parts[i]!;
|
||||
if (!level || !level[part]) {
|
||||
return false;
|
||||
}
|
||||
level = level[part];
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
};
|
||||
|
||||
interface Example {
|
||||
key: string;
|
||||
en: string;
|
||||
translated: string;
|
||||
key: string;
|
||||
en: string;
|
||||
translated: string;
|
||||
}
|
||||
|
||||
interface Template {
|
||||
full_name: string; // "Polish"
|
||||
examples: Example[]
|
||||
full_name: string; // "Polish"
|
||||
examples: Example[]
|
||||
}
|
||||
|
||||
function gen_prompt(description: string, template: Template, key: string, english_translation: string) {
|
||||
let num = 1;
|
||||
for (const example of template.examples) {
|
||||
description += "\nExample " + num + ":\n\n";
|
||||
description += "Translate key `" + example.key + "` from English to " + template.full_name + ":\n\n";
|
||||
description += "```\n";
|
||||
description += example.en + "\n";
|
||||
description += "```\n\n";
|
||||
description += "Result:\n\n";
|
||||
description += "```\n";
|
||||
description += example.translated + "\n";
|
||||
description += "```\n";
|
||||
num += 1;
|
||||
}
|
||||
description += "\nEnd of examples.\n\n";
|
||||
description += "Translate key `" + key + "` from English to " + template.full_name + ":\n\n";
|
||||
description += "```\n";
|
||||
description += english_translation + "\n";
|
||||
description += "```\n";
|
||||
return description;
|
||||
let num = 1;
|
||||
for (const example of template.examples) {
|
||||
description += "\nExample " + num + ":\n\n";
|
||||
description += "Translate key `" + example.key + "` from English to " + template.full_name + ":\n\n";
|
||||
description += "```\n";
|
||||
description += example.en + "\n";
|
||||
description += "```\n\n";
|
||||
description += "Result:\n\n";
|
||||
description += "```\n";
|
||||
description += example.translated + "\n";
|
||||
description += "```\n";
|
||||
num += 1;
|
||||
}
|
||||
description += "\nEnd of examples.\n\n";
|
||||
description += "Translate key `" + key + "` from English to " + template.full_name + ":\n\n";
|
||||
description += "```\n";
|
||||
description += english_translation + "\n";
|
||||
description += "```\n";
|
||||
return description;
|
||||
}
|
||||
|
||||
async function run() {
|
||||
const template = JSON.parse(await fsp.readFile(templates_path + "/" + template_name + ".json", "utf-8")) as Template;
|
||||
const template = JSON.parse(await fsp.readFile(templates_path + "/" + template_name + ".json", "utf-8")) as Template;
|
||||
|
||||
let description_txt = await fsp.readFile(current_path + "/description.txt", "utf-8");
|
||||
description_txt = description_txt.replaceAll("{TARGET_LANG}", template.full_name);
|
||||
let description_txt = await fsp.readFile(current_path + "/description.txt", "utf-8");
|
||||
description_txt = description_txt.replaceAll("{TARGET_LANG}", template.full_name);
|
||||
|
||||
const orig_english_json = JSON.parse(await fsp.readFile(lang_path + "/en.json") as any);
|
||||
const orig_english_json = JSON.parse(await fsp.readFile(lang_path + "/en.json") as any);
|
||||
|
||||
let orig_translated_json = {};
|
||||
try {
|
||||
orig_translated_json = JSON.parse((await fsp.readFile(lang_path + "/" + template_name + ".json")).toString());
|
||||
}
|
||||
catch (_e) { }
|
||||
let orig_translated_json = {};
|
||||
try {
|
||||
orig_translated_json = JSON.parse((await fsp.readFile(lang_path + "/" + template_name + ".json")).toString());
|
||||
}
|
||||
catch (_e) { }
|
||||
|
||||
let llm_translated_json = {};
|
||||
const translated_json_path = lang_path + "/" + template_name + ".json";
|
||||
if (await fsp.exists(translated_json_path)) {
|
||||
console.log("Loading file", translated_json_path);
|
||||
llm_translated_json = JSON.parse((await fsp.readFile(translated_json_path)).toString());
|
||||
}
|
||||
let llm_translated_json = {};
|
||||
const translated_json_path = lang_path + "/" + template_name + ".json";
|
||||
if (await fsp.exists(translated_json_path)) {
|
||||
llm_translated_json = JSON.parse((await fsp.readFile(translated_json_path)).toString());
|
||||
}
|
||||
|
||||
let total_count = 0;
|
||||
await loop_object(orig_english_json, "", async () => {
|
||||
total_count += 1;
|
||||
});
|
||||
let human = 0;
|
||||
let llm = 0;
|
||||
|
||||
await loop_object(llm_translated_json, "", async (key, _) => {
|
||||
if (!key_exists(orig_english_json, key)) {
|
||||
console.log("Removing key", key);
|
||||
set_i18n_key(llm_translated_json, key, undefined);
|
||||
fsp.writeFile(translated_json_path, JSON.stringify(llm_translated_json, undefined, 2));
|
||||
}
|
||||
});
|
||||
let total_count = 0;
|
||||
await loop_object(orig_english_json, "", async () => {
|
||||
total_count += 1;
|
||||
});
|
||||
|
||||
await loop_object(orig_english_json, "", async (key, english_translation) => {
|
||||
if (key_exists(orig_translated_json, key)) {
|
||||
return;
|
||||
}
|
||||
await loop_object(llm_translated_json, "", async (key, _) => {
|
||||
if (!key_exists(orig_english_json, key)) {
|
||||
console.log("Removing key", key);
|
||||
set_i18n_key(llm_translated_json, key, undefined);
|
||||
fsp.writeFile(translated_json_path, JSON.stringify(llm_translated_json, undefined, 2));
|
||||
}
|
||||
});
|
||||
|
||||
if (key_exists(llm_translated_json, key)) {
|
||||
return;
|
||||
}
|
||||
await loop_object(orig_english_json, "", async (key, english_translation) => {
|
||||
if (key_exists(orig_translated_json, key)) {
|
||||
human += 1;
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Translating", key, "...");
|
||||
if (key_exists(llm_translated_json, key)) {
|
||||
llm += 1;
|
||||
return;
|
||||
}
|
||||
|
||||
const prompt = gen_prompt(description_txt, template, key, english_translation);
|
||||
console.log("Translating", key, "...");
|
||||
llm++;
|
||||
|
||||
const response = await ollama.chat({
|
||||
model: model_name,
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
options: {
|
||||
seed: 12345,
|
||||
}
|
||||
})
|
||||
const prompt = gen_prompt(description_txt, template, key, english_translation);
|
||||
|
||||
const msg = extract_backticks(response.message.content);
|
||||
if (msg === undefined || msg[0] === undefined) {
|
||||
throw new Error("backticks failed. Raw content: " + response.message.content);
|
||||
}
|
||||
const response = await ollama.chat({
|
||||
model: model_name,
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
options: {
|
||||
seed: 12345,
|
||||
}
|
||||
})
|
||||
|
||||
console.log(" >>>", msg);
|
||||
const msg = extract_backticks(response.message.content);
|
||||
if (msg === undefined || msg[0] === undefined) {
|
||||
throw new Error("backticks failed. Raw content: " + response.message.content);
|
||||
}
|
||||
|
||||
set_i18n_key(llm_translated_json, key, msg[0]);
|
||||
fsp.writeFile(translated_json_path, JSON.stringify(llm_translated_json, undefined, 2));
|
||||
});
|
||||
console.log(" >>>", msg);
|
||||
|
||||
console.log("Translation", template_name, "finished");
|
||||
set_i18n_key(llm_translated_json, key, msg[0]);
|
||||
fsp.writeFile(translated_json_path, JSON.stringify(llm_translated_json, undefined, 2));
|
||||
});
|
||||
|
||||
console.log("\"" + template_name + "\" translation finished,", human, "were already human translated,", llm, "llm-translated (" + Math.round((llm / total_count) * 100.0) + "% machine-translated)");
|
||||
}
|
||||
|
||||
run().catch((e) => {
|
||||
console.log("Fatal error:", e);
|
||||
exit(-1);
|
||||
console.log("Fatal error:", e);
|
||||
exit(-1);
|
||||
});
|
||||
@@ -1,13 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Do not run this script if you don’t have at least 32 GiB of system memory,
|
||||
# unless you have enabled the mmap option in the Ollama startup settings.
|
||||
# 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.
|
||||
#
|
||||
# Base language: English (en.json)
|
||||
|
||||
set -e
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
@@ -19,5 +10,3 @@ TEMPLATE="pl" bun main.ts
|
||||
TEMPLATE="de" bun main.ts
|
||||
TEMPLATE="ja" bun main.ts
|
||||
TEMPLATE="es" bun main.ts
|
||||
TEMPLATE="it" bun main.ts
|
||||
TEMPLATE="zh_CN" bun main.ts
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"full_name": "Italian",
|
||||
"examples": [
|
||||
{
|
||||
"key": "BAR.OPACITY",
|
||||
"en": "Opacity",
|
||||
"translated": "Opacità"
|
||||
},
|
||||
{
|
||||
"key": "PANEL.CLOSE",
|
||||
"en": "Close panel",
|
||||
"translated": "Chiudi pannello"
|
||||
},
|
||||
{
|
||||
"key": "SETTINGS.LEFT_HANDED_MODE",
|
||||
"en": "Left-handed mode",
|
||||
"translated": "Modalità mancino"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"full_name": "Chinese (Simplified)",
|
||||
"examples": [
|
||||
{
|
||||
"key": "BAR.OPACITY",
|
||||
"en": "Opacity",
|
||||
"translated": "不透明度"
|
||||
},
|
||||
{
|
||||
"key": "PANEL.CLOSE",
|
||||
"en": "Close panel",
|
||||
"translated": "关闭面板"
|
||||
},
|
||||
{
|
||||
"key": "SETTINGS.LEFT_HANDED_MODE",
|
||||
"en": "Left-handed mode",
|
||||
"translated": "左手模式"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -34,6 +34,7 @@
|
||||
|
||||
<!-- Embed sprites -->
|
||||
<sprite width="64" height="64" src="raster.png" />
|
||||
<sprite width="64" height="64" src="dashboard/wayvr_dashboard.svg" />
|
||||
</rectangle>
|
||||
<!-- 2/3 green rects -->
|
||||
<div flex_direction="column">
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<div gap="4">
|
||||
<Button id="button_red" text="Red button" width="150" height="32" color="#FF0000" tooltip_str="I'm at the top" tooltip_side="top" />
|
||||
<Button id="button_aqua" text="Aqua button" width="150" height="32" color="#00FFFF" tooltip_str="I'm at the bottom" tooltip_side="bottom" />
|
||||
<Button id="button_yellow" text="Yellow button" width="150" height="32" color="#FFFF00" tooltip="TESTBED.HELLO_WORLD" tooltip_side="left" />
|
||||
<Button id="button_yellow" text="Yellow button" width="150" height="32" color="#FFFF00" tooltip="TESTBED.HELLO_WORLD" tooltip_side="right" />
|
||||
</div>
|
||||
<div gap="4">
|
||||
<Button id="button_click_me" text="Click me" width="128" height="24" color="#FFFFFF" />
|
||||
@@ -59,18 +59,10 @@
|
||||
</div>
|
||||
</rectangle>
|
||||
|
||||
<div>
|
||||
<rectangle macro="rect">
|
||||
<label text="Context menu test" />
|
||||
<Button id="button_context_menu" text="Show context menu" />
|
||||
</rectangle>
|
||||
<rectangle macro="rect">
|
||||
<label text="Editbox test" />
|
||||
<EditBox text="Initial text. Test, abcdef." width="400" />
|
||||
|
||||
<EditBox margin_left="42" margin_top="42" margin_bottom="40" width="200" />
|
||||
</rectangle>
|
||||
</div>
|
||||
<rectangle macro="rect">
|
||||
<label text="Context menu test" />
|
||||
<Button id="button_context_menu" text="Show context menu" />
|
||||
</rectangle>
|
||||
|
||||
<rectangle macro="rect">
|
||||
<label text="visibility test" weight="bold" />
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"BAR": {
|
||||
"OPACITY": "Opacità",
|
||||
"ADDITIVE": "Additivo"
|
||||
},
|
||||
"TESTBED": {
|
||||
"HELLO_WORLD": "Ciao, mondo! Questo è un testo I18n tradotto."
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"BAR": {
|
||||
"OPACITY": "不透明度",
|
||||
"ADDITIVE": "加法"
|
||||
},
|
||||
"TESTBED": {
|
||||
"HELLO_WORLD": "你好,世界!这是一段翻译后的国际化文本。"
|
||||
}
|
||||
}
|
||||
+33
-57
@@ -1,5 +1,5 @@
|
||||
use glam::{Vec2, vec2};
|
||||
use std::{rc::Rc, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
use testbed::{Testbed, testbed_any::TestbedAny};
|
||||
use tracing_subscriber::EnvFilter;
|
||||
use tracing_subscriber::filter::LevelFilter;
|
||||
@@ -12,13 +12,13 @@ use vulkano::{
|
||||
format::Format,
|
||||
image::{ImageUsage, view::ImageView},
|
||||
swapchain::{
|
||||
ColorSpace, CompositeAlpha, PresentMode, Surface, SurfaceInfo, Swapchain, SwapchainCreateInfo,
|
||||
CompositeAlpha, PresentMode, Surface, SurfaceInfo, Swapchain, SwapchainCreateInfo,
|
||||
SwapchainPresentInfo, acquire_next_image,
|
||||
},
|
||||
sync::GpuFuture,
|
||||
};
|
||||
use wgui::{
|
||||
event::{MouseButtonEvent, MouseButtonIndex, MouseMotionEvent, MouseWheelEvent},
|
||||
event::{MouseButtonIndex, MouseDownEvent, MouseMotionEvent, MouseUpEvent, MouseWheelEvent},
|
||||
gfx::{WGfx, cmd::WGfxClearMode},
|
||||
renderer_vk::{self},
|
||||
};
|
||||
@@ -74,18 +74,13 @@ fn load_testbed(audio_sample_player: &mut audio::SamplePlayer) -> anyhow::Result
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
init_logging();
|
||||
|
||||
let (gfx, event_loop, window, surface, color_space) =
|
||||
let (gfx, event_loop, window, surface) =
|
||||
init_window("[-/=]: gui scale, F10: debug draw, F11: print tree")?;
|
||||
let inner_size = window.inner_size();
|
||||
let mut swapchain_size = [inner_size.width, inner_size.height];
|
||||
|
||||
let mut swapchain_create_info = swapchain_create_info(
|
||||
&gfx,
|
||||
gfx.surface_format,
|
||||
color_space,
|
||||
surface.clone(),
|
||||
swapchain_size,
|
||||
);
|
||||
let mut swapchain_create_info =
|
||||
swapchain_create_info(&gfx, gfx.surface_format, surface.clone(), swapchain_size);
|
||||
|
||||
let (mut swapchain, mut images) = {
|
||||
let (swapchain, images) = Swapchain::new(
|
||||
@@ -174,7 +169,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
testbed
|
||||
.layout()
|
||||
.push_event(
|
||||
&wgui::event::Event::MouseDown(MouseButtonEvent {
|
||||
&wgui::event::Event::MouseDown(MouseDownEvent {
|
||||
pos: mouse / scale,
|
||||
index: MouseButtonIndex::Left,
|
||||
device: 0,
|
||||
@@ -187,7 +182,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
testbed
|
||||
.layout()
|
||||
.push_event(
|
||||
&wgui::event::Event::MouseUp(MouseButtonEvent {
|
||||
&wgui::event::Event::MouseUp(MouseUpEvent {
|
||||
pos: mouse / scale,
|
||||
index: MouseButtonIndex::Left,
|
||||
device: 0,
|
||||
@@ -221,46 +216,33 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
..
|
||||
} => {
|
||||
if event.state == ElementState::Pressed {
|
||||
if !event.repeat {
|
||||
if event.physical_key == PhysicalKey::Code(KeyCode::F10) {
|
||||
debug_draw_enabled = !debug_draw_enabled;
|
||||
if debug_draw_enabled {
|
||||
log::info!(
|
||||
"Debug draw enabled\n\tAqua: widget boundary\n\tMagenta: Scissoring (separate render pass)"
|
||||
);
|
||||
}
|
||||
testbed.layout().mark_redraw();
|
||||
}
|
||||
|
||||
if event.physical_key == PhysicalKey::Code(KeyCode::F11) {
|
||||
testbed.layout().print_tree();
|
||||
}
|
||||
|
||||
if event.physical_key == PhysicalKey::Code(KeyCode::Equal) {
|
||||
scale *= 1.25;
|
||||
render_context
|
||||
.update_viewport(&mut shared_context, swapchain_size, scale)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
if event.physical_key == PhysicalKey::Code(KeyCode::Minus) {
|
||||
scale *= 0.75;
|
||||
render_context
|
||||
.update_viewport(&mut shared_context, swapchain_size, scale)
|
||||
.unwrap();
|
||||
if event.physical_key == PhysicalKey::Code(KeyCode::F10) {
|
||||
debug_draw_enabled = !debug_draw_enabled;
|
||||
if debug_draw_enabled {
|
||||
log::info!(
|
||||
"Debug draw enabled\n\tAqua: widget boundary\n\tMagenta: Scissoring (separate render pass)"
|
||||
);
|
||||
}
|
||||
testbed.layout().mark_redraw();
|
||||
}
|
||||
|
||||
testbed
|
||||
.layout()
|
||||
.push_event(
|
||||
&wgui::event::Event::TextInput(wgui::event::TextInputEvent {
|
||||
text: event.text.map(|text| Rc::from(text.as_str())),
|
||||
}),
|
||||
&mut (),
|
||||
&mut (),
|
||||
)
|
||||
.unwrap();
|
||||
if event.physical_key == PhysicalKey::Code(KeyCode::F11) {
|
||||
testbed.layout().print_tree();
|
||||
}
|
||||
|
||||
if event.physical_key == PhysicalKey::Code(KeyCode::Equal) {
|
||||
scale *= 1.25;
|
||||
render_context
|
||||
.update_viewport(&mut shared_context, swapchain_size, scale)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
if event.physical_key == PhysicalKey::Code(KeyCode::Minus) {
|
||||
scale *= 0.75;
|
||||
render_context
|
||||
.update_viewport(&mut shared_context, swapchain_size, scale)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::WindowEvent {
|
||||
@@ -377,11 +359,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.unwrap();
|
||||
|
||||
if debug_draw_enabled {
|
||||
log::debug!(
|
||||
"pass count: {}, primitive commands count: {}",
|
||||
draw_result.pass_count,
|
||||
draw_result.primitive_commands_count
|
||||
);
|
||||
log::debug!("pass count: {}", draw_result.pass_count);
|
||||
}
|
||||
|
||||
cmd_buf.end_rendering().unwrap();
|
||||
@@ -418,7 +396,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
fn swapchain_create_info(
|
||||
graphics: &WGfx,
|
||||
format: Format,
|
||||
color_space: ColorSpace,
|
||||
surface: Arc<Surface>,
|
||||
extent: [u32; 2],
|
||||
) -> SwapchainCreateInfo {
|
||||
@@ -472,7 +449,6 @@ fn swapchain_create_info(
|
||||
image_format: format,
|
||||
image_extent: extent,
|
||||
image_usage: ImageUsage::COLOR_ATTACHMENT,
|
||||
image_color_space: color_space,
|
||||
composite_alpha,
|
||||
..Default::default()
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ use wgui::{
|
||||
layout::{Layout, LayoutParams, LayoutUpdateParams},
|
||||
parser::{ParseDocumentParams, ParserState},
|
||||
};
|
||||
use wlx_common::locale::WayVRLangProvider;
|
||||
|
||||
pub struct TestbedAny {
|
||||
pub layout: Layout,
|
||||
@@ -29,11 +28,9 @@ impl TestbedAny {
|
||||
AssetPath::BuiltIn(&format!("gui/{name}.xml"))
|
||||
};
|
||||
|
||||
let lang_provider = WayVRLangProvider::default();
|
||||
|
||||
let globals = WguiGlobals::new(
|
||||
assets,
|
||||
&lang_provider,
|
||||
wgui::globals::Defaults::default(),
|
||||
&WguiFontConfig::default(),
|
||||
PathBuf::new(), // cwd
|
||||
)?;
|
||||
@@ -44,7 +41,7 @@ impl TestbedAny {
|
||||
path,
|
||||
extra: Default::default(),
|
||||
},
|
||||
LayoutParams::default(),
|
||||
&LayoutParams::default(),
|
||||
)?;
|
||||
Ok(Self { layout, state })
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::testbed::{Testbed, TestbedUpdateParams};
|
||||
use dash_frontend::frontend::{self, FrontendUpdateParams};
|
||||
use wgui::{layout::Layout, theme::WguiTheme};
|
||||
use wlx_common::{dash_interface_emulated::DashInterfaceEmulated, locale::WayVRLangProvider};
|
||||
use wgui::layout::Layout;
|
||||
use wlx_common::dash_interface_emulated::DashInterfaceEmulated;
|
||||
|
||||
pub struct TestbedDashboard {
|
||||
frontend: frontend::Frontend<()>,
|
||||
@@ -12,14 +10,14 @@ pub struct TestbedDashboard {
|
||||
impl TestbedDashboard {
|
||||
pub fn new() -> anyhow::Result<Self> {
|
||||
let interface = DashInterfaceEmulated::new();
|
||||
let lang_provider = WayVRLangProvider::default();
|
||||
|
||||
let frontend = frontend::Frontend::new(frontend::InitParams {
|
||||
interface: Box::new(interface),
|
||||
has_monado: true,
|
||||
lang_provider: &lang_provider,
|
||||
theme: Rc::new(WguiTheme::default()),
|
||||
})?;
|
||||
let frontend = frontend::Frontend::new(
|
||||
frontend::InitParams {
|
||||
interface: Box::new(interface),
|
||||
has_monado: true,
|
||||
},
|
||||
&mut (),
|
||||
)?;
|
||||
Ok(Self { frontend })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,14 +21,12 @@ use wgui::{
|
||||
parser::{Fetchable, ParseDocumentExtra, ParseDocumentParams, ParserState},
|
||||
taffy::{self, prelude::length},
|
||||
task::Tasks,
|
||||
theme::WguiTheme,
|
||||
widget::{div::WidgetDiv, label::WidgetLabel, rectangle::WidgetRectangle},
|
||||
windowing::{
|
||||
context_menu::{self, TickResult},
|
||||
window::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra},
|
||||
},
|
||||
};
|
||||
use wlx_common::locale::WayVRLangProvider;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum TestbedTask {
|
||||
@@ -55,9 +53,10 @@ fn button_click_callback(
|
||||
label: Widget,
|
||||
text: &'static str,
|
||||
) -> ButtonClickCallback {
|
||||
Rc::new(move |common, _e| {
|
||||
Box::new(move |common, _e| {
|
||||
label
|
||||
.cast::<WidgetLabel>()?
|
||||
.get_as::<WidgetLabel>()
|
||||
.unwrap()
|
||||
.set_text(common, Translation::from_raw_text(text));
|
||||
|
||||
button.try_cast::<ComponentButton>()?.set_text(
|
||||
@@ -87,11 +86,9 @@ impl TestbedGeneric {
|
||||
}
|
||||
|
||||
pub fn new(assets: Box<assets::Asset>) -> anyhow::Result<Self> {
|
||||
let lang_provider = WayVRLangProvider::default();
|
||||
|
||||
let globals = WguiGlobals::new(
|
||||
assets,
|
||||
&lang_provider,
|
||||
wgui::globals::Defaults::default(),
|
||||
&WguiFontConfig::default(),
|
||||
PathBuf::new(), // cwd
|
||||
)?;
|
||||
@@ -125,9 +122,8 @@ impl TestbedGeneric {
|
||||
|
||||
let (layout, parser_state) = wgui::parser::new_layout_from_assets(
|
||||
&TestbedGeneric::doc_params(&globals, extra),
|
||||
LayoutParams {
|
||||
&LayoutParams {
|
||||
resize_to_parent: true,
|
||||
theme: Rc::new(WguiTheme::default()),
|
||||
},
|
||||
)?;
|
||||
|
||||
@@ -152,7 +148,7 @@ impl TestbedGeneric {
|
||||
parser_state.fetch_component_as::<ComponentButton>("button_context_menu")?;
|
||||
let button_click_me = parser_state.fetch_component_as::<ComponentButton>("button_click_me")?;
|
||||
let button = button_click_me.clone();
|
||||
button_click_me.on_click(Rc::new(move |common, _e| {
|
||||
button_click_me.on_click(Box::new(move |common, _e| {
|
||||
button.set_text(common, Translation::from_raw_text("congrats!"));
|
||||
Ok(())
|
||||
}));
|
||||
@@ -173,7 +169,7 @@ impl TestbedGeneric {
|
||||
let cb_first = parser_state.fetch_component_as::<ComponentCheckbox>("cb_first")?;
|
||||
let label = label_cur_option.widget.clone();
|
||||
cb_first.on_toggle(Box::new(move |common, e| {
|
||||
let mut widget = label.cast::<WidgetLabel>()?;
|
||||
let mut widget = label.get_as::<WidgetLabel>().unwrap();
|
||||
let text = format!("checkbox toggle: {}", e.checked);
|
||||
widget.set_text(common, Translation::from_raw_text(&text));
|
||||
Ok(())
|
||||
@@ -192,7 +188,7 @@ impl TestbedGeneric {
|
||||
|
||||
button_popup.on_click({
|
||||
let tasks = testbed.tasks.clone();
|
||||
Rc::new(move |_, _| {
|
||||
Box::new(move |_, _| {
|
||||
tasks.push(TestbedTask::ShowPopup);
|
||||
Ok(())
|
||||
})
|
||||
@@ -200,7 +196,7 @@ impl TestbedGeneric {
|
||||
|
||||
button_context_menu.on_click({
|
||||
let tasks = testbed.tasks.clone();
|
||||
Rc::new(move |_common, m| {
|
||||
Box::new(move |_common, m| {
|
||||
tasks.push(TestbedTask::ShowContextMenu(m.boundary.bottom_left()));
|
||||
Ok(())
|
||||
})
|
||||
|
||||
+8
-11
@@ -1,4 +1,3 @@
|
||||
use anyhow::Context;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use vulkano::{
|
||||
device::{
|
||||
@@ -6,7 +5,6 @@ use vulkano::{
|
||||
physical::{PhysicalDevice, PhysicalDeviceType},
|
||||
},
|
||||
instance::{Instance, InstanceCreateInfo},
|
||||
swapchain::ColorSpace,
|
||||
};
|
||||
use wgui::gfx::WGfx;
|
||||
|
||||
@@ -23,7 +21,6 @@ pub fn init_window(
|
||||
winit::event_loop::EventLoop<()>,
|
||||
Arc<winit::window::Window>,
|
||||
Arc<vulkano::swapchain::Surface>,
|
||||
ColorSpace,
|
||||
)> {
|
||||
use vulkano::{instance::InstanceCreateFlags, swapchain::Surface};
|
||||
use winit::{event_loop::EventLoop, window::Window};
|
||||
@@ -93,12 +90,6 @@ pub fn init_window(
|
||||
log::info!("img_filter_cubic!");
|
||||
}
|
||||
|
||||
let (format, color_space) = physical_device
|
||||
.surface_formats(&surface, vulkano::swapchain::SurfaceInfo::default())?
|
||||
.into_iter()
|
||||
.find(|(format, _)| !format.components().iter().any(|b| *b > 8))
|
||||
.context("Could not read or no suitable surface formats for PhysicalDevice")?;
|
||||
|
||||
let (device, queues) = Device::new(
|
||||
physical_device,
|
||||
DeviceCreateInfo {
|
||||
@@ -122,8 +113,14 @@ pub fn init_window(
|
||||
|
||||
let (queue_gfx, queue_xfer, _) = unwrap_queues(queues.collect());
|
||||
|
||||
let me = WGfx::new_from_raw(instance, device, queue_gfx, queue_xfer, format);
|
||||
Ok((me, event_loop, window, surface, color_space))
|
||||
let me = WGfx::new_from_raw(
|
||||
instance,
|
||||
device,
|
||||
queue_gfx,
|
||||
queue_xfer,
|
||||
vulkano::format::Format::R8G8B8A8_SRGB,
|
||||
);
|
||||
Ok((me, event_loop, window, surface))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -8,7 +8,7 @@ authors = ["galister", "oo8dev"]
|
||||
repository = "https://github.com/wlx-team/wayvr"
|
||||
|
||||
[dependencies]
|
||||
bytes = "1.11.1"
|
||||
bytes = "1.9.0"
|
||||
smallvec = "1.13.2"
|
||||
serde.workspace = true
|
||||
anyhow = "1.0.93"
|
||||
|
||||
+5
-1
@@ -2,4 +2,8 @@
|
||||
|
||||
This repository contains the IPC specification and client implementation for the WayVR protocol. The primary purpose is to enable communication between applications and the WayVR server, offering a range of API functions.
|
||||
|
||||
[Wayvrctl Documentation](https://wayvr.org/docs/various/integrations/wayvr-ipc/)
|
||||
[WayVR Server README](https://github.com/galister/wlx-overlay-s/tree/main/contrib/wayvr)
|
||||
|
||||
## Usage
|
||||
|
||||
[Example WayVR IPC Client usage in WayVR Dashboard (Legacy)](https://github.com/olekolek1000/wayvr-dashboard/blob/master/src-tauri/src/frontend_ipc.rs)
|
||||
@@ -410,16 +410,11 @@ impl WayVRClient {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn fn_wlx_show_hide(client: WayVRClientMutex) -> anyhow::Result<()> {
|
||||
pub async fn fn_wlx_overlay_show_hide(client: WayVRClientMutex) -> anyhow::Result<()> {
|
||||
send_only!(client, &PacketClient::WlxShowHide);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn fn_wlx_switch_set(client: WayVRClientMutex, set: Option<usize>) -> anyhow::Result<()> {
|
||||
send_only!(client, &PacketClient::WlxSwitchSet(set));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn fn_wlx_modify_panel(
|
||||
client: WayVRClientMutex,
|
||||
params: packet_client::WlxModifyPanelParams,
|
||||
|
||||
@@ -68,5 +68,4 @@ pub enum PacketClient {
|
||||
WlxModifyPanel(WlxModifyPanelParams),
|
||||
WlxDeviceHaptics(usize, WlxHapticsParams),
|
||||
WlxShowHide,
|
||||
WlxSwitchSet(Option<usize>),
|
||||
}
|
||||
+11
-9
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "wayvr"
|
||||
version = "26.2.1"
|
||||
version = "26.1.2"
|
||||
edition = "2024"
|
||||
license = "GPL-3.0-only"
|
||||
authors = ["galister", "oo8dev"]
|
||||
@@ -43,9 +43,8 @@ vulkano-shaders.workspace = true
|
||||
xdg.workspace = true
|
||||
|
||||
ash = "^0.38.0" # must match vulkano
|
||||
bytes = { version = "1.11.1" }
|
||||
chrono = { version = "0.4.42", features = [ "unstable-locales"]}
|
||||
pure-rust-locales = "0.8.2"
|
||||
bytes = { version = "1.11.0" }
|
||||
chrono = "0.4.42"
|
||||
chrono-tz = "0.10.4"
|
||||
config = "0.15.19"
|
||||
dbus = { version = "0.9.9" }
|
||||
@@ -58,14 +57,17 @@ interprocess = { version = "2.2.3" }
|
||||
json = { version = "0.12.4", optional = true }
|
||||
json5 = "1.3.0"
|
||||
libc = "0.2.178"
|
||||
libmonado = { version = "1.6.0", optional = true }
|
||||
libmonado = { git = "https://github.com/technobaboo/libmonado-rs.git", rev = "26292e5b14663ee2f089f66f0851438a0c00ee67", optional = true }
|
||||
log-panics = { version = "2.1.0", features = ["with-backtrace"] }
|
||||
mint = "0.5.9"
|
||||
openxr = { version = "0.21.0", features = ["linked", "mint"], optional = true }
|
||||
openxr = { version = "0.21.0", features = [
|
||||
"linked",
|
||||
"mint",
|
||||
], optional = true }
|
||||
ovr_overlay = { features = [
|
||||
"ovr_input",
|
||||
"ovr_system",
|
||||
], git = "https://github.com/galister/ovr_overlay_oyasumi", rev = "e477bd2a9e04293ea68c1e7529ef2cb131f32acc", optional = true }
|
||||
], git = "https://github.com/galister/ovr_overlay_oyasumi", rev = "8d62c73d5f17e4210d6d0cd52e7f3953eb9b481a", optional = true }
|
||||
rosc = { version = "0.11.4", optional = true }
|
||||
serde_json5 = "0.2.1"
|
||||
serde_yaml = "0.9.34"
|
||||
@@ -76,7 +78,7 @@ smithay = { version = "0.7.0", default-features = false, features = [
|
||||
"desktop",
|
||||
"xwayland",
|
||||
"wayland_frontend",
|
||||
] }
|
||||
]}
|
||||
sysinfo = { version = "0.37" }
|
||||
thiserror = "2.0"
|
||||
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
|
||||
@@ -93,7 +95,7 @@ xkbcommon = { version = "0.8.0" } # 0.9.0 breaks keymap import on some distros
|
||||
regex.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["openvr", "openxr", "osc", "x11", "wayland"]
|
||||
default = ["openvr", "openxr", "osc", "x11", "wayland" ]
|
||||
openvr = ["dep:ovr_overlay", "dep:json"]
|
||||
openxr = ["dep:openxr", "dep:libmonado"]
|
||||
osc = ["dep:rosc"]
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/galister/wlx-overlay-s/refs/heads/guide/wayvr/logo.svg" height="120"/>
|
||||
</p>
|
||||
|
||||
# Overview
|
||||
|
||||
### Features
|
||||
|
||||
- Display Wayland applications without GPU overhead (zero-copy via dma-buf)
|
||||
- Mouse and keyboard input, with precision scrolling support
|
||||
- Tested on AMD and Nvidia
|
||||
|
||||
### Supported software
|
||||
|
||||
- Basically all Qt and GTK applications (they work out of the box)
|
||||
- Most XWayland applications via `cage`
|
||||
|
||||
### Launching external apps inside WayVR
|
||||
|
||||
To launch your app externally:
|
||||
|
||||
```sh
|
||||
DISPLAY= WAYLAND_DISPLAY=wayland-$(cat $XDG_RUNTIME_DIR/wayvr.disp) yourapp
|
||||
```
|
||||
|
||||
or (in the most cases):
|
||||
|
||||
```
|
||||
DISPLAY= WAYLAND_DISPLAY=wayland-20 yourapp
|
||||
```
|
||||
|
||||
Setting `DISPLAY` to an empty string forces various apps to use Wayland instead of X11.
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
### My application doesn't launch but others do!
|
||||
|
||||
Even though some applications support Wayland, some still check for the `DISPLAY` environment variable and an available X11 server, throwing an error. This can also be fixed by running `cage` on top of them.
|
||||
|
||||
### Floating windows
|
||||
|
||||
Context menus are not functional in most cases yet, including drag & drop support.
|
||||
@@ -72,10 +72,6 @@
|
||||
<div id="pos_align_to_hmd" >
|
||||
<CheckBox id="align_box" translation="EDIT_MODE.ALIGN_TO_HMD" tooltip_side="bottom" />
|
||||
</div>
|
||||
<div id="pos_global" gap="16" justify_content="center" align_items="center">
|
||||
<CheckBox id="global_box" translation="EDIT_MODE.GLOBAL" tooltip_side="bottom" />
|
||||
<CheckBox id="block_input_box" translation="EDIT_MODE.BLOCK_INPUT" tooltip_side="bottom" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="tab_stereo" display="none" height="100" flex_direction="column">
|
||||
@@ -86,10 +82,6 @@
|
||||
<PosButton id="stereo_topbottom" src="edit/3d_topbottom.svg" tooltip="EDIT_MODE.STEREO_3D_MODE.SPLIT_TOP_BOTTOM" press="::EditModeSetStereo topbottom" />
|
||||
<PosButton id="stereo_bottomtop" src="edit/3d_bottomtop.svg" tooltip="EDIT_MODE.STEREO_3D_MODE.SPLIT_BOTTOM_TOP" press="::EditModeSetStereo bottomtop" />
|
||||
</div>
|
||||
<div justify_content="center" align_items="center" flex_direction="row" gap="16">
|
||||
<CheckBox id="stereo_full_frame_box" translation="EDIT_MODE.STEREO_3D_MODE.FULL_FRAME" tooltip_side="top" />
|
||||
<CheckBox id="stereo_adjust_mouse_box" translation="EDIT_MODE.STEREO_3D_MODE.ADJUST_MOUSE" tooltip_side="top" />
|
||||
</div>
|
||||
</div>
|
||||
<div id="tab_mouse" display="none" height="100" flex_direction="column">
|
||||
<div padding_left="8" padding_right="8" gap="8" justify_content="center" align_items="center">
|
||||
@@ -114,7 +106,6 @@
|
||||
</div>
|
||||
<div width="100%" padding="8" gap="8" justify_content="center" align_items="center">
|
||||
<CheckBox id="additive_box" translation="EDIT_MODE.BLENDING_ADDITIVE" tooltip="EDIT_MODE.ALPHA_BLEND_MODE" tooltip_side="bottom" />
|
||||
<CheckBox id="angle_fade_box" translation="EDIT_MODE.ANGLE_FADE" tooltip="EDIT_MODE.ANGLE_FADE_HELP" tooltip_side="bottom" />
|
||||
</div>
|
||||
</div>
|
||||
<div id="tab_curve" display="none" height="100" flex_direction="column">
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
<template name="KeySymbol">
|
||||
<div macro="keycap_div">
|
||||
<rectangle id="${id}" macro="keycap_rect" gap="3">
|
||||
<label color="~color_text_translucent" text="${text_shift}" size="18" />
|
||||
<label color="~color_text_translucent" text="${text_shift}" size="1" />
|
||||
<label text="${text}" size="18" />
|
||||
</rectangle>
|
||||
</div>
|
||||
@@ -107,7 +107,7 @@
|
||||
|
||||
<!-- An app with a single icon. -->
|
||||
<template name="App">
|
||||
<Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _context_name="${name}" _press_left="::ContextMenuOpen menu_app" _press_right="::OverlayToggle ${name}">
|
||||
<Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _context_name="${name}" _press="::ContextMenuOpen menu_app">
|
||||
<sprite width="38" height="38" color="~color_text" src_ext="${icon}" />
|
||||
</Button>
|
||||
</template>
|
||||
@@ -122,7 +122,7 @@
|
||||
|
||||
<!-- A screen with a shortened connector name, e.g. "H1" for HDMI-A-1 or "D2" for DP-2 -->
|
||||
<template name="Screen">
|
||||
<Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _context_name="${name}" _press_left="::ContextMenuOpen menu_screen" _press_right="::OverlayToggle ${name}">
|
||||
<Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _context_name="${name}" _press="::ContextMenuOpen menu_screen">
|
||||
<sprite width="38" height="38" color="~color_text" src_builtin="edit/screen.svg" />
|
||||
<div position="absolute" margin_top="-7" margin_left="-1">
|
||||
<label text="${display}" size="18" color="~color_faded_20" weight="bold" />
|
||||
@@ -140,7 +140,7 @@
|
||||
</blueprint>
|
||||
|
||||
<template name="Panel">
|
||||
<Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _context_name="${name}" _press_left="::ContextMenuOpen menu_panel" _press_right="::OverlayToggle ${name}">
|
||||
<Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _context_name="${name}" _press="::ContextMenuOpen menu_panel">
|
||||
<sprite width="38" height="38" color="~color_text" src="${icon}" />
|
||||
</Button>
|
||||
</template>
|
||||
@@ -155,7 +155,7 @@
|
||||
</blueprint>
|
||||
|
||||
<template name="Mirror">
|
||||
<Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _context_name="${name}" _press_left="::ContextMenuOpen menu_mirror" _press_right="::OverlayToggle ${name}">
|
||||
<Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _context_name="${name}" _press="::ContextMenuOpen menu_mirror">
|
||||
<sprite width="38" height="38" color="~color_text" src_builtin="edit/mirror.svg" />
|
||||
<div position="absolute" margin_top="5" margin_left="13">
|
||||
<label text="${display}" size="20" color="~color_faded_20" weight="bold" />
|
||||
@@ -163,21 +163,10 @@
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<blueprint name="menu_handsfree">
|
||||
<context_menu >
|
||||
<cell translation="BAR.HANDSFREE.NONE" _press="::HandsfreeMode None" _press2="::ContextMenuClose" />
|
||||
<cell translation="BAR.HANDSFREE.HMD" _press="::HandsfreeMode Hmd" _press2="::ContextMenuClose" />
|
||||
<cell translation="BAR.HANDSFREE.HMD_ONLY" _press="::HandsfreeMode HmdOnly" _press2="::ContextMenuClose" />
|
||||
<cell translation="BAR.HANDSFREE.EYE_TRACKING" _press="::HandsfreeMode EyeTracking" _press2="::ContextMenuClose" />
|
||||
<cell translation="BAR.HANDSFREE.EYE_ONLY" _press="::HandsfreeMode EyeTrackingOnly" _press2="::ContextMenuClose" />
|
||||
</context_menu>
|
||||
</blueprint>
|
||||
|
||||
<blueprint name="menu_burger">
|
||||
<context_menu >
|
||||
<cell translation="BAR.EDIT_MODE_TOGGLE" _press="::EditToggle" _press2="::ContextMenuClose" />
|
||||
<cell translation="BAR.HANDSFREE.TITLE" _press="::ContextMenuOpen menu_handsfree" />
|
||||
<cell translation="BAR.ADD_MIRROR" _press="::NewMirror" />
|
||||
<cell translation="BAR.EDIT_MODE_TOGGLE" _press="::EditToggle" />
|
||||
<cell translation="BAR.ADD_NEW_SET" _press="::AddSet" />
|
||||
<cell translation="BAR.DELETE_CURRENT_SET" _press="::DeleteSet" />
|
||||
</context_menu>
|
||||
@@ -192,14 +181,6 @@
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<template name="Clock">
|
||||
<div flex_direction="column" gap="3" align_items="center">
|
||||
<label text="23:59" _source="clock" _display="time" size="21" weight="bold" />
|
||||
<label text="Tuesday" _source="clock" _display="dow" size="15" />
|
||||
<label text="22/2/2022" _source="clock" _display="date" size="15" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<elements>
|
||||
<div flex_direction="column" interactable="0">
|
||||
<rectangle macro="bg_rect" padding="10" align_items="center" justify_content="space_between">
|
||||
@@ -226,8 +207,10 @@
|
||||
|
||||
<VerticalSeparator />
|
||||
|
||||
<div id="clock_root">
|
||||
<Clock />
|
||||
<div flex_direction="column" gap="3" align_items="center">
|
||||
<label text="23:59" _source="clock" _display="time" size="21" weight="bold" />
|
||||
<label text="Tuesday" _source="clock" _display="dow" size="15" />
|
||||
<label text="22/2/2022" _source="clock" _display="date" size="15" />
|
||||
</div>
|
||||
</div>
|
||||
</rectangle>
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
|
||||
<template name="Set">
|
||||
<Button macro="button_style" id="set_${idx}" _press="::SetToggle ${idx}" tooltip="WATCH.SWITCH_TO_SET" tooltip_side="top">
|
||||
<sprite width="38" height="38" color="~color_text" src_builtin="watch/set2.svg" />
|
||||
<sprite width="40" height="40" color="~color_text" src_builtin="watch/set2.svg" />
|
||||
<div position="absolute" margin_top="9">
|
||||
<label text="${display}" size="24" color="#00050F" weight="bold" />
|
||||
</div>
|
||||
|
||||
@@ -12,15 +12,7 @@
|
||||
"RELOAD_FROM_DISK": "XML-Datei von der Festplatte neu laden",
|
||||
"CLOSE_MIRROR": "Spiegel schließen",
|
||||
"CLOSE_APP": "App schließen",
|
||||
"FORCE_CLOSE_APP": "App zwangsweise schließen",
|
||||
"HANDSFREE": {
|
||||
"TITLE": "Freihändig-Modus",
|
||||
"NONE": "Aus",
|
||||
"HMD": "HMD + Kneifen",
|
||||
"EYE_TRACKING": "Auge + Kneifen",
|
||||
"HMD_ONLY": "Nur HMD",
|
||||
"EYE_ONLY": "Nur Augen"
|
||||
}
|
||||
"FORCE_CLOSE_APP": "App zwangsweise schließen"
|
||||
},
|
||||
"WATCH": {
|
||||
"RECENTER": "Spielbereich neu zentrieren",
|
||||
@@ -62,12 +54,7 @@
|
||||
"SPLIT_LEFT_RIGHT": "LINKS→RECHTS",
|
||||
"SPLIT_RIGHT_LEFT": "RECHTS→LINKS",
|
||||
"SPLIT_TOP_BOTTOM": "OBEN→UNTEN",
|
||||
"SPLIT_BOTTOM_TOP": "UNTEN→OBEN",
|
||||
"FULL_FRAME": "Voll 3D",
|
||||
"FULL_FRAME_SBS": "Full-SBS",
|
||||
"FULL_FRAME_TAB": "Full-TAB",
|
||||
"FULL_FRAME_BAT": "Full-BAT",
|
||||
"ADJUST_MOUSE": "Maus anpassen"
|
||||
"SPLIT_BOTTOM_TOP": "UNTEN→OBEN"
|
||||
},
|
||||
"STEREO_3D_MODE_": {},
|
||||
"ALIGN_TO_HMD": "An HMD ausrichten",
|
||||
@@ -82,11 +69,7 @@
|
||||
"ROTATE90": "90° gedreht",
|
||||
"ROTATE180": "180° gedreht",
|
||||
"ROTATE270": "270° gedreht"
|
||||
},
|
||||
"GLOBAL": "Immer sichtbar",
|
||||
"ANGLE_FADE": "Winkelübergang",
|
||||
"ANGLE_FADE_HELP": "Ausblenden, wenn nicht auf das Headset gerichtet",
|
||||
"BLOCK_INPUT": "Blockiere Spieleingabe"
|
||||
}
|
||||
},
|
||||
"DISABLED": "Deaktiviert",
|
||||
"DEFAULT": "Standard",
|
||||
@@ -109,10 +92,9 @@
|
||||
"LAST_EXISTING_SET": "Dies ist das letzte vorhandene Set.",
|
||||
"EMPTY_SET": "Leeres Set!",
|
||||
"LETS_ADD_OVERLAYS": "Lass uns ein paar Overlays von der Uhr hinzufügen!",
|
||||
"FIXING_FLOOR": "Boden wird in 5 Sekunden fixiert...",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "Lege einen Controller auf den Boden!",
|
||||
"CANNOT_ADD_SET": "Satz kann nicht hinzugefügt werden!",
|
||||
"MAXIMUM_SETS_REACHED": "Maximale Anzahl an Sets erreicht.",
|
||||
"FIXING_FLOOR_IN_X_SECS": "Boden wird in {SECONDS} Sekunde(n) repariert..."
|
||||
},
|
||||
"DONE": "Erledigt"
|
||||
"MAXIMUM_SETS_REACHED": "Maximale Anzahl an Sets erreicht."
|
||||
}
|
||||
}
|
||||
@@ -4,56 +4,31 @@
|
||||
},
|
||||
"BAR": {
|
||||
"ADD_MIRROR": "Add a new mirror overlay",
|
||||
"ADD_NEW_SET": "Add new set",
|
||||
"CLOSE_APP": "Close app",
|
||||
"CLOSE_MIRROR": "Close mirror",
|
||||
"DELETE_CURRENT_SET": "Delete current set",
|
||||
"EDIT_MODE_TOGGLE": "Toggle edit mode",
|
||||
"FORCE_CLOSE_APP": "Force close app",
|
||||
"HANDSFREE": {
|
||||
"EYE_ONLY": "Eye only",
|
||||
"EYE_TRACKING": "Eye + pinch",
|
||||
"HMD": "HMD + pinch",
|
||||
"HMD_ONLY": "HMD only",
|
||||
"NONE": "Off",
|
||||
"TITLE": "Handsfree mode"
|
||||
},
|
||||
"RELOAD_FROM_DISK": "Reload XML from disk",
|
||||
"ADD_NEW_SET": "Add new set",
|
||||
"DELETE_CURRENT_SET": "Delete current set",
|
||||
"TOGGLE_VISIBILITY": "Toggle visibility",
|
||||
"RESET_POSITION": "Reset position",
|
||||
"TOGGLE_VISIBILITY": "Toggle visibility"
|
||||
"RELOAD_FROM_DISK": "Reload XML from disk",
|
||||
"CLOSE_MIRROR": "Close mirror",
|
||||
"CLOSE_APP": "Close app",
|
||||
"FORCE_CLOSE_APP": "Force close app"
|
||||
},
|
||||
"DEFAULT": "Default",
|
||||
"DISABLED": "Disabled",
|
||||
"DONE": "Done",
|
||||
"EDIT_MODE": {
|
||||
"ADJUST_CURVATURE": "Adjust curvature",
|
||||
"ALIGN_TO_HMD": "Align to HMD",
|
||||
"ALPHA_BLEND_MODE": "Alpha blend mode",
|
||||
"ANGLE_FADE": "Angle fade",
|
||||
"ANGLE_FADE_HELP": "Fade when not facing HMD",
|
||||
"BLENDING_ADDITIVE": "Additive blending",
|
||||
"BLOCK_INPUT": "Block game input",
|
||||
"CURVATURE": "Curvature",
|
||||
"DELETE": "Long press to remove from current set",
|
||||
"DISABLE_GRAB": "Disable grab",
|
||||
"GLOBAL": "Always visible",
|
||||
"HINT_POINT_WINDOW": "Point at a window to change its parameters.\nOnce done, leave edit mode using the button on the right.",
|
||||
"INTERPOLATION": "Interpolation",
|
||||
"ALIGN_TO_HMD": "Align to HMD",
|
||||
"KEYBOARD": "Keyboard",
|
||||
"LEAVE": "Leave edit mode",
|
||||
"LOCK_INTERACTION": "Lock interaction",
|
||||
"MOUSE": {
|
||||
"FLIP180": "Flipped 180°",
|
||||
"FLIP270": "Flipped 270°",
|
||||
"FLIP90": "Flipped 90°",
|
||||
"FLIPPED": "Flipped",
|
||||
"NORMAL": "Normal",
|
||||
"ROTATE180": "Rotated 180°",
|
||||
"ROTATE270": "Rotated 270°",
|
||||
"ROTATE90": "Rotated 90°",
|
||||
"TITLE": "Mouse fixes",
|
||||
"WRONG_SCREEN_SELECTION_HELP": "If the cursor moves on a completely different screen,\nthe screens were likely selected wrong. See readme."
|
||||
},
|
||||
"MOVE_PRESS_AND_DRAG": "Move (press & drag)",
|
||||
"OPACITY": "Opacity",
|
||||
"POS_ANCHORED": "Anchored: Moves with center marker. Default.",
|
||||
@@ -65,40 +40,47 @@
|
||||
"POSITIONING": "Positioning",
|
||||
"RESIZE_PRESS_AND_DRAG": "Resize (press & drag)",
|
||||
"STEREO_3D_MODE": {
|
||||
"ADJUST_MOUSE": "Adjust mouse",
|
||||
"FULL_FRAME": "Full 3D",
|
||||
"FULL_FRAME_BAT": "Full-BAT",
|
||||
"FULL_FRAME_SBS": "Full-SBS",
|
||||
"FULL_FRAME_TAB": "Full-TAB",
|
||||
"SPLIT_BOTTOM_TOP": "BOTTOM→TOP",
|
||||
"SPLIT_LEFT_RIGHT": "LEFT→RIGHT",
|
||||
"SPLIT_RIGHT_LEFT": "RIGHT→LEFT",
|
||||
"SPLIT_TOP_BOTTOM": "TOP→BOTTOM",
|
||||
"TITLE": "3D Stereo Mode"
|
||||
},
|
||||
"MOUSE": {
|
||||
"TITLE": "Mouse fixes",
|
||||
"WRONG_SCREEN_SELECTION_HELP": "If the cursor moves on a completely different screen,\nthe screens were likely selected wrong. See readme.",
|
||||
"NORMAL": "Normal",
|
||||
"ROTATE90": "Rotated 90°",
|
||||
"ROTATE180": "Rotated 180°",
|
||||
"ROTATE270": "Rotated 270°",
|
||||
"FLIPPED": "Flipped",
|
||||
"FLIP90": "Flipped 90°",
|
||||
"FLIP180": "Flipped 180°",
|
||||
"FLIP270": "Flipped 270°"
|
||||
}
|
||||
},
|
||||
"GRAB": {
|
||||
"ADJUST_DISTANCE": "Adjust distance",
|
||||
"ADJUST_SIZE": "Adjust size",
|
||||
"UNRESTRICTED_MOVEMENT": "Unrestricted movement",
|
||||
"GRABBING_WATCH": "To swap hands, move the watch in front and grab it with the other hand.",
|
||||
"GRABBING_STATIC": "This overlay is Static and will stay in place, ignoring recenter.",
|
||||
"GRABBING_ANCHORED": "Anchored overlays all move together. Separate a single window by grabbing it with the other hand while still grabbing the anchor.",
|
||||
"GRABBING_ANCHORED_EDIT": "This overlay will stay anchored to the center marker.",
|
||||
"GRABBING_FLOATING": "This overlay is Floating and will stay in place, unless recentered.",
|
||||
"GRABBING_FOLLOW": "This overlay will follow the device it is attached to.",
|
||||
"GRABBING_STATIC": "This overlay is Static and will stay in place, ignoring recenter.",
|
||||
"GRABBING_WATCH": "To swap hands, move the watch in front and grab it with the other hand.",
|
||||
"UNRESTRICTED_MOVEMENT": "Unrestricted movement"
|
||||
"GRABBING_FOLLOW": "This overlay will follow the device it is attached to."
|
||||
},
|
||||
"TOAST": {
|
||||
"CANNOT_ADD_SET": "Cannot add set!",
|
||||
"CANNOT_REMOVE_SET": "Cannot remove set!",
|
||||
"DEFAULT_TITLE": "Notification",
|
||||
"EMPTY_SET": "Empty set!",
|
||||
"ERROR": "Error",
|
||||
"FIXING_FLOOR_IN_X_SECS": "Fixing floor in {SECONDS} second(s)...",
|
||||
"LAST_EXISTING_SET": "This is the last existing set.",
|
||||
"LETS_ADD_OVERLAYS": "Let's add some overlays from the watch!",
|
||||
"CANNOT_ADD_SET": "Cannot add set!",
|
||||
"MAXIMUM_SETS_REACHED": "Maximum number of sets reached.",
|
||||
"CANNOT_REMOVE_SET": "Cannot remove set!",
|
||||
"NO_SET_SELECTED": "No set is selected.",
|
||||
"LAST_EXISTING_SET": "This is the last existing set.",
|
||||
"EMPTY_SET": "Empty set!",
|
||||
"LETS_ADD_OVERLAYS": "Let's add some overlays from the watch!",
|
||||
"FIXING_FLOOR": "Fixing floor in 5 seconds...",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "Place one controller on the floor!"
|
||||
},
|
||||
"WATCH": {
|
||||
|
||||
@@ -12,15 +12,7 @@
|
||||
"RELOAD_FROM_DISK": "Volver a cargar XML desde el disco",
|
||||
"CLOSE_MIRROR": "Cerrar espejo",
|
||||
"CLOSE_APP": "Cerrar aplicación",
|
||||
"FORCE_CLOSE_APP": "Forzar cierre de la aplicación",
|
||||
"HANDSFREE": {
|
||||
"TITLE": "Modo manos libres",
|
||||
"NONE": "Desactivado",
|
||||
"HMD": "HMD + pellizco",
|
||||
"EYE_TRACKING": "Ojo + pellizco",
|
||||
"HMD_ONLY": "Solo HMD",
|
||||
"EYE_ONLY": "Solo ojos"
|
||||
}
|
||||
"FORCE_CLOSE_APP": "Forzar cierre de la aplicación"
|
||||
},
|
||||
"WATCH": {
|
||||
"RECENTER": "Recentrar el área de juego",
|
||||
@@ -62,12 +54,7 @@
|
||||
"SPLIT_LEFT_RIGHT": "IZQ→DER",
|
||||
"SPLIT_RIGHT_LEFT": "DERECHA→IZQUIERDA",
|
||||
"SPLIT_TOP_BOTTOM": "ARRIBA→ABAJO",
|
||||
"SPLIT_BOTTOM_TOP": "ABAJO→ARRIBA",
|
||||
"FULL_FRAME": "3D completo",
|
||||
"FULL_FRAME_SBS": "Full-SBS",
|
||||
"FULL_FRAME_TAB": "Full-TAB",
|
||||
"FULL_FRAME_BAT": "Full-BAT",
|
||||
"ADJUST_MOUSE": "Ajustar ratón"
|
||||
"SPLIT_BOTTOM_TOP": "ABAJO→ARRIBA"
|
||||
},
|
||||
"STEREO_3D_MODE_": {},
|
||||
"ALIGN_TO_HMD": "Alinear al HMD",
|
||||
@@ -82,11 +69,7 @@
|
||||
"ROTATE90": "Rotado 90°",
|
||||
"ROTATE180": "Rotado 180°",
|
||||
"ROTATE270": "Rotado 270°"
|
||||
},
|
||||
"GLOBAL": "Siempre visible",
|
||||
"ANGLE_FADE": "Atenuación angular",
|
||||
"ANGLE_FADE_HELP": "Desvanecer cuando no se mira el HMD",
|
||||
"BLOCK_INPUT": "Bloquear entrada del juego"
|
||||
}
|
||||
},
|
||||
"DISABLED": "Deshabilitado",
|
||||
"DEFAULT": "Por defecto",
|
||||
@@ -109,10 +92,9 @@
|
||||
"LAST_EXISTING_SET": "Este es el último conjunto existente.",
|
||||
"EMPTY_SET": "¡Conjunto vacío!",
|
||||
"LETS_ADD_OVERLAYS": "¡Añadamos algunos overlays desde el reloj!",
|
||||
"FIXING_FLOOR": "Fijando el suelo en 5 segundos...",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "¡Coloca un mando en el suelo!",
|
||||
"CANNOT_ADD_SET": "¡No se puede agregar el conjunto!",
|
||||
"MAXIMUM_SETS_REACHED": "Se ha alcanzado el número máximo de sets.",
|
||||
"FIXING_FLOOR_IN_X_SECS": "Corrigiendo el suelo en {SECONDS} segundo(s)..."
|
||||
},
|
||||
"DONE": "Hecho"
|
||||
"MAXIMUM_SETS_REACHED": "Se ha alcanzado el número máximo de sets."
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
{
|
||||
"ANCHOR": {
|
||||
"CENTER": "Centro"
|
||||
},
|
||||
"BAR": {
|
||||
"ADD_MIRROR": "Aggiungi un nuovo overlay specchio",
|
||||
"EDIT_MODE_TOGGLE": "Attiva/disattiva modalità modifica",
|
||||
"ADD_NEW_SET": "Aggiungi nuovo set",
|
||||
"DELETE_CURRENT_SET": "Elimina set attuale",
|
||||
"TOGGLE_VISIBILITY": "Alterna visibilità",
|
||||
"RESET_POSITION": "Ripristina posizione",
|
||||
"RELOAD_FROM_DISK": "Ricarica XML dal disco",
|
||||
"CLOSE_MIRROR": "Chiudi mirror",
|
||||
"CLOSE_APP": "Chiudi app",
|
||||
"FORCE_CLOSE_APP": "Chiudi forzatamente l'app",
|
||||
"HANDSFREE": {
|
||||
"TITLE": "Modalità a mani libere",
|
||||
"NONE": "Disattivato",
|
||||
"HMD": "HMD + pizzico",
|
||||
"EYE_TRACKING": "Occhio + pizzico",
|
||||
"HMD_ONLY": "Solo HMD",
|
||||
"EYE_ONLY": "Solo occhi"
|
||||
}
|
||||
},
|
||||
"DEFAULT": "Predefinito",
|
||||
"DISABLED": "Disabilitato",
|
||||
"EDIT_MODE": {
|
||||
"ADJUST_CURVATURE": "Regola curvatura",
|
||||
"ALPHA_BLEND_MODE": "Modalità di fusione Alpha",
|
||||
"BLENDING_ADDITIVE": "Blending additivo",
|
||||
"CURVATURE": "Curvatura",
|
||||
"DELETE": "Tenere premuto per rimuovere dal set corrente",
|
||||
"DISABLE_GRAB": "Disabilita l'afferra",
|
||||
"HINT_POINT_WINDOW": "Punta a una finestra per modificarne i parametri.\nUna volta fatto, esci dalla modalità di modifica usando il pulsante a destra.",
|
||||
"INTERPOLATION": "Interpolazione",
|
||||
"ALIGN_TO_HMD": "Allinea all'HMD",
|
||||
"KEYBOARD": "Tastiera",
|
||||
"LEAVE": "Esci dalla modalità di modifica",
|
||||
"LOCK_INTERACTION": "Blocca interazione",
|
||||
"MOVE_PRESS_AND_DRAG": "Muovi (premi e trascina)",
|
||||
"OPACITY": "Opacità",
|
||||
"POS_ANCHORED": "Ancorato: si muove con il marker centrale. Predefinito.",
|
||||
"POS_FLOATING": "Galleggiante: si muove indipendentemente, si recentra quando viene visualizzato.",
|
||||
"POS_HAND_L": "Segui la mano sinistra.",
|
||||
"POS_HAND_R": "Segui la mano destra.",
|
||||
"POS_HMD": "Segui l'HMD.",
|
||||
"POS_STATIC": "Statico: non fa parte di alcun set, senza recentratura.",
|
||||
"POSITIONING": "Posizionamento",
|
||||
"RESIZE_PRESS_AND_DRAG": "Ridimensiona (premi e trascina)",
|
||||
"STEREO_3D_MODE": {
|
||||
"SPLIT_BOTTOM_TOP": "DAL BASSO→IN ALTO",
|
||||
"SPLIT_LEFT_RIGHT": "SINISTRA→DESTRA",
|
||||
"SPLIT_RIGHT_LEFT": "DESTRA→SINISTRA",
|
||||
"SPLIT_TOP_BOTTOM": "TOP→BOTTOM",
|
||||
"TITLE": "Modalità Stereo 3D",
|
||||
"FULL_FRAME": "3D Completo",
|
||||
"FULL_FRAME_SBS": "Full-SBS",
|
||||
"FULL_FRAME_TAB": "Full-TAB",
|
||||
"FULL_FRAME_BAT": "Full-BAT",
|
||||
"ADJUST_MOUSE": "Regola il mouse"
|
||||
},
|
||||
"MOUSE": {
|
||||
"TITLE": "Correzioni del mouse",
|
||||
"WRONG_SCREEN_SELECTION_HELP": "Se il cursore si sposta su uno schermo completamente diverso,\nprobabilmente gli schermi sono stati selezionati in modo errato. Consulta il file readme.",
|
||||
"NORMAL": "Normale",
|
||||
"ROTATE90": "Ruotato di 90°",
|
||||
"ROTATE180": "Ruotato di 180°",
|
||||
"ROTATE270": "Ruotato di 270°",
|
||||
"FLIPPED": "Capovolto",
|
||||
"FLIP90": "Ruotato di 90°",
|
||||
"FLIP180": "Ruotato di 180°",
|
||||
"FLIP270": "Ruotato di 270°"
|
||||
},
|
||||
"GLOBAL": "Sempre visibile",
|
||||
"ANGLE_FADE": "Dissolvenza angolare",
|
||||
"ANGLE_FADE_HELP": "Sfumatura quando non si guarda l'HMD",
|
||||
"BLOCK_INPUT": "Blocca l'input del gioco"
|
||||
},
|
||||
"GRAB": {
|
||||
"ADJUST_DISTANCE": "Regola la distanza",
|
||||
"ADJUST_SIZE": "Regola dimensione",
|
||||
"UNRESTRICTED_MOVEMENT": "Movimento libero",
|
||||
"GRABBING_WATCH": "Per cambiare mano, sposta il watch davanti a te e afferralo con l'altra mano.",
|
||||
"GRABBING_STATIC": "Questo overlay è Statico e rimarrà in posizione, ignorando il recenter.",
|
||||
"GRABBING_ANCHORED": "Gli overlay ancorati si muovono tutti insieme. Separa una singola finestra afferrandola con l'altra mano mentre tieni ancora l'ancora.",
|
||||
"GRABBING_ANCHORED_EDIT": "Questo overlay rimarrà ancorato al marcatore centrale.",
|
||||
"GRABBING_FLOATING": "Questo overlay è mobile e rimarrà in posizione, a meno che non venga recentrato.",
|
||||
"GRABBING_FOLLOW": "Questo overlay seguirà il dispositivo a cui è collegato."
|
||||
},
|
||||
"TOAST": {
|
||||
"DEFAULT_TITLE": "Notifica",
|
||||
"ERROR": "Errore",
|
||||
"CANNOT_ADD_SET": "Impossibile aggiungere il set!",
|
||||
"MAXIMUM_SETS_REACHED": "Numero massimo di set raggiunto.",
|
||||
"CANNOT_REMOVE_SET": "Impossibile rimuovere il set!",
|
||||
"NO_SET_SELECTED": "Nessun set è selezionato.",
|
||||
"LAST_EXISTING_SET": "Questo è l'ultimo set esistente.",
|
||||
"EMPTY_SET": "Set vuoto!",
|
||||
"LETS_ADD_OVERLAYS": "Aggiungiamo degli overlay dal Watch!",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "Posiziona un controller a terra!",
|
||||
"FIXING_FLOOR_IN_X_SECS": "Correzione del pavimento in {SECONDS} secondo/i..."
|
||||
},
|
||||
"WATCH": {
|
||||
"ADD_NEW_SET": "Aggiungi un nuovo set",
|
||||
"CLEANUP_MIRRORS": "Rimuovi gli specchi che\nnon sono attualmente visibili",
|
||||
"DASHBOARD": "Pannello di controllo",
|
||||
"EDIT_MODE": "Modalità modifica",
|
||||
"EDIT_MODE_EXPLANATION": "Gli overlay possono ora essere spostati e regolati individualmente.\nControlla quali overlay sono visibili in questo set:",
|
||||
"FIX_FLOOR": "Fissa il livello del pavimento",
|
||||
"LONG_PRESS_TO_DELETE_SET": "Tenere premuto per eliminare",
|
||||
"RECENTER": "Recentra l'area di gioco",
|
||||
"SWITCH_TO_SET": "Passa a set",
|
||||
"TOGGLE_FOR_CURRENT_SET": "Attiva/disattiva per il set corrente"
|
||||
},
|
||||
"DONE": "Fatto"
|
||||
}
|
||||
@@ -12,15 +12,7 @@
|
||||
"RELOAD_FROM_DISK": "ディスクからXMLを再読み込み",
|
||||
"CLOSE_MIRROR": "ミラーを閉じる",
|
||||
"CLOSE_APP": "アプリを閉じる",
|
||||
"FORCE_CLOSE_APP": "アプリを強制終了",
|
||||
"HANDSFREE": {
|
||||
"TITLE": "ハンズフリーモード",
|
||||
"NONE": "オフ",
|
||||
"HMD": "HMD + ピンチ",
|
||||
"EYE_TRACKING": "視線 + ピンチ",
|
||||
"HMD_ONLY": "HMDのみ",
|
||||
"EYE_ONLY": "目のみ"
|
||||
}
|
||||
"FORCE_CLOSE_APP": "アプリを強制終了"
|
||||
},
|
||||
"WATCH": {
|
||||
"RECENTER": "プレイスペースをリセンター",
|
||||
@@ -61,12 +53,7 @@
|
||||
"SPLIT_LEFT_RIGHT": "左→右",
|
||||
"SPLIT_RIGHT_LEFT": "右→左",
|
||||
"SPLIT_TOP_BOTTOM": "上→下",
|
||||
"SPLIT_BOTTOM_TOP": "下→上",
|
||||
"FULL_FRAME": "フル3D",
|
||||
"FULL_FRAME_SBS": "フルSBS",
|
||||
"FULL_FRAME_TAB": "フルTAB",
|
||||
"FULL_FRAME_BAT": "フルBAT",
|
||||
"ADJUST_MOUSE": "マウス調整"
|
||||
"SPLIT_BOTTOM_TOP": "下→上"
|
||||
},
|
||||
"ALIGN_TO_HMD": "常にHMDの方を向く",
|
||||
"MOUSE": {
|
||||
@@ -80,11 +67,7 @@
|
||||
"ROTATE90": "回転 90°",
|
||||
"ROTATE180": "回転 180°",
|
||||
"ROTATE270": "回転 270°"
|
||||
},
|
||||
"GLOBAL": "常に表示",
|
||||
"ANGLE_FADE": "角度フェード",
|
||||
"ANGLE_FADE_HELP": "HMDに向いていない時にフェード",
|
||||
"BLOCK_INPUT": "ゲーム入力をブロック"
|
||||
}
|
||||
},
|
||||
"DISABLED": "無効",
|
||||
"DEFAULT": "デフォルト",
|
||||
@@ -107,10 +90,9 @@
|
||||
"LAST_EXISTING_SET": "これが最後の設定です。",
|
||||
"EMPTY_SET": "空のセットです!",
|
||||
"LETS_ADD_OVERLAYS": "ウォッチからオーバーレイを追加しましょう!",
|
||||
"FIXING_FLOOR": "5秒後にフロアを固定します...",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "コントローラーを床に置いてください!",
|
||||
"CANNOT_ADD_SET": "セットを追加できません!",
|
||||
"MAXIMUM_SETS_REACHED": "最大セット数に達しました。",
|
||||
"FIXING_FLOOR_IN_X_SECS": "{SECONDS}秒で床を固定中..."
|
||||
},
|
||||
"DONE": "完了"
|
||||
}
|
||||
"MAXIMUM_SETS_REACHED": "最大セット数に達しました。"
|
||||
}
|
||||
}
|
||||
@@ -12,15 +12,7 @@
|
||||
"RELOAD_FROM_DISK": "Przeładuj XML z dysku",
|
||||
"CLOSE_MIRROR": "Zamknij lustro",
|
||||
"CLOSE_APP": "Zamknij aplikację",
|
||||
"FORCE_CLOSE_APP": "Wymuś zamknięcie aplikacji",
|
||||
"HANDSFREE": {
|
||||
"TITLE": "Tryb wolnych rąk",
|
||||
"NONE": "Wyłączone",
|
||||
"HMD": "HMD + uszczypnięcie",
|
||||
"EYE_TRACKING": "Oko + uszczypnięcie",
|
||||
"HMD_ONLY": "Tylko HMD",
|
||||
"EYE_ONLY": "Tylko oczy"
|
||||
}
|
||||
"FORCE_CLOSE_APP": "Wymuś zamknięcie aplikacji"
|
||||
},
|
||||
"WATCH": {
|
||||
"RECENTER": "Wyśrodkuj przestrzeń gry",
|
||||
@@ -47,7 +39,6 @@
|
||||
"MOVE_PRESS_AND_DRAG": "Przesuń (naciśnij i przeciągnij)",
|
||||
"OPACITY": "Przezroczystość",
|
||||
"POSITIONING": "Pozycjonowanie",
|
||||
"BLOCK_INPUT": "Blokuj input z gry",
|
||||
"RESIZE_PRESS_AND_DRAG": "Zmień rozmiar (naciśnij i przeciągnij)",
|
||||
"POS_STATIC": "Statyczne: Pozostaje w miejscu i nigdy nie jest ponownie wyśrodkowywane.",
|
||||
"POS_ANCHORED": "Zakotwiczone: Porusza się razem z resztą zestawu. Domyślne.",
|
||||
@@ -62,12 +53,7 @@
|
||||
"SPLIT_LEFT_RIGHT": "LEWO→PRAWO",
|
||||
"SPLIT_RIGHT_LEFT": "PRAWO→LEWO",
|
||||
"SPLIT_TOP_BOTTOM": "GÓRA→DÓŁ",
|
||||
"TITLE": "Tryb stereoskopowy 3D",
|
||||
"FULL_FRAME": "Pełny 3D",
|
||||
"FULL_FRAME_SBS": "Full-SBS",
|
||||
"FULL_FRAME_TAB": "Full-TAB",
|
||||
"FULL_FRAME_BAT": "Full-BAT",
|
||||
"ADJUST_MOUSE": "Dostosuj mysz"
|
||||
"TITLE": "Tryb stereoskopowy 3D"
|
||||
},
|
||||
"ALIGN_TO_HMD": "Dopasuj do HMD",
|
||||
"MOUSE": {
|
||||
@@ -81,10 +67,7 @@
|
||||
"FLIP90": "Odwrócony o 90°",
|
||||
"FLIP180": "Odwrócony o 180°",
|
||||
"FLIP270": "Odwrócony o 270°"
|
||||
},
|
||||
"GLOBAL": "Zawsze widoczne",
|
||||
"ANGLE_FADE": "Zanik kątowy",
|
||||
"ANGLE_FADE_HELP": "Wyblaknij, gdy nie patrzysz na HMD"
|
||||
}
|
||||
},
|
||||
"DISABLED": "Wyłączone",
|
||||
"DEFAULT": "Domyślny",
|
||||
@@ -107,10 +90,9 @@
|
||||
"LAST_EXISTING_SET": "To jest ostatni istniejący zestaw.",
|
||||
"EMPTY_SET": "Pusty zestaw!",
|
||||
"LETS_ADD_OVERLAYS": "Dodajmy kilka nakładek z zegarka!",
|
||||
"FIXING_FLOOR": "Naprawianie podłogi za 5 sekund...",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "Umieść jeden kontroler na podłodze!",
|
||||
"CANNOT_ADD_SET": "Nie można dodać zestawu!",
|
||||
"MAXIMUM_SETS_REACHED": "Osiągnięto maksymalną liczbę zestawów.",
|
||||
"FIXING_FLOOR_IN_X_SECS": "Naprawianie podłogi za {SECONDS} sekund(y)..."
|
||||
},
|
||||
"DONE": "Gotowe"
|
||||
"MAXIMUM_SETS_REACHED": "Osiągnięto maksymalną liczbę zestawów."
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
{
|
||||
"ANCHOR": {
|
||||
"CENTER": "中心"
|
||||
},
|
||||
"BAR": {
|
||||
"ADD_MIRROR": "添加新的镜像覆盖层",
|
||||
"EDIT_MODE_TOGGLE": "切换编辑模式",
|
||||
"ADD_NEW_SET": "添加新集合",
|
||||
"DELETE_CURRENT_SET": "删除当前集合",
|
||||
"TOGGLE_VISIBILITY": "切换可见性",
|
||||
"RESET_POSITION": "重置位置",
|
||||
"RELOAD_FROM_DISK": "从磁盘重新加载 XML",
|
||||
"CLOSE_MIRROR": "关闭镜像",
|
||||
"CLOSE_APP": "关闭应用",
|
||||
"FORCE_CLOSE_APP": "强制关闭应用",
|
||||
"HANDSFREE": {
|
||||
"TITLE": "免提模式",
|
||||
"NONE": "关闭",
|
||||
"HMD": "HMD + 捏合",
|
||||
"EYE_TRACKING": "眼动 + 捏合",
|
||||
"HMD_ONLY": "仅 HMD",
|
||||
"EYE_ONLY": "仅限眼睛"
|
||||
}
|
||||
},
|
||||
"DEFAULT": "默认",
|
||||
"DISABLED": "已禁用",
|
||||
"EDIT_MODE": {
|
||||
"ADJUST_CURVATURE": "调整曲率",
|
||||
"ALPHA_BLEND_MODE": "Alpha 混合模式",
|
||||
"BLENDING_ADDITIVE": "叠加混合",
|
||||
"CURVATURE": "曲率",
|
||||
"DELETE": "长按以从当前集合中移除",
|
||||
"DISABLE_GRAB": "禁用抓取",
|
||||
"HINT_POINT_WINDOW": "指向窗口以更改其参数。\n完成后,使用右侧按钮退出编辑模式。",
|
||||
"INTERPOLATION": "插值",
|
||||
"ALIGN_TO_HMD": "对齐到头显 (HMD)",
|
||||
"KEYBOARD": "键盘",
|
||||
"LEAVE": "离开编辑模式",
|
||||
"LOCK_INTERACTION": "锁定交互",
|
||||
"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": "调整大小 (按住并拖拽)",
|
||||
"STEREO_3D_MODE": {
|
||||
"SPLIT_BOTTOM_TOP": "下→上",
|
||||
"SPLIT_LEFT_RIGHT": "左→右",
|
||||
"SPLIT_RIGHT_LEFT": "右→左",
|
||||
"SPLIT_TOP_BOTTOM": "上→下",
|
||||
"TITLE": "3D 立体模式",
|
||||
"FULL_FRAME": "完整 3D",
|
||||
"FULL_FRAME_SBS": "完整左右 (Full-SBS)",
|
||||
"FULL_FRAME_TAB": "完整上下 (Full-TAB)",
|
||||
"FULL_FRAME_BAT": "完整下上 (Full-BAT)",
|
||||
"ADJUST_MOUSE": "调整鼠标"
|
||||
},
|
||||
"MOUSE": {
|
||||
"TITLE": "鼠标修复",
|
||||
"WRONG_SCREEN_SELECTION_HELP": "如果光标在完全不同的屏幕上移动,\n可能是屏幕选择错误。请参阅 readme。",
|
||||
"NORMAL": "正常",
|
||||
"ROTATE90": "旋转 90°",
|
||||
"ROTATE180": "旋转 180°",
|
||||
"ROTATE270": "旋转 270°",
|
||||
"FLIPPED": "镜像",
|
||||
"FLIP90": "镜像旋转 90°",
|
||||
"FLIP180": "镜像旋转 180°",
|
||||
"FLIP270": "镜像旋转 270°"
|
||||
},
|
||||
"GLOBAL": "始终可见",
|
||||
"ANGLE_FADE": "角度淡出",
|
||||
"ANGLE_FADE_HELP": "未朝向HMD时淡出",
|
||||
"BLOCK_INPUT": "阻止游戏输入"
|
||||
},
|
||||
"GRAB": {
|
||||
"ADJUST_DISTANCE": "调整距离",
|
||||
"ADJUST_SIZE": "调整大小",
|
||||
"UNRESTRICTED_MOVEMENT": "无限制移动",
|
||||
"GRABBING_WATCH": "若要更换手,请将手表移至前方并用另一只手抓取。",
|
||||
"GRABBING_STATIC": "此覆盖层是静态的,将固定在原位,忽略重新居中。",
|
||||
"GRABBING_ANCHORED": "锚定覆盖层将一起移动。通过用另一只手抓取单个窗口,同时仍抓取锚点来分离它。",
|
||||
"GRABBING_ANCHORED_EDIT": "此覆盖层将保持锚定在中心标记上。",
|
||||
"GRABBING_FLOATING": "此覆盖层是悬浮的,将固定在原位,除非重新居中。",
|
||||
"GRABBING_FOLLOW": "此覆盖层将跟随它所连接的设备。"
|
||||
},
|
||||
"TOAST": {
|
||||
"DEFAULT_TITLE": "通知",
|
||||
"ERROR": "错误",
|
||||
"CANNOT_ADD_SET": "无法添加集合!",
|
||||
"MAXIMUM_SETS_REACHED": "已达到集合数量上限。",
|
||||
"CANNOT_REMOVE_SET": "无法移除集合!",
|
||||
"NO_SET_SELECTED": "未选择集合。",
|
||||
"LAST_EXISTING_SET": "这是最后一个现有的集合。",
|
||||
"EMPTY_SET": "空集合!",
|
||||
"LETS_ADD_OVERLAYS": "让我们从手表添加一些覆盖层吧!",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "请将一个控制器放在地面上!",
|
||||
"FIXING_FLOOR_IN_X_SECS": "{SECONDS}秒内固定地面..."
|
||||
},
|
||||
"WATCH": {
|
||||
"ADD_NEW_SET": "添加新集合",
|
||||
"CLEANUP_MIRRORS": "移除当前不可见的镜像",
|
||||
"DASHBOARD": "控制面板 (Dashboard)",
|
||||
"EDIT_MODE": "编辑模式",
|
||||
"EDIT_MODE_EXPLANATION": "覆盖层现在可以单独移动和调整。\n控制此集合中哪些覆盖层可见:",
|
||||
"FIX_FLOOR": "修复地面水平",
|
||||
"LONG_PRESS_TO_DELETE_SET": "按住以删除",
|
||||
"RECENTER": "重置游玩区中心 (Recenter)",
|
||||
"SWITCH_TO_SET": "切换到集合",
|
||||
"TOGGLE_FOR_CURRENT_SET": "切换当前集合"
|
||||
},
|
||||
"DONE": "完成"
|
||||
}
|
||||
+16
-101
@@ -13,7 +13,6 @@ use wlx_common::windowing::{OverlayWindowState, Positioning};
|
||||
|
||||
use crate::backend::task::{InputTask, OverlayTask};
|
||||
use crate::overlays::anchor::{ANCHOR_NAME, GRAB_HELP_NAME};
|
||||
use crate::overlays::keyboard::KEYBOARD_NAME;
|
||||
use crate::overlays::watch::WATCH_NAME;
|
||||
use crate::state::{AppSession, AppState};
|
||||
use crate::subsystem::hid::WheelDelta;
|
||||
@@ -210,8 +209,6 @@ pub struct InteractionState {
|
||||
pub hovered_id: Option<OverlayID>,
|
||||
pub next_push: Instant,
|
||||
pub haptics: Option<f32>,
|
||||
pub should_block_input: bool,
|
||||
pub should_block_poses: bool,
|
||||
}
|
||||
|
||||
impl Default for InteractionState {
|
||||
@@ -223,8 +220,6 @@ impl Default for InteractionState {
|
||||
hovered_id: None,
|
||||
next_push: Instant::now(),
|
||||
haptics: None,
|
||||
should_block_input: false,
|
||||
should_block_poses: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -238,8 +233,6 @@ pub struct Pointer {
|
||||
pub last_click: Instant,
|
||||
pub pending_haptics: Option<Haptics>,
|
||||
pub(super) interaction: InteractionState,
|
||||
pub tracked: bool,
|
||||
pub handsfree: bool,
|
||||
}
|
||||
|
||||
impl Pointer {
|
||||
@@ -254,8 +247,6 @@ impl Pointer {
|
||||
last_click: Instant::now(),
|
||||
pending_haptics: None,
|
||||
interaction: InteractionState::default(),
|
||||
tracked: false,
|
||||
handsfree: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,47 +318,6 @@ pub enum PointerMode {
|
||||
Special,
|
||||
}
|
||||
|
||||
pub struct PointerLine {
|
||||
pub mode: PointerMode,
|
||||
pub a: Vec3A,
|
||||
pub b: Vec3A,
|
||||
}
|
||||
|
||||
fn populate_lines(
|
||||
lines: &mut Vec<PointerLine>,
|
||||
pointer: &Pointer,
|
||||
maybe_hit: Option<(PointerHit, RayHit)>,
|
||||
hmd: &Affine3A,
|
||||
) {
|
||||
let Some((hit, raw_hit)) = maybe_hit else {
|
||||
return;
|
||||
};
|
||||
|
||||
if pointer.handsfree {
|
||||
const HALF_SIZE: f32 = 0.01;
|
||||
|
||||
// horizontal arm
|
||||
lines.push(PointerLine {
|
||||
mode: PointerMode::Left,
|
||||
a: raw_hit.global_pos - (hmd.x_axis * HALF_SIZE),
|
||||
b: raw_hit.global_pos + (hmd.x_axis * HALF_SIZE),
|
||||
});
|
||||
|
||||
// vertical arm
|
||||
lines.push(PointerLine {
|
||||
mode: PointerMode::Special,
|
||||
a: raw_hit.global_pos - (hmd.y_axis * HALF_SIZE),
|
||||
b: raw_hit.global_pos + (hmd.y_axis * HALF_SIZE),
|
||||
});
|
||||
} else {
|
||||
lines.push(PointerLine {
|
||||
mode: hit.mode,
|
||||
a: pointer.pose.translation,
|
||||
b: raw_hit.global_pos,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn update_focus(focus: &mut KeyboardFocus, overlay_keyboard_focus: Option<KeyboardFocus>) {
|
||||
if let Some(f) = &overlay_keyboard_focus
|
||||
&& *focus != *f
|
||||
@@ -380,12 +330,11 @@ fn update_focus(focus: &mut KeyboardFocus, overlay_keyboard_focus: Option<Keyboa
|
||||
pub fn interact<O>(
|
||||
overlays: &mut OverlayWindowManager<O>,
|
||||
app: &mut AppState,
|
||||
lines: &mut Vec<PointerLine>,
|
||||
) -> [Option<Haptics>; 2]
|
||||
) -> [(f32, Option<Haptics>); 2]
|
||||
where
|
||||
O: Default,
|
||||
{
|
||||
let hits = if app.input_state.pointers[1].last_click > app.input_state.pointers[0].last_click {
|
||||
if app.input_state.pointers[1].last_click > app.input_state.pointers[0].last_click {
|
||||
let right = interact_hand(1, overlays, app);
|
||||
let left = interact_hand(0, overlays, app);
|
||||
[left, right]
|
||||
@@ -393,36 +342,21 @@ where
|
||||
let left = interact_hand(0, overlays, app);
|
||||
let right = interact_hand(1, overlays, app);
|
||||
[left, right]
|
||||
};
|
||||
|
||||
for (idx, hit) in hits.iter().enumerate() {
|
||||
populate_lines(
|
||||
lines,
|
||||
&app.input_state.pointers[idx],
|
||||
hit.0,
|
||||
&app.input_state.hmd,
|
||||
);
|
||||
}
|
||||
|
||||
[hits[0].1, hits[1].1]
|
||||
}
|
||||
|
||||
fn interact_hand<O>(
|
||||
idx: usize,
|
||||
overlays: &mut OverlayWindowManager<O>,
|
||||
app: &mut AppState,
|
||||
) -> (Option<(PointerHit, RayHit)>, Option<Haptics>)
|
||||
) -> (f32, Option<Haptics>)
|
||||
where
|
||||
O: Default,
|
||||
{
|
||||
// already grabbing, ignore everything else
|
||||
let mut pointer = &mut app.input_state.pointers[idx];
|
||||
let pending_haptics = pointer.pending_haptics.take();
|
||||
|
||||
if !pointer.tracked {
|
||||
return (None, pending_haptics); // no hit
|
||||
}
|
||||
|
||||
// already grabbing, ignore everything else
|
||||
if let Some(grab_data) = pointer.interaction.grabbed {
|
||||
if let Some(grabbed) = overlays.mut_by_id(grab_data.grabbed_id) {
|
||||
handle_grabbed(idx, grabbed, app);
|
||||
@@ -430,13 +364,13 @@ where
|
||||
log::warn!("Grabbed overlay {:?} does not exist", grab_data.grabbed_id);
|
||||
pointer.interaction.grabbed = None;
|
||||
}
|
||||
return (None, pending_haptics);
|
||||
return (0.1, pending_haptics);
|
||||
}
|
||||
|
||||
let hovered_id = pointer.interaction.hovered_id.take();
|
||||
let (Some((mut hit, raw_hit)), haptics) = get_nearest_hit(idx, overlays, app) else {
|
||||
let (Some(mut hit), haptics) = get_nearest_hit(idx, overlays, app) else {
|
||||
handle_no_hit(idx, hovered_id, overlays, app);
|
||||
return (None, pending_haptics); // no hit
|
||||
return (0.0, pending_haptics); // no hit
|
||||
};
|
||||
|
||||
// focus change
|
||||
@@ -460,7 +394,7 @@ where
|
||||
|
||||
let Some(hovered) = overlays.mut_by_id(hit.overlay) else {
|
||||
log::warn!("Hit overlay {:?} does not exist", hit.overlay);
|
||||
return (None, pending_haptics); // no hit
|
||||
return (0.0, pending_haptics); // no hit
|
||||
};
|
||||
pointer = &mut app.input_state.pointers[idx];
|
||||
pointer.interaction.hovered_id = Some(hit.overlay);
|
||||
@@ -476,19 +410,6 @@ where
|
||||
hit.primary = true;
|
||||
}
|
||||
|
||||
if let Some(state) = hovered.config.active_state.as_ref() {
|
||||
pointer.interaction.should_block_input = state.block_input
|
||||
&& (hovered.config.name.as_ref() != WATCH_NAME
|
||||
|| !app.session.config.block_game_input_ignore_watch);
|
||||
|
||||
pointer.interaction.should_block_poses = state.block_input
|
||||
&& app.session.config.block_poses_on_kbd_interaction
|
||||
&& hovered.config.name.as_ref() == KEYBOARD_NAME;
|
||||
} else {
|
||||
pointer.interaction.should_block_input = false;
|
||||
pointer.interaction.should_block_poses = false;
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
log::trace!("Hit: {} {:?}", hovered.config.name, hit);
|
||||
|
||||
@@ -511,7 +432,7 @@ where
|
||||
);
|
||||
log::debug!("Hand {}: grabbed {}", hit.pointer, hovered.config.name);
|
||||
return (
|
||||
Some((hit, raw_hit)),
|
||||
hit.dist,
|
||||
Some(Haptics {
|
||||
intensity: 0.25,
|
||||
duration: 0.1,
|
||||
@@ -542,7 +463,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
(Some((hit, raw_hit)), haptics.or(pending_haptics))
|
||||
(hit.dist, haptics.or(pending_haptics))
|
||||
}
|
||||
|
||||
fn handle_no_hit<O>(
|
||||
@@ -562,12 +483,9 @@ fn handle_no_hit<O>(
|
||||
}
|
||||
}
|
||||
|
||||
let pointer = &mut app.input_state.pointers[pointer_idx];
|
||||
pointer.interaction.should_block_input = false;
|
||||
pointer.interaction.should_block_poses = false;
|
||||
|
||||
// in case click released while not aiming at anything
|
||||
// send release event to overlay that was originally clicked
|
||||
let pointer = &mut app.input_state.pointers[pointer_idx];
|
||||
if !pointer.now.click
|
||||
&& pointer.before.click
|
||||
&& let Some(clicked_id) = pointer.interaction.clicked_id.take()
|
||||
@@ -644,7 +562,7 @@ fn get_nearest_hit<O>(
|
||||
pointer_idx: usize,
|
||||
overlays: &mut OverlayWindowManager<O>,
|
||||
app: &mut AppState,
|
||||
) -> (Option<(PointerHit, RayHit)>, Option<Haptics>)
|
||||
) -> (Option<PointerHit>, Option<Haptics>)
|
||||
where
|
||||
O: Default,
|
||||
{
|
||||
@@ -676,7 +594,7 @@ where
|
||||
|
||||
hits.sort_by(|a, b| a.dist.total_cmp(&b.dist));
|
||||
|
||||
for hit in hits {
|
||||
for hit in &hits {
|
||||
let overlay = overlays.mut_by_id(hit.overlay).unwrap(); // safe because we just got the id from the overlay
|
||||
|
||||
let Some(uv) = overlay
|
||||
@@ -693,7 +611,7 @@ where
|
||||
continue;
|
||||
}
|
||||
|
||||
let pointer_hit = PointerHit {
|
||||
let hit = PointerHit {
|
||||
pointer: pointer_idx,
|
||||
overlay: hit.overlay,
|
||||
mode,
|
||||
@@ -702,9 +620,9 @@ where
|
||||
dist: hit.dist,
|
||||
};
|
||||
|
||||
let result = overlay.config.backend.on_hover(app, &pointer_hit);
|
||||
let result = overlay.config.backend.on_hover(app, &hit);
|
||||
if result.consume || overlay.config.editing {
|
||||
return (Some((pointer_hit, hit)), result.haptics);
|
||||
return (Some(hit), result.haptics);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -755,7 +673,6 @@ fn start_grab(
|
||||
|
||||
if let Some(hand) = pointer.hand()
|
||||
&& !app.session.config.hide_grab_help
|
||||
&& !pointer.handsfree
|
||||
{
|
||||
let pos = state.positioning;
|
||||
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
|
||||
@@ -815,7 +732,6 @@ where
|
||||
pointer.interaction.mode = PointerMode::Special;
|
||||
let grab_dist = grab_data.offset.translation.length().clamp(0.5, 5.0) * 0.2 + 0.4;
|
||||
handle_scale(&mut app.anchor, pointer.now.scroll_y * grab_dist);
|
||||
handle_scale(&mut grab_data.offset, pointer.now.scroll_y * grab_dist);
|
||||
} else if app.session.config.allow_sliding && pointer.now.scroll_y.is_finite() {
|
||||
// single grab push/pull
|
||||
let grab_dist = grab_data.offset.translation.length().clamp(0.5, 5.0);
|
||||
@@ -838,7 +754,6 @@ where
|
||||
&mut overlay_state.transform,
|
||||
pointer.now.scroll_y * grab_dist,
|
||||
);
|
||||
handle_scale(&mut grab_data.offset, pointer.now.scroll_y * grab_dist);
|
||||
} else if app.session.config.allow_sliding && pointer.now.scroll_y.is_finite() {
|
||||
// single grab push/pull
|
||||
let grab_dist = grab_data.offset.translation.length().clamp(0.5, 5.0);
|
||||
|
||||
@@ -15,6 +15,7 @@ use wlx_common::config_io;
|
||||
use crate::{
|
||||
backend::input::{Haptics, TrackedDevice, TrackedDeviceRole},
|
||||
state::AppState,
|
||||
windowing::OverlayID,
|
||||
};
|
||||
|
||||
use super::helpers::{Affine3AConvert, OVRError};
|
||||
@@ -40,7 +41,6 @@ const PATH_SCROLL: &str = "/actions/default/in/Scroll";
|
||||
const PATH_SHOW_HIDE: &str = "/actions/default/in/ShowHide";
|
||||
const PATH_SPACE_DRAG: &str = "/actions/default/in/SpaceDrag";
|
||||
const PATH_SPACE_ROTATE: &str = "/actions/default/in/SpaceRotate";
|
||||
const PATH_SPACE_RESET: &str = "/actions/default/in/SpaceReset";
|
||||
const PATH_TOGGLE_DASHBOARD: &str = "/actions/default/in/ToggleDashboard";
|
||||
|
||||
const INPUT_ANY: InputValueHandle = InputValueHandle(ovr_overlay::sys::k_ulInvalidInputValueHandle);
|
||||
@@ -56,7 +56,6 @@ pub(super) struct OpenVrInputSource {
|
||||
toggle_dashboard_hnd: ActionHandle,
|
||||
space_drag_hnd: ActionHandle,
|
||||
space_rotate_hnd: ActionHandle,
|
||||
space_reset_hnd: ActionHandle,
|
||||
click_modifier_right_hnd: ActionHandle,
|
||||
click_modifier_middle_hnd: ActionHandle,
|
||||
move_mouse_hnd: ActionHandle,
|
||||
@@ -82,7 +81,6 @@ impl OpenVrInputSource {
|
||||
let toggle_dashboard_hnd = input.get_action_handle(PATH_TOGGLE_DASHBOARD)?;
|
||||
let space_drag_hnd = input.get_action_handle(PATH_SPACE_DRAG)?;
|
||||
let space_rotate_hnd = input.get_action_handle(PATH_SPACE_ROTATE)?;
|
||||
let space_reset_hnd = input.get_action_handle(PATH_SPACE_RESET)?;
|
||||
let click_modifier_right_hnd = input.get_action_handle(PATH_CLICK_MODIFIER_RIGHT)?;
|
||||
let click_modifier_middle_hnd = input.get_action_handle(PATH_CLICK_MODIFIER_MIDDLE)?;
|
||||
let move_mouse_hnd = input.get_action_handle(PATH_MOVE_MOUSE)?;
|
||||
@@ -121,7 +119,6 @@ impl OpenVrInputSource {
|
||||
toggle_dashboard_hnd,
|
||||
space_drag_hnd,
|
||||
space_rotate_hnd,
|
||||
space_reset_hnd,
|
||||
click_modifier_right_hnd,
|
||||
click_modifier_middle_hnd,
|
||||
move_mouse_hnd,
|
||||
@@ -146,41 +143,26 @@ impl OpenVrInputSource {
|
||||
input: &mut InputManager,
|
||||
system: &mut SystemManager,
|
||||
app: &mut AppState,
|
||||
watch_id: OverlayID,
|
||||
) {
|
||||
let should_block_input_left = app.input_state.pointers[0].interaction.should_block_input
|
||||
&& app.session.config.block_game_input;
|
||||
let should_block_input = app.input_state.pointers.iter().any(|p| {
|
||||
p.interaction.hovered_id.is_some_and(|id| {
|
||||
id != watch_id || !app.session.config.block_game_input_ignore_watch
|
||||
})
|
||||
}) && app.session.config.block_game_input;
|
||||
|
||||
let should_block_input_right = app.input_state.pointers[1].interaction.should_block_input
|
||||
&& app.session.config.block_game_input;
|
||||
|
||||
let aas_left = ActiveActionSet(ovr_overlay::sys::VRActiveActionSet_t {
|
||||
let aas = ActiveActionSet(ovr_overlay::sys::VRActiveActionSet_t {
|
||||
ulActionSet: self.set_hnd.0,
|
||||
ulRestrictedToDevice: self.hands[0].input_hnd.0,
|
||||
ulRestrictedToDevice: 0,
|
||||
ulSecondaryActionSet: 0,
|
||||
unPadding: 0,
|
||||
// the range between 0x01000000 and 0x01FFFFFF overrides game action sets as long as
|
||||
// global input from overlays is enabled in SteamVR developer settings
|
||||
// (taken from https://github.com/ValveSoftware/openvr/issues/1236)
|
||||
nPriority: if should_block_input_left {
|
||||
0x01000000
|
||||
} else {
|
||||
0x0
|
||||
},
|
||||
nPriority: if should_block_input { 0x01000000 } else { 0x0 },
|
||||
});
|
||||
|
||||
let aas_right = ActiveActionSet(ovr_overlay::sys::VRActiveActionSet_t {
|
||||
ulActionSet: self.set_hnd.0,
|
||||
ulRestrictedToDevice: self.hands[1].input_hnd.0,
|
||||
ulSecondaryActionSet: 0,
|
||||
unPadding: 0,
|
||||
nPriority: if should_block_input_right {
|
||||
0x01000000
|
||||
} else {
|
||||
0x0
|
||||
},
|
||||
});
|
||||
|
||||
let _ = input.update_actions(&mut [aas_left, aas_right]);
|
||||
let _ = input.update_actions(&mut [aas]);
|
||||
|
||||
let devices = system.get_device_to_absolute_tracking_pose(universe.clone(), 0.005);
|
||||
app.input_state.hmd = devices[0].mDeviceToAbsoluteTracking.to_affine();
|
||||
@@ -193,9 +175,6 @@ impl OpenVrInputSource {
|
||||
app_hand.raw_pose = devices[device.0 as usize]
|
||||
.mDeviceToAbsoluteTracking
|
||||
.to_affine();
|
||||
app_hand.tracked = devices[device.0 as usize].bPoseIsValid;
|
||||
} else {
|
||||
app_hand.tracked = false;
|
||||
}
|
||||
|
||||
hand.has_pose = false;
|
||||
@@ -247,11 +226,6 @@ impl OpenVrInputSource {
|
||||
.map(|x| x.0.bState)
|
||||
.unwrap_or(false);
|
||||
|
||||
app_hand.now.space_reset = input
|
||||
.get_digital_action_data(self.space_reset_hnd, hand.input_hnd)
|
||||
.map(|x| x.0.bState)
|
||||
.unwrap_or(false);
|
||||
|
||||
app_hand.now.click_modifier_right = input
|
||||
.get_digital_action_data(self.click_modifier_right_hnd, hand.input_hnd)
|
||||
.map(|x| x.0.bState)
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use ash::vk::SubmitInfo;
|
||||
use glam::{Affine3A, Quat, Vec3, Vec3A, Vec4};
|
||||
use glam::{Affine3A, Vec3, Vec3A, Vec4};
|
||||
use idmap::IdMap;
|
||||
use ovr_overlay::overlay::OverlayManager;
|
||||
use ovr_overlay::sys::ETrackingUniverseOrigin;
|
||||
@@ -103,43 +103,27 @@ impl LinePool {
|
||||
id
|
||||
}
|
||||
|
||||
pub(super) fn draw_between(
|
||||
pub fn draw_from(
|
||||
&mut self,
|
||||
id: usize,
|
||||
start: Vec3A,
|
||||
end: Vec3A,
|
||||
mut from: Affine3A,
|
||||
len: f32,
|
||||
color: usize,
|
||||
hmd: &Affine3A,
|
||||
) {
|
||||
let dir = end - start;
|
||||
let len = dir.length();
|
||||
|
||||
if len < 0.01 {
|
||||
return;
|
||||
}
|
||||
|
||||
debug_assert!(color < self.colors.len());
|
||||
|
||||
let center = (start + end) * 0.5;
|
||||
let dir_norm = dir / len;
|
||||
|
||||
let xform = Affine3A::from_rotation_translation(
|
||||
Quat::from_rotation_arc(Vec3::Z, dir_norm.into()),
|
||||
center.into(),
|
||||
);
|
||||
|
||||
let rotation = Affine3A::from_axis_angle(Vec3::X, -PI * 0.5);
|
||||
let mut transform = xform * rotation * Affine3A::from_scale(Vec3::new(1., len / 0.002, 1.));
|
||||
let to_hmd = hmd.translation - center;
|
||||
let sides = [Vec3A::Z, Vec3A::X, Vec3A::NEG_Z, Vec3A::NEG_X];
|
||||
|
||||
from.translation += from.transform_vector3a(Vec3A::NEG_Z) * (len * 0.5);
|
||||
let mut transform = from * rotation * Affine3A::from_scale(Vec3::new(1., len / 0.002, 1.));
|
||||
|
||||
let to_hmd = hmd.translation - from.translation;
|
||||
let sides = [Vec3A::Z, Vec3A::X, Vec3A::NEG_Z, Vec3A::NEG_X];
|
||||
let rotations = [
|
||||
Affine3A::IDENTITY,
|
||||
Affine3A::from_axis_angle(Vec3::Y, PI * 0.5),
|
||||
Affine3A::from_axis_angle(Vec3::Y, PI * 1.0),
|
||||
Affine3A::from_axis_angle(Vec3::Y, PI * 1.5),
|
||||
];
|
||||
|
||||
let mut closest = (0, 0.0);
|
||||
for (i, &side) in sides.iter().enumerate() {
|
||||
let dot = to_hmd.dot(transform.transform_vector3a(side));
|
||||
@@ -150,6 +134,8 @@ impl LinePool {
|
||||
|
||||
transform *= rotations[closest.0];
|
||||
|
||||
debug_assert!(color < self.colors.len());
|
||||
|
||||
self.draw_transform(id, transform, self.colors[color]);
|
||||
}
|
||||
|
||||
@@ -173,7 +159,7 @@ impl LinePool {
|
||||
for data in self.lines.values_mut() {
|
||||
data.after_input(overlay, app)?;
|
||||
let state = data.config.active_state.as_mut().unwrap();
|
||||
if state.alpha > 0.05 {
|
||||
if state.alpha > 0.01 {
|
||||
if data.config.dirty {
|
||||
data.upload_texture(overlay, &app.gfx);
|
||||
data.config.dirty = false;
|
||||
@@ -191,13 +177,6 @@ impl LinePool {
|
||||
data.config.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
for data in self.lines.values_mut() {
|
||||
let state = data.config.active_state.as_mut().unwrap();
|
||||
state.alpha = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LineBackend {
|
||||
|
||||
@@ -30,7 +30,10 @@ use crate::{
|
||||
},
|
||||
config::{save_settings, save_state},
|
||||
graphics::{GpuFutures, init_openvr_graphics},
|
||||
overlays::toast::Toast,
|
||||
overlays::{
|
||||
toast::Toast,
|
||||
watch::{WATCH_NAME, watch_fade},
|
||||
},
|
||||
state::AppState,
|
||||
subsystem::notifications::NotificationManager,
|
||||
windowing::{
|
||||
@@ -131,6 +134,8 @@ pub fn openvr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
|
||||
log::info!("HMD running @ {refresh_rate} Hz");
|
||||
|
||||
let watch_id = overlays.lookup(WATCH_NAME).unwrap(); // want panic
|
||||
|
||||
// want at least half refresh rate
|
||||
let frame_timeout = 2 * (1000.0 / refresh_rate).floor() as u32;
|
||||
|
||||
@@ -139,7 +144,6 @@ pub fn openvr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
|
||||
let mut lines = LinePool::new(app.gfx.clone())?;
|
||||
let pointer_lines = [lines.allocate(), lines.allocate()];
|
||||
let mut current_lines = Vec::with_capacity(2);
|
||||
|
||||
'main_loop: loop {
|
||||
let _ = overlay_mgr.wait_frame_sync(frame_timeout);
|
||||
@@ -226,8 +230,6 @@ pub fn openvr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
let _ = adjust_gain(&mut settings_mgr, channel, value);
|
||||
}
|
||||
},
|
||||
#[cfg(feature = "openxr")]
|
||||
TaskType::OpenXR(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,7 +240,13 @@ pub fn openvr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
let universe = playspace.get_universe();
|
||||
|
||||
app.input_state.pre_update();
|
||||
input_source.update(universe.clone(), &mut input_mgr, &mut system_mgr, &mut app);
|
||||
input_source.update(
|
||||
universe.clone(),
|
||||
&mut input_mgr,
|
||||
&mut system_mgr,
|
||||
&mut app,
|
||||
watch_id,
|
||||
);
|
||||
app.input_state.post_update(&app.session);
|
||||
|
||||
if app
|
||||
@@ -261,27 +269,26 @@ pub fn openvr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
.enqueue(TaskType::Overlay(OverlayTask::ToggleDashboard));
|
||||
}
|
||||
|
||||
overlays.values_mut().for_each(|o| o.config.tick(&mut app));
|
||||
overlays
|
||||
.values_mut()
|
||||
.for_each(|o| o.config.auto_movement(&mut app));
|
||||
|
||||
watch_fade(&mut app, overlays.mut_by_id(watch_id).unwrap()); // want panic
|
||||
playspace.update(&mut chaperone_mgr, &mut overlays, &app);
|
||||
|
||||
current_lines.clear();
|
||||
|
||||
let haptics = interact(&mut overlays, &mut app, &mut current_lines);
|
||||
for (idx, haptics) in haptics.iter().enumerate() {
|
||||
let lengths_haptics = interact(&mut overlays, &mut app);
|
||||
for (idx, (len, haptics)) in lengths_haptics.iter().enumerate() {
|
||||
lines.draw_from(
|
||||
pointer_lines[idx],
|
||||
app.input_state.pointers[idx].pose,
|
||||
*len,
|
||||
app.input_state.pointers[idx].interaction.mode as usize + 1,
|
||||
&app.input_state.hmd,
|
||||
);
|
||||
if let Some(haptics) = haptics {
|
||||
input_source.haptics(&mut input_mgr, idx, haptics);
|
||||
}
|
||||
}
|
||||
for (idx, line) in current_lines.iter().enumerate() {
|
||||
lines.draw_between(
|
||||
pointer_lines[idx],
|
||||
line.a,
|
||||
line.b,
|
||||
line.mode as usize + 1,
|
||||
&app.input_state.hmd,
|
||||
);
|
||||
}
|
||||
|
||||
app.hid_provider.inner.commit();
|
||||
let mut futures = GpuFutures::default();
|
||||
@@ -314,7 +321,7 @@ pub fn openvr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
let tgt = RenderTarget {
|
||||
views: smallvec![o.ensure_staging_image(&mut app, meta.extent)?],
|
||||
};
|
||||
let mut rdr = RenderResources::new(app.gfx.clone(), tgt, &meta)?;
|
||||
let mut rdr = RenderResources::new(app.gfx.clone(), tgt, &meta, 1.0)?;
|
||||
o.render(&mut app, &mut rdr)?;
|
||||
o.data.image_dirty = true;
|
||||
futures.execute_results(rdr.end()?)?;
|
||||
@@ -328,8 +335,6 @@ pub fn openvr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
.values_mut()
|
||||
.for_each(|o| o.after_render(universe.clone(), &mut overlay_mgr, &app.gfx));
|
||||
|
||||
lines.reset();
|
||||
|
||||
// chaperone
|
||||
} // main_loop
|
||||
|
||||
@@ -343,7 +348,7 @@ pub fn openvr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
}
|
||||
|
||||
log::warn!("OpenVR shutdown");
|
||||
unsafe { context.shutdown() };
|
||||
// context.shutdown() called by Drop
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -178,14 +178,6 @@ impl PlayspaceMover {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (_, pointer) in app.input_state.pointers.iter().enumerate() {
|
||||
if pointer.now.space_reset && !pointer.before.space_reset {
|
||||
self.reset_offset(chaperone_mgr, &app.input_state);
|
||||
log::info!("Space reset");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_offset(&mut self, chaperone_mgr: &mut ChaperoneSetupManager, input: &InputState) {
|
||||
|
||||
@@ -1,95 +1,74 @@
|
||||
use libmonado::{BlockFlags, ClientLogic, ClientState, Monado, Version};
|
||||
use log::trace;
|
||||
use wgui::log::LogErr;
|
||||
use libmonado::{ClientState, Monado};
|
||||
use log::{trace, warn};
|
||||
|
||||
use crate::state::AppState;
|
||||
use crate::{state::AppState, windowing::OverlayID};
|
||||
|
||||
pub(super) struct InputBlocker {
|
||||
use_io_blocks: bool,
|
||||
inputs_blocked_last_frame: bool,
|
||||
poses_blocked_last_frame: bool,
|
||||
blocked_last_frame: bool,
|
||||
}
|
||||
|
||||
impl InputBlocker {
|
||||
pub fn new(monado: &Monado) -> Self {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
use_io_blocks: monado.get_api_version() >= Version::new(1, 6, 0),
|
||||
inputs_blocked_last_frame: false,
|
||||
poses_blocked_last_frame: false,
|
||||
blocked_last_frame: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unblock(&self, monado: &mut Monado) {
|
||||
self.block_inputs(monado, false, false);
|
||||
}
|
||||
|
||||
pub fn update(&mut self, app: &mut AppState) {
|
||||
pub fn update(&mut self, app: &mut AppState, watch_id: OverlayID) {
|
||||
let Some(monado) = &mut app.monado else {
|
||||
return; // monado not available
|
||||
};
|
||||
|
||||
let should_block_inputs = app
|
||||
.input_state
|
||||
.pointers
|
||||
.iter()
|
||||
.any(|p| p.interaction.should_block_input)
|
||||
&& app.session.config.block_game_input;
|
||||
let should_block = app.input_state.pointers.iter().any(|p| {
|
||||
p.interaction.hovered_id.is_some_and(|id| {
|
||||
id != watch_id || !app.session.config.block_game_input_ignore_watch
|
||||
})
|
||||
}) && app.session.config.block_game_input;
|
||||
|
||||
let should_block_poses = app
|
||||
.input_state
|
||||
.pointers
|
||||
.iter()
|
||||
.any(|p| p.interaction.should_block_poses)
|
||||
&& app.session.config.block_poses_on_kbd_interaction;
|
||||
|
||||
if should_block_inputs != self.inputs_blocked_last_frame
|
||||
|| should_block_poses != self.poses_blocked_last_frame
|
||||
{
|
||||
if should_block_inputs {
|
||||
match (should_block, self.blocked_last_frame) {
|
||||
(true, false) => {
|
||||
trace!("Blocking input");
|
||||
} else {
|
||||
set_clients_io_active(monado, false);
|
||||
}
|
||||
(false, true) => {
|
||||
trace!("Unblocking input");
|
||||
set_clients_io_active(monado, true);
|
||||
}
|
||||
self.block_inputs(monado, should_block_inputs, should_block_poses);
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.inputs_blocked_last_frame = should_block_inputs;
|
||||
self.poses_blocked_last_frame = should_block_poses;
|
||||
self.blocked_last_frame = should_block;
|
||||
}
|
||||
}
|
||||
|
||||
fn block_inputs(&self, monado: &mut Monado, block_inputs: bool, block_poses: bool) {
|
||||
let Ok(clients) = monado
|
||||
.clients()
|
||||
.log_warn("Failed to get clients from Monado")
|
||||
else {
|
||||
return;
|
||||
};
|
||||
for mut client in clients {
|
||||
let Ok(name) = client.name().log_warn("Failed to get client name") else {
|
||||
continue;
|
||||
};
|
||||
if name == "wayvr" {
|
||||
continue;
|
||||
}
|
||||
fn set_clients_io_active(monado: &mut Monado, active: bool) {
|
||||
match monado.clients() {
|
||||
Ok(clients) => {
|
||||
for mut client in clients {
|
||||
let name = match client.name() {
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
warn!("Failed to get client name: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let Ok(state) = client.state().log_warn("Failed to get client state") else {
|
||||
continue;
|
||||
};
|
||||
let state = match client.state() {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
warn!("Failed to get client state: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if state.contains(ClientState::ClientSessionActive | ClientState::ClientSessionVisible)
|
||||
{
|
||||
let _ = if self.use_io_blocks {
|
||||
let flags = match (block_inputs, block_poses) {
|
||||
(true, true) => BlockFlags::BlockPoses | BlockFlags::BlockInputs,
|
||||
(true, false) => BlockFlags::BlockInputs.into(),
|
||||
(false, _) => BlockFlags::None.into(),
|
||||
};
|
||||
client.set_io_blocks(flags)
|
||||
} else {
|
||||
client.set_io_active(!block_inputs)
|
||||
if name != "wayvr"
|
||||
&& state.contains(ClientState::ClientSessionVisible)
|
||||
&& let Err(e) = client.set_io_active(active)
|
||||
{
|
||||
warn!("Failed to set io active for client: {e}");
|
||||
}
|
||||
.log_warn("Failed to set IO active for client");
|
||||
}
|
||||
}
|
||||
Err(e) => warn!("Failed to get clients from Monado: {e}"),
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,6 @@ use glam::{Affine3A, Quat, Vec3, Vec3A};
|
||||
use openxr::{self as xr, SessionCreateFlags, Version, sys::Handle};
|
||||
use xr::OverlaySessionCreateFlagsEXTX;
|
||||
|
||||
macro_rules! next_chain_insert {
|
||||
($layer:expr, $payload:expr) => {{
|
||||
let payload_ptr = $payload.as_mut() as *mut _ as *mut xr::sys::BaseInStructure;
|
||||
let new_elem = payload_ptr.as_mut().unwrap();
|
||||
let mut raw = $layer.into_raw();
|
||||
new_elem.next = raw.next as _;
|
||||
raw.next = payload_ptr as *const _;
|
||||
raw
|
||||
}};
|
||||
}
|
||||
pub(crate) use next_chain_insert;
|
||||
|
||||
pub(super) fn init_xr() -> Result<(xr::Instance, xr::SystemId), anyhow::Error> {
|
||||
let entry = xr::Entry::linked();
|
||||
|
||||
@@ -49,11 +37,6 @@ pub(super) fn init_xr() -> Result<(xr::Instance, xr::SystemId), anyhow::Error> {
|
||||
} else {
|
||||
log::warn!("Missing EXT_hand_interaction extension.");
|
||||
}
|
||||
if available_extensions.ext_eye_gaze_interaction {
|
||||
enabled_extensions.ext_eye_gaze_interaction = true;
|
||||
} else {
|
||||
log::warn!("Missing EXT_eye_gaze_interaction extension.");
|
||||
}
|
||||
if available_extensions.khr_composition_layer_cylinder {
|
||||
enabled_extensions.khr_composition_layer_cylinder = true;
|
||||
} else {
|
||||
@@ -64,11 +47,6 @@ pub(super) fn init_xr() -> Result<(xr::Instance, xr::SystemId), anyhow::Error> {
|
||||
} else {
|
||||
log::warn!("Missing EXT_composition_layer_equirect2 extension.");
|
||||
}
|
||||
if available_extensions.khr_composition_layer_color_scale_bias {
|
||||
enabled_extensions.khr_composition_layer_color_scale_bias = true;
|
||||
} else {
|
||||
log::warn!("Missing XR_KHR_composition_layer_color_scale_bias extension.");
|
||||
}
|
||||
|
||||
let xr_mndx_system_buttons = "XR_MNDX_system_buttons".as_bytes().to_vec();
|
||||
if available_extensions.other.contains(&xr_mndx_system_buttons) {
|
||||
|
||||
@@ -5,10 +5,10 @@ use std::{
|
||||
};
|
||||
|
||||
use glam::{Affine3A, Quat, Vec3, bool};
|
||||
use libmonado::{self as mnd, DeviceLogic};
|
||||
use libmonado as mnd;
|
||||
use openxr::{self as xr, Quaternionf, Vector2f, Vector3f};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use wlx_common::{config::HandsfreePointer, config_io};
|
||||
use wlx_common::config_io;
|
||||
|
||||
use crate::{
|
||||
backend::input::{Haptics, InputState, Pointer, TrackedDevice, TrackedDeviceRole},
|
||||
@@ -25,11 +25,10 @@ static CLICK_TIMES: [Duration; 3] = [
|
||||
|
||||
pub(super) struct OpenXrInputSource {
|
||||
action_set: xr::ActionSet,
|
||||
pointers: [OpenXrPointer; 2],
|
||||
handsfree_pointer: OpenXrPointer,
|
||||
hands: [OpenXrHand; 2],
|
||||
}
|
||||
|
||||
pub(super) struct OpenXrPointer {
|
||||
pub(super) struct OpenXrHand {
|
||||
source: OpenXrHandSource,
|
||||
space: xr::Space,
|
||||
}
|
||||
@@ -66,7 +65,7 @@ impl<const COUNT: usize> MultiClickHandler<COUNT> {
|
||||
|
||||
if !state {
|
||||
let res = self.action_f32.state(session, xr::Path::NULL)?;
|
||||
state = res.is_active && res.current_state >= threshold - 0.001;
|
||||
state = res.is_active && res.current_state > threshold;
|
||||
}
|
||||
|
||||
if !state {
|
||||
@@ -171,27 +170,22 @@ impl OpenXrInputSource {
|
||||
|
||||
let left_source = OpenXrHandSource::new(&mut action_set, "left")?;
|
||||
let right_source = OpenXrHandSource::new(&mut action_set, "right")?;
|
||||
let fallback_source = OpenXrHandSource::new(&mut action_set, "handsfree")?;
|
||||
|
||||
suggest_bindings(
|
||||
&xr.instance,
|
||||
&[&left_source, &right_source, &fallback_source],
|
||||
);
|
||||
suggest_bindings(&xr.instance, &[&left_source, &right_source]);
|
||||
|
||||
xr.session.attach_action_sets(&[&action_set])?;
|
||||
|
||||
Ok(Self {
|
||||
action_set,
|
||||
pointers: [
|
||||
OpenXrPointer::new(xr, left_source)?,
|
||||
OpenXrPointer::new(xr, right_source)?,
|
||||
hands: [
|
||||
OpenXrHand::new(xr, left_source)?,
|
||||
OpenXrHand::new(xr, right_source)?,
|
||||
],
|
||||
handsfree_pointer: OpenXrPointer::new(xr, fallback_source)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn haptics(&self, xr: &XrState, hand: usize, haptics: &Haptics) {
|
||||
let action = &self.pointers[hand].source.haptics;
|
||||
let action = &self.hands[hand].source.haptics;
|
||||
|
||||
let duration_nanos = f64::from(haptics.duration) * 1_000_000_000.0;
|
||||
|
||||
@@ -210,14 +204,11 @@ impl OpenXrInputSource {
|
||||
|
||||
let loc = xr.view.locate(&xr.stage, xr.predicted_display_time)?;
|
||||
let hmd = posef_to_transform(&loc.pose);
|
||||
let mut hmd_tracked = true;
|
||||
if loc
|
||||
.location_flags
|
||||
.contains(xr::SpaceLocationFlags::ORIENTATION_VALID)
|
||||
{
|
||||
state.input_state.hmd.matrix3 = hmd.matrix3;
|
||||
} else {
|
||||
hmd_tracked = false;
|
||||
}
|
||||
|
||||
if loc
|
||||
@@ -225,26 +216,11 @@ impl OpenXrInputSource {
|
||||
.contains(xr::SpaceLocationFlags::POSITION_VALID)
|
||||
{
|
||||
state.input_state.hmd.translation = hmd.translation;
|
||||
} else {
|
||||
hmd_tracked = false;
|
||||
}
|
||||
|
||||
let mut any_tracked = false;
|
||||
for i in 0..2 {
|
||||
let pointer = &mut state.input_state.pointers[i];
|
||||
self.pointers[i].update(pointer, xr, &state.session)?;
|
||||
any_tracked |= pointer.tracked;
|
||||
self.hands[i].update(&mut state.input_state.pointers[i], xr, &state.session)?;
|
||||
}
|
||||
if !any_tracked {
|
||||
self.handsfree_pointer.update_handsfree(
|
||||
&mut state.input_state.pointers[0],
|
||||
xr,
|
||||
&state.session,
|
||||
hmd,
|
||||
hmd_tracked,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -329,7 +305,7 @@ impl OpenXrInputSource {
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenXrPointer {
|
||||
impl OpenXrHand {
|
||||
pub(super) fn new(xr: &XrState, source: OpenXrHandSource) -> Result<Self, xr::sys::Result> {
|
||||
let space = source
|
||||
.pose
|
||||
@@ -338,70 +314,11 @@ impl OpenXrPointer {
|
||||
Ok(Self { source, space })
|
||||
}
|
||||
|
||||
pub(super) fn update_handsfree(
|
||||
&mut self,
|
||||
pointer: &mut Pointer,
|
||||
xr: &XrState,
|
||||
session: &AppSession,
|
||||
hmd: Affine3A,
|
||||
hmd_tracked: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
match session.config.handsfree_pointer {
|
||||
HandsfreePointer::None => return Ok(()),
|
||||
HandsfreePointer::Hmd | HandsfreePointer::HmdOnly => {
|
||||
pointer.tracked = hmd_tracked;
|
||||
pointer.raw_pose = hmd;
|
||||
pointer.pose = hmd;
|
||||
let (cur_quat, cur_pos) =
|
||||
(Quat::from_affine3(&pointer.pose), pointer.pose.translation);
|
||||
|
||||
let (new_quat, new_pos) = (Quat::from_affine3(&hmd), Vec3::from(hmd.translation));
|
||||
let lerp_factor =
|
||||
(1.0 / (xr.fps / 100.0) * session.config.pointer_lerp_factor).clamp(0.1, 1.0);
|
||||
pointer.raw_pose = Affine3A::from_rotation_translation(new_quat, new_pos);
|
||||
pointer.pose = Affine3A::from_rotation_translation(
|
||||
cur_quat.lerp(new_quat, lerp_factor),
|
||||
cur_pos.lerp(new_pos.into(), lerp_factor).into(),
|
||||
);
|
||||
}
|
||||
HandsfreePointer::EyeTracking | HandsfreePointer::EyeTrackingOnly => {
|
||||
// more aggressive smoothing for eye
|
||||
self.pointer_load_pose(pointer, xr, session.config.pointer_lerp_factor * 0.5)?;
|
||||
}
|
||||
}
|
||||
|
||||
pointer.handsfree = pointer.tracked;
|
||||
if matches!(
|
||||
session.config.handsfree_pointer,
|
||||
HandsfreePointer::HmdOnly | HandsfreePointer::EyeTrackingOnly
|
||||
) {
|
||||
// skip actions
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.pointer_load_actions(pointer, xr, session)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn update(
|
||||
&mut self,
|
||||
pointer: &mut Pointer,
|
||||
xr: &XrState,
|
||||
session: &AppSession,
|
||||
) -> anyhow::Result<()> {
|
||||
pointer.handsfree = false;
|
||||
self.pointer_load_pose(pointer, xr, session.config.pointer_lerp_factor)?;
|
||||
self.pointer_load_actions(pointer, xr, session)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pointer_load_pose(
|
||||
&mut self,
|
||||
pointer: &mut Pointer,
|
||||
xr: &XrState,
|
||||
lerp_factor: f32,
|
||||
) -> anyhow::Result<()> {
|
||||
let location = self.space.locate(&xr.stage, xr.predicted_display_time)?;
|
||||
if location
|
||||
@@ -416,25 +333,15 @@ impl OpenXrPointer {
|
||||
transmute::<Vector3f, Vec3>(location.pose.position),
|
||||
)
|
||||
};
|
||||
let lerp_factor = (1.0 / (xr.fps / 100.0) * lerp_factor).clamp(0.1, 1.0);
|
||||
let lerp_factor =
|
||||
(1.0 / (xr.fps / 100.0) * session.config.pointer_lerp_factor).clamp(0.1, 1.0);
|
||||
pointer.raw_pose = Affine3A::from_rotation_translation(new_quat, new_pos);
|
||||
pointer.pose = Affine3A::from_rotation_translation(
|
||||
cur_quat.lerp(new_quat, lerp_factor),
|
||||
cur_pos.lerp(new_pos.into(), lerp_factor).into(),
|
||||
);
|
||||
pointer.tracked = true;
|
||||
} else {
|
||||
pointer.tracked = false;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pointer_load_actions(
|
||||
&mut self,
|
||||
pointer: &mut Pointer,
|
||||
xr: &XrState,
|
||||
session: &AppSession,
|
||||
) -> anyhow::Result<()> {
|
||||
pointer.now.click = self.source.click.state(pointer.before.click, xr, session)?;
|
||||
|
||||
pointer.now.grab = self.source.grab.state(pointer.before.grab, xr, session)?;
|
||||
@@ -557,12 +464,11 @@ fn is_bool(path_str: &str) -> bool {
|
||||
macro_rules! add_custom {
|
||||
($action:expr, $field:ident, $hands:expr, $bindings:expr, $instance:expr) => {
|
||||
if let Some(action) = $action.as_ref() {
|
||||
for i in 0..3 {
|
||||
let spec = match i {
|
||||
0 => action.left.as_ref(),
|
||||
1 => action.right.as_ref(),
|
||||
2 => action.handsfree.as_ref(),
|
||||
_ => unreachable!(),
|
||||
for i in 0..2 {
|
||||
let spec = if i == 0 {
|
||||
action.left.as_ref()
|
||||
} else {
|
||||
action.right.as_ref()
|
||||
};
|
||||
|
||||
if let Some(spec) = spec {
|
||||
@@ -620,12 +526,11 @@ macro_rules! add_custom {
|
||||
macro_rules! add_custom_lr {
|
||||
($action:expr, $field:ident, $hands:expr, $bindings:expr, $instance:expr) => {
|
||||
if let Some(action) = $action {
|
||||
for i in 0..3 {
|
||||
let spec = match i {
|
||||
0 => action.left.as_ref(),
|
||||
1 => action.right.as_ref(),
|
||||
2 => action.handsfree.as_ref(),
|
||||
_ => unreachable!(),
|
||||
for i in 0..2 {
|
||||
let spec = if i == 0 {
|
||||
action.left.as_ref()
|
||||
} else {
|
||||
action.right.as_ref()
|
||||
};
|
||||
|
||||
if let Some(spec) = spec {
|
||||
@@ -646,14 +551,12 @@ macro_rules! add_custom_lr {
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
|
||||
fn suggest_bindings(instance: &xr::Instance, hands: &[&OpenXrHandSource; 3]) {
|
||||
fn suggest_bindings(instance: &xr::Instance, hands: &[&OpenXrHandSource; 2]) {
|
||||
let profiles = load_action_profiles();
|
||||
|
||||
for profile in profiles {
|
||||
log::warn!("Loading profile {}", &profile.profile);
|
||||
|
||||
let Ok(profile_path) = instance.string_to_path(&profile.profile) else {
|
||||
log::warn!("Profile not supported: {}", profile.profile);
|
||||
log::debug!("Profile not supported: {}", profile.profile);
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -715,11 +618,6 @@ fn suggest_bindings(instance: &xr::Instance, hands: &[&OpenXrHandSource; 3]) {
|
||||
{
|
||||
log::error!("Bad bindings for {}", &profile.profile[22..]);
|
||||
log::error!("Verify config: ~/.config/wayvr/openxr_actions.json5");
|
||||
} else {
|
||||
log::debug!(
|
||||
"Bindings for {} bound successfully.",
|
||||
&profile.profile[22..]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -735,7 +633,6 @@ enum OneOrMany<T> {
|
||||
struct OpenXrActionConfAction {
|
||||
left: Option<OneOrMany<String>>,
|
||||
right: Option<OneOrMany<String>>,
|
||||
handsfree: Option<OneOrMany<String>>,
|
||||
threshold: Option<[f32; 2]>,
|
||||
double_click: Option<bool>,
|
||||
triple_click: Option<bool>,
|
||||
@@ -775,8 +672,6 @@ fn load_action_profiles() -> Vec<OpenXrActionConfProfile> {
|
||||
for new in override_profiles {
|
||||
if let Some(i) = profiles.iter().position(|old| old.profile == new.profile) {
|
||||
profiles[i] = new;
|
||||
} else {
|
||||
profiles.push(new);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use glam::{Affine3A, Quat, Vec3, Vec3A};
|
||||
use glam::{Affine3A, Vec3, Vec3A};
|
||||
use idmap::IdMap;
|
||||
use openxr as xr;
|
||||
use smallvec::SmallVec;
|
||||
@@ -96,17 +96,14 @@ impl LinePool {
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
pub(super) fn draw_between(
|
||||
pub(super) fn draw_from(
|
||||
&mut self,
|
||||
id: usize,
|
||||
start: Vec3A,
|
||||
end: Vec3A,
|
||||
mut from: Affine3A,
|
||||
len: f32,
|
||||
color: usize,
|
||||
hmd: Affine3A,
|
||||
hmd: &Affine3A,
|
||||
) {
|
||||
let dir = end - start;
|
||||
let len = dir.length();
|
||||
|
||||
if len < 0.01 {
|
||||
return;
|
||||
}
|
||||
@@ -118,19 +115,13 @@ impl LinePool {
|
||||
return;
|
||||
};
|
||||
|
||||
let center = (start + end) * 0.5;
|
||||
let dir_norm = dir / len;
|
||||
|
||||
let xform = Affine3A::from_rotation_translation(
|
||||
Quat::from_rotation_arc(Vec3::Z, dir_norm.into()),
|
||||
center.into(),
|
||||
);
|
||||
|
||||
let rotation = Affine3A::from_axis_angle(Vec3::X, PI * 1.5);
|
||||
let mut transform = xform * rotation;
|
||||
let to_hmd = hmd.translation - center;
|
||||
let sides = [Vec3A::Z, Vec3A::X, Vec3A::NEG_Z, Vec3A::NEG_X];
|
||||
|
||||
from.translation += from.transform_vector3a(Vec3A::NEG_Z) * (len * 0.5);
|
||||
let mut transform = from * rotation;
|
||||
|
||||
let to_hmd = hmd.translation - from.translation;
|
||||
let sides = [Vec3A::Z, Vec3A::X, Vec3A::NEG_Z, Vec3A::NEG_X];
|
||||
#[allow(clippy::neg_multiply)]
|
||||
let rotations = [
|
||||
Affine3A::IDENTITY,
|
||||
@@ -138,7 +129,6 @@ impl LinePool {
|
||||
Affine3A::from_axis_angle(Vec3::Y, PI * -1.0),
|
||||
Affine3A::from_axis_angle(Vec3::Y, PI * 1.5),
|
||||
];
|
||||
|
||||
let mut closest = (0, 0.0);
|
||||
for (i, &side) in sides.iter().enumerate() {
|
||||
let dot = to_hmd.dot(transform.transform_vector3a(side));
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::{
|
||||
use glam::{Affine3A, Vec3};
|
||||
use input::OpenXrInputSource;
|
||||
use openxr as xr;
|
||||
use skybox::{Skybox, create_skybox};
|
||||
use skybox::create_skybox;
|
||||
use vulkano::{Handle, VulkanObject};
|
||||
use wlx_common::overlays::{StereoMode, ToastTopic};
|
||||
|
||||
@@ -18,11 +18,14 @@ use crate::{
|
||||
BackendError, XrBackend,
|
||||
input::interact,
|
||||
openxr::{lines::LinePool, overlay::OpenXrOverlayData},
|
||||
task::{OpenXrTask, OverlayTask, TaskType},
|
||||
task::{OverlayTask, TaskType},
|
||||
},
|
||||
config::{save_settings, save_state},
|
||||
graphics::{GpuFutures, init_openxr_graphics},
|
||||
overlays::{toast::Toast, watch::WATCH_NAME},
|
||||
overlays::{
|
||||
toast::Toast,
|
||||
watch::{WATCH_NAME, watch_fade},
|
||||
},
|
||||
state::AppState,
|
||||
subsystem::notifications::NotificationManager,
|
||||
windowing::{
|
||||
@@ -66,7 +69,17 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
AppState::from_graphics(gfx, gfx_extras, XrBackend::OpenXR)?
|
||||
};
|
||||
|
||||
let modes = xr_instance.enumerate_environment_blend_modes(system, VIEW_TYPE)?;
|
||||
let environment_blend_mode = {
|
||||
let modes = xr_instance.enumerate_environment_blend_modes(system, VIEW_TYPE)?;
|
||||
if modes.contains(&xr::EnvironmentBlendMode::ALPHA_BLEND)
|
||||
&& app.session.config.use_passthrough
|
||||
{
|
||||
xr::EnvironmentBlendMode::ALPHA_BLEND
|
||||
} else {
|
||||
modes[0]
|
||||
}
|
||||
};
|
||||
log::info!("Using environment blend mode: {environment_blend_mode:?}");
|
||||
|
||||
if show_by_default {
|
||||
app.tasks.enqueue_at(
|
||||
@@ -77,7 +90,6 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
|
||||
let mut overlays = OverlayWindowManager::<OpenXrOverlayData>::new(&mut app, headless)?;
|
||||
let mut lines = LinePool::new(&app)?;
|
||||
let mut current_lines = Vec::with_capacity(2);
|
||||
|
||||
let mut notifications = NotificationManager::new();
|
||||
notifications.run_dbus(&mut app.dbus);
|
||||
@@ -93,7 +105,7 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
.ok()
|
||||
});
|
||||
|
||||
let mut blocker = app.monado.as_ref().map(blocker::InputBlocker::new);
|
||||
let mut blocker = app.monado.is_some().then(blocker::InputBlocker::new);
|
||||
|
||||
let (session, mut frame_wait, mut frame_stream) = unsafe {
|
||||
let raw_session = helpers::create_overlay_session(
|
||||
@@ -124,7 +136,11 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
view: Arc::new(view),
|
||||
};
|
||||
|
||||
let mut skybox: Option<Skybox> = None;
|
||||
let mut skybox = if environment_blend_mode == xr::EnvironmentBlendMode::OPAQUE {
|
||||
create_skybox(&xr_state, &app)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let pointer_lines = [
|
||||
lines.allocate(&xr_state, &app)?,
|
||||
@@ -144,7 +160,6 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
let mut fps_counter: VecDeque<Instant> = VecDeque::new();
|
||||
|
||||
let mut main_session_visible = false;
|
||||
let mut environment_blend_mode = modes[0];
|
||||
|
||||
'main_loop: loop {
|
||||
let cur_frame = FRAME_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
@@ -171,14 +186,6 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
xr::SessionState::READY => {
|
||||
xr_state.session.begin(VIEW_TYPE)?;
|
||||
session_running = true;
|
||||
reconfigure_environment_blend(
|
||||
&app,
|
||||
&xr_state,
|
||||
&modes,
|
||||
&mut skybox,
|
||||
&mut environment_blend_mode,
|
||||
main_session_visible,
|
||||
);
|
||||
}
|
||||
xr::SessionState::STOPPING => {
|
||||
xr_state.session.end()?;
|
||||
@@ -197,15 +204,17 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
log::warn!("lost {} events", e.lost_event_count());
|
||||
}
|
||||
xr::Event::MainSessionVisibilityChangedEXTX(e) => {
|
||||
main_session_visible = e.visible();
|
||||
reconfigure_environment_blend(
|
||||
&app,
|
||||
&xr_state,
|
||||
&modes,
|
||||
&mut skybox,
|
||||
&mut environment_blend_mode,
|
||||
main_session_visible,
|
||||
);
|
||||
if main_session_visible != e.visible() {
|
||||
main_session_visible = e.visible();
|
||||
log::info!("Main session visible: {main_session_visible}");
|
||||
if main_session_visible {
|
||||
log::debug!("Destroying skybox.");
|
||||
skybox = None;
|
||||
} else if environment_blend_mode == xr::EnvironmentBlendMode::OPAQUE {
|
||||
log::debug!("Allocating skybox.");
|
||||
skybox = create_skybox(&xr_state, &app);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -264,7 +273,7 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
app.input_state.post_update(&app.session);
|
||||
|
||||
if let Some(ref mut blocker) = blocker {
|
||||
blocker.update(&mut app);
|
||||
blocker.update(&mut app, watch_id);
|
||||
}
|
||||
|
||||
if app
|
||||
@@ -286,6 +295,7 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
.enqueue(TaskType::Overlay(OverlayTask::ToggleDashboard));
|
||||
}
|
||||
|
||||
watch_fade(&mut app, overlays.mut_by_id(watch_id).unwrap()); // want panic
|
||||
if let Some(ref mut space_mover) = playspace {
|
||||
space_mover.update(&mut overlays, &mut app);
|
||||
}
|
||||
@@ -313,25 +323,23 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
.submit(&mut app);
|
||||
}
|
||||
|
||||
overlays.values_mut().for_each(|o| o.config.tick(&mut app));
|
||||
overlays
|
||||
.values_mut()
|
||||
.for_each(|o| o.config.auto_movement(&mut app));
|
||||
|
||||
current_lines.clear();
|
||||
|
||||
let haptics = interact(&mut overlays, &mut app, &mut current_lines);
|
||||
for (idx, haptics) in haptics.iter().enumerate() {
|
||||
let lengths_haptics = interact(&mut overlays, &mut app);
|
||||
for (idx, (len, haptics)) in lengths_haptics.iter().enumerate() {
|
||||
lines.draw_from(
|
||||
pointer_lines[idx],
|
||||
app.input_state.pointers[idx].pose,
|
||||
*len,
|
||||
app.input_state.pointers[idx].interaction.mode as usize + 1,
|
||||
&app.input_state.hmd,
|
||||
);
|
||||
if let Some(haptics) = haptics {
|
||||
input_source.haptics(&xr_state, idx, haptics);
|
||||
}
|
||||
}
|
||||
for (idx, line) in current_lines.iter().enumerate() {
|
||||
lines.draw_between(
|
||||
pointer_lines[idx],
|
||||
line.a,
|
||||
line.b,
|
||||
line.mode as usize + 1,
|
||||
app.input_state.hmd,
|
||||
);
|
||||
}
|
||||
|
||||
app.hid_provider.inner.commit();
|
||||
|
||||
@@ -345,7 +353,6 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
y: 0.001,
|
||||
z: 0.001,
|
||||
});
|
||||
watch_state.alpha = 0.02; // visible but not really. Monado freaks out if no layers are submitted.
|
||||
}
|
||||
|
||||
if let Err(e) =
|
||||
@@ -363,11 +370,7 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
|
||||
for o in overlays.values_mut() {
|
||||
o.data.cur_visible = false;
|
||||
if o.config
|
||||
.active_state
|
||||
.as_ref()
|
||||
.is_none_or(|s| s.alpha < 0.01)
|
||||
{
|
||||
let Some(alpha) = o.config.active_state.as_ref().map(|x| x.alpha) else {
|
||||
log::trace!("{}: hidden, skip render", o.config.name);
|
||||
continue;
|
||||
};
|
||||
@@ -378,7 +381,11 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
o.data.init = true;
|
||||
}
|
||||
|
||||
let should_render = matches!(o.should_render(&mut app)?, ShouldRender::Should);
|
||||
let should_render = match o.should_render(&mut app)? {
|
||||
ShouldRender::Should => true,
|
||||
ShouldRender::Can => (o.data.last_alpha - alpha).abs() > f32::EPSILON,
|
||||
ShouldRender::Unable => false, //try show old image if exists
|
||||
};
|
||||
log::trace!("{}: should_render returned: {should_render}", o.config.name);
|
||||
|
||||
if should_render {
|
||||
@@ -387,8 +394,9 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
let stereo = !matches!(meta.stereo, StereoMode::None);
|
||||
let wsi = o.ensure_swapchain_acquire(&app, &xr_state, meta.extent, stereo)?;
|
||||
let tgt = RenderTarget { views: wsi.views };
|
||||
let mut rdr = RenderResources::new(app.gfx.clone(), tgt, &meta)?;
|
||||
let mut rdr = RenderResources::new(app.gfx.clone(), tgt, &meta, alpha)?;
|
||||
o.render(&mut app, &mut rdr)?;
|
||||
o.data.last_alpha = alpha;
|
||||
futures.execute_results(rdr.end()?)?;
|
||||
} else if o.data.swapchain.is_none() {
|
||||
log::trace!("{}: not showing due to missing swapchain", o.config.name);
|
||||
@@ -471,18 +479,6 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
playspace.handle_task(&mut app, task);
|
||||
}
|
||||
}
|
||||
TaskType::OpenXR(task) => {
|
||||
if matches!(task, OpenXrTask::SettingsChanged) {
|
||||
reconfigure_environment_blend(
|
||||
&app,
|
||||
&xr_state,
|
||||
&modes,
|
||||
&mut skybox,
|
||||
&mut environment_blend_mode,
|
||||
main_session_visible,
|
||||
);
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "openvr")]
|
||||
TaskType::OpenVR(_) => {}
|
||||
}
|
||||
@@ -499,10 +495,6 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
watch.config.active_state.as_mut().unwrap().transform = watch_transform;
|
||||
} // main_loop
|
||||
|
||||
if let (Some(blocker), Some(monado)) = (blocker, app.monado.as_mut()) {
|
||||
blocker.unblock(monado);
|
||||
}
|
||||
|
||||
overlays.persist_layout(&mut app);
|
||||
if let Err(e) = save_state(&app.session.config) {
|
||||
log::error!("Could not save state: {e:?}");
|
||||
@@ -520,38 +512,3 @@ pub(super) enum CompositionLayer<'a> {
|
||||
Cylinder(xr::CompositionLayerCylinderKHR<'a, xr::Vulkan>),
|
||||
Equirect2(xr::CompositionLayerEquirect2KHR<'a, xr::Vulkan>),
|
||||
}
|
||||
|
||||
fn reconfigure_environment_blend(
|
||||
app: &AppState,
|
||||
xr_state: &XrState,
|
||||
modes: &[xr::EnvironmentBlendMode],
|
||||
skybox: &mut Option<Skybox>,
|
||||
environment_blend_mode: &mut xr::EnvironmentBlendMode,
|
||||
main_session_visible: bool,
|
||||
) {
|
||||
*environment_blend_mode = {
|
||||
if modes.contains(&xr::EnvironmentBlendMode::ALPHA_BLEND)
|
||||
&& app.session.config.use_passthrough
|
||||
{
|
||||
xr::EnvironmentBlendMode::ALPHA_BLEND
|
||||
} else {
|
||||
modes[0]
|
||||
}
|
||||
};
|
||||
|
||||
let want_skybox = *environment_blend_mode == xr::EnvironmentBlendMode::OPAQUE
|
||||
&& app.session.config.use_skybox
|
||||
&& !main_session_visible;
|
||||
|
||||
if want_skybox == skybox.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
if want_skybox {
|
||||
log::debug!("Allocating skybox.");
|
||||
*skybox = create_skybox(xr_state, app);
|
||||
} else {
|
||||
log::debug!("Destroying skybox.");
|
||||
*skybox = None;
|
||||
}
|
||||
}
|
||||
@@ -34,47 +34,46 @@
|
||||
// do not mess with these, unless you know what you're doing
|
||||
|
||||
[
|
||||
// Eye+hand interaction
|
||||
// eye is aim, left pinch is grab, right pinch is click
|
||||
{
|
||||
profile: "/interaction_profiles/ext/eye_gaze_interaction",
|
||||
pose: {
|
||||
handsfree: "/user/eyes_ext/input/gaze_ext/pose",
|
||||
},
|
||||
},
|
||||
{
|
||||
profile: "/interaction_profiles/ext/hand_interaction_ext",
|
||||
click: {
|
||||
handsfree: "/user/hand/right/input/pinch_ext/value"
|
||||
},
|
||||
grab: {
|
||||
handsfree: "/user/hand/left/input/pinch_ext/value",
|
||||
},
|
||||
},
|
||||
// Hand tracking
|
||||
// {
|
||||
// profile: "/interaction_profiles/ext/hand_interaction_ext",
|
||||
// pose: {
|
||||
// left: "/user/hand/left/input/aim/pose",
|
||||
// right: "/user/hand/right/input/aim/pose"
|
||||
// },
|
||||
// click: {
|
||||
// left: "/user/hand/left/input/pinch_ext/value",
|
||||
// right: "/user/hand/right/input/pinch_ext/value"
|
||||
// },
|
||||
// grab: {
|
||||
// left: "/user/hand/left/input/grasp_ext/value",
|
||||
// right: "/user/hand/right/input/grasp_ext/value"
|
||||
// },
|
||||
// },
|
||||
|
||||
// Fallback controller, intended for testing
|
||||
//{
|
||||
// profile: "/interaction_profiles/khr/simple_controller",
|
||||
// pose: {
|
||||
// left: "/user/hand/left/input/aim/pose",
|
||||
// right: "/user/hand/right/input/aim/pose"
|
||||
// },
|
||||
// haptic: {
|
||||
// left: "/user/hand/left/output/haptic",
|
||||
// right: "/user/hand/right/output/haptic"
|
||||
// },
|
||||
// click: {
|
||||
{
|
||||
profile: "/interaction_profiles/khr/simple_controller",
|
||||
pose: {
|
||||
left: "/user/hand/left/input/aim/pose",
|
||||
right: "/user/hand/right/input/aim/pose"
|
||||
},
|
||||
haptic: {
|
||||
left: "/user/hand/left/output/haptic",
|
||||
right: "/user/hand/right/output/haptic"
|
||||
},
|
||||
click: {
|
||||
// right trigger is click
|
||||
// right: "/user/hand/right/input/select/click"
|
||||
// },
|
||||
// grab: {
|
||||
right: "/user/hand/right/input/select/click"
|
||||
},
|
||||
grab: {
|
||||
// left trigger is grab
|
||||
// left: "/user/hand/left/input/select/click",
|
||||
// },
|
||||
// show_hide: {
|
||||
// left: "/user/hand/left/input/menu/click"
|
||||
// }
|
||||
//},
|
||||
left: "/user/hand/left/input/select/click",
|
||||
},
|
||||
show_hide: {
|
||||
left: "/user/hand/left/input/menu/click"
|
||||
}
|
||||
},
|
||||
|
||||
// Oculus Touch Controller. Compatible with Quest 2, Quest 3, Quest Pro
|
||||
{
|
||||
@@ -214,11 +213,8 @@
|
||||
left: "/user/hand/left/input/trackpad/x",
|
||||
right: "/user/hand/right/input/trackpad/x"
|
||||
},
|
||||
toggle_dashboard: {
|
||||
right: "/user/hand/right/input/system/click",
|
||||
},
|
||||
show_hide: {
|
||||
left: "/user/hand/left/input/system/click",
|
||||
left: "/user/hand/left/input/menu/click",
|
||||
},
|
||||
space_drag: {
|
||||
right: "/user/hand/right/input/menu/click",
|
||||
@@ -261,11 +257,11 @@
|
||||
right: "/user/hand/right/input/thumbstick/x"
|
||||
},
|
||||
show_hide: {
|
||||
left: "/user/hand/left/input/system/click",
|
||||
left: "/user/hand/left/input/menu/click",
|
||||
},
|
||||
space_drag: {
|
||||
double_click: true,
|
||||
right: "/user/hand/left/input/system/click",
|
||||
right: "/user/hand/left/input/menu/click",
|
||||
},
|
||||
click_modifier_right: {
|
||||
left: "/user/hand/left/input/trackpad/dpad_up",
|
||||
@@ -305,11 +301,11 @@
|
||||
right: "/user/hand/right/input/thumbstick/x"
|
||||
},
|
||||
show_hide: {
|
||||
left: "/user/hand/left/input/system/click",
|
||||
left: "/user/hand/left/input/menu/click",
|
||||
},
|
||||
space_drag: {
|
||||
double_click: true,
|
||||
right: "/user/hand/left/input/system/click",
|
||||
right: "/user/hand/left/input/menu/click",
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
@@ -6,10 +6,7 @@ use xr::EyeVisibility;
|
||||
|
||||
use super::{CompositionLayer, XrState, helpers, swapchain::WlxSwapchain};
|
||||
use crate::{
|
||||
backend::openxr::{
|
||||
helpers::next_chain_insert,
|
||||
swapchain::{SwapchainOpts, WlxSwapchainImage, create_swapchain},
|
||||
},
|
||||
backend::openxr::swapchain::{SwapchainOpts, WlxSwapchainImage, create_swapchain},
|
||||
state::AppState,
|
||||
windowing::window::OverlayWindowData,
|
||||
};
|
||||
@@ -20,7 +17,7 @@ pub struct OpenXrOverlayData {
|
||||
pub(super) swapchain: Option<WlxSwapchain>,
|
||||
pub(super) init: bool,
|
||||
pub(super) cur_visible: bool,
|
||||
color_bias_khr: Option<Box<xr::sys::CompositionLayerColorScaleBiasKHR>>,
|
||||
pub(super) last_alpha: f32,
|
||||
}
|
||||
|
||||
impl OverlayWindowData<OpenXrOverlayData> {
|
||||
@@ -89,13 +86,13 @@ impl OverlayWindowData<OpenXrOverlayData> {
|
||||
|
||||
let transform = state.transform * self.config.backend.frame_meta().unwrap().transform; // contract
|
||||
|
||||
let aspect_ratio = swapchain.extent[0] as f32 / swapchain.extent[1] as f32;
|
||||
let aspect_ratio = swapchain.extent[1] as f32 / swapchain.extent[0] as f32;
|
||||
let (scale_x, scale_y) = if aspect_ratio < 1.0 {
|
||||
let major = transform.matrix3.col(0).length();
|
||||
(major * aspect_ratio, major)
|
||||
(major, major * aspect_ratio)
|
||||
} else {
|
||||
let major = transform.matrix3.col(1).length();
|
||||
(major, major / aspect_ratio)
|
||||
(major / aspect_ratio, major)
|
||||
};
|
||||
|
||||
let flags = if state.additive {
|
||||
@@ -113,10 +110,8 @@ impl OverlayWindowData<OpenXrOverlayData> {
|
||||
let posef = helpers::translation_rotation_to_posef(center_point, quat);
|
||||
let angle = 2.0 * (scale_x / (2.0 * radius));
|
||||
|
||||
try_update_color_scale_bias(xr, &mut self.data.color_bias_khr, state.alpha);
|
||||
|
||||
for sub_image in sub_images {
|
||||
let mut cylinder = xr::CompositionLayerCylinderKHR::new()
|
||||
let cylinder = xr::CompositionLayerCylinderKHR::new()
|
||||
.layer_flags(flags)
|
||||
.pose(posef)
|
||||
.sub_image(sub_image.0)
|
||||
@@ -125,22 +120,12 @@ impl OverlayWindowData<OpenXrOverlayData> {
|
||||
.radius(radius)
|
||||
.central_angle(angle)
|
||||
.aspect_ratio(aspect_ratio);
|
||||
|
||||
if let Some(color_bias_khr) = self.data.color_bias_khr.as_mut() {
|
||||
unsafe {
|
||||
let raw = next_chain_insert!(cylinder, color_bias_khr);
|
||||
cylinder = xr::CompositionLayerCylinderKHR::from_raw(raw);
|
||||
}
|
||||
}
|
||||
|
||||
layers.push(CompositionLayer::Cylinder(cylinder));
|
||||
}
|
||||
} else {
|
||||
let posef = helpers::transform_to_posef(&transform);
|
||||
try_update_color_scale_bias(xr, &mut self.data.color_bias_khr, state.alpha);
|
||||
|
||||
for sub_image in sub_images {
|
||||
let mut quad = xr::CompositionLayerQuad::new()
|
||||
let quad = xr::CompositionLayerQuad::new()
|
||||
.layer_flags(flags)
|
||||
.pose(posef)
|
||||
.sub_image(sub_image.0)
|
||||
@@ -150,14 +135,6 @@ impl OverlayWindowData<OpenXrOverlayData> {
|
||||
width: scale_x,
|
||||
height: scale_y,
|
||||
});
|
||||
|
||||
if let Some(color_bias_khr) = self.data.color_bias_khr.as_mut() {
|
||||
unsafe {
|
||||
let raw = next_chain_insert!(quad, color_bias_khr);
|
||||
quad = xr::CompositionLayerQuad::from_raw(raw);
|
||||
}
|
||||
}
|
||||
|
||||
layers.push(CompositionLayer::Quad(quad));
|
||||
}
|
||||
}
|
||||
@@ -182,35 +159,3 @@ impl OverlayWindowData<OpenXrOverlayData> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn try_update_color_scale_bias(
|
||||
xr_state: &XrState,
|
||||
color_bias_khr: &mut Option<Box<xr::sys::CompositionLayerColorScaleBiasKHR>>,
|
||||
alpha: f32,
|
||||
) {
|
||||
if let Some(item) = color_bias_khr.as_mut() {
|
||||
item.color_scale.a = alpha;
|
||||
return;
|
||||
}
|
||||
|
||||
if xr_state
|
||||
.instance
|
||||
.exts()
|
||||
.khr_composition_layer_color_scale_bias
|
||||
.is_none()
|
||||
{
|
||||
return;
|
||||
}
|
||||
let new_item = Box::new(xr::sys::CompositionLayerColorScaleBiasKHR {
|
||||
ty: xr::StructureType::COMPOSITION_LAYER_COLOR_SCALE_BIAS_KHR,
|
||||
next: std::ptr::null(),
|
||||
color_bias: Default::default(),
|
||||
color_scale: xr::Color4f {
|
||||
r: 1.0,
|
||||
g: 1.0,
|
||||
b: 1.0,
|
||||
a: alpha,
|
||||
},
|
||||
});
|
||||
*color_bias_khr = Some(new_item);
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
use std::{f32::consts::PI, fs::File, sync::Arc};
|
||||
use std::{
|
||||
f32::consts::PI,
|
||||
fs::File,
|
||||
sync::{Arc, LazyLock},
|
||||
};
|
||||
|
||||
use glam::{Quat, Vec3A};
|
||||
use openxr as xr;
|
||||
@@ -10,10 +14,7 @@ use wgui::gfx::{cmd::WGfxClearMode, pipeline::WPipelineCreateInfo};
|
||||
use wlx_common::config_io;
|
||||
|
||||
use crate::{
|
||||
backend::openxr::{
|
||||
helpers::{next_chain_insert, translation_rotation_to_posef},
|
||||
swapchain::SwapchainOpts,
|
||||
},
|
||||
backend::openxr::{helpers::translation_rotation_to_posef, swapchain::SwapchainOpts},
|
||||
graphics::{ExtentExt, GpuFutures, dds::WlxCommandBufferDds},
|
||||
state::AppState,
|
||||
};
|
||||
@@ -27,12 +28,10 @@ pub(super) struct Skybox {
|
||||
view: Arc<ImageView>,
|
||||
sky: Option<WlxSwapchain>,
|
||||
grid: Option<WlxSwapchain>,
|
||||
grid_pose: xr::Posef,
|
||||
grid_color_scale_bias_khr: Option<Box<xr::sys::CompositionLayerColorScaleBiasKHR>>,
|
||||
}
|
||||
|
||||
impl Skybox {
|
||||
pub fn new(xr: &XrState, app: &AppState) -> anyhow::Result<Self> {
|
||||
pub fn new(app: &AppState) -> anyhow::Result<Self> {
|
||||
let mut command_buffer = app
|
||||
.gfx
|
||||
.create_xfer_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
|
||||
@@ -75,30 +74,10 @@ impl Skybox {
|
||||
|
||||
let view = ImageView::new_default(maybe_image.unwrap())?; // safe unwrap
|
||||
|
||||
let grid_color_scale_bias_khr = xr
|
||||
.instance
|
||||
.exts()
|
||||
.khr_composition_layer_color_scale_bias
|
||||
.map(|_| {
|
||||
Box::new(xr::sys::CompositionLayerColorScaleBiasKHR {
|
||||
ty: xr::StructureType::COMPOSITION_LAYER_COLOR_SCALE_BIAS_KHR,
|
||||
next: std::ptr::null(),
|
||||
color_bias: Default::default(),
|
||||
color_scale: xr::Color4f {
|
||||
r: 1.0,
|
||||
g: 1.0,
|
||||
b: 1.0,
|
||||
a: 1.0,
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
Ok(Self {
|
||||
view,
|
||||
sky: None,
|
||||
grid: None,
|
||||
grid_pose: translation_rotation_to_posef(Vec3A::ZERO, Quat::from_rotation_x(PI * -0.5)),
|
||||
grid_color_scale_bias_khr: grid_color_scale_bias_khr,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -226,6 +205,10 @@ impl Skybox {
|
||||
const HI_VERT_ANGLE: f32 = 0.5 * PI;
|
||||
const LO_VERT_ANGLE: f32 = -0.5 * PI;
|
||||
|
||||
static GRID_POSE: LazyLock<xr::Posef> = LazyLock::new(|| {
|
||||
translation_rotation_to_posef(Vec3A::ZERO, Quat::from_rotation_x(PI * -0.5))
|
||||
});
|
||||
|
||||
let pose = xr::Posef {
|
||||
orientation: xr::Quaternionf::IDENTITY,
|
||||
position: xr::Vector3f {
|
||||
@@ -249,12 +232,9 @@ impl Skybox {
|
||||
.lower_vertical_angle(LO_VERT_ANGLE);
|
||||
|
||||
self.grid.as_mut().unwrap().ensure_image_released()?;
|
||||
|
||||
let flags = xr::CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA
|
||||
| xr::CompositionLayerFlags::UNPREMULTIPLIED_ALPHA;
|
||||
let mut grid = xr::CompositionLayerQuad::new()
|
||||
.layer_flags(flags)
|
||||
.pose(self.grid_pose)
|
||||
let grid = xr::CompositionLayerQuad::new()
|
||||
.layer_flags(xr::CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA)
|
||||
.pose(*GRID_POSE)
|
||||
.size(xr::Extent2Df {
|
||||
width: 10.0,
|
||||
height: 10.0,
|
||||
@@ -263,15 +243,6 @@ impl Skybox {
|
||||
.eye_visibility(xr::EyeVisibility::BOTH)
|
||||
.space(&xr.stage);
|
||||
|
||||
if let Some(grid_color_scale_bias_khr) = self.grid_color_scale_bias_khr.as_mut() {
|
||||
let grid_opacity = app.session.config.grid_opacity;
|
||||
grid_color_scale_bias_khr.color_scale.a = (grid_opacity * grid_opacity).clamp(0.0, 1.0);
|
||||
unsafe {
|
||||
let raw = next_chain_insert!(grid, grid_color_scale_bias_khr);
|
||||
grid = xr::CompositionLayerQuad::from_raw(raw);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(vec![
|
||||
CompositionLayer::Equirect2(sky),
|
||||
CompositionLayer::Quad(grid),
|
||||
@@ -280,8 +251,11 @@ impl Skybox {
|
||||
}
|
||||
|
||||
pub(super) fn create_skybox(xr: &XrState, app: &AppState) -> Option<Skybox> {
|
||||
if !app.session.config.use_skybox {
|
||||
return None;
|
||||
}
|
||||
xr.instance
|
||||
.exts()
|
||||
.khr_composition_layer_equirect2
|
||||
.and_then(|_| Skybox::new(xr, app).ok())
|
||||
.and_then(|_| Skybox::new(app).ok())
|
||||
}
|
||||
@@ -53,11 +53,6 @@ pub enum OpenVrTask {
|
||||
ColorGain(ColorChannel, f32),
|
||||
}
|
||||
|
||||
#[cfg(feature = "openxr")]
|
||||
pub enum OpenXrTask {
|
||||
SettingsChanged,
|
||||
}
|
||||
|
||||
pub enum PlayspaceTask {
|
||||
Recenter,
|
||||
Reset,
|
||||
@@ -71,8 +66,6 @@ pub enum ModifyPanelCommand {
|
||||
SetColor(String),
|
||||
SetImage(String),
|
||||
SetVisible(bool),
|
||||
GetValue,
|
||||
SetValue(String),
|
||||
SetStickyState(bool),
|
||||
}
|
||||
|
||||
@@ -116,8 +109,6 @@ pub enum TaskType {
|
||||
Playspace(PlayspaceTask),
|
||||
#[cfg(feature = "openvr")]
|
||||
OpenVR(OpenVrTask),
|
||||
#[cfg(feature = "openxr")]
|
||||
OpenXR(OpenXrTask),
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Copy)]
|
||||
|
||||
@@ -36,6 +36,7 @@ use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::os::fd::OwnedFd;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use wayland_client::WEnum;
|
||||
|
||||
use smithay::utils::Serial;
|
||||
use smithay::wayland::compositor::{self, BufferAssignment, SurfaceAttributes, send_surface_state};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use smithay::backend::input::{AxisRelativeDirection, AxisSource};
|
||||
use smithay::{
|
||||
input,
|
||||
utils::{Logical, Point},
|
||||
@@ -112,32 +111,16 @@ impl Window {
|
||||
}
|
||||
|
||||
pub(super) fn send_mouse_scroll(manager: &mut WayVRCompositor, delta: WheelDelta) {
|
||||
// workaround: it seems that with one event most applications work fine, but cage doesn't
|
||||
manager.seat_pointer.axis(
|
||||
&mut manager.state,
|
||||
input::pointer::AxisFrame {
|
||||
source: Some(AxisSource::Continuous),
|
||||
source: None,
|
||||
relative_direction: (
|
||||
AxisRelativeDirection::Identical,
|
||||
AxisRelativeDirection::Identical,
|
||||
smithay::backend::input::AxisRelativeDirection::Identical,
|
||||
smithay::backend::input::AxisRelativeDirection::Identical,
|
||||
),
|
||||
time: 0,
|
||||
axis: (f64::from(delta.x), 0.0),
|
||||
v120: Some(((delta.x * 64.0) as i32, 0)),
|
||||
stop: (false, false),
|
||||
},
|
||||
);
|
||||
|
||||
manager.seat_pointer.axis(
|
||||
&mut manager.state,
|
||||
input::pointer::AxisFrame {
|
||||
source: Some(AxisSource::Continuous),
|
||||
relative_direction: (
|
||||
AxisRelativeDirection::Identical,
|
||||
AxisRelativeDirection::Identical,
|
||||
),
|
||||
time: 0,
|
||||
axis: (0.0, f64::from(-delta.y)),
|
||||
axis: (f64::from(delta.x), f64::from(-delta.y)),
|
||||
v120: Some((0, (delta.y * -64.0) as i32)),
|
||||
stop: (false, false),
|
||||
},
|
||||
|
||||
+5
-17
@@ -6,11 +6,9 @@ use wayvr_ipc::packet_client::WvrProcessLaunchParams;
|
||||
use wlx_common::{
|
||||
astr_containers::AStrMap,
|
||||
config::{
|
||||
AltModifier, CaptureMethod, GeneralConfig, HandsfreePointer, SerializedWindowSet,
|
||||
SerializedWindowStates,
|
||||
AltModifier, CaptureMethod, GeneralConfig, SerializedWindowSet, SerializedWindowStates,
|
||||
},
|
||||
config_io,
|
||||
locale::Language,
|
||||
overlays::BackendAttribValue,
|
||||
};
|
||||
|
||||
@@ -105,9 +103,8 @@ pub fn load_general_config() -> GeneralConfig {
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct AutoSettings {
|
||||
pub ui_animation_speed: f32,
|
||||
pub ui_round_multiplier: f32,
|
||||
pub ui_gradient_intensity: f32,
|
||||
pub animation_speed: f32,
|
||||
pub round_multiplier: f32,
|
||||
pub click_freeze_time_ms: i32,
|
||||
pub invert_scroll_direction_x: bool,
|
||||
pub invert_scroll_direction_y: bool,
|
||||
@@ -127,10 +124,8 @@ pub struct AutoSettings {
|
||||
pub left_handed_mouse: bool,
|
||||
pub block_game_input: bool,
|
||||
pub block_game_input_ignore_watch: bool,
|
||||
pub block_poses_on_kbd_interaction: bool,
|
||||
pub space_drag_multiplier: f32,
|
||||
pub use_skybox: bool,
|
||||
pub grid_opacity: f32,
|
||||
pub use_passthrough: bool,
|
||||
pub screen_render_down: bool,
|
||||
pub pointer_lerp_factor: f32,
|
||||
@@ -144,8 +139,6 @@ pub struct AutoSettings {
|
||||
pub capture_method: CaptureMethod,
|
||||
pub keyboard_middle_click_mode: AltModifier,
|
||||
pub autostart_apps: Vec<WvrProcessLaunchParams>,
|
||||
pub handsfree_pointer: HandsfreePointer,
|
||||
pub language: Option<Language>,
|
||||
}
|
||||
|
||||
fn get_settings_path() -> PathBuf {
|
||||
@@ -156,9 +149,8 @@ fn get_settings_path() -> PathBuf {
|
||||
|
||||
pub fn save_settings(config: &GeneralConfig) -> anyhow::Result<()> {
|
||||
let conf = AutoSettings {
|
||||
ui_animation_speed: config.ui_animation_speed,
|
||||
ui_round_multiplier: config.ui_round_multiplier,
|
||||
ui_gradient_intensity: config.ui_gradient_intensity,
|
||||
animation_speed: config.animation_speed,
|
||||
round_multiplier: config.round_multiplier,
|
||||
click_freeze_time_ms: config.click_freeze_time_ms,
|
||||
invert_scroll_direction_x: config.invert_scroll_direction_x,
|
||||
invert_scroll_direction_y: config.invert_scroll_direction_y,
|
||||
@@ -178,10 +170,8 @@ pub fn save_settings(config: &GeneralConfig) -> anyhow::Result<()> {
|
||||
left_handed_mouse: config.left_handed_mouse,
|
||||
block_game_input: config.block_game_input,
|
||||
block_game_input_ignore_watch: config.block_game_input_ignore_watch,
|
||||
block_poses_on_kbd_interaction: config.block_poses_on_kbd_interaction,
|
||||
space_drag_multiplier: config.space_drag_multiplier,
|
||||
use_skybox: config.use_skybox,
|
||||
grid_opacity: config.grid_opacity,
|
||||
use_passthrough: config.use_passthrough,
|
||||
screen_render_down: config.screen_render_down,
|
||||
pointer_lerp_factor: config.pointer_lerp_factor,
|
||||
@@ -195,8 +185,6 @@ pub fn save_settings(config: &GeneralConfig) -> anyhow::Result<()> {
|
||||
capture_method: config.capture_method,
|
||||
keyboard_middle_click_mode: config.keyboard_middle_click_mode,
|
||||
autostart_apps: config.autostart_apps.clone(),
|
||||
handsfree_pointer: config.handsfree_pointer,
|
||||
language: config.language,
|
||||
};
|
||||
|
||||
let json = serde_json::to_string_pretty(&conf).unwrap(); // want panic
|
||||
|
||||
@@ -17,9 +17,7 @@ use vulkano::{
|
||||
memory::{
|
||||
DedicatedAllocation, DeviceMemory, ExternalMemoryHandleType, ExternalMemoryHandleTypes,
|
||||
MemoryAllocateInfo, MemoryImportInfo, MemoryPropertyFlags, ResourceMemory,
|
||||
allocator::{
|
||||
AllocationCreateInfo, MemoryAllocatePreference, MemoryAllocator, MemoryTypeFilter,
|
||||
},
|
||||
allocator::{AllocationCreateInfo, MemoryAllocator, MemoryTypeFilter},
|
||||
},
|
||||
sync::Sharing,
|
||||
};
|
||||
@@ -331,7 +329,6 @@ pub fn export_dmabuf_image(
|
||||
..Default::default()
|
||||
},
|
||||
AllocationCreateInfo {
|
||||
allocate_preference: MemoryAllocatePreference::AlwaysAllocate,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
|
||||
Loaded 100 of 187 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user