Compare commits

..
81 changed files with 611 additions and 1858 deletions

No files matched your search

@@ -21,9 +21,6 @@ export LDAI_UPDATE_INFORMATION="gh-releases-zsync|wayvr-org|wayvr|latest|WayVR-*
echo "Packaging AppImage for ${APPIMAGE_ARCH}"
# linuxdeploy doesn't like multiple -d's being passed
install -Dm 0644 wayvr-headless.desktop "${APPDIR}/usr/share/applications/wayvr-headless.desktop"
"./${LINUXDEPLOY}" \
-dwayvr.desktop \
-iwayvr.png \
-46
View File
@@ -1,46 +0,0 @@
# Contributing
Contributions are warmly welcome. Before you start, please read the entire document carefully.
## AI Policy
Do not use LLMs to generate substantial pieces of code. You need to fully understand every line of code you change. No exceptions.
Commits, issues and pull requests must be made by humans. Co-authoring by AI is not allowed.
Looking to create an issue on the project, such as a feature request or bug report? Focus on writing what you know for sure in simple terms. We'll figure it out from there.
Usage of Gen-AI for visuals and audio is prohibited.
The only exception is LLM-translated strings generated by the scripts/translator tool.
## Basics
Before committing, make sure to format your Rust code by running:
```
cargo fmt
```
You should also check for any compiler warnings too:
```
cargo check
```
or even better:
```
cargo clippy
```
## Large contributions
If you want to submit a relatively big pull requests, it's highly advisable to create a separate issue request for it. That way we can discuss the approach and sort out the details first.
## Adding translations
Look at `scripts/translator/run.sh` for more information.
## Licensing
WayVR is developed and published under [GPL-3 License](https://www.gnu.org/licenses/gpl-3.0.en.html).
Generated
+3 -118
View File
@@ -689,12 +689,6 @@ dependencies = [
"wyz",
]
[[package]]
name = "block"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
[[package]]
name = "block-buffer"
version = "0.10.4"
@@ -735,17 +729,6 @@ dependencies = [
"piper",
]
[[package]]
name = "bstr"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f"
dependencies = [
"memchr",
"regex-automata",
"serde_core",
]
[[package]]
name = "built"
version = "0.8.1"
@@ -1418,7 +1401,6 @@ dependencies = [
"rust-embed",
"serde",
"serde_json",
"shell-words",
"smol",
"strum",
"ureq",
@@ -1939,21 +1921,6 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
[[package]]
name = "freedesktop-desktop-entry"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42a69ad6111a2b572cb41e0f3542d546386e841013cee7c45d705b61a15a87dd"
dependencies = [
"bstr",
"gettext-rs",
"log",
"memchr",
"thiserror 2.0.18",
"unicase",
"xdg 3.0.0",
]
[[package]]
name = "fs_extra"
version = "1.3.0"
@@ -2133,26 +2100,6 @@ dependencies = [
"rand_core 0.10.1",
]
[[package]]
name = "gettext-rs"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9df5e3fa8c077b15fdfa8ce130e2bc73d663b8aaa6d51b71421b8659980b1d80"
dependencies = [
"gettext-sys",
"locale_config",
]
[[package]]
name = "gettext-sys"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4caa7e4460b196107cc1bb2629488e8b6dbb7f34f6683ea6753c4ac22f590785"
dependencies = [
"cc",
"temp-dir",
]
[[package]]
name = "gif"
version = "0.14.2"
@@ -2922,19 +2869,6 @@ version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]]
name = "locale_config"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d2c35b16f4483f6c26f0e4e9550717a2f6575bcd6f12a53ff0c490a94a6934"
dependencies = [
"lazy_static",
"objc",
"objc-foundation",
"regex",
"winapi",
]
[[package]]
name = "lock_api"
version = "0.4.14"
@@ -2996,15 +2930,6 @@ dependencies = [
"libc",
]
[[package]]
name = "malloc_buf"
version = "0.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
dependencies = [
"libc",
]
[[package]]
name = "matchers"
version = "0.2.0"
@@ -3296,26 +3221,6 @@ dependencies = [
"syn 2.0.118",
]
[[package]]
name = "objc"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
dependencies = [
"malloc_buf",
]
[[package]]
name = "objc-foundation"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
dependencies = [
"block",
"objc",
"objc_id",
]
[[package]]
name = "objc-sys"
version = "0.3.5"
@@ -3623,15 +3528,6 @@ dependencies = [
"objc2-foundation 0.2.2",
]
[[package]]
name = "objc_id"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
dependencies = [
"objc",
]
[[package]]
name = "object"
version = "0.37.3"
@@ -4773,7 +4669,8 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "rxscreen"
version = "0.1.7"
source = "git+https://github.com/galister/rxscreen.git?rev=7197e1de6622b65859e6f2060b5750185e0dad2a#7197e1de6622b65859e6f2060b5750185e0dad2a"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afbab13f83503a8272b7150d487494992cf6c59c299dcaf5d8ffac75bad9fc96"
dependencies = [
"libc",
]
@@ -5446,12 +5343,6 @@ version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
[[package]]
name = "temp-dir"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83176759e9416cf81ee66cb6508dbfe9c96f20b8b56265a39917551c23c70964"
[[package]]
name = "tempfile"
version = "3.27.0"
@@ -5802,12 +5693,6 @@ dependencies = [
"wlx-common",
]
[[package]]
name = "unicase"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
[[package]]
name = "unicode-bidi"
version = "0.3.18"
@@ -6854,7 +6739,6 @@ version = "0.1.0"
dependencies = [
"anyhow",
"chrono",
"freedesktop-desktop-entry",
"glam",
"glob",
"identicons-svg",
@@ -6862,6 +6746,7 @@ dependencies = [
"idmap-derive",
"log",
"rodio",
"rust-ini",
"serde",
"serde_json",
"serde_json5",
+1 -1
View File
@@ -32,7 +32,6 @@ rust-embed = "8.9.0"
serde = { version = "1", features = ["derive"] }
serde_json = "1.0.145"
serde_json5 = "0.2.1"
shell-words = "1.1.1"
slotmap = "1.1.1"
smallvec = "1.15.2"
smol = "2.0.2"
@@ -45,6 +44,7 @@ wayland-protocols-misc = { version = "0.3.12" }
winit = "0.30.12"
xdg = "3.0.0"
[profile.dev]
opt-level = 1
debug = true
-1
View File
@@ -19,7 +19,6 @@ log.workspace = true
rust-embed.workspace = true
serde = { workspace = true, features = ["rc"] }
serde_json.workspace = true
shell-words.workspace = true
smol = { workspace = true }
strum.workspace = true
uuid.workspace = true
-1
View File
@@ -1 +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="M17 10q-.825 0-1.412-.575T15 8V4q0-.85.588-1.425T17 2q.85 0 1.425.575T19 4v4q0 .85-.575 1.425T17 10M5 22q-.825 0-1.412-.587T3 20V4q0-.825.588-1.412T5 2h8v2H5v16h11v-2h2v2q0 .825-.587 1.413T16 22zm2-4v-2h7v2zm0-3v-2h5v2zm11 1h-2v-2.6q-1.925-.35-3.213-1.862T11.5 8h2q0 1.45 1.025 2.475T17 11.5q1.475 0 2.488-1.025T20.5 8h2q0 2.025-1.275 3.538T18 13.4z"/></svg>

Before

Width:  |  Height:  |  Size: 585 B

+2 -6
View File
@@ -263,9 +263,7 @@
},
"TUTORIAL": "Tutorial",
"RESTART_WAYVR": "WayVR neu starten",
"CLOSE_WAYVR": "WayVR schließen",
"SPEECH_TO_TEXT": "Sprache zu Text",
"WHISPER_GPU_SELECT": "GPU auswählen"
"CLOSE_WAYVR": "WayVR schließen"
},
"APPLICATION_LAUNCHER": "Programmstarter",
"APPLICATION_STARTED": "Anwendung gestartet",
@@ -375,9 +373,7 @@
"TITLE": "Energieoptionen",
"SHUTDOWN": "Herunterfahren",
"SUSPEND": "Ruhezustand",
"SHUTDOWN_ARE_YOU_SURE": "Bist du sicher, dass du den Computer ausschalten willst?",
"NOT_SUPPORTED": "Energieoptionen erfordern systemd oder elogind.",
"SUSPEND_ARE_YOU_SURE": "Bist du sicher, dass du den Computer in den Ruhezustand versetzen willst?"
"SHUTDOWN_ARE_YOU_SURE": "Bist du sicher, dass du den Computer ausschalten willst?"
},
"YES": "Ja",
"NO": "Nein"
-4
View File
@@ -259,8 +259,6 @@ APP_SETTINGS.WHISPER_MODEL,Speech-to-text model,Dropdown label in Settings > Fea
APP_SETTINGS.WHISPER_MODEL_HELP,"Whisper model to use.
Larger models produce more accurate
results, but consume more VRAM.",Tooltip for the speech-to-text model dropdown.
APP_SETTINGS.SPEECH_TO_TEXT,"Speech to text","Category header in Settings"
APP_SETTINGS.WHISPER_GPU_SELECT,"Select GPU","A list of GPUs the user can select from"
APP_SETTINGS.TUTORIAL,Tutorial,Tooltip for tutorial button in the main screen.
APP_SETTINGS.GRID_OPACITY,Floor grid opacity,Slider in Settings > Skybox tab. Controls the opacity of the floor grid when skybox is enabled.
APP_SETTINGS.GRID_OPACITY_HELP,Opacity of the floor grid when the skymap is enabled,"Tooltip for the floor grid opacity slider. ""Skymap"" was previously translated as ""${DEFINITIONS.SKYMAP}""."
@@ -341,9 +339,7 @@ POPUP_ADD_DISPLAY.RESOLUTION,Resolution,Label for the resolution field in the ad
POWER_OPTIONS.TITLE,"Power options","A button which allows the user to perform a specific power action"
POWER_OPTIONS.SHUTDOWN,"Shut down","One of the power options: shuts down the computer"
POWER_OPTIONS.SUSPEND,"Suspend","One of the power options: suspends the computer"
POWER_OPTIONS.NOT_SUPPORTED,"Power options require systemd or elogind.","Notification to the user that power options (shutdown and suspend) are not available. 'systemd' and 'elogind' are package names and should not be translated."
POWER_OPTIONS.SHUTDOWN_ARE_YOU_SURE,"Are you sure you want to turn off your computer?","Question dialog box before shutting down the computer"
POWER_OPTIONS.SUSPEND_ARE_YOU_SURE,"Are you sure you want to put your computer to sleep?","Question dialog box before putting the computer in sleep mode"
PROCESS.FORCE_KILL,Force-kill,Tooltip on the kill button in the running games list.
PROCESS.STOP,Stop,Tooltip on the stop button in the running games list.
PROCESS_LIST,Process list,Tab name and group box title in the Monado runtime process list section.
1 DEFINITIONS.ANCHORED Anchored A movement option that makes this window move together with the rest of the workspace. Emphasize the "moving together" part as opposed to "not moving at all".
259 DONATE.SUPPORT_US DONATE.THANK_YOU_WAYVR Support us Thank you from the WayVR Team! Button tooltip on the home screen donate button. Thank you message displayed on the donate tab.
260 DONATE.DESCRIPTION DONATE.BUTTON_TOOLTIP We're a small team of 2 developers building WayVR in our free time. If you enjoy using this software, please consider supporting us - every donation helps us keep going. Donate via Open Collective Description text on the donate tab explaining the project. Tooltip on the donate button on the donate tab.
261 DONATE.THANK_YOU_WAYVR DONATE.CURRENT_SUPPORTERS Thank you from the WayVR Team! Current Supporters Thank you message displayed on the donate tab. Section header on the donate tab listing supporters.
DONATE.BUTTON_TOOLTIP Donate via Open Collective Tooltip on the donate button on the donate tab.
DONATE.CURRENT_SUPPORTERS Current Supporters Section header on the donate tab listing supporters.
262 DONATE.BROUGHT_TO_YOU_VARIANTS_OFTEN Supported by {USER}|Special thanks to {USER}|Sponsored by {USER}|Backed by {USER}|Driven by community members like {USER}|Made possible by {USER}|Sustained by {USER}|Contributed by {USER}|Funded by {USER} This text will show up a random person from donations page - line below this string will display the supporter user. There are many variants of it.
263 DONATE.BROUGHT_TO_YOU_VARIANTS_RARE Coffee provided by {USER}|Fuelled by {USER}|Shoutout to {USER}!|Keeping us going: {USER}|Helping us grow: {USER}|Keeping the servers running: {USER}|With gratitude to {USER}|Powered by {USER}|Supported by friends like {USER} This text will show up a random person from donations page - line below this string will display the supporter user. There are many variants of it (rare versions).
264 DONATE.BROUGHT_TO_YOU_VARIANTS_VERY_RARE Supported by legends like {USER}|Caffeine supplied by {USER}|A true legend: {USER}|Our hero of the day: {USER}|An absolute superstar: {USER}|Paying for the electricity bill: {USER}|The savior of our sanity: {USER}|Leading the way: {USER}|Caffeine-to-code converter provided by {USER}|Keeping the production stable: {USER}|The one and only: {USER}|Passing dmabuf buffers: {USER} This text will show up a random person from donations page - line below this string will display the supporter user. There are many variants of it (rare versions).
339
340
341
342
343
344
345
+1 -5
View File
@@ -258,8 +258,6 @@
},
"WHISPER_MODEL": "Speech-to-text model",
"WHISPER_MODEL_HELP": "Whisper model to use.\nLarger models produce more accurate\nresults, but consume more VRAM.",
"SPEECH_TO_TEXT": "Speech to text",
"WHISPER_GPU_SELECT": "Select GPU",
"TUTORIAL": "Tutorial",
"GRID_OPACITY": "Floor grid opacity",
"GRID_OPACITY_HELP": "Opacity of the floor grid when the skymap is enabled",
@@ -352,9 +350,7 @@
"TITLE": "Power options",
"SHUTDOWN": "Shut down",
"SUSPEND": "Suspend",
"NOT_SUPPORTED": "Power options require systemd or elogind.",
"SHUTDOWN_ARE_YOU_SURE": "Are you sure you want to turn off your computer?",
"SUSPEND_ARE_YOU_SURE": "Are you sure you want to put your computer to sleep?"
"SHUTDOWN_ARE_YOU_SURE": "Are you sure you want to turn off your computer?"
},
"PROCESS": {
"FORCE_KILL": "Force-kill",
+2 -6
View File
@@ -263,9 +263,7 @@
},
"TUTORIAL": "Tutorial",
"RESTART_WAYVR": "Reiniciar WayVR",
"CLOSE_WAYVR": "Cerrar WayVR",
"SPEECH_TO_TEXT": "Dictado por voz",
"WHISPER_GPU_SELECT": "Seleccionar GPU"
"CLOSE_WAYVR": "Cerrar WayVR"
},
"APPLICATION_LAUNCHER": "Lanzador de aplicaciones",
"APPLICATION_STARTED": "Aplicación iniciada",
@@ -375,9 +373,7 @@
"TITLE": "Opciones de energía",
"SHUTDOWN": "Apagar",
"SUSPEND": "Suspender",
"SHUTDOWN_ARE_YOU_SURE": "¿Seguro que quieres apagar el ordenador?",
"NOT_SUPPORTED": "Las opciones de energía requieren systemd o elogind.",
"SUSPEND_ARE_YOU_SURE": "¿Estás seguro de que quieres suspender el equipo?"
"SHUTDOWN_ARE_YOU_SURE": "¿Seguro que quieres apagar el ordenador?"
},
"YES": "Sí",
"NO": "No"
+2 -6
View File
@@ -263,9 +263,7 @@
},
"TUTORIAL": "Tutorial",
"RESTART_WAYVR": "Riavvia WayVR",
"CLOSE_WAYVR": "Chiudi WayVR",
"SPEECH_TO_TEXT": "Speech-to-text",
"WHISPER_GPU_SELECT": "Seleziona GPU"
"CLOSE_WAYVR": "Chiudi WayVR"
},
"APPLICATION_LAUNCHER": "Avvio applicazioni",
"APPLICATION_STARTED": "Applicazione avviata",
@@ -375,9 +373,7 @@
"TITLE": "Opzioni di alimentazione",
"SHUTDOWN": "Spegni",
"SUSPEND": "Sospendi",
"SHUTDOWN_ARE_YOU_SURE": "Sei sicuro di voler spegnere il computer?",
"NOT_SUPPORTED": "Le opzioni di alimentazione richiedono systemd o elogind.",
"SUSPEND_ARE_YOU_SURE": "Sei sicuro di voler mettere il computer in sospensione?"
"SHUTDOWN_ARE_YOU_SURE": "Sei sicuro di voler spegnere il computer?"
},
"YES": "Sì",
"NO": "No"
+2 -6
View File
@@ -263,9 +263,7 @@
},
"TUTORIAL": "チュートリアル",
"RESTART_WAYVR": "WayVRを再起動",
"CLOSE_WAYVR": "WayVRを閉じる",
"SPEECH_TO_TEXT": "音声入力",
"WHISPER_GPU_SELECT": "GPUを選択"
"CLOSE_WAYVR": "WayVRを閉じる"
},
"APPLICATION_LAUNCHER": "アプリランチャー",
"APPLICATION_STARTED": "アプリが起動しました",
@@ -375,9 +373,7 @@
"TITLE": "電源オプション",
"SHUTDOWN": "シャットダウン",
"SUSPEND": "サスペンド",
"SHUTDOWN_ARE_YOU_SURE": "コンピューターをシャットダウンしてもよろしいですか?",
"NOT_SUPPORTED": "電源オプションには systemd または elogind が必要です。",
"SUSPEND_ARE_YOU_SURE": "コンピューターをスリープ状態にしますか?"
"SHUTDOWN_ARE_YOU_SURE": "コンピューターをシャットダウンしてもよろしいですか?"
},
"YES": "はい",
"NO": "いいえ"
+2 -6
View File
@@ -263,9 +263,7 @@
},
"TUTORIAL": "Samouczek",
"RESTART_WAYVR": "Zrestartuj WayVR",
"CLOSE_WAYVR": "Zamknij WayVR",
"SPEECH_TO_TEXT": "Zamiana mowy na tekst",
"WHISPER_GPU_SELECT": "Wybierz GPU"
"CLOSE_WAYVR": "Zamknij WayVR"
},
"APPLICATION_LAUNCHER": "Launcher aplikacji",
"APPLICATION_STARTED": "Uruchomiono aplikację",
@@ -375,9 +373,7 @@
"TITLE": "Opcje zasilania",
"SHUTDOWN": "Wyłącz",
"SUSPEND": "Uśpij",
"SHUTDOWN_ARE_YOU_SURE": "Czy na pewno chcesz wyłączyć komputer?",
"NOT_SUPPORTED": "Opcje zasilania wymagają systemd lub elogind.",
"SUSPEND_ARE_YOU_SURE": "Czy na pewno chcesz uśpić komputer?"
"SHUTDOWN_ARE_YOU_SURE": "Czy na pewno chcesz wyłączyć komputer?"
},
"YES": "Tak",
"NO": "Nie"
-384
View File
@@ -1,384 +0,0 @@
{
"DEFINITIONS": {
"ANCHORED": "Förankrad",
"FLOATING": "Flytande",
"STATIC": "Stationär",
"SET": "Set",
"WATCH": "Klocka",
"DASHBOARD": "Kontrollpanel",
"EDIT_MODE": "Redigeringsläge",
"SETTINGS": "Inställningar",
"KEYBOARD": "Tangentbord",
"TASKBAR": "Uppgiftsfält",
"FEATURES": "Funktioner",
"OVERLAY": "Overlay",
"SKYMAP": "Skymap",
"PLAYSPACE": "Playspace",
"D_PAD": "D-pad"
},
"ACTIONS": {
"RECENTER_PLAYSPACE": "Centrera om playspace"
},
"APP_LAUNCHER": {
"ASPECT": {
"SEMI_TALL": "Halvhög",
"SEMI_WIDE": "Halvbredd",
"SQUARE": "Kvadratisk",
"TALL": "Hög",
"WIDE": "Bredbild"
},
"ASPECT_TITLE": "Bildförhållande",
"AUTOSTART": "Kör automatiskt vid start",
"LAUNCH": "Starta",
"MODE": {
"CAGE": "Kompatibilitetsläge (Cage)",
"NATIVE": "Nativt läge"
},
"RES_TITLE": "Upplösning"
},
"APP_SETTINGS": {
"ALLOW_SLIDING": "Stick-interaktion vid grepp",
"ANIMATION_SPEED": "Hastighet för UI-animationer",
"AUTOSTART_APPS": "Appar som ska köras vid start",
"BLOCK_GAME_INPUT": "Blockera spelindata",
"BLOCK_GAME_INPUT_HELP": "Blockerar all input när en overlay hovras över",
"BLOCK_GAME_INPUT_IGNORE_WATCH": "Ignorera klockan vid blockering av input",
"BLOCK_GAME_INPUT_IGNORE_WATCH_HELP": "Blockera inte inmatning när klockan är markerad",
"BLOCK_POSES_ON_KBD_INTERACTION": "Blockera poser vid interaktion med tangentbord",
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Blockerar spelets mottagning av poser när tangentbordet hovras över och 'Blockera spel-input' är aktiverat",
"BINDINGS": {
"COMP": {
"CLICK": "Klick",
"FORCE": "Tryckkraft",
"TOUCH": "Beröring",
"VALUE": "Värde",
"PROXIMITY": "Närhet",
"X_AXIS": "X-axel",
"Y_AXIS": "Y-axel"
},
"TYPE": {
"BUMPER": "Bumper",
"DPAD_UP": "D-pad upp",
"DPAD_DOWN": "D-pad ned",
"DPAD_LEFT": "D-pad vänster",
"DPAD_RIGHT": "D-pad höger",
"JOYSTICK": "Joystick",
"MENU": "Meny",
"SHOULDER": "Axel",
"SQUEEZE": "Grepp",
"SYSTEM": "System",
"THUMBREST": "Tumstöd",
"THUMBSTICK": "Thumbstick",
"TRACKPAD": "Trackpad",
"TRIGGER": "Trigger",
"VIEW": "Visa"
},
"CLICK": {
"ANY": "Vilken som helst",
"DOUBLE": "Dubbelklicka",
"TRIPLE": "Trippelklick",
"TYPE": "Antal tryck"
},
"ACTION": {
"CLICK": "Klicka*",
"GRAB": "Greppa*",
"ALT_CLICK": "Anpassat shell-kommando (ställ in alt_click i config)",
"SHOW_HIDE": "Visa, dölj, centrera om*",
"TOGGLE_DASHBOARD": "Växla instrumentpanel",
"SPACE_DRAG": "Dra playspace",
"SPACE_ROTATE": "Rotera playspace",
"SPACE_RESET": "Återställ Playspace",
"CLICK_MODIFIER_RIGHT": "Högerklick-modifierare",
"CLICK_MODIFIER_MIDDLE": "Modifierare för mittklick",
"MOVE_MOUSE": "Flytta mus (om avstängd som standard)",
"SCROLL": "Scrolla*"
},
"LEFT": "Vänster",
"RIGHT": "Höger",
"CHORD": "Kombination",
"CHORD_ADD_INPUT": "Lägg till inmatning för kombination",
"CHORD_INPUT_HAND": "Inmatningshand",
"COMPONENT": "Aktiveringstyp",
"SUBPATH": "Aktiverande kontroll",
"THRESHOLD": "Aktivera vid övre värdet\nAvaktivera vid nedre värdet"
},
"BROWSE_ONLINE_CATALOG": "Bläddra i onlinekatalogen...",
"BROWSE_SKYMAPS": "Bläddra bland skymaps",
"CAPTURE_METHOD": "Metod för skärmavbildning",
"CAPTURE_METHOD_HELP": "Prova att ändra detta om du\nupplever svarta eller glitchiga skärmar",
"COLOR_KEYING": "Färgnyckelning",
"COLOR_PALETTE": "Färgpalett",
"COLOR_PALETTE_HELP": "Klicka för att bläddra bland färgpaletter.\nSe webbplatsen för att skapa egna!",
"COLOR_PALETTES": "Färgpaletter",
"COLOR_PALETTE_CURRENT": "Nuvarande aktivt tema.",
"COLOR_PALETTE_CUSTOM_URL": "Lägg till ett eget anpassat tema!",
"COLOR_PALETTE_CUSTOM_URL2": "Klicka för att öppna webbmanualen.",
"COLOR_PALETTE_ACTIVATE": "Klicka för att använda detta tema.",
"CLEAR_PIPEWIRE_TOKENS": "Rensa PipeWire-tokens",
"CLEAR_PIPEWIRE_TOKENS_HELP": "Fråga om skärmval vid nästa start",
"CLEAR_SAVED_STATE": "Återställ sparade inställningar",
"CLEAR_SAVED_STATE_HELP": "Återställ sets och overlay-positioner",
"CLICK_FREEZE_TIME_MS": "Klickfrysningstid (ms)",
"CLICK_FREEZE_TIME_MS_HELP": "Hjälper till med precision vid dubbelklick",
"CLOCK_12H": "12-timmarsklocka",
"CONTROLS": "Kontroller",
"DELETE_ALL_CONFIGS": "Rensa konfiguration",
"DELETE_ALL_CONFIGS_HELP": "Ta bort alla konfigurationsfiler från conf.d",
"DOUBLE_CURSOR_FIX": "Fix för dubbla markörer",
"DOUBLE_CURSOR_FIX_HELP": "Aktivera om du ser två markörer",
"DEFAULT_OVERLAY_SCALE": "Standardstorlek för overlay",
"DEFAULT_OVERLAY_SCALE_HELP": "Skapa nya overlays i den här skalan.",
"DEFAULT_CURVATURE": "Standardkurvatur för overlay",
"DEFAULT_OPACITY": "Standardopacitet för overlay",
"DEFAULT_POSITIONING": "Standardposition för overlay",
"OVERLAY_DEFAULTS_HELP": "Gäller endast nya overlays",
"ENABLED": "Aktiverad",
"FEATURES": "Funktioner",
"FOCUS_FOLLOWS_MOUSE_MODE": "Musrörelse vid trigger-tryck",
"HANDSFREE_POINTER": "Handsfree-läge",
"HANDSFREE_POINTER_HELP": "Inmatning som används när rörelsekontroller saknas.\nVänster nyp är greppa, höger är klicka.",
"HANDSFREE_ALT_TAB": "Sikte för app-växlare",
"HANDSFREE_ALT_TAB_HELP": "Håll in Super+X, sikta på önskat fönster och släpp sedan för att byta fokus.",
"DIAGNOSTICS": {
"CPU_ACCESSIBLE_VRAM": "CPU-tillgängligt VRAM: {NUM} MiB",
"TITLE": "Diagnostik",
"ENABLE_REBAR": "Aktivera [resizable bar] i BIOS-inställningarna för optimal VR-prestanda"
},
"DIAGNOSTICS_REASON": {
"PACTL": "Krävs för kontroll av högtalare/mikrofon och byte av ljudkort",
"CAGE": "Rekommenderas vid inkompatibilitet med vissa X11-applikationer (kompatibilitetsläge)",
"XWAYLAND_SATELLITE": "Rekommenderas om du vill köra X11 (Xwayland)-applikationer i nativt läge",
"XDG_OPEN": "Krävs för att öppna URL:er eller Steam-spel"
},
"HIDE_GRAB_HELP": "Dölj grepphjälp",
"HIDE_USERNAME": "Dölj användarnamn",
"INPUT_EMULATION_METHOD": "Metod för inmatningssimulering",
"INPUT_EMULATION_METHOD_HELP": "Prova att ändra detta om mus- eller\ntangentbordindata inte registreras.",
"INPUT_CAPTURE_METHOD": "Metod för inmatningsfångst",
"INPUT_CAPTURE_METHOD_HELP": "Möjliggör användning av fysisk kringutrustning i WayVR.\n Super+Z: Växla input mellan WayVR och skrivbordet\n Super+X: Växla mellan WayVR-appar",
"INPUT_PROFILES": "Ändra styrinställningar",
"INPUT_PROFILES_HELP": "OpenXR-kontrollbindningar",
"INVERT_SCROLL_DIRECTION_X": "Invertera horisontell scrollriktning",
"INVERT_SCROLL_DIRECTION_Y": "Invertera vertikal rullningsriktning",
"LENS_SEPARATION_MM": "Linsavstånd (mm)",
"KEYBOARD_MIDDLE_CLICK": "Tangentbordets mittklick",
"KEYBOARD_MIDDLE_CLICK_HELP": "Modifier som används vid skrivning\nmed lila laser",
"KEYBOARD_SOUND_ENABLED": "Tangentbordsljud",
"LANGUAGE": "Språk",
"LEFT_HANDED_MOUSE": "Vänstermus",
"LEFT_HANDED_MOUSE_HELP": "Används om musknapparna är ombytta",
"LONG_PRESS_DURATION": "Långtryckstid",
"LOOK_AND_FEEL": "Utseende och känsla",
"MISC": "Övrigt",
"MIDDLE_GRABS_SINGLE": "Modifierare för mittklick greppar enskilda objekt",
"MIDDLE_GRABS_SINGLE_HELP": "Att greppa med modifieraren för mittklick (lila laser)\ngreppar enskilda overlays istället för hela setet",
"NOTIFICATIONS_ENABLED": "Aktivera aviseringar",
"NOTIFICATIONS_SOUND_ENABLED": "Ljud för aviseringar",
"NOT_SUPPORTED": "Ej stöds",
"NO_SKYMAPS_FOUND": "Inga skymaps hittades",
"OPAQUE_BACKGROUND": "Opak bakgrund",
"OPTION": {
"ANCHORED_HELP": "Följer centermarkören. Rekommenderas.",
"FLOATING_HELP": "Flyttas oberoende, centreras om.\nLätt att tappa bort; rekommenderas ej.",
"STATIC_HELP": "Håller sig på plats i förhållande till världen.",
"AUTO": "Automatisk",
"AUTO_HELP": "ScreenCopy GPU om det stöds,\nannars PipeWire GPU.",
"EYE_ONLY": "Ögonlaser",
"EYE_PINCH": "Öga + pinch",
"HMD_ONLY": "HMD-laser",
"HMD_PINCH": "HMD + pinch",
"NONE": "Ingen",
"NONE_INPUT_HELP": "Emulera inte mus och tangentbord.\nInteraktion med skärmar blir inte möjlig.",
"NONE_INPUT_CAPTURE_HELP": "Fånga inte mus och tangentbord.\nDu kommer inte att kunna använda fysisk mus\neller tangentbord i appar som startas i WayVR.",
"PIPEWIRE_HELP": "Snabb GPU-avbildning,\nstandard för alla skrivbord.",
"PW_FALLBACK_HELP": "Långsam metod med hög CPU-belastning.\nProva om PipeWire GPU inte fungerar",
"SCREENCOPY_GPU_HELP": "Snabb, inga popup-fönster för skärmdelning.\nFungerar på: Hyprland, Niri, River, Sway",
"SCREENCOPY_HELP": "Långsam, inga popup-fönster för skärmdelning.\nFungerar på: Hyprland, Niri, River, Sway",
"UINPUT": "Enhetsemulering",
"UINPUT_HELP": "Använder modulen uinput för att emulera en enhet.\nFungerar generellt bra på de flesta skrivbord.",
"EVDEV_HELP": "Ta kontroll över inmatningsenheter för exklusiv användning.\nAnvändaren måste tillhöra gruppen 'input'.",
"WL_VIRTUAL": "Wayland virtuell inmatning",
"WL_VIRTUAL_HELP": "Skickar händelser till Wayland-kompositorn via\nzwlr_virtual_pointer och zwp_virtual_keyboard.\nFungerar endast på vissa skrivbord.\nFungerar INTE på KDE, GNOME eller COSMIC."
},
"POINTER_ACCELERATION": "Musacceleration",
"POINTER_ACCELERATION_HELP": "På: Adaptiv musacceleration\nAv: Konstant hastighet",
"POINTER_LERP_FACTOR": "Utjämning av pekare",
"POINTER_SPEED": "Mushastighet",
"REQUIRES_RESTART": "Kräver omstart",
"RESET_PLAYSPACE": "Återställ playspace",
"RESET_PLAYSPACE_HELP": "Nollställ offset för playspace.",
"SAVE_PLAYSPACE_CENTER": "Spara playspace-center",
"SAVE_PLAYSPACE_CENTER_HELP": "Använd nuvarande playspace för bindningen \"återställ playspace\".",
"RESET_PLAYSPACE_CENTER": "Återställ playspace-center",
"RESET_PLAYSPACE_CENTER_HELP": "Rensa tidigare inställd mittpunkt för playspace.",
"RESTART_WAYVR": "Starta om WayVR",
"CLOSE_WAYVR": "Stäng WayVR",
"ROUND_MULTIPLIER": "UI-kanternas rundning",
"SCREEN_RENDER_DOWN": "Rendera skärm i lägre upplösning",
"SCREEN_RENDER_DOWN_HELP": "Hjälper mot aliasing på skärmar med hög upplösning",
"SCROLL_SPEED": "Scrollningshastighet",
"SELECT_VARIANT": "Välj variant",
"SNAP_ANGLE_DEG": "Snapvinkel för fönster (pitch & roll)",
"SNAP_ANGLE_DEG_HELP": "Greppade fönster snäpps till denna vinkel på pitch- och rollaxeln.",
"ENABLE_WATCH": "Aktivera klockan",
"SETS_ON_WATCH": "Set på klockan",
"SKYBOX": "Skymap",
"SKYMAP_ALREADY_DOWNLOADED": "Denna skymap är redan nedladdad. Välj åtgärd.",
"SPACE_DRAG": "Playspace",
"SPACE_DRAG_MULTIPLIER": "Multiplikator för space-drag",
"SPACE_DRAG_AFFECTS_WORLD": "Lämna overlays kvar vid flytt av playspace",
"SPACE_DRAG_UNLOCKED": "Tillåt space-drag på alla axlar",
"SPACE_GRAVITY_DAMPING": "Dämpning",
"SPACE_GRAVITY_DAMPING_HELP": "Artificiell dämpning för att sakta ner rörelser. 0,1 – hög dämpning, 1,0 – ingen dämpning",
"SPACE_GRAVITY_FLING_STRENGTH": "Kaststyrka",
"SPACE_GRAVITY_FLING_STRENGTH_HELP": "Intensitetsmultiplikator för gravitationskraften efter space-drag.\n0.0 - ingen rörelse alls, 2.0 - dubbel intensitet",
"SPACE_GRAVITY_GRAVITY": "Gravitation",
"SPACE_GRAVITY_GRAVITY_HELP": "Mängd nedåtgående kraft. 0.0 - ingen gravitation",
"SPACE_GRAVITY_GROUND_FRICTION": "Markfriktion",
"SPACE_GRAVITY_GROUND_FRICTION_HELP": "Mängden friktion som saktar ner dig när du rör vid marken.\n0.0 - ingen friktion (som på is), 1.0 - sträv yta",
"SPACE_GRAVITY_FLOOR_HEIGHT": "Golvhöjd",
"SPACE_GRAVITY_FLOOR_HEIGHT_HELP": "Y-positionen för golvet. Gravitationen upphör när denna höjd nås.",
"SPACE_ROTATE_UNLOCKED": "Tillåt rotation av playspace på alla axlar",
"ROTATIONS": "HMD-rotationer",
"SESSION_TIME": "Sessionstid",
"STATISTICS": "Statistik",
"TROUBLESHOOTING": "Felsökning",
"UI_GRADIENT_INTENSITY": "Intensitet för UI-gradient",
"UPRIGHT_SCREEN_FIX": "Fix för upprätt skärm",
"UPRIGHT_SCREEN_FIX_HELP": "Fixar upprätt ställda skärmar på vissa skrivbord",
"USE_PASSTHROUGH": "Aktivera passthrough",
"USE_PASSTHROUGH_HELP": "Tillåt passthrough om XR-runtime stöder det",
"USE_SKYBOX": "Visa skymap",
"USE_SKYBOX_HELP": "Visa en skymap om ingen scenapp eller passthrough används",
"WATCH_VIEW_ANGLE": "Klockans synvinklar",
"WATCH_VIEW_ANGLE_HELP": "Styr hur klockan tonas ut",
"WHISPER": {
"NEED_TO_DOWNLOAD_MODEL": "Den valda modellen måste laddas ner.\nVill du ladda ner den nu?",
"REMOVE_UNUSED_MODELS": "Du stänger av tal-till-text-funktionen.\nVill du ta bort nedladdade Whisper-modeller för att frigöra utrymme?"
},
"WHISPER_MODEL": "Tal-till-text-modell",
"WHISPER_MODEL_HELP": "Whisper-modell som ska användas.\nStörre modeller ger mer precisa\nresultat, men förbrukar mer VRAM.",
"SPEECH_TO_TEXT": "Tal till text",
"WHISPER_GPU_SELECT": "Välj GPU",
"TUTORIAL": "Handbok",
"GRID_OPACITY": "Golvgridens opacitet",
"GRID_OPACITY_HELP": "Opacitet för golvgridet när skymap är aktiverat",
"XWAYLAND_BY_DEFAULT": "Kör appar i kompatibilitetsläge som standard",
"SAVE": "Spara",
"CANCEL": "Avbryt"
},
"APPLICATION_LAUNCHER": "App-startare",
"APPLICATION_STARTED": "Applikationen startades",
"APPLICATIONS": "Applikationer",
"AUDIO": {
"SWITCH_TO_THIS_DEVICE": "Byt till den här enheten",
"AUTO_SWITCH_TO_VR_AUDIO": "Byt automatiskt till VR-ljud",
"CARDS": "Kort",
"FAILED_TO_SWITCH_MICROPHONE": "Kunde inte byta mikrofon",
"MICROPHONE_SET_SUCCESSFULLY": "Mikrofonen har ändrats",
"MICROPHONES": "Mikrofoner",
"NO_VR_MICROPHONE_SWITCH_MANUALLY": "Ingen VR-mikrofon hittades. Byt manuellt.",
"NO_VR_SPEAKERS_FOUND_SWITCH_MANUALLY": "Inga VR-högtalare hittades. Byt manuellt.",
"SELECT_AUDIO_CARD_PROFILE": "Välj ljudkortsprofil",
"SETTINGS": "Ljudinställningar",
"SPEAKERS": "Högtalare",
"SPEAKERS_SET_SUCCESSFULLY": "Högtalarna har ändrats",
"VOLUME": "Volym"
},
"CLOSE_WINDOW": "Stäng fönster",
"CREATION_DATE": "Skapad datum",
"DEBUG_INFO": "Debuginfo",
"DISPLAY_BRIGHTNESS": "Skärmljusstyrka",
"DONATE": {
"SUPPORT_US": "Stöd oss",
"DESCRIPTION": "Vi är ett litet team på två utvecklare som bygger WayVR på vår fritid. Om du gillar att använda programvaran, överväg att stötta oss – varje donation hjälper oss att fortsätta.",
"THANK_YOU_WAYVR": "Tack från WayVR-teamet!",
"BUTTON_TOOLTIP": "Donera via Open Collective",
"CURRENT_SUPPORTERS": "Nuvarande supportrar",
"BROUGHT_TO_YOU_VARIANTS_OFTEN": "Stöds av {USER}|Särskilt tack till {USER}|Sponsras av {USER}|Backas av {USER}|Drivs av community-medlemmar som {USER}|Möjliggjorts av {USER}|Upprätthålls av {USER}|Bidrag från {USER}|Finansierat av {USER}",
"BROUGHT_TO_YOU_VARIANTS_RARE": "Kaffe sponsrat av {USER}|Drivs av {USER}|Hälsningar till {USER}!|Håller oss igång: {USER}|Hjälper oss att växa: {USER}|Håller servrarna igång: {USER}|Med tacksamhet till {USER}|Drivs av {USER}|Stöds av vänner som {USER}",
"BROUGHT_TO_YOU_VARIANTS_VERY_RARE": "Stöttas av legender som {USER}|Koffein sponsrat av {USER}|En sann legend: {USER}|Dagens hjälte: {USER}|En total superstjärna: {USER}|Betalar elräkningen: {USER}|Räddaren av vårt förstånd: {USER}|Visar vägen: {USER}|Koffein-till-kod-omvandlare tillhandahållen av {USER}|Håller produktionen stabil: {USER}|Den ende och ende: {USER}|Skickar dmabuf-buffertar: {USER}"
},
"DOWNLOAD": "Ladda ner",
"DOWNLOADER": "Nedladdare",
"WELCOME": {
"ALL_SET": "Nu är du redo!",
"BASICS": "Dessa grunder gör att du enkelt kan börja använda WayVR.",
"CENTER_MARKER": "Centreringsmarkören visas framför dig vid omcentrering. Placera overlays runt den!",
"DOUBLE_PRESS_TO_TOGGLE": "Dubbeltryck B (på Index) eller Y (på Meta) för att visa, centrera om eller dölja ditt set. Prova nu.",
"FEATURES_DOCUMENTATION": "Det finns många andra funktioner värda att anpassa. För mer information, se dokumentationen på",
"FINISH_TUTORIAL": "Avsluta handledningen",
"GRAB_BOTH_HANDS": "Greppa med båda händerna om du vill flytta ett specifikt overlay i ditt set.",
"GRAB_ONE_HAND": "Greppa med en hand för att flytta ditt set.",
"GREETING": "Hej!",
"GUIDE_INTRO": "Låt oss guida dig genom dina första steg.",
"LASER_COLORS": "Laserfärger",
"LASER_FUNCTIONALITY": "Mycket av funktionaliteten i WayVR beror på vilken laserfärg som används för att interagera med ett UI-element. Du behöver inte trycka på knapparna, det räcker att röra vid dem!",
"LAST_PAGE_INFO": "Denna handledning slutar visas när du når den sista sidan.",
"LOOK_AT_LEFT_WRIST": "Titta på din vänstra handled. Det är din klocka.",
"MIDDLE_CLICK_MODE_PURPLE_LASER": "Mittenklick-läge: Lila laser",
"MOVING_YOUR_SET": "Flytta ditt set",
"NEXT_BUTTON": "Tryck på nästa-knappen nedan för att komma igång.",
"REGULAR_MODE_BLUE_LASER": "Vanligt läge: Blå laser",
"REPOSITION_THIS_WINDOW_BY_GRABBING": "Flytta det här fönstret med kontrollens grip-funktion.",
"RIGHT_CLICK_MODE_ORANGE_LASER": "Högerklicksläge: Orange laser",
"THANKS_FOR_INSTALLING": "Tack för att du installerade WayVR!",
"WATCH": "Klocka",
"WORKING_SET": "Aktivt Set"
},
"DOWNLOAD_AGAIN": "Ladda ner igen",
"DOWNLOADING_FILE": "Laddar ner fil...",
"FAILED_TO_LAUNCH_APPLICATION": "Kunde inte starta applikationen:",
"GAME_LAUNCHED": "Spelet har startats",
"GAME_LIST": {
"NO_GAMES_FOUND": "Inga spel hittades",
"RUNNING_GAMES_LIST": "Lista över körande spel"
},
"GAMES": "Spel",
"GENERAL_SETTINGS": "Allmänna inställningar",
"GETTING_STARTED": "Kom igång",
"HEIGHT": "Höjd",
"HELLO": "Hej!",
"HELLO_USER": "Hej, {USER}!",
"HIDE": "Dölj",
"HOME_SCREEN": "Hem",
"MODIFICATION_DATE": "Ändringsdatum",
"MONADO_RUNTIME": "Monado runtime",
"LOADING": "Laddar...",
"POPUP_ADD_DISPLAY": {
"RESOLUTION": "Upplösning"
},
"POWER_OPTIONS": {
"TITLE": "Strömalternativ",
"SHUTDOWN": "Stäng av",
"SUSPEND": "Viloläge",
"NOT_SUPPORTED": "Strömalternativ kräver systemd eller elogind.",
"SHUTDOWN_ARE_YOU_SURE": "Är du säker på att du vill stänga av datorn?",
"SUSPEND_ARE_YOU_SURE": "Är du säker på att du vill försätta datorn i viloläge?"
},
"PROCESS": {
"FORCE_KILL": "Tvinga avslut",
"STOP": "Stoppa"
},
"PROCESS_LIST": "Processlista",
"REFRESH": "Uppdatera",
"REMOVE": "Ta bort",
"APPLY": "Verkställ",
"RELOAD_FROM_DISK": "Ladda om från disk",
"SETTINGS": "Inställningar",
"SHOW": "Visa",
"TARGET_PATH": "Mål sökväg",
"TERMINATE_PROCESS": "Avsluta process",
"VERSION": "Version",
"WIDTH": "Bredd",
"PINNED_APPS": "Fästa applikationer",
"SAVED_TO_FAVOURITES": "Sparad i fästa applikationer",
"ADD_TO_FAVOURITES": "Lägg till i fästa applikationer",
"ADD_TO_FAVOURITES_HINT": "Spara förinställning och fäst den i app-menyn",
"REMOVE_FROM_FAVOURITES": "Ta bort från fästa applikationer",
"EMPTY": "Tomt",
"INSTALLED": "Installerad",
"NOT_INSTALLED": "Ej installerad",
"YES": "Ja",
"NO": "Nej"
}
+2 -6
View File
@@ -263,9 +263,7 @@
},
"TUTORIAL": "教程",
"RESTART_WAYVR": "重启 WayVR",
"CLOSE_WAYVR": "关闭 WayVR",
"SPEECH_TO_TEXT": "语音转文本",
"WHISPER_GPU_SELECT": "选择 GPU"
"CLOSE_WAYVR": "关闭 WayVR"
},
"APPLICATION_LAUNCHER": "应用启动器",
"APPLICATION_STARTED": "应用程序已启动",
@@ -375,9 +373,7 @@
"TITLE": "电源选项",
"SHUTDOWN": "关机",
"SUSPEND": "暂停",
"SHUTDOWN_ARE_YOU_SURE": "确定要关机吗?",
"NOT_SUPPORTED": "电源选项需要 systemd 或 elogind。",
"SUSPEND_ARE_YOU_SURE": "确定要让计算机进入睡眠状态吗?"
"SHUTDOWN_ARE_YOU_SURE": "确定要关机吗?"
},
"YES": "确定",
"NO": "否"
+20 -33
View File
@@ -25,7 +25,7 @@ use wgui::{
use wlx_common::{
async_executor::AsyncExecutor,
audio,
dash_interface::{BoxDashInterface, ConfigChangeKind, DashBackendTask, DashPlayspaceTask},
dash_interface::{BoxDashInterface, ConfigChangeKind, DashPlayspaceTask},
locale::WayVRLangProvider,
palette::load_palette,
timestep::{self, Timestep},
@@ -131,7 +131,6 @@ pub enum FrontendTask {
RunCommand(String /* program */, Vec<String> /* args */),
HideDashboard,
MarkTutorialGraduated,
BackendTask(DashBackendTask),
}
impl<T: 'static> Frontend<T> {
@@ -286,23 +285,21 @@ impl<T: 'static> Frontend<T> {
Ok(())
}
fn power_action_confirm(&mut self, power_action: PowerAction) {
let translation_key = match power_action {
PowerAction::Suspend => "POWER_OPTIONS.SUSPEND_ARE_YOU_SURE",
PowerAction::Poweroff => "POWER_OPTIONS.SHUTDOWN_ARE_YOU_SURE",
};
fn action_poweroff_confirm(&mut self) {
let tasks = self.tasks.clone();
views::dialog_box::mount_popup(
self.popup_dialog_box.clone(),
self.tasks.clone(),
views::dialog_box::Params {
globals: self.globals.clone(),
message: Translation::from_translation_key(translation_key),
message: Translation::from_translation_key("POWER_OPTIONS.SHUTDOWN_ARE_YOU_SURE"),
entries: views::dialog_box::get_entries_yes_no(),
on_action_click: Box::new(move |action| {
if action == "yes" {
tasks.push(task_for_power_action(power_action));
tasks.push(FrontendTask::RunCommand(
String::from("loginctl"),
vec![String::from("poweroff")],
));
}
}),
},
@@ -310,6 +307,14 @@ impl<T: 'static> Frontend<T> {
}
fn process_power_action(&mut self, action: &str, data: &mut T) {
if !is_exec_installed("loginctl") {
// TODO: add support for other init systems?
self.tasks.push(FrontendTask::PushToast(Translation::from_raw_text(
"loginctl is not installed",
)));
return;
}
match action {
"restart" => {
self.interface.restart(data);
@@ -318,10 +323,13 @@ impl<T: 'static> Frontend<T> {
self.interface.close(data);
}
"suspend" => {
self.power_action_confirm(PowerAction::Suspend);
self.tasks.push(FrontendTask::RunCommand(
String::from("loginctl"),
vec![String::from("suspend")],
));
}
"shutdown" => {
self.power_action_confirm(PowerAction::Poweroff);
self.action_poweroff_confirm();
}
_ => {
unreachable!()
@@ -447,7 +455,6 @@ impl<T: 'static> Frontend<T> {
FrontendTask::OpenURL(url) => self.action_open_url(url),
FrontendTask::RunCommand(program, args) => self.action_run_command(program, args),
FrontendTask::MarkTutorialGraduated => self.action_tutorial_graduated(params.data),
FrontendTask::BackendTask(task) => self.interface.backend_task(params.data, task)?,
};
Ok(())
}
@@ -674,23 +681,3 @@ impl<T: 'static> Frontend<T> {
};
}
}
#[derive(Debug, Clone, Copy)]
pub enum PowerAction {
Suspend,
Poweroff,
}
pub fn task_for_power_action(action: PowerAction) -> FrontendTask {
for cmd in ["systemctl", "loginctl"] {
if is_exec_installed(cmd) {
let (command, args) = match action {
PowerAction::Suspend => (cmd.to_string(), vec!["suspend".to_string()]),
PowerAction::Poweroff => (cmd.to_string(), vec!["poweroff".to_string()]),
};
log::debug!("Command line for {action:?}: {command} {}", args.join(" "));
return FrontendTask::RunCommand(command, args);
}
}
FrontendTask::PushToast(Translation::from_translation_key("POWER_OPTIONS.NOT_SUPPORTED"))
}
+11 -14
View File
@@ -61,7 +61,7 @@ impl<T> Tab<T> for TabApps<T> {
}
}
fn find_entry_from_app_name<'a>(app_id: &str, entries: &'a [Rc<DesktopEntry>]) -> Option<&'a Rc<DesktopEntry>> {
fn find_entry_from_app_name<'a>(app_id: &str, entries: &'a [DesktopEntry]) -> Option<&'a DesktopEntry> {
entries.iter().find(|&entry| *entry.app_id == *app_id)
}
@@ -72,7 +72,7 @@ impl<T> TabApps<T> {
let mut stale_entries = Vec::<Rc<str>>::new();
let mut pinned_desktop_entries = Vec::<(PinnedApp, Rc<DesktopEntry>)>::new();
let mut pinned_desktop_entries = Vec::<(PinnedApp, &DesktopEntry)>::new();
// collect pinned desktop entries
{
@@ -83,7 +83,7 @@ impl<T> TabApps<T> {
continue;
};
pinned_desktop_entries.push((pinned_app.clone(), desktop_entry.clone()));
pinned_desktop_entries.push((pinned_app.clone(), desktop_entry));
}
// cleanup:
// remove non-existent app ids from pinned apps
@@ -119,7 +119,7 @@ impl<T> TabApps<T> {
&mut state.parser_state,
&doc_params(frontend.globals.clone()),
self.pinned_apps_parent,
desktop_entry.as_ref(),
desktop_entry,
Some(tooltip_string),
)?;
@@ -139,8 +139,8 @@ impl<T> TabApps<T> {
struct AppList {
//data: Vec<ParserData>,
all_entries: Vec<Rc<DesktopEntry>>,
entries_to_mount: VecDeque<Rc<DesktopEntry>>,
all_entries: Vec<DesktopEntry>,
entries_to_mount: VecDeque<DesktopEntry>,
list_parent: WidgetPair,
prev_category_name: String,
}
@@ -150,7 +150,7 @@ fn on_app_click(
frontend_tasks: FrontendTasks,
tasks: Tasks<Task>,
globals: WguiGlobals,
entry: Rc<DesktopEntry>,
entry: DesktopEntry,
state: Rc<RefCell<State>>,
pinned_app: Option<PinnedApp>,
) -> ButtonClickCallback {
@@ -307,17 +307,14 @@ fn get_category_name_score(name: &str) -> u8 {
Scores::Unknown as u8
}
fn get_best_category_name<S>(categories: &[S]) -> Option<&str>
where
S: AsRef<str>,
{
fn get_best_category_name(categories: &[Rc<str>]) -> Option<&Rc<str>> {
let mut best_score: u8 = 0;
let mut best_category: Option<&str> = None;
let mut best_category: Option<&Rc<str>> = None;
for cat in categories {
let score = get_category_name_score(cat.as_ref());
let score = get_category_name_score(cat);
if score > best_score {
best_category = Some(cat.as_ref());
best_category = Some(cat);
best_score = score;
}
}
+66 -57
View File
@@ -1,14 +1,16 @@
use std::rc::Rc;
use crate::{
tab::settings::{self, SettingType, Task, horiz_cell, mount_requires_restart},
util::wgui_simple,
};
use crate::tab::settings::{self, SettingType, Task, horiz_cell, mount_requires_restart};
use wgui::{
components::{button::ComponentButton, checkbox::ComponentCheckbox, slider::ComponentSlider},
components::{
button::{ButtonClickEvent, ComponentButton},
checkbox::ComponentCheckbox,
slider::ComponentSlider,
},
layout::{Layout, WidgetID},
parser::{Fetchable, ParseDocumentParams, ParserState, TemplateParams},
task::Tasks,
widget::label::WidgetLabel,
windowing::context_menu,
};
use wlx_common::{DesktopBackend, XrBackend, config::GeneralConfig, dash_interface::InterfaceFeats};
@@ -295,6 +297,7 @@ where
mp.idx += 1;
let mut params = TemplateParams::new();
params.insert("id", &id);
match setting.get_translation() {
Ok(translation) => params.insert("translation", translation),
@@ -307,63 +310,69 @@ where
let id_cell = horiz_cell(mp.layout, parent)?;
let setting_str = setting.as_ref();
let title = setting.get_enum_title(mp.config);
let feats = mp.feats;
let cells = EnumType::VARIANTS
.iter()
.filter_map(|item| {
if item.get_bool("Hidden").unwrap_or(false) {
return None;
}
if item
.get_str("Backend")
.is_some_and(|x| XrBackend::try_from(x).unwrap() != feats.xr_backend)
{
return None;
}
if item
.get_str("Desktop")
.is_some_and(|x| DesktopBackend::try_from(x).unwrap() != feats.desktop_backend)
{
return None;
}
let value = item.as_ref();
let title = SettingType::get_enum_title_inner(*item);
let tooltip = SettingType::get_enum_tooltip_inner(*item);
let text = &title.text;
let translated = if title.translated { "1" } else { "0" };
let action = format!("{setting_str};{id};{value};{text};{translated}");
Some(context_menu::Cell::Button(context_menu::Button {
action_name: Some(action.into()),
title,
tooltip,
..Default::default()
}))
})
.collect();
let tasks = mp.tasks.clone();
wgui_simple::mount_dropdown(wgui_simple::MountDropdownParams {
layout: mp.layout,
parser_state: mp.parser_state,
doc_params: mp.doc_params,
parent: id_cell,
id,
params,
current_text: title,
cells,
on_open: Box::new(move |pos, cells| tasks.push(Task::OpenContextMenu(pos, cells))),
})?;
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)?;
}
let setting_str = setting.as_ref();
let title = setting.get_enum_title(mp.config);
{
let mut label = mp
.parser_state
.fetch_widget_as::<WidgetLabel>(&mp.layout.state, &format!("{id}_value"))?;
label.set_text_simple(&mut mp.layout.state.globals.get(), title);
}
let btn = mp.parser_state.fetch_component_as::<ComponentButton>(&id)?;
btn.on_click(Rc::new({
let feats = mp.feats;
let tasks = mp.tasks.clone();
move |_common, e: ButtonClickEvent| {
tasks.push(Task::OpenContextMenu(
e.mouse_pos_absolute.into(),
EnumType::VARIANTS
.iter()
.filter_map(|item| {
if item.get_bool("Hidden").unwrap_or(false) {
return None;
}
if item
.get_str("Backend")
.is_some_and(|x| XrBackend::try_from(x).unwrap() != feats.xr_backend)
{
return None;
}
if item
.get_str("Desktop")
.is_some_and(|x| DesktopBackend::try_from(x).unwrap() != feats.desktop_backend)
{
return None;
}
let value = item.as_ref();
let title = SettingType::get_enum_title_inner(*item);
let tooltip = SettingType::get_enum_tooltip_inner(*item);
let text = &title.text;
let translated = if title.translated { "1" } else { "0" };
Some(context_menu::Cell::Button(context_menu::Button {
action_name: Some(format!("{setting_str};{id};{value};{text};{translated}").into()),
title,
tooltip,
..Default::default()
}))
})
.collect(),
));
Ok(())
}
}));
Ok(())
}
+5 -16
View File
@@ -141,7 +141,6 @@ pub struct TabSettings<T> {
tasks: Tasks<Task>,
marker: PhantomData<T>,
frontend_tasks: FrontendTasks,
feats: InterfaceFeats,
}
impl<T> Tab<T> for TabSettings<T> {
@@ -698,14 +697,10 @@ 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);
self.current_tab = None;
let globals = frontend.layout.state.globals.clone();
self.current_tab = None;
let vulkan_devices = if matches!(name, TabNameEnum::Features) {
Some(frontend.interface.enumerate_vulkan_devices(data)?)
} else {
None
};
let feats = frontend.interface.get_feats(data);
let mut mp = MacroParams {
layout: &mut frontend.layout,
@@ -714,14 +709,14 @@ impl<T> TabSettings<T> {
config: frontend.interface.general_config(data),
tasks: self.tasks.clone(),
idx: 9001,
feats: self.feats,
feats,
};
let settings_mount_params = SettingsMountParams {
mp: &mut mp,
id_parent: root,
frontend_tasks: &self.frontend_tasks,
feats: self.feats,
feats,
};
match name {
@@ -729,10 +724,7 @@ impl<T> TabSettings<T> {
self.current_tab = Some(Box::new(tab_look_and_feel::State::mount(settings_mount_params)?));
}
TabNameEnum::Features => {
self.current_tab = Some(Box::new(tab_features::State::mount(
settings_mount_params,
vulkan_devices.unwrap(), /* safe */
)?));
self.current_tab = Some(Box::new(tab_features::State::mount(settings_mount_params)?));
}
TabNameEnum::SpaceDrag => {
self.current_tab = Some(Box::new(tab_space_drag::State::mount(settings_mount_params)?));
@@ -798,8 +790,6 @@ impl<T> TabSettings<T> {
tasks.push(Task::SetTab(TabNameEnum::LookAndFeel));
let feats = frontend.interface.get_feats(data);
Ok(Self {
app_button_ids: Vec::new(),
tasks,
@@ -808,7 +798,6 @@ impl<T> TabSettings<T> {
context_menu: ContextMenu::default(),
current_tab: None,
frontend_tasks: frontend.tasks.clone(),
feats,
})
}
}
+6 -23
View File
@@ -3,10 +3,7 @@ use wgui::parser::TemplateParams;
use wgui::{
components::button::ComponentButton, globals::WguiGlobals, layout::WidgetID, parser::Fetchable, task::Tasks,
};
use wlx_common::XrBackend;
use wlx_common::dash_interface::DashBackendTask;
use crate::frontend::FrontendTask;
use crate::tab::settings::{UpdateExtra, horiz_cell, mount_requires_restart};
use crate::util::popup_manager::PopupHolder;
@@ -21,8 +18,7 @@ use crate::{
#[derive(Clone)]
enum Task {
OpenInputProfilesDash,
OpenInputProfilesBackend,
OpenInputProfiles,
}
pub struct State {
@@ -38,23 +34,13 @@ impl SettingsTab for State {
for task in self.tasks.drain() {
match task {
Task::OpenInputProfilesDash => {
Task::OpenInputProfiles => {
input_profiles::mount_popup(
self.frontend_tasks.clone(),
self.globals.clone(),
self.popup_input_profiles.clone(),
);
}
Task::OpenInputProfilesBackend => {
self
.frontend_tasks
.push(FrontendTask::BackendTask(DashBackendTask::ShowBindingsUI));
// overlay sort order doesn't work on steamvr,
// so the dashboard would be drawn over the bindings UI
// while the bindings UI has input focus
self.frontend_tasks.push(FrontendTask::HideDashboard);
}
}
}
@@ -67,7 +53,6 @@ fn create_input_profiles_button(
parent: WidgetID,
tasks: Tasks<Task>,
_popup: &PopupHolder<input_profiles::View>,
xr_backend: XrBackend,
) -> anyhow::Result<()> {
let id = mp.idx.to_string();
mp.idx += 1;
@@ -86,11 +71,7 @@ fn create_input_profiles_button(
btn.on_click(Rc::new({
let tasks = tasks.clone();
move |_common, _e| {
if xr_backend.is_open_vr() {
tasks.push(Task::OpenInputProfilesBackend);
} else {
tasks.push(Task::OpenInputProfilesDash);
}
tasks.push(Task::OpenInputProfiles);
Ok(())
}
}));
@@ -111,7 +92,9 @@ impl State {
"APP_SETTINGS.CONTROLS",
"@/dashboard/controller.svg",
)?;
create_input_profiles_button(par.mp, c, tasks.clone(), &popup, par.feats.xr_backend)?;
if par.feats.xr_backend.is_open_xr() {
create_input_profiles_button(par.mp, c, tasks.clone(), &popup)?;
}
options_dropdown::<wlx_common::config::AltModifier>(par.mp, c, &SettingType::KeyboardMiddleClick)?;
options_dropdown::<wlx_common::config::HandsfreePointer>(par.mp, c, &SettingType::HandsfreePointer)?;
options_dropdown::<wlx_common::config::HandsfreeAltTab>(par.mp, c, &SettingType::HandsfreeAltTab)?;
+75 -130
View File
@@ -1,6 +1,7 @@
use std::rc::Rc;
use wgui::{
components::button::{ButtonClickEvent, ComponentButton},
globals::WguiGlobals,
i18n::Translation,
layout::WidgetID,
@@ -8,7 +9,7 @@ use wgui::{
parser::{Fetchable, TemplateParams},
task::Tasks,
widget::label::WidgetLabel,
windowing::context_menu,
windowing::context_menu::{self},
};
use wlx_common::{async_executor::AsyncExecutor, config::GeneralConfig, dash_interface::ConfigChangeKind};
@@ -20,7 +21,6 @@ use crate::{
},
util::{
popup_manager::PopupHolder,
wgui_simple,
whisper::{
WHISPER_MODELS, WhisperModel, whisper_any_models_downloaded, whisper_delete_all_models, whisper_model_from_name,
whisper_model_path,
@@ -104,60 +104,41 @@ impl SettingsTab for State {
layout: &mut wgui::layout::Layout,
state: &mut wgui::parser::ParserState,
) -> anyhow::Result<()> {
let (Some(_), Some(_type), Some(id), Some(value)) = ({
let mut s = action.splitn(4, ';');
(s.next(), s.next(), s.next(), s.next())
}) else {
debug_assert!(false);
return Ok(());
};
match _type {
"whisper_model" => {
let model_name = value;
if !model_name.is_empty()
&& let Some(model) = whisper_model_from_name(model_name)
{
let mut common = layout.common();
let mut label = state.fetch_widget_as::<WidgetLabel>(common.state, &format!("{id}_value"))?;
label.set_text(&mut common, Translation::from_raw_text(model.display_name));
config.whisper_model = model.file_name.into();
if !whisper_model_path(model.file_name).exists() {
self.show_whisper_model_dialog_box_download(model)?;
} else {
change_kind.replace(ConfigChangeKind::Other);
}
} else {
config.whisper_model = "".into();
change_kind.replace(ConfigChangeKind::Other);
// re-init the whole tab
if whisper_any_models_downloaded().unwrap_or_default() {
self.show_whisper_model_dialog_box_cleanup()?;
} else {
self.parent_tasks.push(ParentTask::SetTab(TabNameEnum::Features));
}
}
}
"whisper_gpu" => {
if let (Some(_), Some(id), Some(model_name)) = {
let mut s = action.splitn(3, ';');
(s.next(), s.next(), s.next())
} {
if !model_name.is_empty()
&& let Some(model) = whisper_model_from_name(model_name)
{
let mut common = layout.common();
let mut label = state.fetch_widget_as::<WidgetLabel>(common.state, &format!("{id}_value"))?;
config.whisper_gpu = value.parse::<u32>().unwrap();
label.set_text(
&mut common,
Translation::from_raw_text_string(config.whisper_gpu.to_string()),
);
label.set_text(&mut common, Translation::from_raw_text(model.display_name));
config.whisper_model = model.file_name.into();
if !whisper_model_path(model.file_name).exists() {
self.show_whisper_model_dialog_box_download(model)?;
} else {
change_kind.replace(ConfigChangeKind::Other);
}
} else {
config.whisper_model = "".into();
change_kind.replace(ConfigChangeKind::Other);
// re-init the whole tab
if whisper_any_models_downloaded().unwrap_or_default() {
self.show_whisper_model_dialog_box_cleanup()?;
} else {
self.parent_tasks.push(ParentTask::SetTab(TabNameEnum::Features));
}
}
_ => unreachable!(),
};
}
Ok(())
}
}
impl State {
pub fn mount(par: SettingsMountParams, vulkan_devices: Vec<String>) -> anyhow::Result<State> {
pub fn mount(par: SettingsMountParams) -> anyhow::Result<State> {
let tasks = Tasks::<Task>::new();
let popup_download = PopupHolder::<views::download_file::View>::default();
let popup_dialog = PopupHolder::<views::dialog_box::View>::default();
@@ -170,9 +151,7 @@ impl State {
)?;
if par.feats.whisper {
let c = options_category(par.mp, c, "APP_SETTINGS.SPEECH_TO_TEXT", "@/dashboard/stt.svg")?;
whisper_models_dropdown(par.mp, c)?;
whisper_gpu_select_dropdown(par.mp, c, vulkan_devices)?;
}
options_checkbox(par.mp, c, SettingType::NotificationsEnabled)?;
@@ -308,48 +287,6 @@ impl State {
}
}
fn whisper_gpu_select_dropdown(
mp: &mut MacroParams,
parent: WidgetID,
vulkan_devices: Vec<String>,
) -> anyhow::Result<()> {
let id = mp.idx.to_string();
mp.idx += 1;
let parent = horiz_cell(mp.layout, parent)?;
let mut params = TemplateParams::new();
params.insert("translation", "APP_SETTINGS.WHISPER_GPU_SELECT");
let current_text = Translation::from_raw_text_string(mp.config.whisper_gpu.to_string());
let cells = vulkan_devices
.iter()
.enumerate()
.filter(|(_, name)| !name.contains("llvmpipe"))
.map(|(idx, name)| {
context_menu::Cell::Button(context_menu::Button {
action_name: Some(format!(";whisper_gpu;{};{}", id, idx).into()),
title: Translation::from_raw_text_string(name.to_string()),
..Default::default()
})
})
.collect();
let tasks = mp.tasks.clone();
wgui_simple::mount_dropdown(wgui_simple::MountDropdownParams {
layout: mp.layout,
parser_state: mp.parser_state,
doc_params: mp.doc_params,
parent,
id,
params,
current_text,
cells,
on_open: Box::new(move |pos, cells| tasks.push(ParentTask::OpenContextMenu(pos, cells))),
})
}
fn whisper_models_dropdown(mp: &mut MacroParams, parent: WidgetID) -> anyhow::Result<()> {
let id = mp.idx.to_string();
mp.idx += 1;
@@ -370,49 +307,57 @@ fn whisper_models_dropdown(mp: &mut MacroParams, parent: WidgetID) -> anyhow::Re
});
let mut params = TemplateParams::new();
params.insert("id", &id);
params.insert("translation", "APP_SETTINGS.WHISPER_MODEL");
params.insert("tooltip", "APP_SETTINGS.WHISPER_MODEL_HELP");
let mut cells = WHISPER_MODELS
.iter()
.enumerate()
.map(|(idx, item)| {
if downloaded_models.contains(&idx) {
context_menu::Cell::Button(context_menu::Button {
action_name: Some(format!(";whisper_model;{id};{}", item.file_name).into()),
title: Translation::from_raw_text(item.display_name),
sprite_src_builtin: Some("@/dashboard/check.svg".into()),
..Default::default()
})
} else {
context_menu::Cell::Button(context_menu::Button {
action_name: Some(format!(";whisper_model;{id};{}", item.file_name).into()),
title: Translation::from_raw_text(item.display_name),
..Default::default()
})
}
})
.collect::<Vec<_>>();
mp.parser_state
.instantiate_template(mp.doc_params, "DropdownButton", mp.layout, parent, params)?;
cells.insert(
0,
context_menu::Cell::Button(context_menu::Button {
action_name: Some(format!(";whisper_model;{id};").into()),
title: Translation::from_translation_key("APP_SETTINGS.OPTION.NONE"),
..Default::default()
}),
);
{
let mut label = mp
.parser_state
.fetch_widget_as::<WidgetLabel>(&mp.layout.state, &format!("{id}_value"))?;
label.set_text_simple(&mut mp.layout.state.globals.get(), current_translation);
}
let tasks = mp.tasks.clone();
wgui_simple::mount_dropdown(wgui_simple::MountDropdownParams {
layout: mp.layout,
parser_state: mp.parser_state,
doc_params: mp.doc_params,
parent,
id,
params,
current_text: current_translation,
cells,
on_open: Box::new(move |pos, cells| tasks.push(ParentTask::OpenContextMenu(pos, cells))),
})
let btn = mp.parser_state.fetch_component_as::<ComponentButton>(&id)?;
btn.on_click(Rc::new({
let parent_tasks = mp.tasks.clone();
move |_common, e: ButtonClickEvent| {
let mut cells = WHISPER_MODELS
.iter()
.enumerate()
.map(|(idx, item)| {
context_menu::Cell::Button(context_menu::Button {
action_name: Some(format!(";{id};{}", item.file_name).into()),
title: Translation::from_raw_text(item.display_name),
tooltip: None,
attribs: vec![],
sprite_src_builtin: if downloaded_models.contains(&idx) {
Some("@/dashboard/check.svg".into())
} else {
None
},
})
})
.collect::<Vec<_>>();
cells.insert(
0,
context_menu::Cell::Button(context_menu::Button {
action_name: Some(format!(";{id};").into()),
title: Translation::from_translation_key("APP_SETTINGS.OPTION.NONE"),
tooltip: None,
attribs: vec![],
..Default::default()
}),
);
parent_tasks.push(ParentTask::OpenContextMenu(e.mouse_pos_absolute.into(), cells));
Ok(())
}
}));
Ok(())
}
+5 -10
View File
@@ -81,7 +81,7 @@ impl BindingsDropdown for XrInputComponent {
pub struct ParsedOpenXrInputPath {
pub side: XrInputSide,
pub subpath: XrInputSubpathKind,
pub component: Option<XrInputComponent>,
pub component: XrInputComponent,
}
impl TryFrom<&str> for ParsedOpenXrInputPath {
@@ -101,18 +101,13 @@ impl TryFrom<&str> for ParsedOpenXrInputPath {
bail!("missing input prefix");
}
let (identifier, component) = match rest.rsplit_once('/') {
Some((ident, comp)) if !comp.is_empty() => {
let comp = XrInputComponent::try_from(comp).context("bad component")?;
(ident, Some(comp))
}
_ => (rest, None),
};
let (identifier, component) = rest.rsplit_once('/').context("missing identifier or component")?;
if identifier.is_empty() {
bail!("identifier empty");
if identifier.is_empty() || component.is_empty() {
bail!("identifier or component empty");
}
let component = XrInputComponent::try_from(component).context("bad component")?;
let subpath = XrInputSubpathKind::try_from(identifier).context("bad subpath")?;
Ok(Self {
+2 -43
View File
@@ -1,16 +1,12 @@
use glam::{Mat4, Vec2};
use std::rc::Rc;
use wgui::{
animation::{Animation, AnimationDuration, AnimationEasing},
assets::AssetPathRef,
color::WguiColorName,
components::{
self,
button::{ButtonClickCallback, ButtonClickEvent, ComponentButton},
},
components::{self, button::ButtonClickCallback},
i18n::Translation,
layout::{Layout, LayoutTask, WidgetID},
parser::{Fetchable, ParseDocumentParams, ParserState, TemplateParams},
parser::{Fetchable, ParseDocumentParams},
renderer_vk::{
text::{FontWeight, TextStyle, custom_glyph::CustomGlyphData},
util::centered_matrix,
@@ -21,7 +17,6 @@ use wgui::{
label::{WidgetLabel, WidgetLabelParams},
sprite::{WidgetSprite, WidgetSpriteParams},
},
windowing::context_menu,
};
pub struct CreateButtonParams<'a> {
@@ -171,39 +166,3 @@ pub fn create_loading(par: CreateLoadingParams) -> anyhow::Result<WidgetID> {
Ok(id_root)
}
pub struct MountDropdownParams<'a> {
pub layout: &'a mut Layout,
pub parser_state: &'a mut ParserState,
pub doc_params: &'a ParseDocumentParams<'a>,
pub parent: WidgetID,
pub id: String,
pub params: TemplateParams,
pub current_text: Translation,
pub cells: Vec<context_menu::Cell>,
pub on_open: Box<dyn Fn(context_menu::Position, Vec<context_menu::Cell>)>,
}
pub fn mount_dropdown(par: MountDropdownParams) -> anyhow::Result<()> {
let mut params = par.params;
params.insert("id", &par.id);
par
.parser_state
.instantiate_template(par.doc_params, "DropdownButton", par.layout, par.parent, params)?;
{
let mut label = par
.parser_state
.fetch_widget_as::<WidgetLabel>(&par.layout.state, &format!("{}_value", par.id))?;
label.set_text_simple(&mut par.layout.state.globals.get(), par.current_text);
}
let btn = par.parser_state.fetch_component_as::<ComponentButton>(&par.id)?;
btn.on_click(Rc::new(move |_common, e: ButtonClickEvent| {
(par.on_open)(e.mouse_pos_absolute.into(), par.cells.clone());
Ok(())
}));
Ok(())
}
+19 -21
View File
@@ -53,7 +53,7 @@ struct LaunchParams<'a, T> {
pub struct View {
#[allow(dead_code)]
state: ParserState,
entry: Rc<DesktopEntry>,
entry: DesktopEntry,
tasks: Tasks<Task>,
frontend_tasks: FrontendTasks,
globals: WguiGlobals,
@@ -80,7 +80,7 @@ pub struct View {
pub struct Params<'a> {
pub globals: &'a WguiGlobals,
pub entry: Rc<DesktopEntry>,
pub entry: DesktopEntry,
pub layout: &'a mut Layout,
pub parent_id: WidgetID,
pub config: &'a GeneralConfig,
@@ -119,8 +119,10 @@ impl View {
{
let mut label_exec = state.fetch_widget_as::<WidgetLabel>(&params.layout.state, "label_exec")?;
let command = shell_words::join(params.entry.exec_argv.iter().map(|x| x.as_ref()));
label_exec.set_text_simple(&mut params.globals.get(), Translation::from_raw_text_string(command));
label_exec.set_text_simple(
&mut params.globals.get(),
Translation::from_raw_text_string(format!("{} {}", params.entry.exec_path, params.entry.exec_args)),
);
}
let tasks = Tasks::new();
@@ -404,19 +406,14 @@ impl View {
env.push("ELECTRON_OZONE_PLATFORM_HINT=wayland".into());
}
let argv = match params.compositor_mode {
AppCompositorMode::Cage => ["cage", "--"]
.iter()
.copied()
.chain(params.application.exec_argv.iter().map(|x| x.as_ref()))
.map(|x| x.to_string())
.collect::<Vec<_>>(),
AppCompositorMode::Native => params
.application
.exec_argv
.iter()
.map(|x| x.to_string())
.collect::<Vec<_>>(),
let args = match params.compositor_mode {
AppCompositorMode::Cage => format!("-- {} {}", params.application.exec_path, params.application.exec_args),
AppCompositorMode::Native => params.application.exec_args.to_string(),
};
let exec = match params.compositor_mode {
AppCompositorMode::Cage => "cage".to_string(),
AppCompositorMode::Native => params.application.exec_path.to_string(),
};
let pos_mode = match params.pos_mode {
@@ -426,7 +423,7 @@ impl View {
};
let mut userdata = HashMap::new();
userdata.insert("desktop-entry".into(), serde_json::to_string(params.application)?);
userdata.insert("desktop-entry".to_string(), serde_json::to_string(params.application)?);
let resolution = Self::calculate_resolution(params.res_mode, params.orientation_mode);
@@ -435,11 +432,12 @@ impl View {
params.auto_start,
WvrProcessLaunchParams {
env,
argv,
exec,
name: params.application.app_name.to_string(),
args,
resolution,
pos_mode,
icon: params.application.icon_path.as_deref().map(String::from),
icon: params.application.icon_path.as_ref().map(|x| x.as_ref().to_string()),
userdata,
},
)?;
@@ -488,7 +486,7 @@ impl View {
pub fn mount_popup(
frontend_tasks: FrontendTasks,
globals: WguiGlobals,
entry: Rc<DesktopEntry>,
entry: DesktopEntry,
popup: PopupHolder<View>,
on_app_pins_changed: Box<dyn Fn()>,
pinned_app: Option<PinnedApp>,
+51 -52
View File
@@ -3,13 +3,17 @@ use std::rc::Rc;
use strum::EnumProperty;
use wgui::{
assets::AssetPathRef,
components::{button::ComponentButton, slider::ComponentSlider},
components::{
button::{ButtonClickEvent, ComponentButton},
slider::ComponentSlider,
},
globals::WguiGlobals,
i18n::Translation,
layout::{Layout, WidgetID},
log::LogErr,
parser::{Fetchable, ParseDocumentParams, ParserState, TemplateParams},
task::Tasks,
widget::label::WidgetLabel,
windowing::context_menu::{self, TickResult},
};
use wlx_common::{
@@ -158,7 +162,6 @@ impl ViewTrait for View {
"subpath" => {
apply_subpath(
action_binding_mut(action_mut, side),
action_name,
side,
value,
self.controller_profile,
@@ -614,7 +617,7 @@ fn chord_member_controls(
output_side,
member_idx,
available_components,
parsed.and_then(|x| x.component),
parsed.as_ref().map(|x| x.component),
)?;
let tasks = mp.tasks.clone();
@@ -635,7 +638,7 @@ fn chord_member_controls(
})?;
// put threshold slider on its own row
if parsed.and_then(|x| x.component).is_some_and(|x| x.is_analog()) {
if parsed.as_ref().is_some_and(|x| x.component.is_analog()) {
chord_threshold_slider(mp, parent, action.clone(), output_side, member_idx, member.threshold())?;
}
@@ -663,6 +666,13 @@ fn input_controls_for_hand(mp: &mut MacroParams, par: InputControlsForHandParams
let parent = horiz_cell(mp.layout, par.parent)?;
let available_components: Rc<[XrInputComponent]> = current
.as_ref()
.and_then(|par| user_path.find_subpath(par.subpath))
.map(|subp| subp.components)
.unwrap_or_default()
.into();
let available_subpaths: Rc<[XrInputSubpathKind]> = user_path
.paths
.iter()
@@ -679,33 +689,23 @@ fn input_controls_for_hand(mp: &mut MacroParams, par: InputControlsForHandParams
current.as_ref().map(|x| x.subpath),
)?;
if &**par.action == "scroll" {
clicks_dropdown(mp, parent, par.action.clone(), par.click_type)?;
return Ok(());
}
let available_components: Rc<[XrInputComponent]> = current
.as_ref()
.and_then(|par| user_path.find_subpath(par.subpath))
.map(|subp| subp.components)
.unwrap_or_default()
.into();
if !component_dropdown(
mp,
parent,
par.action.clone(),
par.side,
available_components,
current.and_then(|x| x.component),
current.as_ref().map(|x| x.component),
)? {
return Ok(());
}
clicks_dropdown(mp, parent, par.action.clone(), par.click_type)?;
if let Some(component) = current.and_then(|x| x.component)
if let Some(component) = current.as_ref().map(|x| x.component)
&& component.is_analog()
&& &**par.action != "scroll"
// hax
{
threshold_slider(mp, parent, par.action.clone(), par.side, par.threshold)?;
}
@@ -1008,7 +1008,7 @@ fn create_dropdown<B: 'static + BindingsDropdown>(
.iter()
.map(|item| {
context_menu::Cell::Button(context_menu::Button {
action_name: Some(item.action_str(&action, side).into()),
action_name: Some(item.action_str(&action, side)),
title: item.translation(),
..Default::default()
})
@@ -1019,7 +1019,7 @@ fn create_dropdown<B: 'static + BindingsDropdown>(
cells.insert(
0,
context_menu::Cell::Button(context_menu::Button {
action_name: Some(action_str.into()),
action_name: Some(action_str),
title: Translation::from_translation_key("APP_SETTINGS.OPTION.NONE"),
..Default::default()
}),
@@ -1032,29 +1032,39 @@ fn create_dropdown<B: 'static + BindingsDropdown>(
fn create_dropdown_with_cells(
mp: &mut MacroParams,
parent: WidgetID,
params: TemplateParams,
mut params: TemplateParams,
current_text: Translation,
cells: Vec<context_menu::Cell>,
) -> anyhow::Result<()> {
let id = mp.idx.to_string();
mp.idx += 1;
let tasks = mp.tasks.clone();
wgui_simple::mount_dropdown(wgui_simple::MountDropdownParams {
layout: mp.layout,
parser_state: mp.parser_state,
doc_params: mp.doc_params,
parent,
id,
params,
current_text,
cells,
on_open: Box::new(move |pos, cells| tasks.push(Task::OpenContextMenu(pos, cells))),
})
params.insert("id", &id);
mp.parser_state
.instantiate_template(mp.doc_params, "DropdownButton", mp.layout, parent, params)?;
{
let mut label = mp
.parser_state
.fetch_widget_as::<WidgetLabel>(&mp.layout.state, &format!("{id}_value"))?;
label.set_text_simple(&mut mp.layout.state.globals.get(), current_text);
}
let btn = mp.parser_state.fetch_component_as::<ComponentButton>(&id)?;
btn.on_click(Rc::new({
let tasks = mp.tasks.clone();
let cells = cells.clone();
move |_common, e: ButtonClickEvent| {
tasks.push(Task::OpenContextMenu(e.mouse_pos_absolute.into(), cells.clone()));
Ok(())
}
}));
Ok(())
}
fn apply_subpath(
side_mut: &mut Option<OneOrMany<String>>,
action_name: &str,
side_str: &str,
subpath_str: &str,
profile: &XrControllerProfile,
@@ -1065,12 +1075,6 @@ fn apply_subpath(
) else {
return;
};
if action_name == "scroll" {
*side_mut = Some(OneOrMany::One(format!("/user/hand/{side_str}/input/{subpath_str}")));
return;
}
let Some(subpath_obj) = profile.find_userpath(side).and_then(|p| p.find_subpath(subpath)) else {
return;
};
@@ -1083,12 +1087,11 @@ fn apply_subpath(
return;
};
let parsed_compo = parsed.component.unwrap_or(*subpath_obj.components.first().unwrap());
if subpath_obj.components.contains(&parsed_compo) {
parsed_compo
} else {
*subpath_obj.components.first().unwrap()
let mut parsed_compo = parsed.component;
if !subpath_obj.components.contains(&parsed_compo) {
parsed_compo = *subpath_obj.components.first().unwrap();
}
parsed_compo
} else {
*subpath_obj.components.first().unwrap()
};
@@ -1223,7 +1226,7 @@ fn apply_chord_hand(member: &mut OpenXrInputChordMember, side: XrInputSide, prof
};
let component = parsed
.and_then(|parsed| parsed.component)
.map(|parsed| parsed.component)
.filter(|component| subpath.components.contains(component))
.unwrap_or(subpath.components[0]);
let path = format!(
@@ -1253,12 +1256,8 @@ fn apply_chord_subpath(
return;
};
let component = if let Some(comp) = parsed.component {
if subpath_obj.components.contains(&comp) {
comp
} else {
first_component
}
let component = if subpath_obj.components.contains(&parsed.component) {
parsed.component
} else {
first_component
};
+2 -2
View File
@@ -92,7 +92,7 @@ impl ViewTrait for View {
par.layout,
self.id_loading_parent,
Vec2::splat(32.0),
AssetPathRef::BuiltIn("dashboard/check.svg"),
AssetPathRef::BuiltIn("@/dashboard/check.svg"),
)?;
// "Close window" button
@@ -114,7 +114,7 @@ impl ViewTrait for View {
par.layout,
self.id_loading_parent,
Vec2::splat(32.0),
AssetPathRef::BuiltIn("dashboard/error.svg"),
AssetPathRef::BuiltIn("@/dashboard/error.svg"),
)?;
}
Task::Close => {
+4 -34
View File
@@ -1,32 +1,6 @@
#!/bin/bash
# This is the translation tool intended for machine-translating strings.
#
# ###########################
# # Adding new translations #
# ###########################
#
# If you want to add a new language, follow these steps:
#
# 1. Include the language in `wlx-common/src/locale.rs`:
# - add your entry in `enum Language`
# - add your entry in Language::code() function
# - add your language code in Language::add_codes() function
#
# 2. Create a new template file
# - Add your language code in `scripts/translator/templates/<lang code>.json`
# (you can copy one of nearby files and adapt them accordingly)
#
# 3. Modify this very file
# - Go to the bottom and add your language code entry here too
#
# 4. Run the script (it will hint you which LANG_PATHs are supported)
#
# ############################################
# # Running machine translations (local LLM) #
# ############################################
#
# Do not run this script if you don't have at least ~16 GiB of free system memory.
# Do not run this script if you don't have at least 32 GiB of system memory.
# Submitting PRs that lack translated strings is okay if you don't meet the
# system requirements to run this script, or if you simply prefer not to; we are
# regularly updating the missing translation strings anyway.
@@ -34,18 +8,15 @@
# Base language: English (en.json)
#
# This tool expects llama.cpp server being present (llama-server) at port 8080.
#
# Currently we're using Gemma 4 for machine translations.
# Model URL:
# https://huggingface.co/unsloth/gemma-4-31B-it-GGUF
# Recommended quantization: Q4_K_M
#
set -e
cd "$(dirname "$0")"
bun install
# model URL:
# https://huggingface.co/unsloth/gemma-4-31B-it-GGUF
# you can specify your own if you want to.
if [ -z "${MODEL}" ]; then
export MODEL="gemma-4-31B-it-Q4_K_M"
fi
@@ -60,5 +31,4 @@ TEMPLATE="de" bun main.ts
TEMPLATE="ja" bun main.ts
TEMPLATE="es" bun main.ts
TEMPLATE="it" bun main.ts
TEMPLATE="sv" bun main.ts
TEMPLATE="zh_CN" bun main.ts
-20
View File
@@ -1,20 +0,0 @@
{
"full_name": "Swedish",
"examples": [
{
"key": "BAR.OPACITY",
"en": "Opacity",
"translated": "Opacitet"
},
{
"key": "PANEL.CLOSE",
"en": "Close panel",
"translated": "Stäng panelen"
},
{
"key": "SETTINGS.LEFT_HANDED_MODE",
"en": "Left-handed mode",
"translated": "Vänsterhänt läge"
}
]
}
+5 -8
View File
@@ -1,5 +1,5 @@
use glam::{Vec2, vec2};
use std::{rc::Rc, sync::Arc};
use std::rc::Rc;
use testbed::{Testbed, testbed_any::TestbedAny};
use tracing_subscriber::EnvFilter;
use tracing_subscriber::filter::LevelFilter;
@@ -8,7 +8,7 @@ use tracing_subscriber::util::SubscriberInitExt;
use vulkan::init_window;
use wgui::{
event::{MouseButtonEvent, MouseButtonIndex, MouseMotionEvent, MouseWheelEvent},
gfx::{CommandBufferUsage, PresentStatus, SwapchainError, WGfx, WGfxClearMode},
gfx::{CommandBufferUsage, PresentStatus, SwapchainError, WGfxClearMode},
renderer_vk::{self},
};
use winit::{
@@ -48,16 +48,13 @@ fn init_logging() {
.init();
}
fn load_testbed(
gfx: &Arc<WGfx>,
audio: &mut Box<dyn audio::AudioProvider>,
) -> anyhow::Result<Box<dyn Testbed>> {
fn load_testbed(audio: &mut Box<dyn audio::AudioProvider>) -> anyhow::Result<Box<dyn Testbed>> {
let mut assets = Box::new(assets::Asset {});
audio.register_wgui_samples(assets.as_mut())?;
let name = std::env::var("TESTBED").unwrap_or_default();
Ok(match name.as_str() {
"dashboard" => Box::new(TestbedDashboard::new(gfx.clone())?),
"dashboard" => Box::new(TestbedDashboard::new()?),
"" => Box::new(TestbedGeneric::new(assets)?),
_ => Box::new(TestbedAny::new(assets, &name)?),
})
@@ -78,7 +75,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut scale = window.scale_factor() as f32;
let mut audio: Box<dyn audio::AudioProvider> = Box::new(audio::RealAudioProvider::new());
let mut testbed = load_testbed(&gfx, &mut audio)?;
let mut testbed = load_testbed(&mut audio)?;
let mut mouse = Vec2::ZERO;
+4 -5
View File
@@ -1,8 +1,8 @@
use std::{rc::Rc, sync::Arc};
use std::rc::Rc;
use crate::testbed::{Testbed, TestbedUpdateParams};
use dash_frontend::frontend::{self, FrontendUpdateParams};
use wgui::{gfx::WGfx, i18n::Locale, layout::Layout, theme::WguiTheme};
use wgui::{layout::Layout, theme::WguiTheme};
use wlx_common::{
async_executor::AsyncExecutor, dash_interface_emulated::DashInterfaceEmulated,
locale::WayVRLangProvider,
@@ -14,10 +14,9 @@ pub struct TestbedDashboard {
}
impl TestbedDashboard {
pub fn new(gfx: Arc<WGfx>) -> anyhow::Result<Self> {
pub fn new() -> anyhow::Result<Self> {
let interface = DashInterfaceEmulated::new();
let lang_provider = WayVRLangProvider::default();
let locale = Locale::from_env(&lang_provider);
let interface = DashInterfaceEmulated::new(gfx, &locale);
let palette_name = std::env::var("PALETTE").unwrap_or_else(|_| "Default".to_string());
let executor = wlx_common::async_executor::create_local();
+2 -1
View File
@@ -49,8 +49,9 @@ pub enum HandsfreeParams {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WvrProcessLaunchParams {
pub name: String,
pub argv: Vec<String>,
pub exec: String,
pub env: Vec<String>,
pub args: String,
pub icon: Option<String>,
pub resolution: [u32; 2],
pub pos_mode: PositionMode,
+4 -6
View File
@@ -143,11 +143,7 @@
"NO_SET_SELECTED": "Kein Set ausgewählt.",
"ONE_CONTROLLER_ON_FLOOR": "Einen Controller auf den Boden legen!",
"NO_KEYMAPS_CONFIGURED": "Keine Tastaturbelegungen konfiguriert",
"NO_KEYMAPS_CONFIGURED_HELP": "Besuch wayvr.org, um zu erfahren, wie du deine Tastaturlayouts einrichtest!",
"STEAMVR_DASH_TITLE": "SteamVR Dashboard geöffnet",
"STEAMVR_DASH_TEXT": "SteamVR-Dashboard schließen, um WayVR zu nutzen.",
"WAYVR_RUNNING_TITLE": "WayVR läuft!",
"WAYVR_RUNNING_TEXT": "Schau auf dein Handgelenk für die Watch oder nutze die Ein/Aus-Belegung, um zu starten!"
"NO_KEYMAPS_CONFIGURED_HELP": "Besuch wayvr.org, um zu erfahren, wie du deine Tastaturlayouts einrichtest!"
},
"WATCH": {
"ADD_NEW_SET": "Neues Set hinzufügen",
@@ -181,5 +177,7 @@
"SELECT_SCREEN_CAST_FOR": "Screen-Cast für Display {DISPLAY} auswählen",
"CANNOT_CAPTURE_KBD_MOUSE": "Tastatur/Maus-Erfassung konnte nicht initialisiert werden!\nPrüfe, ob dein Benutzer in der Gruppe 'input' ist.",
"WE_ARE_GRABBING": "WayVR steuert jetzt deine Tastatur und Maus!\nDrücke Super+Z, um die Steuerung freizugeben."
}
},
"STEAMVR_DASH_TOAST_TITLE": "SteamVR Dashboard geöffnet",
"STEAMVR_DASH_TOAST_TEXT": "Schließe das SteamVR-Dashboard, um WayVR zu nutzen."
}
+2 -4
View File
@@ -114,10 +114,8 @@ SCREENCAST.FAIL,Could not initialize screen cast source.,Error message on a wind
TOAST.TITLE_SCREENCAST_FAIL,Could not start screen cast source!,Error message on a window that normally displays screen casts.
TOAST.CANNOT_ADD_SET,Cannot add set!,Error message telling the user the set could not be added. “Set” was previously translated as “${DEFINITIONS.SET}”
TOAST.CANNOT_REMOVE_SET,Cannot remove set!,Error message telling the user the set could not be removed. “Set” was previously translated as “${DEFINITIONS.SET}”
TOAST.STEAMVR_DASH_TITLE,SteamVR Dashboard Open,Title of toast shown when SteamVR dashboard is visible
TOAST.STEAMVR_DASH_TEXT,Close SteamVR dashboard to start using WayVR.,Body of toast shown when SteamVR dashboard is visible
TOAST.WAYVR_RUNNING_TITLE,WayVR is running!,Title of toast shown when WayVR is started but its windows are hidden by default
TOAST.WAYVR_RUNNING_TEXT,"Check your wrist for the watch, or use show-hide binding to get started!",Body of toast shown when WayVR is started but its windows are hidden by default
STEAMVR_DASH_TOAST_TITLE,SteamVR Dashboard Open,Title of toast shown when SteamVR dashboard is visible
STEAMVR_DASH_TOAST_TEXT,Close SteamVR dashboard to start using WayVR.,Body of toast shown when SteamVR dashboard is visible
TOAST.DEFAULT_TITLE,Notification,Default title of notifications
TOAST.EMPTY_SET,Empty set!,Title of a pop-up when the user switches to an empty set. “Set” was previously translated as “${DEFINITIONS.SET}”
TOAST.ERROR,Error,Default title for error pop-ups
1 DEFINITIONS.ANCHORED Anchored A movement option that makes this window move together with the rest of the workspace. Emphasize the “moving together” part as opposed to “not moving at all”.
114 TOAST.STEAMVR_DASH_TITLE STEAMVR_DASH_TOAST_TITLE SteamVR Dashboard Open Title of toast shown when SteamVR dashboard is visible
115 TOAST.STEAMVR_DASH_TEXT STEAMVR_DASH_TOAST_TEXT Close SteamVR dashboard to start using WayVR. Body of toast shown when SteamVR dashboard is visible
116 TOAST.WAYVR_RUNNING_TITLE TOAST.DEFAULT_TITLE WayVR is running! Notification Title of toast shown when WayVR is started but its windows are hidden by default Default title of notifications
117 TOAST.WAYVR_RUNNING_TEXT TOAST.EMPTY_SET Check your wrist for the watch, or use show-hide binding to get started! Empty set! Body of toast shown when WayVR is started but its windows are hidden by default Title of a pop-up when the user switches to an empty set. “Set” was previously translated as “${DEFINITIONS.SET}”
118 TOAST.DEFAULT_TITLE TOAST.ERROR Notification Error Default title of notifications Default title for error pop-ups
TOAST.EMPTY_SET Empty set! Title of a pop-up when the user switches to an empty set. “Set” was previously translated as “${DEFINITIONS.SET}”
TOAST.ERROR Error Default title for error pop-ups
119 TOAST.FIXING_FLOOR_IN_X_SECS Fixing floor in {SECONDS} second(s)... A countdown pop-up for automatically adjusting floor height. Keep the {SECONDS} part intact as it’s a parameter.
120 TOAST.LAST_EXISTING_SET This is the last existing set. Notification body explaining that the set cannot be removed because this is the last existing set. “Set” was previously translated as “${DEFINITIONS.SET}”
121 TOAST.LETS_ADD_OVERLAYS Let's add some overlays from the taskbar! Instructions prompting the user to add overlays. “Taskbar” was previously translated as “${DEFINITIONS.TASKBAR}”, “Overlay” was previously translated as “${DEFINITIONS.OVERLAY}”
+2 -4
View File
@@ -133,10 +133,6 @@
"TITLE_SCREENCAST_FAIL": "Could not start screen cast source!",
"CANNOT_ADD_SET": "Cannot add set!",
"CANNOT_REMOVE_SET": "Cannot remove set!",
"STEAMVR_DASH_TITLE": "SteamVR Dashboard Open",
"STEAMVR_DASH_TEXT": "Close SteamVR dashboard to start using WayVR.",
"WAYVR_RUNNING_TITLE": "WayVR is running!",
"WAYVR_RUNNING_TEXT": "Check your wrist for the watch, or use show-hide binding to get started!",
"DEFAULT_TITLE": "Notification",
"EMPTY_SET": "Empty set!",
"ERROR": "Error",
@@ -149,6 +145,8 @@
"NO_SET_SELECTED": "No set is selected.",
"ONE_CONTROLLER_ON_FLOOR": "Place one controller on the floor!"
},
"STEAMVR_DASH_TOAST_TITLE": "SteamVR Dashboard Open",
"STEAMVR_DASH_TOAST_TEXT": "Close SteamVR dashboard to start using WayVR.",
"WATCH": {
"ADD_NEW_SET": "Add a new set",
"CLEANUP_MIRRORS": "Remove mirrors that are\nnot currently visible",
+4 -6
View File
@@ -143,11 +143,7 @@
"NO_SET_SELECTED": "No hay ningún set seleccionado.",
"ONE_CONTROLLER_ON_FLOOR": "¡Coloca un controlador en el suelo!",
"NO_KEYMAPS_CONFIGURED": "Sin distribuciones de teclado configuradas",
"NO_KEYMAPS_CONFIGURED_HELP": "¡Consulta wayvr.org para configurar tus diseños de teclado!",
"STEAMVR_DASH_TITLE": "Panel de SteamVR abierto",
"STEAMVR_DASH_TEXT": "Cierra el tablero de SteamVR para empezar a usar WayVR.",
"WAYVR_RUNNING_TITLE": "¡WayVR está ejecutándose!",
"WAYVR_RUNNING_TEXT": "¡Mira tu muñeca para ver el reloj o usa el mando de mostrar/ocultar para empezar!"
"NO_KEYMAPS_CONFIGURED_HELP": "¡Consulta wayvr.org para configurar tus diseños de teclado!"
},
"WATCH": {
"ADD_NEW_SET": "Añadir un set nuevo",
@@ -181,5 +177,7 @@
"SELECT_SCREEN_CAST_FOR": "Seleccionar transmisión de pantalla para la pantalla {DISPLAY}",
"CANNOT_CAPTURE_KBD_MOUSE": "¡No se pudo inicializar la captura de teclado/ratón!\nComprueba que tu usuario esté en el grupo 'input'.",
"WE_ARE_GRABBING": "¡WayVR tiene el control de tu teclado y ratón!\nPulsa Super+Z para liberarlos."
}
},
"STEAMVR_DASH_TOAST_TITLE": "Panel de SteamVR abierto",
"STEAMVR_DASH_TOAST_TEXT": "Cierra el tablero de SteamVR para empezar a usar WayVR."
}
+4 -6
View File
@@ -143,11 +143,7 @@
"NO_SET_SELECTED": "Nessun set selezionato.",
"ONE_CONTROLLER_ON_FLOOR": "Appoggia un controller sul pavimento!",
"NO_KEYMAPS_CONFIGURED": "Nessuna disposizione tastiera configurata",
"NO_KEYMAPS_CONFIGURED_HELP": "Consulta wayvr.org per configurare i layout della tastiera!",
"STEAMVR_DASH_TITLE": "Dashboard SteamVR Aperta",
"STEAMVR_DASH_TEXT": "Chiudi il pannello di SteamVR per iniziare a usare WayVR.",
"WAYVR_RUNNING_TITLE": "WayVR è attivo!",
"WAYVR_RUNNING_TEXT": "Controlla il polso per l'orologio, o usa il comando show-hide per iniziare!"
"NO_KEYMAPS_CONFIGURED_HELP": "Consulta wayvr.org per configurare i layout della tastiera!"
},
"WATCH": {
"ADD_NEW_SET": "Aggiungi un nuovo set",
@@ -181,5 +177,7 @@
"SELECT_SCREEN_CAST_FOR": "Seleziona la trasmissione schermo per il display {DISPLAY}",
"CANNOT_CAPTURE_KBD_MOUSE": "Impossibile inizializzare la cattura di tastiera/mouse!\nControlla che il tuo utente sia nel gruppo 'input'.",
"WE_ARE_GRABBING": "WayVR sta controllando tastiera e mouse!\nPremi Super+Z per rilasciare il controllo."
}
},
"STEAMVR_DASH_TOAST_TITLE": "Dashboard di SteamVR aperta",
"STEAMVR_DASH_TOAST_TEXT": "Chiudi il pannello di SteamVR per iniziare a usare WayVR."
}
+4 -6
View File
@@ -143,11 +143,7 @@
"NO_SET_SELECTED": "セットが選択されていません。",
"ONE_CONTROLLER_ON_FLOOR": "コントローラーを片方、床に置いてください!",
"NO_KEYMAPS_CONFIGURED": "キーボードレイアウトが設定されていない",
"NO_KEYMAPS_CONFIGURED_HELP": "キーボードレイアウトの設定方法は wayvr.org を確認してね!",
"STEAMVR_DASH_TITLE": "SteamVRダッシュボード展開中",
"STEAMVR_DASH_TEXT": "WayVRを使用するには、SteamVRダッシュボードを閉じてください。",
"WAYVR_RUNNING_TITLE": "WayVR 実行中!",
"WAYVR_RUNNING_TEXT": "手首のウォッチを確認するか、表示・非表示のバインドを使用して開始してください!"
"NO_KEYMAPS_CONFIGURED_HELP": "キーボードレイアウトの設定方法は wayvr.org を確認してね!"
},
"WATCH": {
"ADD_NEW_SET": "新しいセットを追加",
@@ -181,5 +177,7 @@
"SELECT_SCREEN_CAST_FOR": "ディスプレイ {DISPLAY} のスクリーンキャストを選択してください",
"CANNOT_CAPTURE_KBD_MOUSE": "キーボード/マウスのキャプチャを初期化できませんでした!\nユーザーが 'input' グループに所属しているか確認してください。",
"WE_ARE_GRABBING": "WayVR がキーボードとマウスを制御しています。\n解除するには、Super+Zを押してください。"
}
},
"STEAMVR_DASH_TOAST_TITLE": "SteamVRダッシュボード展開中",
"STEAMVR_DASH_TOAST_TEXT": "WayVRを使用するには、SteamVRダッシュボードを閉じてください。"
}
+4 -6
View File
@@ -143,11 +143,7 @@
"NO_SET_SELECTED": "Żaden zestaw nie jest wybrany.",
"ONE_CONTROLLER_ON_FLOOR": "Połóż jeden kontroler na podłodze!",
"NO_KEYMAPS_CONFIGURED": "Brak skonfigurowanych układów klawiatury",
"NO_KEYMAPS_CONFIGURED_HELP": "Odwiedź stronę wayvr.org, aby dowiedzieć się, jak skonfigurować układy klawiatury!",
"STEAMVR_DASH_TITLE": "Panel SteamVR otwarty",
"STEAMVR_DASH_TEXT": "Zamknij pulpit SteamVR, aby zacząć korzystać z WayVR.",
"WAYVR_RUNNING_TITLE": "WayVR działa!",
"WAYVR_RUNNING_TEXT": "Spójrz na nadgarstek, by zobaczyć zegarek, lub użyj przycisku pokaż/ukryj, aby zacząć!"
"NO_KEYMAPS_CONFIGURED_HELP": "Odwiedź stronę wayvr.org, aby dowiedzieć się, jak skonfigurować układy klawiatury!"
},
"WATCH": {
"ADD_NEW_SET": "Dodaj nowy zestaw",
@@ -181,5 +177,7 @@
"SELECT_SCREEN_CAST_FOR": "Wybierz przesyłanie ekranu dla wyświetlacza {DISPLAY}",
"CANNOT_CAPTURE_KBD_MOUSE": "Nie można zainicjować przechwytywania klawiatury/myszy!\nSprawdź, czy Twój użytkownik należy do grupy 'input'.",
"WE_ARE_GRABBING": "WayVR przejął kontrolę nad klawiaturą i myszą!\nNaciśnij Super+Z, aby zwolnić."
}
},
"STEAMVR_DASH_TOAST_TITLE": "Panel SteamVR otwarty",
"STEAMVR_DASH_TOAST_TEXT": "Zamknij pulpit SteamVR, aby zacząć korzystać z WayVR."
}
-185
View File
@@ -1,185 +0,0 @@
{
"DEFINITIONS": {
"ANCHORED": "Förankrad",
"FLOATING": "Flytande",
"STATIC": "Statisk",
"SET": "Set",
"WATCH": "Klocka",
"DASHBOARD": "Kontrollpanel",
"EDIT_MODE": "Redigeringsläge",
"MIRROR": "Spegling",
"SETTINGS": "Inställningar",
"KEYBOARD": "Tangentbord",
"TASKBAR": "Uppgiftspanel",
"FEATURES": "Funktioner",
"OVERLAY": "Overlay"
},
"ANCHOR": {
"CENTER": "Center"
},
"BAR": {
"ADD_MIRROR": "Lägg till ny spegling-overlay",
"ADD_NEW_SET": "Lägg till ny set",
"ADD_PASSTHRU": "Lägg till ny passthrough-utskärning",
"CLOSE_APP": "Stäng appen",
"CLOSE_OVERLAY": "Stäng {$DEFINITIONS.OVERLAY}",
"DELETE_CURRENT_SET": "Ta bort nuvarande set",
"EDIT_MODE_TOGGLE": "Växla redigeringsläge",
"FORCE_CLOSE_APP": "Tvinga stäng app",
"HANDSFREE": {
"EYE_ONLY": "Ögonsiktet",
"EYE_TRACKING": "Öga + pinch",
"HMD": "HMD + pinch",
"HMD_ONLY": "HMD-laser",
"NONE": "Av",
"TITLE": "Händsfree-läge"
},
"RELOAD_FROM_DISK": "Ladda om XML från disk",
"RESET_POSITION": "Återställ position",
"TOGGLE_VISIBILITY": "Växla synlighet"
},
"DEFAULT": "Standard",
"DISABLED": "Inaktiverat",
"DONE": "Klar",
"EDIT_MODE": {
"ADJUST_CURVATURE": "Justera krökning",
"ALIGN_TO_HMD": "Rikta mot HMD",
"ALPHA_BLEND_MODE": "Alfablendingsläge",
"ANGLE_FADE": "Vinkeltoning",
"ANGLE_FADE_HELP": "Tona ut när den inte är vänd mot HMD",
"BLENDING_ADDITIVE": "Additiv blandning",
"BLOCK_INPUT": "Blockera spel-input",
"CURVATURE": "Krökning",
"DELETE": "Håll inne för att ta bort från nuvarande set",
"DISABLE_GRAB": "Inaktivera grepp",
"GLOBAL": "Alltid synlig",
"HINT_POINT_WINDOW": "Peka på ett fönster för att ändra dess parametrar.\nNär du är klar, lämna redigeringsläget via knappen till höger.",
"INTERPOLATION": "Interpolation",
"KEYBOARD": "Tangentbord",
"LEAVE": "Lämna redigeringsläge",
"LOCK_INTERACTION": "Lås interaktion",
"MOUSE": {
"FLIP180": "Speglad 180°",
"FLIP270": "Speglad 270°",
"FLIP90": "Speglad 90°",
"FLIPPED": "Speglad",
"NORMAL": "Normal",
"ROTATE180": "Roterad 180°",
"ROTATE270": "Roterad 270°",
"ROTATE90": "Roterad 90°",
"TITLE": "Musfixar",
"WRONG_SCREEN_SELECTION_HELP": "Om markören rör sig på en helt annan skärm\nhar skärmarna troligtvis valts fel. Se readme."
},
"MOVE_PRESS_AND_DRAG": "Flytta (tryck & dra)",
"OPACITY": "Opacitet",
"POS_ANCHORED": "Förankrad: Flyttas tillsammans med set. Standard.",
"POS_FLOATING": "Flytande: Flyttas oberoende, centreras om vid visning.",
"POS_HAND_L": "Följ vänster hand.",
"POS_HAND_R": "Följ höger hand.",
"POS_HMD": "Följ HMD.",
"POS_STATIC": "Statisk: Inte en del av något set, ingen återcentrering.",
"POSITIONING": "Positionering",
"RESIZE_PRESS_AND_DRAG": "Ändra storlek (tryck & dra)",
"RESIZE_CORNER": "Dra för att ändra storlek",
"STEREO_3D_MODE": {
"ADJUST_MOUSE": "Justera mus",
"FULL_FRAME": "Full 3D",
"FULL_FRAME_BAT": "Full-BAT",
"FULL_FRAME_SBS": "Full-SBS",
"FULL_FRAME_TAB": "Full-TAB",
"SPLIT_BOTTOM_TOP": "NEDRE→ÖVRE",
"SPLIT_LEFT_RIGHT": "VÄNSTRER→HÖGER",
"SPLIT_RIGHT_LEFT": "HÖGER→VÄNSTER",
"SPLIT_TOP_BOTTOM": "TOPP→BOTTEN",
"TITLE": "3D-stereoläge"
}
},
"ALTTAB": {
"RELEASE_TO_FOCUS": "Sikta på en app och släpp Super för att fokusera på den.",
"WHILE_AIMING": "Vid sikte",
"CLICK": "VR-pekarklick",
"GRABALL": "Flytta hela setet",
"GRAB": "Flytta enskild overlay",
"DASH": "Växla WayVR Dashboard",
"EDIT": "Växla redigeringsläge",
"KEYBOARD": "Växla tangentbord",
"SET": "Byt set",
"RECENTER": "Visa/dölj/centrera",
"GENERAL_BINDS": "Allmänna bindningar",
"TOGGLE_CAPTURE": "Återlämna kontrollen till skrivbordet",
"FOCUS_MODE": "Fokusläge (håll in Super)",
"CLOSE": "Stäng fokuserad app",
"MOUSE": "Klick och skroll med fysisk mus översätts även till VR-pekaren"
},
"GRAB": {
"ADJUST_DISTANCE": "Justera avstånd",
"ADJUST_SIZE": "Justera storlek",
"GRABBING_ANCHORED": "Förankrade överlägg rör sig med setet. Separera ett enskilt fönster genom att greppa det med den andra handen samtidigt som du håller i setet.",
"GRABBING_ANCHORED_EDIT": "Det här överlägget kommer att förbli förankrat vid centreringsmarkören.",
"GRABBING_FLOATING": "Det här överlägget är flytande och kommer att stanna på plats, om det inte centreras om.",
"GRABBING_FOLLOW": "Detta overlay följer den enhet den är kopplad till.",
"GRABBING_STATIC": "Det här överlägget är statiskt och kommer att stanna kvar på platsen, oavsett om du centrerar om.",
"GRABBING_WATCH": "För att byta hand, flytta klockan framför dig och greppa den med den andra handen.",
"UNRESTRICTED_MOVEMENT": "Obegränsad rörelse"
},
"SCREENCAST": {
"PENDING": "Vänligen vänta...",
"QUEUED": "Väntar på att en annan overlays skärmdelningsförfrågan ska godkännas.",
"WAITING_FOR_USER_DESC": "Kontrollera skrivbordet efter ett popup-fönster för skärmdelning.\nVälj följande:",
"WAITING_FOR_USER": "Kontrollera din skrivbordsdisplay för ett popup-fönster om skärmdelning.",
"FAIL": "Kunde inte initiera källan för skärmöverföring."
},
"TOAST": {
"TITLE_SCREENCAST_FAIL": "Kunde inte starta källan för skärmdelning!",
"CANNOT_ADD_SET": "Kunde inte lägga till set!",
"CANNOT_REMOVE_SET": "Kan inte ta bort set!",
"STEAMVR_DASH_TITLE": "SteamVR-kontrollpanelen är öppen",
"STEAMVR_DASH_TEXT": "Stäng SteamVR-panelen för att börja använda WayVR.",
"WAYVR_RUNNING_TITLE": "WayVR körs!",
"WAYVR_RUNNING_TEXT": "Kolla handleden för klockan, eller använd bindningen för visa/dölj för att komma igång!",
"DEFAULT_TITLE": "Avisering",
"EMPTY_SET": "Tomt set!",
"ERROR": "Fel",
"FIXING_FLOOR_IN_X_SECS": "Justerar golvet om {SECONDS} sekunder...",
"LAST_EXISTING_SET": "Detta är det sista befintliga setet.",
"LETS_ADD_OVERLAYS": "Låt oss lägga till några overlays från uppgiftspanelen!",
"MAXIMUM_SETS_REACHED": "Maximalt antal set har uppnåtts.",
"NO_KEYMAPS_CONFIGURED": "Inga tangentbordslayouts konfigurerade",
"NO_KEYMAPS_CONFIGURED_HELP": "Kolla wayvr.org för att se hur du ställer in dina tangentbordslayouter!",
"NO_SET_SELECTED": "Inget Set är valt.",
"ONE_CONTROLLER_ON_FLOOR": "Placera en kontroller på golvet!"
},
"WATCH": {
"ADD_NEW_SET": "Lägg till ett nytt set",
"CLEANUP_MIRRORS": "Ta bort speglingar som\ninte är synliga just nu",
"DASHBOARD": "Kontrollpanel",
"EDIT_MODE": "Redigeringsläge",
"FIX_FLOOR": "Justera golvnivå",
"LONG_PRESS_TO_DELETE_SET": "Håll för att ta bort",
"RECENTER": "Centrera playspace",
"SWITCH_TO_SET": "Byt till set",
"TOGGLE_FOR_CURRENT_SET": "Växla för nuvarande set"
},
"WHISPER": {
"TRANSCRIBE": "Håll ned för att transkribera text",
"PASTE": "Kopiera transkribering till urklipp",
"PASTE_AND_GO": "Kopiera transkribering och skicka Ctrl-V till fokuserad app",
"SEND_OSC": "Skicka transkription till OSC-chatt",
"UNLOAD_AND_CLOSE": "Avlasta modell och stäng",
"MODEL_NOT_DOWNLOADED": "Tal-till-text är inte konfigurerat!",
"DOWNLOAD_GUIDANCE": "Konfigurera tal-till-text under inställningar, funktioner!",
"INITIAL_TEXT": "Håll inne mikroknappen för att börja transkribera...",
"INIT_ERROR": "Fel vid start av Whisper-motorn",
"ENTER_KEY": "Enter-tangent"
},
"NOTIFICATION": {
"COUD_NOT_CONNECT_TO_VR_RUNTIME": "Kunde inte ansluta till VR-runtime.",
"NOT_RUNNING_XR_VR": "Se till att Monado, WiVRn eller SteamVR är igång.",
"NOT_RUNNING_VR": "Se till att Monado, WiVRn eller SteamVR är igång.",
"NOT_RUNNING_XR": "Se till att Monado eller WiVRn är igång.",
"INVALID_ARGS": "Kontrollera dina startargument.",
"SELECT_SCREEN_CAST_FOR": "Välj skärmdelning för display {DISPLAY}",
"CANNOT_CAPTURE_KBD_MOUSE": "Kunde inte initiera fångst av tangentbord/mus!\nKontrollera att din användare tillhör gruppen 'input'.",
"WE_ARE_GRABBING": "WayVR styr nu ditt tangentbord och din mus!\nTryck Super+Z för att släppa."
}
}
+4 -6
View File
@@ -143,11 +143,7 @@
"NO_SET_SELECTED": "未选择套装。",
"ONE_CONTROLLER_ON_FLOOR": "将一只控制器放在地面上!",
"NO_KEYMAPS_CONFIGURED": "尚未配置键盘布局",
"NO_KEYMAPS_CONFIGURED_HELP": "请访问 wayvr.org 了解如何设置键盘布局!",
"STEAMVR_DASH_TITLE": "SteamVR 控制面板已开启",
"STEAMVR_DASH_TEXT": "关闭 SteamVR 仪表板以开始使用 WayVR。",
"WAYVR_RUNNING_TITLE": "WayVR 正在运行!",
"WAYVR_RUNNING_TEXT": "查看手腕上的手表,或使用显示/隐藏快捷键开始!"
"NO_KEYMAPS_CONFIGURED_HELP": "请访问 wayvr.org 了解如何设置键盘布局!"
},
"WATCH": {
"ADD_NEW_SET": "添加新套装",
@@ -181,5 +177,7 @@
"SELECT_SCREEN_CAST_FOR": "为显示器 {DISPLAY} 选择屏幕投射",
"CANNOT_CAPTURE_KBD_MOUSE": "无法初始化键盘/鼠标捕获!\n请检查您的用户是否在 'input' 组中。",
"WE_ARE_GRABBING": "WayVR 正在接管你的键盘和鼠标!\n按 Super+Z 释放。"
}
},
"STEAMVR_DASH_TOAST_TITLE": "SteamVR 控制面板已开启",
"STEAMVR_DASH_TOAST_TEXT": "关闭 SteamVR 仪表板以开始使用 WayVR。"
}
-4
View File
@@ -138,10 +138,6 @@ impl OpenVrInputSource {
})
}
pub fn open_bindings(&mut self, input: &mut InputManager) {
let _ = input.open_binding_ui(None, None, self.hands[0].input_hnd, false);
}
pub fn haptics(&mut self, input: &mut InputManager, hand: usize, haptics: &Haptics) {
let action_handle = self.hands[hand].haptics_hnd;
let _ = input.trigger_haptic_vibration_action(
+2 -19
View File
@@ -214,9 +214,9 @@ pub fn openvr_run(args: &Args, params: RunParams) -> Result<(), BackendError> {
let id = Toast::new(
ToastTopic::Priority,
Some(Translation::from_translation_key(
"TOAST.STEAMVR_DASH_TITLE",
"STEAMVR_DASH_TOAST_TITLE",
)),
Translation::from_translation_key("TOAST.STEAMVR_DASH_TEXT"),
Translation::from_translation_key("STEAMVR_DASH_TOAST_TEXT"),
)
.with_timeout(300.0) // we'll zero this if dash gets hidden
.submit(&mut app);
@@ -227,16 +227,6 @@ pub fn openvr_run(args: &Args, params: RunParams) -> Result<(), BackendError> {
dashboard_hidden = true;
if let Some(id) = dash_toast_id {
Toast::expire_now(&mut app, id);
} else if !args.show {
Toast::new(
ToastTopic::Priority,
Some(Translation::from_translation_key(
"TOAST.WAYVR_RUNNING_TITLE",
)),
Translation::from_translation_key("TOAST.WAYVR_RUNNING_TEXT"),
)
.with_timeout(5.0)
.submit(&mut app);
}
}
}
@@ -270,13 +260,6 @@ pub fn openvr_run(args: &Args, params: RunParams) -> Result<(), BackendError> {
OpenVrTask::ColorGain(channel, value) => {
let _ = adjust_gain(&mut settings_mgr, channel, value);
}
OpenVrTask::ShowBindingsUI => {
// hide current set because overlay sort order is broken
// and will cover the bindings UI while not taking input
overlays.switch_to_set(&mut app, None, false);
input_source.open_bindings(&mut input_mgr);
}
},
#[cfg(feature = "openxr")]
TaskType::OpenXR(_) => {}
+1
View File
@@ -1,3 +1,4 @@
use core::f32;
use std::sync::Arc;
use glam::Vec4;
+9
View File
@@ -339,6 +339,15 @@ impl PlayspaceMover {
);
self.universe = new_universe;
}
if self.drag.is_some() {
log::info!("Space drag interrupted by external change");
self.drag = None;
}
if self.rotate.is_some() {
log::info!("Space rotate interrupted by external change");
self.rotate = None;
}
}
pub fn get_universe(&self) -> ETrackingUniverseOrigin {
-12
View File
@@ -179,18 +179,6 @@ pub fn openxr_run(args: &Args, params: RunParams) -> Result<(), BackendError> {
// declared last so rust drops this before all long-lived XR objects
let _gpu_idle_before_xr_drop = OpenXrGpuIdleGuard(app.gfx.clone());
if !args.show {
Toast::new(
ToastTopic::Priority,
Some(Translation::from_translation_key(
"TOAST.WAYVR_RUNNING_TITLE",
)),
Translation::from_translation_key("TOAST.WAYVR_RUNNING_TEXT"),
)
.with_timeout(5.0)
.submit(&mut app);
}
'main_loop: loop {
let now = Instant::now();
app.delta_time = (now.duration_since(last_frame_time).as_secs_f32()).clamp(0.001, 0.2); // 5 - 1000 fps
+19 -26
View File
@@ -21,7 +21,7 @@ struct MoverData<T> {
pub(super) struct PlayspaceMover {
drag: Option<MoverData<Vec3A>>,
rotate: Option<MoverData<Affine3A>>,
rotate: Option<MoverData<Quat>>,
gravity: SpaceGravity,
playspace_state: PlayspaceState,
}
@@ -93,37 +93,32 @@ impl PlayspaceMover {
return;
}
let new_hand = data.pose * app.input_state.pointers[data.hand].raw_pose;
let before_pose = data.pose;
let pose_quat = Quat::from_affine3(&data.pose).normalize();
let new_hand = (pose_quat
* Quat::from_affine3(&app.input_state.pointers[data.hand].raw_pose))
.normalize();
if app.session.config.space_rotate_unlocked {
// pivot on the hand so the body orbits it
let pose = new_hand * data.hand_pose.inverse() * data.pose;
data.pose = Affine3A::from_rotation_translation(
Quat::from_affine3(&pose).normalize(),
pose.translation.into(),
);
let dq = (pose_quat.conjugate() * (new_hand * data.hand_pose.conjugate()) * pose_quat)
.normalize();
let mut space_transform = if app.session.config.space_rotate_unlocked {
Affine3A::from_quat(dq)
} else {
let pose_quat = Quat::from_affine3(&data.pose).normalize();
let dq = (pose_quat.conjugate()
* Quat::from_affine3(&(new_hand * data.hand_pose.inverse()))
* pose_quat)
.normalize();
let rel_y = f32::atan2(
2.0 * dq.y.mul_add(dq.w, dq.x * dq.z),
2.0f32.mul_add(dq.w.mul_add(dq.w, dq.x * dq.x), -1.0),
);
let mut space_transform = Affine3A::from_rotation_y(rel_y);
space_transform.translation = (space_transform
.transform_vector3a(app.input_state.hmd.translation)
- app.input_state.hmd.translation)
* -1.0;
Affine3A::from_rotation_y(rel_y)
};
let offset = (space_transform.transform_vector3a(app.input_state.hmd.translation)
- app.input_state.hmd.translation)
* -1.0;
data.pose *= space_transform;
data.pose.matrix3 = Mat3A::from_quat(Quat::from_affine3(&data.pose).normalize());
}
space_transform.translation = offset;
let before_pose = data.pose;
data.pose *= space_transform;
data.pose.matrix3 = Mat3A::from_quat(Quat::from_affine3(&data.pose).normalize());
if !app.session.config.space_drag_affects_world {
playspace_common::shift_world(overlays, &mut app.anchor, &before_pose, &data.pose);
}
@@ -131,8 +126,6 @@ impl PlayspaceMover {
apply_offset(data.pose, &mut monado.ipc);
self.rotate = Some(data);
// drag and rotate can share a button
return;
} else {
// start space rotate
for (i, pointer) in app.input_state.pointers.iter().enumerate() {
@@ -151,7 +144,7 @@ impl PlayspaceMover {
return;
};
let hand_pose = pose * pointer.raw_pose;
let hand_pose = Quat::from_affine3(&(pose * pointer.raw_pose)).normalize();
self.rotate = Some(MoverData {
pose,
hand: i,
-1
View File
@@ -56,7 +56,6 @@ pub enum InputTask {
#[allow(dead_code)]
pub enum OpenVrTask {
ColorGain(ColorChannel, f32),
ShowBindingsUI,
}
#[cfg(feature = "openxr")]
+16 -15
View File
@@ -21,7 +21,7 @@ use smithay::{
use wgui::log::LogErr;
use xkbcommon::xkb;
use crate::backend::wayvr::WayVRTask;
use crate::backend::wayvr::{ExternalProcessRequest, WayVRTask};
use super::{
ProcessWayVREnv,
@@ -32,7 +32,6 @@ use super::{
pub struct WayVRClient {
pub client: wayland_server::Client,
pub pid: u32,
pub process: process::ProcessHandle,
}
pub struct WayVRCompositor {
@@ -161,17 +160,16 @@ impl WayVRCompositor {
let process_env = get_wayvr_env_from_pid(credentials.pid)?;
// Find suitable auth key from the process list
for (handle, process) in processes.iter() {
if let process::Process::Managed(process) = process
for (_, process) in processes {
if let process::Process::Managed(process) = &process
&& let Some(auth_key) = &process_env.display_auth
{
// Find process with matching auth key
if process.auth_key.as_str() == auth_key {
// Associate client now, instead of trying to match env later
// Add client
self.add_client(WayVRClient {
client,
pid: credentials.pid as u32,
process: handle,
});
return Ok(());
}
@@ -180,15 +178,18 @@ impl WayVRCompositor {
// This is a new process which we didn't met before.
// Treat external processes exclusively (spawned by the user or external program)
let pid = credentials.pid as u32;
log::warn!("External process ID {pid} connected to this Wayland server");
let process =
processes.insert(process::Process::External(process::ExternalProcess { pid }));
self.add_client(WayVRClient {
client,
pid,
process,
});
log::warn!(
"External process ID {} connected to this Wayland server",
credentials.pid
);
self.state
.wayvr_tasks
.send(WayVRTask::NewExternalProcess(ExternalProcessRequest {
env: process_env,
client,
pid: credentials.pid as u32,
}));
Ok(())
}
+35 -16
View File
@@ -118,6 +118,14 @@ pub struct ProcessWayVREnv {
pub display_name: Option<String>, // Externally spawned process by a user script
}
#[derive(Clone)]
pub struct ExternalProcessRequest {
#[allow(dead_code)]
pub env: ProcessWayVREnv,
pub client: wayland_server::Client,
pub pid: u32,
}
#[derive(Clone)]
pub enum WayVRTask {
NewToplevel(ClientId, ToplevelSurface),
@@ -125,6 +133,7 @@ pub enum WayVRTask {
MinimizeRequest(ClientId, ToplevelSurface),
TitleChange(ClientId, ToplevelSurface),
VisibilityChange(window::WindowHandle, bool),
NewExternalProcess(ExternalProcessRequest),
ProcessTerminationRequest(process::ProcessHandle, KillSignal),
CloseWindowRequest(window::WindowHandle),
DndStarted {
@@ -164,6 +173,10 @@ pub enum PointerFocusTarget {
None,
}
pub enum TickTask {
NewExternalProcess(ExternalProcessRequest), // Call WayVRCompositor::add_client after receiving this message
}
const KEY_REPEAT_DELAY: i32 = 500;
const KEY_REPEAT_RATE: i32 = 50;
const WAYVR_SCREEN_RES: [i32; 2] = [2560, 1440];
@@ -312,29 +325,26 @@ impl WvrServerState {
#[allow(clippy::too_many_lines)]
#[allow(clippy::significant_drop_tightening)]
pub fn tick_events(app: &mut AppState) -> anyhow::Result<()> {
pub fn tick_events(app: &mut AppState) -> anyhow::Result<Vec<TickTask>> {
let mut tasks: Vec<TickTask> = Vec::new();
let Some(wvr_server) = app.wvr_server.as_mut() else {
return Ok(());
return Ok(tasks);
};
app.ipc_server.tick(&mut ipc_server::TickParams {
wvr_server,
input_state: &app.input_state,
tasks: &mut tasks,
signals: &app.wayvr_signals,
hid_wrapper: &mut app.hid_provider,
});
// Keep launch records while wl client is attached
let attached_processes: HashSet<_> = wvr_server
.manager
.clients
.iter()
.map(|client| client.process)
.collect();
// Tick all child processes
let mut to_remove: SmallVec<[process::ProcessHandle; 2]> = SmallVec::new();
for (handle, process) in &mut wvr_server.processes {
if !process.is_running() && !attached_processes.contains(&handle) {
if !process.is_running() {
to_remove.push(handle);
}
}
@@ -346,6 +356,9 @@ impl WvrServerState {
while let Some(task) = wvr_server.tasks.read() {
match task {
WayVRTask::NewExternalProcess(req) => {
tasks.push(TickTask::NewExternalProcess(req));
}
WayVRTask::NewToplevel(client_id, toplevel) => {
let toplevel = Rc::new(toplevel);
@@ -355,14 +368,15 @@ impl WvrServerState {
continue;
}
let process_handle = client.process;
if !wvr_server.processes.contains_key(process_handle) {
let Some(process_handle) =
process::find_by_pid(&wvr_server.processes, client.pid)
else {
log::error!(
"WayVR window creation failed: Process for PID {} was removed after the client connected.",
"WayVR window creation failed: Unexpected process ID {}. It wasn't registered before.",
client.pid
);
continue;
}
};
let output_bounds = wvr_server.manager.state.output_logical_size();
@@ -587,7 +601,7 @@ impl WvrServerState {
}
let Some(window_handle) = wvr_server.wm.find_window_handle(&toplevel)
else {
log::warn!("TitleChange: Couldn't find matching window handle");
log::warn!("MinimizeRequest: Couldn't find matching window handle");
continue;
};
if let Some(oid) = wvr_server.window_to_overlay.get(&window_handle) {
@@ -689,7 +703,7 @@ impl WvrServerState {
wvr_server.ticks += 1;
Ok(())
Ok(tasks)
}
pub fn config_changed(&mut self, config: &GeneralConfig) {
@@ -1293,6 +1307,11 @@ impl WvrServerState {
self.manager.state.set_clipboard_text(content);
}
pub fn add_external_process(&mut self, pid: u32) -> process::ProcessHandle {
self.processes
.insert(process::Process::External(process::ExternalProcess { pid }))
}
#[allow(clippy::too_many_arguments)]
pub fn spawn_process(
&mut self,
+9 -13
View File
@@ -87,14 +87,12 @@ impl Process {
impl Drop for WayVRProcess {
fn drop(&mut self) {
if self.is_running() {
log::info!(
"Sending SIGTERM (graceful exit) to process {} ({})",
self.child.id(),
self.exec_path.as_str()
);
self.kill(libc::SIGTERM);
}
log::info!(
"Sending SIGTERM (graceful exit) to process {} ({})",
self.child.id(),
self.exec_path.as_str()
);
self.kill(libc::SIGTERM);
}
}
@@ -129,11 +127,9 @@ impl WayVRProcess {
}
fn kill(&mut self, signal: i32) {
if self.is_running() {
unsafe {
// Gracefully stop process
libc::kill(self.child.id() as i32, signal);
}
unsafe {
// Gracefully stop process
libc::kill(self.child.id() as i32, signal);
}
}
-2
View File
@@ -189,7 +189,6 @@ pub struct AutoSettings {
pub input_emulation_method: InputEmulationMethod,
pub tutorial_graduated: bool,
pub whisper_model: Arc<str>,
pub whisper_gpu: u32,
pub default_overlay_scale: f32,
pub color_palette: Arc<str>,
pub snap_angle_deg: f32,
@@ -266,7 +265,6 @@ pub fn save_settings(config: &GeneralConfig) -> anyhow::Result<()> {
input_emulation_method: config.input_emulation_method,
tutorial_graduated: config.tutorial_graduated,
whisper_model: config.whisper_model.clone(),
whisper_gpu: config.whisper_gpu,
default_overlay_scale: config.default_overlay_scale,
color_palette: config.color_palette.clone(),
snap_angle_deg: config.snap_angle_deg,
+23 -2
View File
@@ -1,6 +1,6 @@
use std::sync::Arc;
use crate::backend::wayvr::{WayVRTask, WvrServerState};
use crate::backend::wayvr::{self, WayVRTask, WvrServerState};
use wayvr_ipc::packet_client::{HandsfreeMode, HandsfreeParams};
use wayvr_ipc::packet_server::PacketServer;
use wlx_common::config::HandsfreePointer;
@@ -15,6 +15,24 @@ use crate::{
windowing::{OverlaySelector, manager::OverlayWindowManager},
};
fn process_tick_tasks(
tick_tasks: Vec<backend::wayvr::TickTask>,
server_state: &mut WvrServerState,
) {
for tick_task in tick_tasks {
match tick_task {
backend::wayvr::TickTask::NewExternalProcess(request) => {
log::info!("Registering external process with PID {}", request.pid);
server_state.add_external_process(request.pid);
server_state.manager.add_client(wayvr::client::WayVRClient {
client: request.client,
pid: request.pid,
});
}
}
}
}
pub fn tick_events<O>(
app: &mut AppState,
overlays: &mut OverlayWindowManager<O>,
@@ -117,7 +135,10 @@ where
}
}
WvrServerState::tick_events(app)?;
let tick_tasks = WvrServerState::tick_events(app)?;
if let Some(wayvr_server) = app.wvr_server.as_mut() {
process_tick_tasks(tick_tasks, wayvr_server);
}
Ok(())
}
+21 -22
View File
@@ -6,7 +6,6 @@ use crate::{
backend::input::InputState,
ipc::{event_queue::SyncEventQueue, signal::WayVRSignal},
};
use anyhow::Context;
use bytes::BufMut;
use glam::Vec3A;
use interprocess::local_socket::{self, ToNsName, traits::Listener};
@@ -89,11 +88,17 @@ fn read_payload(conn: &mut local_socket::Stream, size: u32) -> anyhow::Result<Op
pub struct TickParams<'a> {
pub wvr_server: &'a mut WvrServerState,
#[allow(dead_code)]
pub tasks: &'a mut Vec<wayvr::TickTask>,
pub signals: &'a SyncEventQueue<WayVRSignal>,
pub input_state: &'a InputState,
pub hid_wrapper: &'a mut HidWrapper,
}
pub fn gen_args_vec(input: &str) -> Vec<&str> {
input.split_whitespace().collect()
}
pub fn gen_env_vec(input: &[String]) -> Vec<(&str, &str)> {
input
.iter()
@@ -221,28 +226,22 @@ impl Connection {
serial: ipc::Serial,
packet_params: packet_client::WvrProcessLaunchParams,
) -> anyhow::Result<()> {
let res = {
let mut argv_iter = packet_params.argv.iter().map(String::as_str);
let exec_path = argv_iter
.next()
.context("No executable for process_launch")?;
let exec_args = argv_iter.collect::<Box<_>>();
let env_vec = gen_env_vec(&packet_params.env);
let args_vec = gen_args_vec(&packet_params.args);
let env_vec = gen_env_vec(&packet_params.env);
params.wvr_server.spawn_process(
&packet_params.name,
exec_path,
&exec_args,
&env_vec,
packet_params.resolution,
packet_params.pos_mode,
None,
packet_params.icon.as_deref(),
packet_params.userdata,
)
}
.map(ProcessHandle::as_packet)
.map_err(|e| e.to_string());
let res = params.wvr_server.spawn_process(
&packet_params.name,
&packet_params.exec,
&args_vec,
&env_vec,
packet_params.resolution,
packet_params.pos_mode,
None,
packet_params.icon.as_deref(),
packet_params.userdata,
);
let res = res.map(ProcessHandle::as_packet).map_err(|e| e.to_string());
send_packet(
&mut self.conn,
+5 -28
View File
@@ -4,7 +4,6 @@ use std::{
time::{Duration, Instant},
};
use anyhow::Context;
use dash_frontend::frontend::{self, FrontendTask, FrontendUpdateParams};
use glam::{Affine2, Affine3A, Quat, Vec2, vec2, vec3};
use wayvr_ipc::{
@@ -21,7 +20,7 @@ use wgui::{
widget::EventResult,
};
use wlx_common::{
dash_interface::{self, ConfigChangeKind, DashBackendTask, DashInterface, DashPlayspaceTask},
dash_interface::{self, ConfigChangeKind, DashInterface, DashPlayspaceTask},
locale::WayVRLangProvider,
overlays::{BackendAttrib, BackendAttribValue},
};
@@ -44,7 +43,7 @@ use crate::{
},
},
config::{none_if_0, save_settings},
ipc::ipc_server::gen_env_vec,
ipc::ipc_server::{gen_args_vec, gen_env_vec},
state::AppState,
subsystem::hid::WheelDelta,
windowing::{
@@ -392,10 +391,6 @@ impl DashInterfaceLive {
}
impl DashInterface<AppState> for DashInterfaceLive {
fn enumerate_vulkan_devices(&mut self, data: &mut AppState) -> anyhow::Result<Vec<String>> {
data.gfx.enumerate_devices()
}
fn window_list(&mut self, app: &mut AppState) -> anyhow::Result<Vec<WvrWindow>> {
let wvr_server = app.wvr_server.as_mut().unwrap();
Ok(wvr_server
@@ -441,11 +436,7 @@ impl DashInterface<AppState> for DashInterfaceLive {
) -> anyhow::Result<WvrProcessHandle> {
let wvr_server = app.wvr_server.as_mut().unwrap();
let mut argv_iter = params.argv.iter().map(String::as_str);
let exec_path = argv_iter
.next()
.context("No executable for process_launch")?;
let exec_args = argv_iter.collect::<Box<_>>();
let args_vec = gen_args_vec(&params.args);
let env_vec = gen_env_vec(&params.env);
if auto_start {
@@ -456,8 +447,8 @@ impl DashInterface<AppState> for DashInterfaceLive {
wvr_server
.spawn_process(
&params.name,
exec_path,
&exec_args,
&params.exec,
&args_vec,
&env_vec,
params.resolution,
params.pos_mode,
@@ -526,20 +517,6 @@ impl DashInterface<AppState> for DashInterfaceLive {
Ok(())
}
#[allow(unreachable_patterns)]
fn backend_task(&mut self, app: &mut AppState, mode: DashBackendTask) -> anyhow::Result<()> {
#[cfg(feature = "openvr")]
if app.feats.xr_backend.is_open_vr() {
use crate::backend::task::OpenVrTask;
let task = match mode {
DashBackendTask::ShowBindingsUI => OpenVrTask::ShowBindingsUI,
_ => return Ok(()),
};
app.tasks.enqueue(TaskType::OpenVR(task));
}
Ok(())
}
fn desktop_finder<'a>(
&'a mut self,
data: &'a mut AppState,
+19 -63
View File
@@ -31,9 +31,6 @@ use crate::{
const CURSOR_SIZE: f32 = 16. / 1440.;
// synchronize queue shared by the WlxCapture worker threads
static CAPTURE_QUEUE_MUTEX: Mutex<()> = Mutex::new(());
struct BufPass {
pass: WGfxPass<Vert2Uv>,
buf_vert: Arc<Buffer<Vert2Uv>>,
@@ -667,17 +664,6 @@ fn upload_image(
height: u32,
format: Format,
data: &[u8],
) -> Option<Arc<Image>> {
upload_image_with_row_length(me, width, height, format, data, 0)
}
fn upload_image_with_row_length(
me: &WlxCaptureIn,
width: u32,
height: u32,
format: Format,
data: &[u8],
buffer_row_length: u32,
) -> Option<Arc<Image>> {
let cmd_result = if me.use_capture_queue {
me.gfx
@@ -693,23 +679,15 @@ fn upload_image_with_row_length(
return None;
}
};
let image =
match cmd_xfer.upload_image_with_row_length(width, height, format, data, buffer_row_length)
{
Ok(x) => x,
Err(e) => {
log::error!("{}: Could not create vkImage: {:?}", me.name, e);
return None;
}
};
let result = if me.use_capture_queue {
let _guard = CAPTURE_QUEUE_MUTEX.lock().unwrap(); // mutex poison only
cmd_xfer.build_and_execute_now()
} else {
cmd_xfer.build_and_execute_now()
let image = match cmd_xfer.upload_image(width, height, format, data) {
Ok(x) => x,
Err(e) => {
log::error!("{}: Could not create vkImage: {:?}", me.name, e);
return None;
}
};
if let Err(e) = result {
if let Err(e) = cmd_xfer.build_and_execute_now() {
log::error!("{}: Could not execute upload: {:?}", me.name, e);
return None;
}
@@ -779,22 +757,8 @@ pub(super) fn receive_callback(me: &WlxCaptureIn, frame: WlxFrame) -> Option<Wlx
}
};
// SAFETY: the MemPtr params & allocation and must be valid and must outlive the callback
let pixels = unsafe {
std::slice::from_raw_parts(
(frame.ptr as *const u8).add(frame.offset),
frame.size - frame.offset,
)
};
let buffer_row_length = (frame.stride / 4) as u32;
let image = upload_image_with_row_length(
me,
frame.format.width,
frame.format.height,
format,
pixels,
buffer_row_length,
)?;
let data = unsafe { std::slice::from_raw_parts(frame.ptr as *const u8, frame.size) };
let image = upload_image(me, frame.format.width, frame.format.height, format, data)?;
Some(WlxCaptureOut {
image: me.gfx.create_image_view(image).ok()?,
@@ -847,8 +811,6 @@ pub(super) enum MainThreadFrame {
Cpu {
format: FrameFormat,
data: Vec<u8>,
offset: usize,
stride: usize,
mouse: Option<MouseMeta>,
},
Dmabuf {
@@ -908,16 +870,12 @@ where
MainThreadFrame::Cpu {
format,
data,
offset,
stride,
mouse,
} => {
let frame = wlx_frame::MemPtrFrame {
format,
ptr: data.as_ptr() as usize,
size: data.len(),
offset,
stride,
mouse,
};
receive_callback(
@@ -952,24 +910,22 @@ fn receive_callback_dummy(_: &DmaExporterProxy, frame: WlxFrame) -> Option<MainT
Some(MainThreadFrame::Cpu {
format: frame.format,
data: map.as_slice().to_vec(),
offset: frame.plane.offset as _,
stride: (frame.format.width as usize).checked_mul(4)?,
mouse: frame.mouse,
})
}
WlxFrame::MemPtr(frame) => {
// SAFETY: Allocation must be valid & must outlive the callback
let pixels = unsafe {
std::slice::from_raw_parts(
(frame.ptr as *const u8).add(frame.offset),
frame.size - frame.offset,
)
let data = if frame.size == 0 {
Vec::new()
} else {
if frame.ptr == 0 {
log::error!("Main-thread CPU capture received a null MemPtr frame");
return None;
}
unsafe { std::slice::from_raw_parts(frame.ptr as *const u8, frame.size) }.to_vec()
};
Some(MainThreadFrame::Cpu {
format: frame.format,
data: pixels.to_vec(),
offset: frame.offset,
stride: frame.stride,
data,
mouse: frame.mouse,
})
}
+2 -7
View File
@@ -107,8 +107,6 @@ fn start_progress_state(
let mut st = st.borrow_mut();
let mut update_text = false;
common.alterables.mark_redraw();
while let Ok(msg) = progress_rx.try_recv() {
match msg {
PttProgress::VuVolume(volume) => {
@@ -232,11 +230,8 @@ pub fn create_whisper(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig>
let model_path = data_dir::get_path("whisper")
.join(app.session.config.whisper_model.as_ref());
if model_path.is_file() {
app.whisper_sst = match WhisperStt::new(
model_path,
app.session.config.whisper_gpu,
)
.log_err("Error while starting Whisper engine")
app.whisper_sst = match WhisperStt::new(model_path)
.log_err("Error while starting Whisper engine")
{
Ok(x) => Some(x),
Err(e) => {
+2 -2
View File
@@ -172,14 +172,14 @@ impl AppState {
let mut theme = WguiTheme {
animation_mult: 1. / session.config.ui_animation_speed,
rounding_mult: session.config.ui_round_multiplier,
gradient_intensity: session.config.ui_gradient_intensity,
..Default::default()
};
let dbus = DbusConnector::default();
let ipc_server = ipc_server::WayVRServer::new()?;
let mut desktop_finder = DesktopFinder::new(params.wgui_globals.i18n().get_locale());
let mut desktop_finder = DesktopFinder::new();
desktop_finder.refresh();
#[cfg(feature = "pipewire")]
+2 -4
View File
@@ -116,10 +116,8 @@ pub enum PttProgress {
}
impl WhisperStt {
pub fn new(model_path: impl AsRef<Path>, gpu_idx: u32) -> Result<Self, WhisperSttError> {
let mut conf = WhisperSttConfig::new(model_path);
conf.gpu_device = gpu_idx as i32;
Self::init(conf)
pub fn new(model_path: impl AsRef<Path>) -> Result<Self, WhisperSttError> {
Self::init(WhisperSttConfig::new(model_path))
}
pub fn init(config: WhisperSttConfig) -> Result<Self, WhisperSttError> {
+2 -6
View File
@@ -320,12 +320,8 @@ impl OverlayWindowConfig {
return;
}
let to_hmd =
(state.transform.translation - app.input_state.hmd.translation).normalize_or_zero();
let watch_normal = state
.transform
.transform_vector3a(Vec3A::NEG_Z)
.normalize_or_zero();
let to_hmd = (state.transform.translation - app.input_state.hmd.translation).normalize();
let watch_normal = state.transform.transform_vector3a(Vec3A::NEG_Z).normalize();
let dot = to_hmd.dot(watch_normal);
let range = (app.session.config.watch_view_angle_max
-10
View File
@@ -1,10 +0,0 @@
[Desktop Entry]
Type=Application
Name=WayVR (Headless)
Comment=Your way to enjoy VR on Linux
Exec=wayvr --headless
Icon=wayvr
Terminal=false
Categories=Utility;X-WiVRn-VR;
X-XR-Plugin=true
X-XR-Plugin-Exec=wayvr --openxr --headless
+1 -1
View File
@@ -15,7 +15,7 @@ clap.workspace = true
log.workspace = true
serde.workspace = true
serde_json.workspace = true
shell-words.workspace = true
smol.workspace = true
env_logger = "0.11.11"
shell-words = "1.1.1"
+4 -2
View File
@@ -107,12 +107,13 @@ pub async fn wvr_process_terminate(
#[allow(clippy::too_many_arguments)]
pub async fn wvr_process_launch(
state: &mut WayVRClientState,
argv: Vec<String>,
exec: String,
name: String,
env: Vec<String>,
resolution: [u32; 2],
pos_mode: PositionMode,
icon: Option<String>,
args: String,
userdata: HashMap<String, String>,
) {
handle_result(
@@ -122,8 +123,9 @@ pub async fn wvr_process_launch(
state.serial_generator.increment_get(),
packet_client::WvrProcessLaunchParams {
env,
argv,
exec,
name,
args,
resolution,
pos_mode,
icon,
+10 -8
View File
@@ -157,12 +157,13 @@ async fn run_once(state: &mut WayVRClientState, args: Args) -> anyhow::Result<()
wvr_process_terminate(state, handle).await;
}
Subcommands::ProcessLaunch {
exec,
name,
env,
resolution,
pos,
icon,
command,
args,
} => {
let env = env.split(",").map(|s| s.to_string()).collect::<Vec<_>>();
let resolution = resolution
@@ -180,12 +181,13 @@ async fn run_once(state: &mut WayVRClientState, args: Args) -> anyhow::Result<()
wvr_process_launch(
state,
command,
exec,
name,
env,
resolution,
pos_mode,
icon,
args,
HashMap::new(),
)
.await;
@@ -314,17 +316,17 @@ enum Subcommands {
/// Environment variables, separated by comma
#[arg(short, long, default_value = "")]
env: String,
#[arg(short, long, default_value = "1920x1080")]
/// Executable to run
exec: String,
#[arg(default_value = "1920x1080")]
resolution: String,
/// Default positioning
#[arg(short, long, default_value = "anchored")]
pos: PosModeEnum,
/// Absolute path to the app icon
#[arg(short, long, default_value = "")]
icon: Option<String>,
/// Executable and optional arguments
#[arg(required = true, num_args = 1..)]
command: Vec<String>,
/// Arguments to pass to executable
#[arg(default_value = "")]
args: String,
},
/// Trigger haptics on the user's controller
Haptics {
+1 -1
View File
@@ -1,4 +1,4 @@
use std::{cell::RefCell, rc::Rc};
use std::{cell::RefCell, f32, rc::Rc};
use glam::{Mat4, Vec2, Vec3};
use taffy::prelude::{length, percent};
+3 -18
View File
@@ -139,17 +139,6 @@ impl CommandBufferBuilder<CmdBufGfx> {
impl CommandBufferBuilder<CmdBufXfer> {
pub fn upload_image(&mut self, width: u32, height: u32, format: Format, data: &[u8]) -> anyhow::Result<Arc<Image>> {
self.upload_image_with_row_length(width, height, format, data, 0)
}
pub fn upload_image_with_row_length(
&mut self,
width: u32,
height: u32,
format: Format,
data: &[u8],
buffer_row_length: u32,
) -> anyhow::Result<Arc<Image>> {
let image = self.graphics.create_image(ImageCreateInfo::new_2d(
width,
height,
@@ -160,13 +149,9 @@ impl CommandBufferBuilder<CmdBufXfer> {
.graphics
.empty_buffer::<u8>(BufferUsage::TRANSFER_SRC, data.len() as u64)?;
staging.write()?.copy_from_slice(data);
self.raw.copy_buffer_to_image_with_row_length(
&staging.raw,
&image.raw,
[0, 0, 0],
[width, height, 1],
buffer_row_length,
);
self
.raw
.copy_buffer_to_image(&staging.raw, &image.raw, [0, 0, 0], [width, height, 1]);
self.keepalive.push(Box::new(staging));
self.keepalive.push(Box::new(image.clone()));
Ok(image)
-4
View File
@@ -462,8 +462,4 @@ impl WGfx {
}
cmd::CommandBufferBuilder::new(self.clone(), QueueType::Transfer, usage, PhantomData)
}
pub fn enumerate_devices(&self) -> anyhow::Result<Vec<String>> {
self.raw.enumerate_devices()
}
}
+1 -31
View File
@@ -1687,25 +1687,6 @@ impl Context {
external_images: BTreeMap::new(),
})
}
pub fn enumerate_devices(&self) -> anyhow::Result<Vec<String>> {
let devices = unsafe { self.instance.enumerate_physical_devices() }
.map_err(|e| anyhow!("vkEnumeratePhysicalDevices failed: {e:?}"))?;
let devices: Vec<String> = devices
.iter()
.map(|dev| unsafe {
let mut id = vk::PhysicalDeviceIDProperties::default();
let mut props2 = vk::PhysicalDeviceProperties2::default().push_next(&mut id);
self.instance.get_physical_device_properties2(*dev, &mut props2);
CStr::from_ptr(props2.properties.device_name.as_ptr())
.to_string_lossy()
.to_string()
})
.collect();
Ok(devices)
}
}
impl Drop for Context {
@@ -2494,22 +2475,11 @@ impl RawCommandBuffer {
image: &Arc<RawImage>,
offset: [u32; 3],
extent: [u32; 3],
) {
self.copy_buffer_to_image_with_row_length(buffer, image, offset, extent, 0);
}
pub(super) fn copy_buffer_to_image_with_row_length(
&mut self,
buffer: &RawBuffer,
image: &Arc<RawImage>,
offset: [u32; 3],
extent: [u32; 3],
buffer_row_length: u32,
) {
self.transition_image(image, ImageLayout::TransferDst);
let regions = [vk::BufferImageCopy::default()
.buffer_offset(0)
.buffer_row_length(buffer_row_length)
.buffer_row_length(0)
.buffer_image_height(0)
.image_subresource(vk::ImageSubresourceLayers {
aspect_mask: vk::ImageAspectFlags::COLOR,
-10
View File
@@ -69,16 +69,6 @@ pub trait LangsList {
}
impl Locale {
#[must_use]
/// Returns a clone of `self` without `region`
pub fn without_region(&self) -> Locale {
Locale {
lang: self.lang.clone(),
region: None,
matched: self.matched.clone(),
}
}
fn match_locale<'o>(
default_lang: &'static str,
lang: &str,
+1 -1
View File
@@ -660,7 +660,7 @@ impl Layout {
pub fn recompute_layout(&mut self, new_size: Option<Vec2>) -> anyhow::Result<()> {
let size = match new_size {
Some(size) => Vec2::new(size.x.round(), size.y.round()),
None => self.prev_size,
None => self.content_size,
};
if !self.state.tree.dirty(self.tree_root_node)? && self.prev_size == size {
+1 -3
View File
@@ -31,9 +31,7 @@ wayland-protocols = {
drm-fourcc = "2.2.0"
rxscreen = {
git = "https://github.com/galister/rxscreen.git",
rev = "7197e1de6622b65859e6f2060b5750185e0dad2a",
# version = "0.1.7",
version = "0.1.7",
features = [
"mouse",
"shm",
-2
View File
@@ -128,8 +128,6 @@ pub struct MemPtrFrame {
pub format: FrameFormat,
pub ptr: usize,
pub size: usize,
pub offset: usize,
pub stride: usize,
pub mouse: Option<MouseMeta>,
}
+2 -16
View File
@@ -376,24 +376,10 @@ where
}
}
DataType::MemPtr => {
let raw = datas[0].as_raw();
let chunk = datas[0].chunk();
let offset = chunk.offset() as usize;
let chunk_size = chunk.size() as usize;
let Some(size) = offset.checked_add(chunk_size) else {
log::error!("{name}: PipeWire chunk range overflow");
return;
};
if size > raw.maxsize as usize {
log::error!("{name}: PipeWire chunk extends beyond allocation");
return;
}
let memptr = MemPtrFrame {
format: *format,
ptr: raw.data as _,
size,
offset,
stride: chunk.stride() as _,
ptr: datas[0].as_raw().data as _,
size: datas[0].chunk().size() as _,
mouse: mouse_meta,
};
-1
View File
@@ -262,7 +262,6 @@ where
&client.queue_handle,
(),
);
params.destroy();
log::trace!("{name}: ScreenCopy with Dmabuf");
// copy_with_damage seems to not work here
-2
View File
@@ -109,8 +109,6 @@ where
transform: Transform::Normal,
},
ptr: unsafe { image.as_ptr() as _ },
offset: 0,
stride: image.bytes_per_line() as _,
size,
mouse: d.root_mouse_position().and_then(|root_pos| {
monitor.mouse_to_local(root_pos).map(|(x, y)| MouseMeta {
+1 -1
View File
@@ -24,7 +24,6 @@ smol.workspace = true
strum.workspace = true
xdg.workspace = true
freedesktop-desktop-entry = "0.8.4"
glob = "0.3.3"
identicons-svg = "0.1.0"
rodio = {
@@ -32,5 +31,6 @@ rodio = {
default-features = false,
features = ["hound", "mp3", "playback", "recording"]
}
rust-ini = "0.21.3"
walkdir = "2.5.0"
wgui = { path = "../wgui/" }
-3
View File
@@ -533,9 +533,6 @@ pub struct GeneralConfig {
#[serde(default)]
pub whisper_model: Arc<str>,
#[serde(default)]
pub whisper_gpu: u32,
#[serde(default = "def_default")]
pub color_palette: Arc<str>,
+1 -8
View File
@@ -45,11 +45,6 @@ pub enum DashPlayspaceTask {
ResetCenter,
}
#[derive(Clone, Copy)]
pub enum DashBackendTask {
ShowBindingsUI,
}
#[derive(Clone, Copy)]
pub struct InterfaceFeats {
pub xr_backend: XrBackend,
@@ -97,8 +92,7 @@ pub trait DashInterface<T> {
fn monado_brightness_set(&mut self, data: &mut T, brightness: f32) -> Option<()>;
fn monado_metrics_set_enabled(&mut self, data: &mut T, enabled: bool) -> bool;
fn monado_metrics_dump_session_frames(&mut self, data: &mut T) -> Vec<MonadoDumpSessionFrame>;
fn playspace_task(&mut self, data: &mut T, task: DashPlayspaceTask) -> anyhow::Result<()>;
fn backend_task(&mut self, data: &mut T, task: DashBackendTask) -> anyhow::Result<()>;
fn playspace_task(&mut self, data: &mut T, mode: DashPlayspaceTask) -> anyhow::Result<()>;
fn desktop_finder<'a>(&'a mut self, data: &'a mut T) -> &'a mut DesktopFinder;
fn general_config<'a>(&'a mut self, data: &'a mut T) -> &'a mut GeneralConfig;
fn config_changed(&mut self, data: &mut T, kind: ConfigChangeKind);
@@ -107,7 +101,6 @@ pub trait DashInterface<T> {
fn toggle_dashboard(&mut self, data: &mut T);
fn get_feats(&mut self, data: &mut T) -> InterfaceFeats;
fn hmd_stats(&mut self, data: &mut T) -> HmdStats;
fn enumerate_vulkan_devices(&mut self, data: &mut T) -> anyhow::Result<Vec<String>>;
}
#[derive(Default, Clone, Copy)]
+8 -16
View File
@@ -1,15 +1,12 @@
use std::sync::Arc;
use slotmap::{DenseSlotMap, new_key_type};
use wayvr_ipc::{
packet_client::WvrProcessLaunchParams,
packet_server::{WvrProcess, WvrProcessHandle, WvrWindow, WvrWindowHandle},
};
use wgui::{gfx::WGfx, i18n::Locale};
use crate::{
config::GeneralConfig,
dash_interface::{self, ConfigChangeKind, DashBackendTask, DashInterface, DashPlayspaceTask},
dash_interface::{self, ConfigChangeKind, DashInterface, DashPlayspaceTask},
desktop_finder::DesktopFinder,
};
@@ -69,11 +66,10 @@ pub struct DashInterfaceEmulated {
brightness: f32,
debug_frame_id: u64,
debug_counter: u64,
gfx: Arc<WGfx>,
}
impl DashInterfaceEmulated {
pub fn new(gfx: Arc<WGfx>, locale: &Locale) -> Self {
pub fn new() -> Self {
let mut processes = DenseSlotMap::<EmuProcessHandle, EmuProcess>::default();
let process_handle = processes.insert(EmuProcess {
name: String::from("My app"),
@@ -85,7 +81,7 @@ impl DashInterfaceEmulated {
visible: true,
});
let mut desktop_finder = DesktopFinder::new(locale);
let mut desktop_finder = DesktopFinder::new();
desktop_finder.refresh();
// Use serde defaults
@@ -133,16 +129,17 @@ impl DashInterfaceEmulated {
brightness: 1.0,
debug_counter: 0,
debug_frame_id: 0,
gfx,
}
}
}
impl DashInterface<()> for DashInterfaceEmulated {
fn enumerate_vulkan_devices(&mut self, _: &mut ()) -> anyhow::Result<Vec<String>> {
self.gfx.enumerate_devices()
impl Default for DashInterfaceEmulated {
fn default() -> Self {
Self::new()
}
}
impl DashInterface<()> for DashInterfaceEmulated {
fn window_list(&mut self, _: &mut ()) -> anyhow::Result<Vec<WvrWindow>> {
Ok(self.windows.iter().map(|(handle, w)| w.to(handle)).collect())
}
@@ -222,11 +219,6 @@ impl DashInterface<()> for DashInterfaceEmulated {
Ok(())
}
fn backend_task(&mut self, _: &mut (), _: DashBackendTask) -> anyhow::Result<()> {
// stub!
Ok(())
}
fn desktop_finder<'a>(&'a mut self, _: &'a mut ()) -> &'a mut DesktopFinder {
&mut self.desktop_finder
}
+86 -67
View File
@@ -10,35 +10,37 @@ use std::{
time::Instant,
};
use crate::{cache_dir, locale::Language};
use crate::cache_dir;
use anyhow::Context;
use freedesktop_desktop_entry::DesktopEntry as FreedesktopDesktopEntry;
use ini::Ini;
use serde::{Deserialize, Serialize};
use walkdir::WalkDir;
use wgui::i18n::Locale;
struct DesktopEntryOwned {
app_id: Box<str>,
exec_argv: Box<[Box<str>]>,
app_name: Box<str>,
icon_path: Option<Box<str>>,
categories: Box<[Box<str>]>,
app_id: String,
exec_path: String,
exec_args: String,
app_name: String,
icon_path: Option<String>,
categories: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DesktopEntry {
pub exec_argv: Rc<[Rc<str>]>,
pub exec_path: Rc<str>,
pub exec_args: Rc<str>,
pub app_id: Rc<str>,
pub app_name: Rc<str>,
pub icon_path: Option<Rc<str>>,
pub categories: Rc<[Rc<str>]>,
pub categories: Vec<Rc<str>>,
}
impl From<DesktopEntryOwned> for DesktopEntry {
fn from(value: DesktopEntryOwned) -> Self {
Self {
app_id: value.app_id.into(),
exec_argv: value.exec_argv.into_iter().map(|x| x.into()).collect(),
exec_path: value.exec_path.into(),
exec_args: value.exec_args.into(),
app_name: value.app_name.into(),
icon_path: value.icon_path.map(|x| x.into()),
categories: value.categories.into_iter().map(|x| x.into()).collect(),
@@ -58,18 +60,17 @@ struct DesktopFinderParams {
size_preferences: Vec<&'static OsStr>,
icon_folders: Vec<String>,
app_folders: Vec<String>,
langs: Vec<String>,
}
pub struct DesktopFinder {
params: Arc<DesktopFinderParams>,
entry_cache: HashMap<String, Rc<DesktopEntry>>,
entry_cache: HashMap<String, DesktopEntry>,
bg_task: Option<JoinHandle<HashMap<String, DesktopEntryOwned>>>,
}
impl DesktopFinder {
#[allow(clippy::new_without_default)]
pub fn new(locale: &Locale) -> Self {
pub fn new() -> Self {
let xdg = xdg::BaseDirectories::new();
let mut app_folders = vec![];
@@ -106,21 +107,11 @@ impl DesktopFinder {
.map(OsStr::new)
.collect();
let langs = {
let main_lang = locale.without_region().to_string();
if main_lang == Language::get_default().code() {
vec![main_lang]
} else {
vec![main_lang, Language::get_default().code().to_string()]
}
};
Self {
params: Arc::new(DesktopFinderParams {
app_folders,
icon_folders,
size_preferences,
langs,
}),
entry_cache: HashMap::new(),
bg_task: None,
@@ -155,53 +146,79 @@ impl DesktopFinder {
}
let file_name = entry.file_name().to_string_lossy();
let file_path = entry.path().display().to_string();
let Some(app_id) = Path::new(entry.file_name())
.file_stem()
.map(|x| x.to_string_lossy().to_string())
else {
continue;
};
let desktop = match FreedesktopDesktopEntry::from_path(entry.path(), Some(&params.langs)) {
Ok(desktop) => desktop,
if known_files.contains(file_name.as_ref()) {
// as per xdg spec, user entries of the same filename will override system entries
continue;
}
let file_path = format!("{}/{}", path, file_name);
let ini = match Ini::load_from_file(&file_path) {
Ok(ini) => ini,
Err(e) => {
log::debug!("Failed to read .desktop file {}: {:?}, skipping", file_path, e);
log::debug!("Failed to read INI for .desktop file {}: {:?}, skipping", file_path, e);
continue;
}
};
if desktop.groups.desktop_entry().is_none() {
let Some(section) = ini.section(Some("Desktop Entry")) else {
log::debug!("Failed to get [Desktop Entry] section for file {}, skipping", file_path);
continue;
};
if section.contains_key("OnlyShowIn") {
continue; // probably XFCE, KDE, GNOME or other DE-specific stuff
}
// prefer entries from earlier data directories
let app_id = desktop.id();
if known_files.contains(app_id) {
if let Some(x) = section.get("Terminal")
&& x == "true"
{
continue;
}
if desktop.only_show_in().is_some() || desktop.terminal() || desktop.no_display() || desktop.hidden() {
continue;
if let Some(x) = section.get("NoDisplay")
&& x.eq_ignore_ascii_case("true")
{
continue; // This application is hidden
}
let Some(exec) = desktop.exec() else {
if let Some(x) = section.get("Hidden")
&& x.eq_ignore_ascii_case("true")
{
continue; // This application is hidden
}
let Some(exec) = section.get("Exec") else {
log::debug!("Failed to get desktop entry Exec for file {}, skipping", file_path);
continue;
};
if CMD_BLOCKLIST.iter().any(|blocked| exec.contains(blocked)) {
continue;
for entry in &CMD_BLOCKLIST {
if exec.contains(entry) {
continue 'entries;
}
}
let exec_argv = desktop
.parse_exec_with_uris(&[], &params.langs)
.into_iter()
.flatten()
.map(|x| x.into_boxed_str())
.collect::<Box<[_]>>();
if exec_argv.is_empty() {
log::debug!("Invalid Exec in desktop file {}, skipping", file_path);
continue;
let (exec_path, exec_args) = match exec.split_once(" ") {
Some((left, right)) => (
left,
right
.split(" ")
.filter(|arg| !arg.starts_with('%')) // exclude arguments like "%f"
.map(String::from)
.collect(),
),
None => (exec, Vec::new()),
};
let Some(app_name) = desktop.name(&params.langs) else {
let Some(app_name) = section.get("Name") else {
log::debug!(
"Failed to get desktop entry application name for file {}, skipping",
file_path
@@ -209,31 +226,33 @@ impl DesktopFinder {
continue;
};
let icon_path = desktop
.icon()
let icon_path = section
.get("Icon")
.and_then(|icon_name| Self::find_icon(&params, icon_name))
.or_else(|| Self::create_icon(&file_name).ok());
let mut vec_categories = Vec::<Box<str>>::new();
for cat in desktop.categories().unwrap_or_default() {
if CATEGORY_TYPE_BLOCKLIST.contains(&cat) {
continue 'entries;
}
if !cat.is_empty() {
vec_categories.push(cat.into());
let mut vec_categories = Vec::<String>::new();
if let Some(categories) = section.get("Categories") {
for cat in categories.split(";") {
if CATEGORY_TYPE_BLOCKLIST.contains(&cat) {
continue 'entries;
}
vec_categories.push(String::from(cat));
}
}
known_files.insert(app_id.to_owned());
known_files.insert(file_name.to_string());
entries.insert(
app_id.to_owned(),
app_id.clone(),
DesktopEntryOwned {
app_id: app_id.into(),
app_name: app_name.into(),
exec_argv,
icon_path: icon_path.map(|i| i.into_boxed_str()),
categories: vec_categories.into_boxed_slice(),
app_id,
app_name: String::from(app_name),
exec_path: String::from(exec_path),
exec_args: exec_args.join(" "),
icon_path,
categories: vec_categories,
},
);
}
@@ -312,15 +331,15 @@ impl DesktopFinder {
self.entry_cache.clear();
for (app_id, entry) in entries {
self.entry_cache.insert(app_id, Rc::new(entry.into()));
self.entry_cache.insert(app_id, entry.into());
}
}
pub fn get_cached_entry(&self, app_id: &str) -> Option<&Rc<DesktopEntry>> {
pub fn get_cached_entry(&self, app_id: &str) -> Option<&DesktopEntry> {
self.entry_cache.get(app_id)
}
pub fn find_entries(&mut self) -> HashMap<String, Rc<DesktopEntry>> {
pub fn find_entries(&mut self) -> HashMap<String, DesktopEntry> {
self.wait_for_entries();
self.entry_cache.clone()
}
+1 -4
View File
@@ -23,8 +23,6 @@ pub enum Language {
ChineseSimplified,
#[strum(props(Text = "Español"))]
Spanish,
#[strum(props(Text = "Svenska"))]
Swedish,
}
impl Language {
@@ -38,7 +36,6 @@ impl Language {
Language::Italian => "it_IT",
Language::ChineseSimplified => "zh_CN",
Language::Spanish => "es_ES",
Language::Swedish => "sv_SE",
}
}
@@ -47,7 +44,7 @@ impl Language {
}
pub const fn all_codes() -> &'static [&'static str] {
&["en", "pl", "ja", "de", "it", "zh_CN", "es", "sv"]
&["en", "pl", "ja", "de", "it", "zh_CN", "es"]
}
}
+1 -110
View File
@@ -92,112 +92,6 @@ pub struct OpenXrInputProfile {
pub haptic: Option<OpenXrInputAction>,
}
fn strip_axis_component(path: &str) -> String {
if let Some((prefix, suffix)) = path.rsplit_once('/')
&& (suffix == "x" || suffix == "y")
&& (prefix.ends_with("/thumbstick") || prefix.ends_with("/trackpad") || prefix.ends_with("/joystick"))
{
return prefix.to_owned();
}
path.to_owned()
}
// TODO: remove this after thumbstick issue is not a thing anymore
fn normalize_profile(profile: &mut OpenXrInputProfile) {
if let Some(action) = profile.pose.as_mut() {
normalize_action(action);
}
if let Some(action) = profile.click.as_mut() {
normalize_action(action);
}
if let Some(action) = profile.grab.as_mut() {
normalize_action(action);
}
if let Some(action) = profile.alt_click.as_mut() {
normalize_action(action);
}
if let Some(action) = profile.show_hide.as_mut() {
normalize_action(action);
}
if let Some(action) = profile.toggle_dashboard.as_mut() {
normalize_action(action);
}
if let Some(action) = profile.space_drag.as_mut() {
normalize_action(action);
}
if let Some(action) = profile.space_rotate.as_mut() {
normalize_action(action);
}
if let Some(action) = profile.space_reset.as_mut() {
normalize_action(action);
}
if let Some(action) = profile.click_modifier_right.as_mut() {
normalize_action(action);
}
if let Some(action) = profile.click_modifier_middle.as_mut() {
normalize_action(action);
}
if let Some(action) = profile.move_mouse.as_mut() {
normalize_action(action);
}
if let Some(action) = profile.scroll.as_mut() {
normalize_action(action);
}
if let Some(action) = profile.haptic.as_mut() {
normalize_action(action);
}
}
fn normalize_action(action: &mut OpenXrInputAction) {
let process_one_or_many = |opt: &mut Option<OneOrMany<String>>| {
if let Some(paths) = opt {
match paths {
OneOrMany::One(p) => {
let normalized = strip_axis_component(p);
if normalized != *p {
*p = normalized;
}
}
OneOrMany::Many(ps) => {
for p in ps.iter_mut() {
let normalized = strip_axis_component(p);
if normalized != *p {
*p = normalized;
}
}
}
}
}
};
process_one_or_many(&mut action.left);
process_one_or_many(&mut action.right);
process_one_or_many(&mut action.handsfree);
let process_chord = |members: &mut Option<Vec<OpenXrInputChordMember>>| {
if let Some(m) = members {
for member in m.iter_mut() {
let path = member.path().to_owned();
let normalized = strip_axis_component(&path);
if normalized != path {
let threshold = member.threshold();
*member = if threshold.is_some() {
OpenXrInputChordMember::Detailed {
path: normalized,
threshold,
}
} else {
OpenXrInputChordMember::Path(normalized)
};
}
}
}
};
process_chord(&mut action.left_chord);
process_chord(&mut action.right_chord);
}
pub fn load_xr_input_profiles() -> Vec<OpenXrInputProfile> {
let mut profiles: Vec<OpenXrInputProfile> = serde_json5::from_str(DEFAULT_XR_INPUT_PROFILES).unwrap(); // want panic
@@ -206,10 +100,7 @@ pub fn load_xr_input_profiles() -> Vec<OpenXrInputProfile> {
};
match serde_json5::from_str::<Vec<OpenXrInputProfile>>(&conf) {
Ok(mut override_profiles) => {
for new in override_profiles.iter_mut() {
normalize_profile(new);
}
Ok(override_profiles) => {
for new in override_profiles {
if let Some(i) = profiles.iter().position(|old| old.profile == new.profile) {
profiles[i] = new;