Compare commits

..
Author SHA1 Message Date
Aleksander af733b6817 wgui: do not pass AttribPairs with empty values further, dash-frontend: xml refactor, use template children in various places, remove unused files 2026-08-29 11:40:22 +02:00
Aleksander 936c8bf27d wgui: Add <CHILDREN/> support in templates (Closes #550) 2026-08-29 11:40:22 +02:00
Aleksander 998c566fc6 Changes (wgui):
- All new layout xml files now are required to include this metadata
at the top, if they want to be parsed in the new layout v2 behavior:
```xml
<layout version="2">
    <!-- rest of the markup -->
</layout>
```

- Add path expansion using '@' for widget resources and includes
The '@' character is always expanded to the assets root directory
(builtin/internal), or the root xml directory (external).
Paths directing to the built-in resources like:
    `edit/mouse.svg`
are now required to be written as:
    `@/edit/mouse.svg`,
For example:
    ```xml
        <sprite src_builtin="@/edit/mouse.svg" />
    ```

- All paths without '/' at the start are now treated as relative
to the currently parsing XML (paths like `/usr/share/icons/...`
are still accessible)

- Add support for relative paths for widgets instantiated from
templates

- Rename `<theme>` to `<vars>`

Changes (dash-frontend):

- Adapt and refactor XML files

Changes (wayvr):

- Adapt and refactor XML files
2026-08-29 11:40:17 +02:00
195 changed files with 4289 additions and 12678 deletions

No files matched your search

+1 -3
View File
@@ -9,9 +9,7 @@ assignees: ''
## Description
<!--
*** Do NOT use AI to generate a bug report! ***
Simply write down what's wrong and maybe provide screenshots or a video.
If this has worked before, please mention which version was last working.
If this is a regression, please mention which version was working previously.
-->
## System Info
+4 -17
View File
@@ -1,7 +1,6 @@
name: Build AppImage
on:
workflow_dispatch:
push:
branches:
- 'main'
@@ -13,17 +12,10 @@ env:
CARGO_TERM_COLOR: always
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache"
jobs:
build_appimage:
strategy:
matrix:
include:
- runner: ubuntu-24.04
arch: x86_64
- runner: ubuntu-24.04-arm
arch: aarch64
runs-on: ${{ matrix.runner }}
runs-on: ubuntu-24.04
defaults:
run:
working-directory: ./wayvr
@@ -46,10 +38,5 @@ jobs:
- name: Upload AppImage
uses: actions/upload-artifact@v4
with:
name: WayVR-${{ github.ref_name }}-${{ matrix.arch }}.AppImage
path: ./wayvr/WayVR-${{ matrix.arch }}.AppImage
- name: Upload AppImage zsync
uses: actions/upload-artifact@v4
with:
name: WayVR-${{ github.ref_name }}-${{ matrix.arch }}.AppImage.zsync
path: ./wayvr/WayVR-${{ matrix.arch }}.AppImage.zsync
name: WayVR-${{ github.ref_name }}-x86_64.AppImage
path: ./wayvr/WayVR-x86_64.AppImage
-10
View File
@@ -83,16 +83,6 @@ jobs:
asset_name: WayVR-${{ github.ref_name }}-x86_64.AppImage
asset_content_type: application/octet-stream
- name: Upload AppImage zsync
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_KEY }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./wayvr/WayVR-x86_64.AppImage.zsync
asset_name: WayVR-${{ github.ref_name }}-x86_64.AppImage.zsync
asset_content_type: application/octet-stream
- name: Upload vendor tarball
uses: actions/upload-release-asset@v1
env:
@@ -1,7 +1,6 @@
#!/bin/sh
set -eu
sudo apt-get install -y \
sudo apt install \
xwayland \
libxcb1-dev \
libxcb-cursor-dev \
@@ -12,7 +11,7 @@ git clone https://github.com/Supreeeme/xwayland-satellite.git
cd xwayland-satellite
cargo build --release
chmod +x target/release/xwayland-satellite
chmod +x ../target/release/xwayland-satellite
cd ..
cp xwayland-satellite/target/release/xwayland-satellite "${APPDIR}/usr/bin"
cp xwayland-satellite/target/release/xwayland-satellite ${APPDIR}/usr/bin
@@ -1,6 +1,4 @@
#!/bin/sh
set -eu
cargo build --release
chmod +x ../target/release/wayvr
cp ../target/release/wayvr "${APPDIR}/usr/bin"
cp ../target/release/wayvr ${APPDIR}/usr/bin
+3 -31
View File
@@ -1,32 +1,4 @@
#!/bin/sh
set -eu
case "$(uname -m)" in
x86_64|amd64)
APPIMAGE_ARCH=x86_64
;;
aarch64|arm64)
APPIMAGE_ARCH=aarch64
;;
*)
echo "Unsupported architecture: $(uname -m)" >&2
exit 1
;;
esac
LINUXDEPLOY="linuxdeploy-${APPIMAGE_ARCH}.AppImage"
VERSION="${GITHUB_REF_NAME:-dev}"
export VERSION
export LDAI_UPDATE_INFORMATION="gh-releases-zsync|wayvr-org|wayvr|latest|WayVR-*-${APPIMAGE_ARCH}.AppImage.zsync"
echo "Packaging AppImage for ${APPIMAGE_ARCH}"
"./${LINUXDEPLOY}" \
-dwayvr.desktop \
-iwayvr.png \
--appdir="${APPDIR}" \
--output appimage \
--exclude-library '*libpipewire*'
mv "WayVR-${VERSION}-${APPIMAGE_ARCH}.AppImage" "WayVR-${APPIMAGE_ARCH}.AppImage"
mv "WayVR-${VERSION}-${APPIMAGE_ARCH}.AppImage.zsync" "WayVR-${APPIMAGE_ARCH}.AppImage.zsync"
export VERSION=$GITHUB_REF_NAME
./linuxdeploy-x86_64.AppImage -dwayvr.desktop -iwayvr.png --appdir=${APPDIR} --output appimage --exclude-library '*libpipewire*'
mv WayVR-$VERSION-x86_64.AppImage WayVR-x86_64.AppImage
@@ -1,39 +1,17 @@
#!/bin/sh
set -eu
case "$(uname -m)" in
x86_64|amd64)
APPIMAGE_ARCH=x86_64
;;
aarch64|arm64)
APPIMAGE_ARCH=aarch64
;;
*)
echo "Unsupported architecture: $(uname -m)" >&2
exit 1
;;
esac
LINUXDEPLOY="linuxdeploy-${APPIMAGE_ARCH}.AppImage"
echo "Preparing AppImage environment for ${APPIMAGE_ARCH}"
sudo apt-get update
sudo apt-get install -y fuse cmake pkg-config fontconfig libasound2-dev libxkbcommon-dev libxkbcommon-x11-0 libxkbcommon-x11-dev \
libopenxr-dev libfontconfig-dev libdbus-1-dev libpipewire-0.3-0 libpipewire-0.3-dev libspa-0.2-dev libx11-6 libxext6 libxrandr2 \
libx11-dev libxext-dev libxrandr-dev libopenvr-dev libopenvr-api1t64 libwayland-dev libegl-dev libxcb-glx0 libxcb-glx0-dev \
libx11-dev libxext-dev libxrandr-dev libopenvr-dev libopenvr-api1 libwayland-dev libegl-dev libxcb-glx0 libxcb-glx0-dev \
dav1d libdav1d-dev libvulkan-dev glslc vulkan-tools libudev-dev libudev1 libinput-dev libinput10
rustup update
if [ -n "${APPDIR:-}" ]; then
if [ ! -f "$LINUXDEPLOY" ]; then
wget -q "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/${LINUXDEPLOY}"
fi
chmod +x "$LINUXDEPLOY"
if [ "$APPDIR" != "" ]; then
test -f linuxdeploy-x86_64.AppImage || wget -q "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage"
chmod +x linuxdeploy-x86_64.AppImage
if [ -d "$APPDIR" ]; then
rm -rf "$APPDIR"
fi
mkdir -p "$APPDIR/usr/bin"
test -d ${APPDIR} && rm -rf ${APPDIR}
mkdir -p ${APPDIR}/usr/bin
fi
Generated
+141 -17
View File
@@ -1250,6 +1250,15 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-queue"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
@@ -1726,7 +1735,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -3140,7 +3149,7 @@ version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -3259,7 +3268,7 @@ dependencies = [
"objc2-core-data",
"objc2-core-image",
"objc2-foundation 0.2.2",
"objc2-quartz-core",
"objc2-quartz-core 0.2.2",
]
[[package]]
@@ -3368,7 +3377,7 @@ dependencies = [
"block2 0.5.1",
"objc2 0.5.2",
"objc2-foundation 0.2.2",
"objc2-metal",
"objc2-metal 0.2.2",
]
[[package]]
@@ -3449,6 +3458,17 @@ dependencies = [
"objc2-foundation 0.2.2",
]
[[package]]
name = "objc2-metal"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794"
dependencies = [
"bitflags 2.13.0",
"objc2 0.6.4",
"objc2-foundation 0.3.2",
]
[[package]]
name = "objc2-open-directory"
version = "0.3.2"
@@ -3470,7 +3490,20 @@ dependencies = [
"block2 0.5.1",
"objc2 0.5.2",
"objc2-foundation 0.2.2",
"objc2-metal",
"objc2-metal 0.2.2",
]
[[package]]
name = "objc2-quartz-core"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
dependencies = [
"bitflags 2.13.0",
"objc2 0.6.4",
"objc2-core-foundation",
"objc2-foundation 0.3.2",
"objc2-metal 0.3.2",
]
[[package]]
@@ -3498,7 +3531,7 @@ dependencies = [
"objc2-core-location",
"objc2-foundation 0.2.2",
"objc2-link-presentation",
"objc2-quartz-core",
"objc2-quartz-core 0.2.2",
"objc2-symbols",
"objc2-uniform-type-identifiers",
"objc2-user-notifications",
@@ -3618,7 +3651,7 @@ dependencies = [
[[package]]
name = "ovr_overlay"
version = "0.0.0"
source = "git+https://github.com/galister/ovr_overlay_oyasumi?rev=3b7d24cee24aa2b1ad7dffc5d5c8568508049bdd#3b7d24cee24aa2b1ad7dffc5d5c8568508049bdd"
source = "git+https://github.com/galister/ovr_overlay_oyasumi?rev=e477bd2a9e04293ea68c1e7529ef2cb131f32acc#e477bd2a9e04293ea68c1e7529ef2cb131f32acc"
dependencies = [
"byteorder",
"derive_more",
@@ -3633,7 +3666,7 @@ dependencies = [
[[package]]
name = "ovr_overlay_sys"
version = "0.0.0"
source = "git+https://github.com/galister/ovr_overlay_oyasumi?rev=3b7d24cee24aa2b1ad7dffc5d5c8568508049bdd#3b7d24cee24aa2b1ad7dffc5d5c8568508049bdd"
source = "git+https://github.com/galister/ovr_overlay_oyasumi?rev=e477bd2a9e04293ea68c1e7529ef2cb131f32acc#e477bd2a9e04293ea68c1e7529ef2cb131f32acc"
dependencies = [
"autocxx",
"autocxx-build",
@@ -4021,7 +4054,7 @@ version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042"
dependencies = [
"heck 0.4.1",
"heck 0.5.0",
"itertools 0.14.0",
"log",
"multimap",
@@ -4324,6 +4357,18 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
[[package]]
name = "raw-window-metal"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135"
dependencies = [
"objc2 0.6.4",
"objc2-core-foundation",
"objc2-foundation 0.3.2",
"objc2-quartz-core 0.3.2",
]
[[package]]
name = "rayon"
version = "1.12.0"
@@ -4622,7 +4667,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys 0.12.1",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -4981,6 +5026,12 @@ version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "slabbin"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9db491c0d4152a069911a0fbdaca959691bf0b9d7110d98a7ed1c8e59b79ab30"
[[package]]
name = "slice-of-array"
version = "0.3.2"
@@ -5350,10 +5401,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
"fastrand",
"getrandom 0.3.4",
"getrandom 0.4.3",
"once_cell",
"rustix 1.1.4",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -5688,6 +5739,8 @@ dependencies = [
"rust-embed",
"tracing-subscriber",
"ureq",
"vulkano",
"vulkano-shaders",
"wgui",
"winit",
"wlx-common",
@@ -5852,6 +5905,70 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "vk-parse"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3859da4d7b98bec73e68fb65815d47a263819c415c90eed42b80440a02cbce8c"
dependencies = [
"xml-rs",
]
[[package]]
name = "vulkano"
version = "0.35.2"
source = "git+https://github.com/galister/vulkano.git?rev=cf7f92867928a56ce16b376037c1120f2b167678#cf7f92867928a56ce16b376037c1120f2b167678"
dependencies = [
"ash",
"bytemuck",
"crossbeam-queue",
"foldhash 0.1.5",
"half",
"heck 0.4.1",
"indexmap 2.14.0",
"libloading 0.8.9",
"nom 7.1.3",
"once_cell",
"parking_lot",
"proc-macro2",
"quote",
"raw-window-handle",
"raw-window-metal",
"serde",
"serde_json",
"slabbin",
"smallvec",
"thread_local",
"vk-parse",
"vulkano-macros",
]
[[package]]
name = "vulkano-macros"
version = "0.35.0"
source = "git+https://github.com/galister/vulkano.git?rev=cf7f92867928a56ce16b376037c1120f2b167678#cf7f92867928a56ce16b376037c1120f2b167678"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 2.0.118",
]
[[package]]
name = "vulkano-shaders"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bf501461be7cef2893c0e62c50945add9763cc482051d29053f6157089d5ea9"
dependencies = [
"foldhash 0.1.5",
"heck 0.4.1",
"proc-macro2",
"quote",
"shaderc",
"syn 2.0.118",
"vulkano",
]
[[package]]
name = "walkdir"
version = "2.5.0"
@@ -6094,6 +6211,7 @@ name = "wayvr"
version = "26.8.0"
dependencies = [
"anyhow",
"ash",
"bytes",
"calloop 0.14.4",
"calloop-wayland-source 0.4.1",
@@ -6131,7 +6249,6 @@ dependencies = [
"serde_json",
"serde_json5",
"serde_yaml",
"shaderc",
"signal-hook",
"slotmap",
"smallvec",
@@ -6144,6 +6261,8 @@ dependencies = [
"tracing",
"tracing-subscriber",
"uuid",
"vulkano",
"vulkano-shaders",
"wayland-client",
"wayland-protocols",
"wayland-protocols-misc",
@@ -6230,7 +6349,6 @@ name = "wgui"
version = "0.1.0"
dependencies = [
"anyhow",
"ash",
"bytes",
"cosmic-text",
"dav1d-sys",
@@ -6242,18 +6360,18 @@ dependencies = [
"lru",
"ouroboros",
"parking_lot",
"raw-window-handle",
"regex",
"resvg",
"roxmltree 0.21.1",
"rust-embed",
"rustc-hash",
"serde_json",
"shaderc",
"slotmap",
"smallvec",
"strum",
"taffy",
"vulkano",
"vulkano-shaders",
]
[[package]]
@@ -6307,7 +6425,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.48.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -6864,6 +6982,12 @@ dependencies = [
"bytemuck",
]
[[package]]
name = "xml-rs"
version = "0.8.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f"
[[package]]
name = "xmlparser"
version = "0.13.6"
+13
View File
@@ -38,12 +38,25 @@ smol = "2.0.2"
strum = { version = "0.27.2", features = ["derive"] }
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
uuid = { version = "1.19.0", features = ["fast-rng", "serde", "v4"] }
vulkano = {
version = "0.35.2",
default-features = false,
features = [
"macros",
]
}
vulkano-shaders = "0.35.0"
wayland-client = { version = "0.31" }
wayland-protocols = { version = "0.32" }
wayland-protocols-misc = { version = "0.3.12" }
winit = "0.30.12"
xdg = "3.0.0"
[patch.crates-io]
vulkano = {
git = "https://github.com/galister/vulkano.git",
rev = "cf7f92867928a56ce16b376037c1120f2b167678"
}
[profile.dev]
opt-level = 1
+1 -15
View File
@@ -37,7 +37,7 @@ There are multiple ways to install WayVR:
**Note:** If you are using Monado or WiVRn, no additional setup steps are required for Flatpak Steam compatibility—most people use WayVR seamlessly with Monado/WiVRn.
### SteamVR (PC) via Steam Flatpak
### SteamVR via Steam Flatpak
For users specifically running **SteamVR via Steam Flatpak**, follow these steps:
@@ -50,20 +50,6 @@ For users specifically running **SteamVR via Steam Flatpak**, follow these steps
1. Start SteamVR.
1. `flatpak run --command='/path/to/squashfs-root/AppRun' com.valvesoftware.Steam`
### Installing on Steam Frame
Steam Frame support is experimental. Please report issues.
It's possible to install and run WayVR on the Steam Frame in headless mode. You will not have access to your KDE desktop, but you will be able to run applications in 3D space around you.
Steps to install on Steam Frame:
1. Grab the latest aarch64 AppImage
- For nightly AppImages go [here](https://github.com/wayvr-org/wayvr/actions/workflows/build-appimage.yml) and select the topmost list item a ✅️
- The downloads will be on the bottom of the page (GitHub login required).
1. Install the AppImage using GearLever
1. (Recommended) In Frame's SteamVR settings, set overlay quality to High.
1. Start WayVR from the taskbar (same place where you'd start Destkop)
## First Start
**When the screen share pop-up appears, check your notifications or the terminal and select the screens in the order it requests.**
-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="m12 13.4l-4.9 4.9q-.275.275-.7.275t-.7-.275t-.275-.7t.275-.7l4.9-4.9l-4.9-4.9q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l4.9 4.9l4.9-4.9q.275-.275.7-.275t.7.275t.275.7t-.275.7L13.4 12l4.9 4.9q.275.275.275.7t-.275.7t-.7.275t-.7-.275z"/></svg>

Before

Width:  |  Height:  |  Size: 468 B

@@ -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="M9 22q-.825 0-1.412-.587T7 20v-3H4q-.825 0-1.412-.587T2 15v-2h6.45l1.7 2.55q.125.2.35.325t.475.125q.325 0 .6-.2t.375-.5l1.35-4.05l.85 1.3q.15.2.375.325T15 13h7v2q0 .825-.588 1.413T20 17h-3v3q0 .825-.587 1.413T15 22zm1.7-9.25l-.875-1.3q-.125-.2-.35-.325T9 11H2V9q0-.825.588-1.412T4 7h3V4q0-.825.588-1.412T9 2h6q.825 0 1.413.588T17 4v3h3q.825 0 1.413.588T22 9v2h-6.475l-1.7-2.55q-.125-.2-.35-.325T13 8q-.325 0-.587.2t-.363.5z"/></svg>

Before

Width:  |  Height:  |  Size: 660 B

-12
View File
@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="32"
height="32"
viewBox="0 0 24 24"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs id="defs1" />
<path fill="currentColor" d="m 10,12 c 0,-1.104569 0.895431,-2 2,-2 1.104569,0 2,0.895431 2,2 0,1.104569 -0.895431,2 -2,2 -1.104569,0 -2,-0.895431 -2,-2" id="path1" />
</svg>

Before

Width:  |  Height:  |  Size: 422 B

-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 Lucide by Lucide Contributors - https://github.com/lucide-icons/lucide/blob/main/LICENSE --><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M2 17h18a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2H2m0 16V3m5 14v3a1 1 0 0 0 1 1h5a1 1 0 0 0 1-1v-3"/><circle cx="16" cy="11" r="2"/><circle cx="8" cy="11" r="2"/></g></svg>

Before

Width:  |  Height:  |  Size: 462 B

-13
View File
@@ -6,16 +6,6 @@
<var key="side_button_size" value="48" />
</vars>
<blueprint name="context_menu_power_options">
<context_menu>
<cell translation="APP_SETTINGS.RESTART_WAYVR" action="restart" sprite_src_builtin="@/dashboard/refresh.svg"/>
<cell translation="APP_SETTINGS.CLOSE_WAYVR" action="close" sprite_src_builtin="@/dashboard/close.svg"/>
<separator/>
<cell translation="POWER_OPTIONS.SUSPEND" action="suspend" sprite_src_builtin="@/dashboard/sleep.svg"/>
<cell translation="POWER_OPTIONS.SHUTDOWN" action="shutdown" sprite_src_builtin="@/dashboard/power.svg"/>
</context_menu>
</blueprint>
<template name="SideButton">
<Button
id="${id}"
@@ -146,9 +136,6 @@
<!-- Left bottom side -->
<div margin_left="8">
<Button id="btn_power" color="background(transparent)" border_color="background(transparent)" tooltip="POWER_OPTIONS.TITLE" tooltip_side="top">
<sprite src_builtin="@/dashboard/power.svg" width="24" height="24" margin="8" />
</Button>
<Button id="btn_audio" color="background(transparent)" border_color="background(transparent)" tooltip="AUDIO.VOLUME" tooltip_side="top">
<sprite src_builtin="@/dashboard/volume.svg" width="24" height="24" margin="8" />
</Button>
+1 -2
View File
@@ -9,9 +9,8 @@
gap="8"
round="8"/>
<!-- id -->
<template name="GroupBox">
<rectangle macro="group_box" id="rect">
<rectangle macro="group_box">
<CHILDREN/>
</rectangle>
</template>
+11 -18
View File
@@ -1,21 +1,6 @@
<layout version="2">
<include src="@/gui/t_menu_button.xml" />
<template name="MiscButton">
<Button
id="${id}" macro="home_button"
min_width="64" height="36"
padding_left="8" padding_right="8">
<div gap="8"
align_items="center"
justify_content="center"
flex_direction="row">
<sprite src_builtin="${icon}" width="24" height="24" />
<label weight="bold" size="18" text="${text}" translation="${translation}" />
</div>
</Button>
</template>
<elements>
<div
flex_direction="column"
@@ -48,9 +33,17 @@
</div>
<div gap="8">
<MiscButton id="btn_welcome_screen" icon="@/dashboard/welcome.svg" translation="APP_SETTINGS.TUTORIAL"/>
<MiscButton id="btn_website" icon="@/dashboard/globe.svg" text="wayvr.org"/>
<MiscButton id="btn_donate" icon="@/dashboard/opencollective.svg" translation="DONATE.SUPPORT_US"/>
<Button id="btn_welcome_screen" macro="home_button" tooltip="APP_SETTINGS.SHOW_WELCOME_SCREEN">
<sprite src_builtin="@/dashboard/welcome.svg" width="32" height="32" margin="16" />
</Button>
<Button id="btn_website" macro="home_button" tooltip_str="wayvr.org">
<sprite src_builtin="@/dashboard/globe.svg" width="32" height="32" margin="16" />
</Button>
<Button id="btn_donate" macro="home_button" tooltip="DONATE.SUPPORT_US">
<sprite src_builtin="@/dashboard/opencollective.svg" width="32" height="32" margin="16" />
</Button>
</div>
</div>
</elements>
-22
View File
@@ -31,27 +31,6 @@
<RadioBox text="${text}" translation="${translation}" value="${value}" tooltip="${tooltip}" />
</template>
<!-- title, sprite, color, tooltip_str -->
<template name="DiagnosticsRow">
<div align_items="center">
<label text="${title}" weight="bold" min_width="140"/>
<Button tooltip_str="${tooltip_str}" color="background(transparent)">
<sprite width="24" height="24" color="${color}" src_builtin="${sprite}" parent_color="ignore"/>
</Button>
</div>
</template>
<!-- name, info -->
<template name="DiagnosticsDrmCard">
<GroupBox>
<div gap="8">
<sprite width="24" height="24" src_builtin="@/dashboard/gpu.svg"/>
<label text="${name}"/>
</div>
<label text="${info}"/>
</GroupBox>
</template>
<template name="StatRow">
<div flex_direction="row" justify_content="space_between" align_items="center" gap="12" width="100%">
<label weight="bold" translation="${translation}" />
@@ -98,7 +77,6 @@
<Tab macro="tab_style" name="misc" translation="APP_SETTINGS.MISC" sprite_src_builtin="@/dashboard/blocks.svg" />
<Tab macro="tab_style" name="autostart_apps" translation="APP_SETTINGS.AUTOSTART_APPS" sprite_src_builtin="@/dashboard/apps.svg" />
<Tab macro="tab_style" name="statistics" translation="APP_SETTINGS.STATISTICS" sprite_src_builtin="@/dashboard/barchart.svg" />
<Tab macro="tab_style" name="diagnostics" translation="APP_SETTINGS.DIAGNOSTICS.TITLE" sprite_src_builtin="@/dashboard/diagnostics.svg" />
<Tab macro="tab_style" name="troubleshooting" translation="APP_SETTINGS.TROUBLESHOOTING" sprite_src_builtin="@/dashboard/cpu.svg" />
</Tabs>
<div flex_wrap="wrap" justify_content="stretch" gap="4" id="settings_root" width="100%">
@@ -3,15 +3,15 @@
<!-- device_name, device_icon -->
<template name="DeviceSlider">
<GroupBox id="rect">
<GroupBox>
<div width="100%" align_items="center" justify_content="center" gap="8">
<sprite src_builtin="${device_icon}" width="24" height="24" color="on_background" />
<sprite src_builtin="${device_icon}" width="16" height="16" color="on_background" />
<label text="${device_name}" margin_right="8" size="12" weight="bold" color="on_background" />
</div>
<div width="100%" align_items="center">
<CheckBox id="checkbox" tooltip="AUDIO.SWITCH_TO_THIS_DEVICE" />
<Slider id="slider" flex_grow="1" height="16" min_value="0" max_value="150" margin_left="8" margin_right="8" />
<Button sprite_src_builtin="${volume_icon}" id="btn_mute" width="32" height="32" />
<CheckBox id="checkbox" />
<Button sprite_src_builtin="${volume_icon}" id="btn_mute" width="32" />
<Slider id="slider" flex_grow="1" height="16" min_value="0" max_value="150" margin_left="8" />
</div>
</GroupBox>
</template>
@@ -47,17 +47,8 @@
<elements>
<div flex_direction="column" gap="4">
<!--
this div shouldn't be technically needed, but there's a bug in
the newest taffy version (or we're doing something wrong),
causing the scrollbar to show up, even if it's not overflowing.
OVERFLOW_START_THRESHOLD_UNITS is already set to 3, but it's not
sufficient enough in this case?? (margin_bottom="4" fixes that).
-->
<div width="100%" flex_direction="column" max_height="400" overflow_y="scroll">
<div id="devices" width="100%" flex_direction="column" gap="4" margin_bottom="4">
<div id="devices" flex_direction="column" gap="4" max_height="400" overflow_y="scroll">
</div>
</div>
<!-- bottom buttons -->
@@ -20,7 +20,7 @@
</template>
<elements>
<div flex_direction="column" gap="8" width="100%" align_items="center" overflow_y="scroll">
<div flex_direction="column" gap="8" width="100%" align_items="center">
<div id="list_parent" flex_grow="1" flex_wrap="wrap" flex_direction="row" gap="4" width="100%" justify_content="center"></div>
</div>
</elements>
+1 -4
View File
@@ -1,9 +1,6 @@
<layout version="2">
<template name="DialogBoxButton">
<Button id="btn" translation="CLOSE_WINDOW" align_self="start" sprite_src_builtin="${icon}"
text_size="16"
padding_left="16" padding_right="16"
padding_top="8" padding_bottom="8"/>
<Button id="btn" translation="CLOSE_WINDOW" align_self="start" sprite_src_builtin="${icon}"/>
</template>
<elements>
@@ -20,7 +20,7 @@
<rectangle position="absolute" width="100%" height="3" round="4" color="highlight(opacity-0.1)" />
<!-- Top bar contents -->
<div gap="16" align_items="center" id="top_bar_contents">
<div gap="16" align_items="center">
<!-- Back button -->
<Button id="but_back" width="48" height="48" color="on_background_contrast(transparent)" border_color="on_background_contrast(transparent)">
<sprite src_builtin="@/dashboard/back.svg" width="24" height="24" color="on_background_contrast" />
@@ -1,9 +1,7 @@
<layout version="2">
<elements>
<div flex_direction="column" overflow_y="scroll">
<div id="list" gap="8" flex_direction="row" flex_wrap="wrap">
<!-- filled-in at runtime -->
</div>
<div id="list" gap="8" flex_wrap="wrap" align_self="baseline">
<!-- filled-in at runtime -->
</div>
</elements>
</layout>
@@ -1,11 +1,11 @@
<layout version="2">
<elements>
<div flex_direction="column" gap="8" width="100%" >
<div flex_direction="column" gap="8" width="100%" align_items="center">
<div flex_direction="row" gap="4" align_self="end">
<Button id="btn_refresh" tooltip="RELOAD_FROM_DISK" width="32" height="32" sprite_src_builtin="@/dashboard/refresh.svg" />
<Button id="btn_download_skymaps" height="32" translation="APP_SETTINGS.BROWSE_ONLINE_CATALOG" sprite_src_builtin="@/dashboard/download.svg"/>
</div>
<div id="list_parent" gap="8" flex_direction="row" flex_wrap="wrap" overflow_y="scroll" justify_content="center"/>
<div id="list_parent" gap="8" flex_direction="row" flex_wrap="wrap" />
</div>
</elements>
</layout>
@@ -15,12 +15,9 @@
flex_direction="column"
gap="4"
width="256"
max_width="450"
height="200"
align_items="center"
flex_grow="1"
align_self="start">
<image id="image_preview" width="100%" aspect_ratio="2.0" round="6">
<image id="image_preview" width="100%" height="128" round="6">
<!-- new_pass is required, because we need to render rectangles at the top of the image. Sorry. -->
<div new_pass="1" id="resolution_pips" gap="4" margin="6"/>
</image>
@@ -37,7 +34,7 @@
-->
<template name="ResolutionPip">
<rectangle color="${color}" padding_left="4" padding_right="4" padding_top="2" padding_bottom="2" round="3" align_self="start">
<label text="${text}" weight="bold" size="12" color="#FFFFFF" shadow="#000000" shadow_x="2" shadow_y="2" parent_color="ignore"/>
<label text="${text}" weight="bold" size="12" color="#FFFFFF" shadow="#000000" shadow_x="2" shadow_y="2"/>
</rectangle>
</template>
</layout>
+7 -34
View File
@@ -97,10 +97,7 @@
"RIGHT": "Rechts",
"COMPONENT": "Aktivierungsart",
"SUBPATH": "Bedienelement",
"THRESHOLD": "Aktivieren bei Überschreiten des oberen Wertes\nDeaktivieren bei Unterschreiten des unteren Wertes",
"CHORD": "Chord",
"CHORD_ADD_INPUT": "Chord-Input hinzufügen",
"CHORD_INPUT_HAND": "Eingabehand"
"THRESHOLD": "Aktivieren bei Überschreiten des oberen Wertes\nDeaktivieren bei Unterschreiten des unteren Wertes"
},
"BROWSE_ONLINE_CATALOG": "Online-Katalog durchsuchen...",
"BROWSE_SKYMAPS": "Skymaps durchsuchen",
@@ -197,6 +194,8 @@
"SAVE_PLAYSPACE_CENTER_HELP": "Nutze aktuellen Playspace für die Playspace-Reset-Belegung.",
"RESET_PLAYSPACE_CENTER": "Playspace-Zentrum zurücksetzen",
"RESET_PLAYSPACE_CENTER_HELP": "Vorheriges Playspace-Zentrum zurücksetzen.",
"RESTART_SOFTWARE": "Software neu starten",
"RESTART_SOFTWARE_HELP": "Einstellungen übernehmen, die einen Neustart erfordern",
"ROUND_MULTIPLIER": "Abrundung der UI-Kanten",
"SCREEN_RENDER_DOWN": "Bildschirm mit niedrigerer Auflösung rendern",
"SCREEN_RENDER_DOWN_HELP": "Hilft gegen Treppeneffekte bei hochauflösenden Bildschirmen",
@@ -206,6 +205,7 @@
"SNAP_ANGLE_DEG_HELP": "Gegriffene Fenster rasten auf der Nick- und Rollachse in diesem Winkel ein.",
"ENABLE_WATCH": "Handgelenk-Uhr aktivieren",
"SETS_ON_WATCH": "Sets auf Handgelenk-Uhr",
"SHOW_WELCOME_SCREEN": "Willkommensbildschirm anzeigen",
"SKYBOX": "Skymap",
"SKYMAP_ALREADY_DOWNLOADED": "Diese Skymap ist bereits heruntergeladen. Wähle eine Aktion aus.",
"SPACE_DRAG": "Playspace",
@@ -247,23 +247,7 @@
"STATISTICS": "Statistiken",
"ROTATIONS": "HMD-Rotationen",
"SESSION_TIME": "Sitzungszeit",
"LENS_SEPARATION_MM": "Linsenabstand (mm)",
"MIDDLE_GRABS_SINGLE": "Mittelklick-Modifier greift einzeln",
"MIDDLE_GRABS_SINGLE_HELP": "Greifen mit dem Mittelklick-Modifier (lila Laser) \ngreift einzelne Overlays statt des gesamten Sets",
"DIAGNOSTICS_REASON": {
"PACTL": "Erforderlich für Lautsprecher-/Mikrofonsteuerung und das Wechseln der Soundkarte",
"CAGE": "Empfohlen bei Inkompatibilitäten mit einigen X11-Anwendungen (Kompatibilitätsmodus)",
"XWAYLAND_SATELLITE": "Empfohlen, wenn X11 (Xwayland) Anwendungen im nativen Modus ausgeführt werden sollen",
"XDG_OPEN": "Erforderlich zum Öffnen von URLs oder Steam-Spielen"
},
"DIAGNOSTICS": {
"CPU_ACCESSIBLE_VRAM": "CPU-zugänglicher VRAM: {NUM} MiB",
"TITLE": "Diagnose",
"ENABLE_REBAR": "Aktiviere resizable bar in den BIOS-Einstellungen für optimale VR-Performance"
},
"TUTORIAL": "Tutorial",
"RESTART_WAYVR": "WayVR neu starten",
"CLOSE_WAYVR": "WayVR schließen"
"LENS_SEPARATION_MM": "Linsenabstand (mm)"
},
"APPLICATION_LAUNCHER": "Programmstarter",
"APPLICATION_STARTED": "Anwendung gestartet",
@@ -280,8 +264,7 @@
"SETTINGS": "Audio-Einstellungen",
"SPEAKERS": "Lautsprecher",
"SPEAKERS_SET_SUCCESSFULLY": "Lautsprecher erfolgreich eingestellt",
"VOLUME": "Lautstärke",
"SWITCH_TO_THIS_DEVICE": "Zu diesem Gerät wechseln"
"VOLUME": "Lautstärke"
},
"CLOSE_WINDOW": "Fenster schließen",
"CREATION_DATE": "Erstellungsdatum",
@@ -366,15 +349,5 @@
"ADD_TO_FAVOURITES": "Zu angehefteten Anwendungen hinzufügen",
"ADD_TO_FAVOURITES_HINT": "Voreinstellung speichern und an die App-Schublade anheften",
"REMOVE_FROM_FAVOURITES": "Aus angehefteten Anwendungen entfernen",
"EMPTY": "Leer",
"INSTALLED": "Installiert",
"NOT_INSTALLED": "Nicht installiert",
"POWER_OPTIONS": {
"TITLE": "Energieoptionen",
"SHUTDOWN": "Herunterfahren",
"SUSPEND": "Ruhezustand",
"SHUTDOWN_ARE_YOU_SURE": "Bist du sicher, dass du den Computer ausschalten willst?"
},
"YES": "Ja",
"NO": "Nein"
"EMPTY": "Leer"
}
+3 -25
View File
@@ -74,9 +74,6 @@ APP_SETTINGS.BINDINGS.ACTION.MOVE_MOUSE,Move mouse (if off by default),Label for
APP_SETTINGS.BINDINGS.ACTION.SCROLL,Scroll*,Label for the scroll action row. Maps controller input to scrolling. Leave the '*' at the end.
APP_SETTINGS.BINDINGS.LEFT,Left,Label in the bindings editor. Refers to the left controller/hand configuration.
APP_SETTINGS.BINDINGS.RIGHT,Right,Label in the bindings editor. Refers to the right controller/hand configuration.
APP_SETTINGS.BINDINGS.CHORD,Chord,Label for a group of controller inputs that must all be active together. As in Input Chord
APP_SETTINGS.BINDINGS.CHORD_ADD_INPUT,Add chord input,"Button in the controller bindings editor that adds another physical input to a chord. ""Chord"" was previously translated as ""${APP_SETTINGS.BINDINGS.CHORD}""."
APP_SETTINGS.BINDINGS.CHORD_INPUT_HAND,Input hand,"Selector for the hand to which we will be binding the input action to."
APP_SETTINGS.BINDINGS.COMPONENT,Actuation type,"Tooltip for the component dropdown in the bindings editor. Asks user to select what type of actuation (click, touch, axis, etc.)."
APP_SETTINGS.BINDINGS.SUBPATH,Actuating control,Tooltip for the subpath dropdown in the bindings editor. Asks user to select which controller button/stick/trackpad to bind.
APP_SETTINGS.BINDINGS.THRESHOLD,"Activate when above upper value
@@ -122,13 +119,6 @@ controllers are unavailable.
Left pinch is grab, right is click.",Tooltip for the handsfree mode dropdown.
APP_SETTINGS.HANDSFREE_ALT_TAB,App switcher aim,Dropdown/setting in Settings > Features tab. Controls how the alt-tab app switcher is aimed.
APP_SETTINGS.HANDSFREE_ALT_TAB_HELP,"Hold Super+X, aim at the desired window, then release to switch focus.",Tooltip explaining the handsfree alt-tab behavior.
APP_SETTINGS.DIAGNOSTICS.CPU_ACCESSIBLE_VRAM,"CPU-accessible VRAM: {NUM} MiB","Amount of VRAM accessible by the CPU. For example: 256/4096"
APP_SETTINGS.DIAGNOSTICS.TITLE,"Diagnostics","Tab name for the hardware/software check panel"
APP_SETTINGS.DIAGNOSTICS.ENABLE_REBAR,"Enable Resizable BAR in the BIOS settings for optimal VR performance","Info shown if resizable bar is disabled in the BIOS. Leave [resizable bar] translation as-is."
APP_SETTINGS.DIAGNOSTICS_REASON.PACTL,"Required for speaker/microphone control and audio card switching","Reason for why the given software is recommended"
APP_SETTINGS.DIAGNOSTICS_REASON.CAGE,"Recommended in case if there's an incompatibility with some X11 applications (Compatibility mode)","Reason for why the given software is required or recommended"
APP_SETTINGS.DIAGNOSTICS_REASON.XWAYLAND_SATELLITE,"Recommended in case if you want to run X11 (Xwayland) applications in native mode","Reason for why the given software is required or recommended"
APP_SETTINGS.DIAGNOSTICS_REASON.XDG_OPEN,"Required for opening URLs or Steam games","Reason for why the given software is required or recommended"
APP_SETTINGS.HIDE_GRAB_HELP,Hide grab help,Checkbox label in Settings > Look & Feel tab. Hides the grab help overlay/text.
APP_SETTINGS.HIDE_USERNAME,Hide username,Checkbox label in Settings > Look & Feel tab. Hides the user's username from the UI.
APP_SETTINGS.INPUT_EMULATION_METHOD,Input emulation method,"Dropdown label in Settings > Miscellaneous tab. Selects how WayVR emulates mouse/keyboard input (uinput, Wayland virtual, none)."
@@ -153,9 +143,6 @@ APP_SETTINGS.LEFT_HANDED_MOUSE_HELP,Use this if mouse buttons are swapped,Toolti
APP_SETTINGS.LONG_PRESS_DURATION,Long press duration,Setting in Settings > Features tab. Controls how long a press must be held to register as a long press.
APP_SETTINGS.LOOK_AND_FEEL,Look & Feel,"Category header in Settings. Groups visual/customization settings like language, opacity, animation speed, color palettes."
APP_SETTINGS.MISC,Miscellaneous,"Category header in Settings. Groups troubleshooting and system-level settings like capture method, input emulation, xwayland."
APP_SETTINGS.MIDDLE_GRABS_SINGLE,"Middle-click modifier grabs individual","Checkbox label in Settings > Controls tab. When enabled, grabbing with the middle modifier will always grab individual objects, never the entire set of objects. ""Middle-click modifier"" was previously translated as ""${APP_SETTINGS.BINDINGS.ACTION.CLICK_MODIFIER_MIDDLE}"""
APP_SETTINGS.MIDDLE_GRABS_SINGLE_HELP,"Grabbing with the middle-click modifier (purple laser)
will grab individual overlays, not the entire set","Tooltip for the middle modifier grabs single overlays checkbox. ""Middle-click modifier"" was previously translated as ""${APP_SETTINGS.BINDINGS.ACTION.CLICK_MODIFIER_MIDDLE}"""
APP_SETTINGS.NOTIFICATIONS_ENABLED,Enable notifications,Checkbox label in Settings > Features tab. Enables or disables system notifications within WayVR.
APP_SETTINGS.NOTIFICATIONS_SOUND_ENABLED,Notification sounds,Checkbox label in Settings > Features tab. Enables or disables sound effects for notifications.
APP_SETTINGS.NOT_SUPPORTED,Not supported,Label shown in the space drag/physics settings when a feature (like gravity) is not supported by the current backend.
@@ -208,8 +195,8 @@ APP_SETTINGS.SAVE_PLAYSPACE_CENTER,Save playspace center,"Button in Settings > S
APP_SETTINGS.SAVE_PLAYSPACE_CENTER_HELP,Use current playspace for reset playspace binding.,"Tooltip for the Save playspace center button. ""Playspace"" was previously translated as ""${DEFINITIONS.PLAYSPACE}""."
APP_SETTINGS.RESET_PLAYSPACE_CENTER,Reset playspace center,"Button in Settings > Space Drag tab. Clears the previously saved playspace center. ""Playspace"" was previously translated as ""${DEFINITIONS.PLAYSPACE}""."
APP_SETTINGS.RESET_PLAYSPACE_CENTER_HELP,Clear previously set playspace center.,"Tooltip for the Reset playspace center button. ""Playspace"" was previously translated as ""${DEFINITIONS.PLAYSPACE}""."
APP_SETTINGS.RESTART_WAYVR,Restart WayVR,Button in Settings which restarts WayVR
APP_SETTINGS.CLOSE_WAYVR,Close WayVR,Button in Settings which closes WayVR
APP_SETTINGS.RESTART_SOFTWARE,Restart software,Button in Settings > Troubleshooting tab. Restarts the WayVR software to apply settings that require a restart.
APP_SETTINGS.RESTART_SOFTWARE_HELP,Apply settings that require a restart,Tooltip for the Restart software button.
APP_SETTINGS.ROUND_MULTIPLIER,UI Edge roundness,Slider in Settings > Look & Feel tab. Controls how rounded the corners of UI elements appear (range 0.1-5.0).
APP_SETTINGS.SCREEN_RENDER_DOWN,Render screen at lower resolution,Checkbox in Settings > Miscellaneous tab. Renders the captured desktop screen at a lower resolution to improve performance or reduce aliasing.
APP_SETTINGS.SCREEN_RENDER_DOWN_HELP,Helps with aliasing on high-res screens,Tooltip for the lower-resolution screen render setting.
@@ -219,6 +206,7 @@ APP_SETTINGS.SNAP_ANGLE_DEG,Window pitch & roll snap angle,Slider in Settings >
APP_SETTINGS.SNAP_ANGLE_DEG_HELP,Grabbed windows snap to this angle on pitch and roll axis.,Tooltip for the snap angle setting.
APP_SETTINGS.ENABLE_WATCH,Enable watch,"Checkbox in Settings > Look & Feel tab. Enables the wrist watch UI element. ""Watch"" was previously translated as ""${DEFINITIONS.WATCH}""."
APP_SETTINGS.SETS_ON_WATCH,Sets on watch,"Checkbox in Settings > Look & Feel tab. Shows overlay sets on the wrist watch display. ""Watch"" was previously translated as ""${DEFINITIONS.WATCH}"". ""Set"" was previously translated as ""${DEFINITIONS.SET}""."
APP_SETTINGS.SHOW_WELCOME_SCREEN,Show welcome screen,Checkbox in Settings > Features tab. Controls whether the welcome/tutorial screen is shown on first launch.
APP_SETTINGS.SKYBOX,Skymap,"Category header in Settings > Skymap tab. Groups skymap-related settings. ""Skymap"" was previously translated as ""${DEFINITIONS.SKYMAP}""."
APP_SETTINGS.SKYMAP_ALREADY_DOWNLOADED,This skymap is already downloaded. Select desired action.,"Message shown in a dialog when attempting to download a skymap that is already present locally. ""Skymap"" was previously translated as ""${DEFINITIONS.SKYMAP}""."
APP_SETTINGS.SPACE_DRAG,Playspace,"Category header in Settings. Groups playspace drag/physics settings including reset buttons and gravity. ""Playspace"" was previously translated as ""${DEFINITIONS.PLAYSPACE}""."
@@ -259,7 +247,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.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}""."
APP_SETTINGS.XWAYLAND_BY_DEFAULT,Run apps in Compatibility mode by default,Checkbox in Settings > Miscellaneous tab. Launches apps in Cage/compatibility mode by default.
@@ -268,7 +255,6 @@ APP_SETTINGS.CANCEL,Cancel,Button label in the controller bindings editor popup
APPLICATION_LAUNCHER,Application launcher,App launcher configuration popup title - appears as the label for the app launch/options dialog.
APPLICATION_STARTED,Application started,Toast notification shown after a successful app launch.
APPLICATIONS,Applications,Tab label in the dashboard sidebar for the Apps tab.
AUDIO.SWITCH_TO_THIS_DEVICE,Switch to this device,Button in the audio settings dialog
AUDIO.AUTO_SWITCH_TO_VR_AUDIO,Auto-switch to VR audio,Tooltip on the auto-switch audio button in the audio settings popup.
AUDIO.CARDS,Cards,Button label in the audio settings popup for selecting audio card profiles.
AUDIO.FAILED_TO_SWITCH_MICROPHONE,Failed to switch microphone,Error toast shown when switching to a VR microphone fails.
@@ -336,10 +322,6 @@ MODIFICATION_DATE,Modification date,Column label in the remote skymap downloader
MONADO_RUNTIME,Monado runtime,Tab label in the dashboard sidebar for the Monado runtime tab.
LOADING,Loading...,Label in the loading spinner template used across the UI.
POPUP_ADD_DISPLAY.RESOLUTION,Resolution,Label for the resolution field in the add display popup.
POWER_OPTIONS.TITLE,"Power options","A button which allows the user to perform a specific power action"
POWER_OPTIONS.SHUTDOWN,"Shut down","One of the power options: shuts down the computer"
POWER_OPTIONS.SUSPEND,"Suspend","One of the power options: suspends the computer"
POWER_OPTIONS.SHUTDOWN_ARE_YOU_SURE,"Are you sure you want to turn off your computer?","Question dialog box before shutting down the computer"
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.
@@ -359,7 +341,3 @@ ADD_TO_FAVOURITES,Add to pinned apps,"Label on the pin button in the app launche
ADD_TO_FAVOURITES_HINT,Save preset and pin it to the app drawer,"Tooltip on the add to favourites button in the app launcher popup. ""Pinned apps"" was previously translated as ""${PINNED_APPS}""."
REMOVE_FROM_FAVOURITES,Remove from pinned apps,"Label on the unpin section in the app launcher popup. ""Pinned apps"" was previously translated as ""${PINNED_APPS}""."
EMPTY,Empty,Label shown in the pinned apps section when there are no pinned apps.
INSTALLED,"Installed","Installation flag (installed/not installed)"
NOT_INSTALLED,"Not installed","Installation flag (installed/not installed)"
YES,"Yes","Just a simple 'Yes' message, button in the dialog box"
NO,"No","Just a simple 'No' message, button in the dialog box"
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".
74 APP_SETTINGS.BINDINGS.ACTION.SCROLL Scroll* Label for the scroll action row. Maps controller input to scrolling. Leave the '*' at the end.
75 APP_SETTINGS.BINDINGS.LEFT Left Label in the bindings editor. Refers to the left controller/hand configuration.
76 APP_SETTINGS.BINDINGS.RIGHT Right Label in the bindings editor. Refers to the right controller/hand configuration.
APP_SETTINGS.BINDINGS.CHORD Chord Label for a group of controller inputs that must all be active together. As in Input Chord
APP_SETTINGS.BINDINGS.CHORD_ADD_INPUT Add chord input Button in the controller bindings editor that adds another physical input to a chord. "Chord" was previously translated as "${APP_SETTINGS.BINDINGS.CHORD}".
APP_SETTINGS.BINDINGS.CHORD_INPUT_HAND Input hand Selector for the hand to which we will be binding the input action to.
77 APP_SETTINGS.BINDINGS.COMPONENT Actuation type Tooltip for the component dropdown in the bindings editor. Asks user to select what type of actuation (click, touch, axis, etc.).
78 APP_SETTINGS.BINDINGS.SUBPATH Actuating control Tooltip for the subpath dropdown in the bindings editor. Asks user to select which controller button/stick/trackpad to bind.
79 APP_SETTINGS.BINDINGS.THRESHOLD Activate when above upper value Deactivate when below lower value Tooltip for the threshold slider in the bindings editor. Explains the two-value threshold range for analog inputs.
119 APP_SETTINGS.DIAGNOSTICS.ENABLE_REBAR APP_SETTINGS.INPUT_EMULATION_METHOD Enable Resizable BAR in the BIOS settings for optimal VR performance Input emulation method Info shown if resizable bar is disabled in the BIOS. Leave [resizable bar] translation as-is. Dropdown label in Settings > Miscellaneous tab. Selects how WayVR emulates mouse/keyboard input (uinput, Wayland virtual, none).
120 APP_SETTINGS.DIAGNOSTICS_REASON.PACTL APP_SETTINGS.INPUT_EMULATION_METHOD_HELP Required for speaker/microphone control and audio card switching Try change this in case mouse or keyboard input does not register. Reason for why the given software is recommended Tooltip for the input emulation method dropdown.
121 APP_SETTINGS.DIAGNOSTICS_REASON.CAGE APP_SETTINGS.INPUT_CAPTURE_METHOD Recommended in case if there's an incompatibility with some X11 applications (Compatibility mode) Input capture method Reason for why the given software is required or recommended Dropdown label in Settings > Miscellaneous tab. Selects how physical input devices are captured for use inside WayVR.
APP_SETTINGS.DIAGNOSTICS_REASON.XWAYLAND_SATELLITE Recommended in case if you want to run X11 (Xwayland) applications in native mode Reason for why the given software is required or recommended
APP_SETTINGS.DIAGNOSTICS_REASON.XDG_OPEN Required for opening URLs or Steam games Reason for why the given software is required or recommended
APP_SETTINGS.HIDE_GRAB_HELP Hide grab help Checkbox label in Settings > Look & Feel tab. Hides the grab help overlay/text.
APP_SETTINGS.HIDE_USERNAME Hide username Checkbox label in Settings > Look & Feel tab. Hides the user's username from the UI.
APP_SETTINGS.INPUT_EMULATION_METHOD Input emulation method Dropdown label in Settings > Miscellaneous tab. Selects how WayVR emulates mouse/keyboard input (uinput, Wayland virtual, none).
APP_SETTINGS.INPUT_EMULATION_METHOD_HELP Try change this in case mouse or keyboard input does not register. Tooltip for the input emulation method dropdown.
APP_SETTINGS.INPUT_CAPTURE_METHOD Input capture method Dropdown label in Settings > Miscellaneous tab. Selects how physical input devices are captured for use inside WayVR.
122 APP_SETTINGS.INPUT_CAPTURE_METHOD_HELP Allows using physical peripherial inside WayVR. Super+Z: Switch input between WayVR and real desktop Super+X: Switch between WayVR apps Tooltip explaining the input capture method setting.
123 APP_SETTINGS.INPUT_PROFILES Change Input Bindings Button label in Settings > Controls tab. Opens the controller bindings editor popup.
124 APP_SETTINGS.INPUT_PROFILES_HELP OpenXR controller input bindings Tooltip for the Change Input Bindings button.
143 APP_SETTINGS.OPAQUE_BACKGROUND APP_SETTINGS.OPTION.FLOATING_HELP Opaque background Moves independently, recenters. Easy to misplace; not recommended. Checkbox label in Settings > Look & Feel tab. Makes overlay backgrounds fully opaque (no transparency). Tooltip for the Floating positioning option.
144 APP_SETTINGS.OPTION.ANCHORED_HELP APP_SETTINGS.OPTION.STATIC_HELP Moves with center marker. Recommended. Stays in place relative to world. Tooltip for the Anchored positioning option. Tooltip for the Static positioning option.
145 APP_SETTINGS.OPTION.FLOATING_HELP APP_SETTINGS.OPTION.AUTO Moves independently, recenters. Easy to misplace; not recommended. Automatic Tooltip for the Floating positioning option. Dropdown option for screen capture method. Uses ScreenCopy GPU if supported, otherwise PipeWire GPU.
APP_SETTINGS.OPTION.STATIC_HELP Stays in place relative to world. Tooltip for the Static positioning option.
APP_SETTINGS.OPTION.AUTO Automatic Dropdown option for screen capture method. Uses ScreenCopy GPU if supported, otherwise PipeWire GPU.
APP_SETTINGS.OPTION.AUTO_HELP ScreenCopy GPU if supported, otherwise PipeWire GPU. Tooltip for the Automatic capture method option.
146 APP_SETTINGS.OPTION.EYE_ONLY APP_SETTINGS.OPTION.AUTO_HELP Eye laser ScreenCopy GPU if supported, otherwise PipeWire GPU. Dropdown option for handsfree pointer mode. Uses only eye-tracking laser (no pinch). Tooltip for the Automatic capture method option.
147 APP_SETTINGS.OPTION.EYE_PINCH APP_SETTINGS.OPTION.EYE_ONLY Eye + pinch Eye laser Dropdown option for handsfree pointer mode. Uses eye-tracking for aiming and pinch for click. Dropdown option for handsfree pointer mode. Uses only eye-tracking laser (no pinch).
148 APP_SETTINGS.OPTION.HMD_ONLY APP_SETTINGS.OPTION.EYE_PINCH HMD laser Eye + pinch Dropdown option for handsfree pointer/alt-tab mode. Uses only the HMD forward laser. Dropdown option for handsfree pointer mode. Uses eye-tracking for aiming and pinch for click.
195 APP_SETTINGS.SPACE_GRAVITY_GRAVITY_HELP APP_SETTINGS.SPACE_GRAVITY_FLING_STRENGTH_HELP Amount of downwards force. 0.0 - no gravity Intensity multiplier of gravitational launch force after space-drag. 0.0 - no movement at all, 2.0 - double intensity Tooltip for the gravity setting. Tooltip for the fling strength setting. "Space" in this context refers to "Playspace" which was translated as "${DEFINITIONS.PLAYSPACE}".
196 APP_SETTINGS.SPACE_GRAVITY_GROUND_FRICTION APP_SETTINGS.SPACE_GRAVITY_GRAVITY Ground friction Gravity Setting in the space gravity physics panel. Friction when touching the ground (0.0 = ice-like, 1.0 = rough). Setting in the space gravity physics panel. Amount of downward force (0.0 = no gravity).
197 APP_SETTINGS.SPACE_GRAVITY_GROUND_FRICTION_HELP APP_SETTINGS.SPACE_GRAVITY_GRAVITY_HELP Amount of friction slowing you down if you're touching the ground. 0.0 - no friction (just like on ice), 1.0 - rough surface Amount of downwards force. 0.0 - no gravity Tooltip for the ground friction setting. Tooltip for the gravity setting.
198 APP_SETTINGS.SPACE_GRAVITY_FLOOR_HEIGHT APP_SETTINGS.SPACE_GRAVITY_GROUND_FRICTION Floor height Ground friction Setting in the space gravity physics panel. The Y position where the virtual floor is. Setting in the space gravity physics panel. Friction when touching the ground (0.0 = ice-like, 1.0 = rough).
199 APP_SETTINGS.SPACE_GRAVITY_FLOOR_HEIGHT_HELP APP_SETTINGS.SPACE_GRAVITY_GROUND_FRICTION_HELP The Y position where the floor is. Gravity stops when you reach this height. Amount of friction slowing you down if you're touching the ground. 0.0 - no friction (just like on ice), 1.0 - rough surface Tooltip for the floor height setting. Tooltip for the ground friction setting.
200 APP_SETTINGS.SPACE_ROTATE_UNLOCKED APP_SETTINGS.SPACE_GRAVITY_FLOOR_HEIGHT Allow space rotate on all axes Floor height Checkbox in Settings > Space Drag tab. Unlocks space rotate to work on all axes. "Space" in this context refers to "Playspace" which was translated as "${DEFINITIONS.PLAYSPACE}". Setting in the space gravity physics panel. The Y position where the virtual floor is.
201 APP_SETTINGS.ROTATIONS APP_SETTINGS.SPACE_GRAVITY_FLOOR_HEIGHT_HELP HMD rotations The Y position where the floor is. Gravity stops when you reach this height. Number of full 360-degree rotations of the HMD since startup. Shown in Settings > Statistics. Tooltip for the floor height setting.
202 APP_SETTINGS.SESSION_TIME APP_SETTINGS.SPACE_ROTATE_UNLOCKED Session time Allow space rotate on all axes Time since WayVR started. Shown in Settings > Statistics. Checkbox in Settings > Space Drag tab. Unlocks space rotate to work on all axes. "Space" in this context refers to "Playspace" which was translated as "${DEFINITIONS.PLAYSPACE}".
206 APP_SETTINGS.UPRIGHT_SCREEN_FIX APP_SETTINGS.TROUBLESHOOTING Upright screen fix Troubleshooting Checkbox in Settings > Miscellaneous tab. Fixes upright screen orientation on some desktop environments. Category header in Settings. Groups diagnostic/reset actions like clearing tokens, wiping config, and restarting.
207 APP_SETTINGS.UPRIGHT_SCREEN_FIX_HELP APP_SETTINGS.UI_GRADIENT_INTENSITY Fixes upright screens on some desktops UI Gradient intensity Tooltip for the upright screen fix checkbox. Slider in Settings > Look & Feel tab. Controls the intensity of gradient effects on the UI (range 0.0-1.0).
208 APP_SETTINGS.USE_PASSTHROUGH APP_SETTINGS.UPRIGHT_SCREEN_FIX Enable passthrough Upright screen fix Checkbox in Settings > Skybox tab. Allows camera passthrough mode if the XR runtime supports it. Checkbox in Settings > Miscellaneous tab. Fixes upright screen orientation on some desktop environments.
209 APP_SETTINGS.UPRIGHT_SCREEN_FIX_HELP Fixes upright screens on some desktops Tooltip for the upright screen fix checkbox.
210 APP_SETTINGS.USE_PASSTHROUGH_HELP APP_SETTINGS.USE_PASSTHROUGH Allow passthrough if the XR runtime supports it Enable passthrough Tooltip for the passthrough checkbox. Checkbox in Settings > Skybox tab. Allows camera passthrough mode if the XR runtime supports it.
211 APP_SETTINGS.USE_SKYBOX APP_SETTINGS.USE_PASSTHROUGH_HELP Show skymap Allow passthrough if the XR runtime supports it Checkbox in Settings > Skybox tab. Shows a skybox when there's no scene app or passthrough active. "Skymap" was previously translated as "${DEFINITIONS.SKYMAP}". Tooltip for the passthrough checkbox.
212 APP_SETTINGS.USE_SKYBOX_HELP APP_SETTINGS.USE_SKYBOX Show a skymap if there's no scene app or passthrough Show skymap Tooltip for the enable skybox checkbox. "Skymap" was previously translated as "${DEFINITIONS.SKYMAP}". Checkbox in Settings > Skybox tab. Shows a skybox when there's no scene app or passthrough active. "Skymap" was previously translated as "${DEFINITIONS.SKYMAP}".
247 DONATE.THANK_YOU_WAYVR DONATE.BUTTON_TOOLTIP Thank you from the WayVR Team! Donate via Open Collective Thank you message displayed on the donate tab. Tooltip on the donate button on the donate tab.
248 DONATE.BUTTON_TOOLTIP DONATE.CURRENT_SUPPORTERS Donate via Open Collective Current Supporters Tooltip on the donate button on the donate tab. Section header on the donate tab listing supporters.
249 DONATE.CURRENT_SUPPORTERS DONATE.BROUGHT_TO_YOU_VARIANTS_OFTEN Current Supporters 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} Section header on the donate tab listing supporters. 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.
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.
250 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).
251 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).
252 DOWNLOAD Download Button in the Whisper model download dialog to confirm downloading a model.
255 WELCOME.BASICS These basics will allow you to start using WayVR with ease. Description text on the final welcome page under the You are all set heading.
256 WELCOME.CENTER_MARKER Center marker is what will appear in front of you when recentering. Place overlays around it! Description of the center marker on the moving your set tutorial page. "Overlay" was previously translated as "${DEFINITIONS.OVERLAY}".
257 WELCOME.DOUBLE_PRESS_TO_TOGGLE Double-press B (on Index) or Y (on Meta) to show, recenter or hide your set. Try it now. Instruction text on the working set tutorial page explaining the double-press gesture. "Set" was previously translated as "${DEFINITIONS.SET}".
WELCOME.FEATURES_DOCUMENTATION There are many other features worth customizing. For more information, check the documentation at Text on the final welcome page linking to documentation.
258 WELCOME.FINISH_TUTORIAL WELCOME.FEATURES_DOCUMENTATION Finish tutorial There are many other features worth customizing. For more information, check the documentation at Button text on the final welcome page to complete the tutorial. Text on the final welcome page linking to documentation.
259 WELCOME.GRAB_BOTH_HANDS WELCOME.FINISH_TUTORIAL Grab it with both hands if you want to move a specific overlay in your set. Finish tutorial Instruction text on the moving your set tutorial page for two-hand grab. Button text on the final welcome page to complete the tutorial.
260 WELCOME.GRAB_ONE_HAND WELCOME.GRAB_BOTH_HANDS Grab it with one hand to move your set. Grab it with both hands if you want to move a specific overlay in your set. Instruction text on the moving your set tutorial page for one-hand grab. Instruction text on the moving your set tutorial page for two-hand grab.
322
323
324
325
326
327
341
342
343
+4 -31
View File
@@ -95,9 +95,6 @@
},
"LEFT": "Left",
"RIGHT": "Right",
"CHORD": "Chord",
"CHORD_ADD_INPUT": "Add chord input",
"CHORD_INPUT_HAND": "Input hand",
"COMPONENT": "Actuation type",
"SUBPATH": "Actuating control",
"THRESHOLD": "Activate when above upper value\nDeactivate when below lower value"
@@ -139,17 +136,6 @@
"HANDSFREE_POINTER_HELP": "Input to use when motion\ncontrollers are unavailable.\nLeft pinch is grab, right is click.",
"HANDSFREE_ALT_TAB": "App switcher aim",
"HANDSFREE_ALT_TAB_HELP": "Hold Super+X, aim at the desired window, then release to switch focus.",
"DIAGNOSTICS": {
"CPU_ACCESSIBLE_VRAM": "CPU-accessible VRAM: {NUM} MiB",
"TITLE": "Diagnostics",
"ENABLE_REBAR": "Enable Resizable BAR in the BIOS settings for optimal VR performance"
},
"DIAGNOSTICS_REASON": {
"PACTL": "Required for speaker/microphone control and audio card switching",
"CAGE": "Recommended in case if there's an incompatibility with some X11 applications (Compatibility mode)",
"XWAYLAND_SATELLITE": "Recommended in case if you want to run X11 (Xwayland) applications in native mode",
"XDG_OPEN": "Required for opening URLs or Steam games"
},
"HIDE_GRAB_HELP": "Hide grab help",
"HIDE_USERNAME": "Hide username",
"INPUT_EMULATION_METHOD": "Input emulation method",
@@ -170,8 +156,6 @@
"LONG_PRESS_DURATION": "Long press duration",
"LOOK_AND_FEEL": "Look & Feel",
"MISC": "Miscellaneous",
"MIDDLE_GRABS_SINGLE": "Middle-click modifier grabs individual",
"MIDDLE_GRABS_SINGLE_HELP": "Grabbing with the middle-click modifier (purple laser)\nwill grab individual overlays, not the entire set",
"NOTIFICATIONS_ENABLED": "Enable notifications",
"NOTIFICATIONS_SOUND_ENABLED": "Notification sounds",
"NOT_SUPPORTED": "Not supported",
@@ -211,8 +195,8 @@
"SAVE_PLAYSPACE_CENTER_HELP": "Use current playspace for reset playspace binding.",
"RESET_PLAYSPACE_CENTER": "Reset playspace center",
"RESET_PLAYSPACE_CENTER_HELP": "Clear previously set playspace center.",
"RESTART_WAYVR": "Restart WayVR",
"CLOSE_WAYVR": "Close WayVR",
"RESTART_SOFTWARE": "Restart software",
"RESTART_SOFTWARE_HELP": "Apply settings that require a restart",
"ROUND_MULTIPLIER": "UI Edge roundness",
"SCREEN_RENDER_DOWN": "Render screen at lower resolution",
"SCREEN_RENDER_DOWN_HELP": "Helps with aliasing on high-res screens",
@@ -222,6 +206,7 @@
"SNAP_ANGLE_DEG_HELP": "Grabbed windows snap to this angle on pitch and roll axis.",
"ENABLE_WATCH": "Enable watch",
"SETS_ON_WATCH": "Sets on watch",
"SHOW_WELCOME_SCREEN": "Show welcome screen",
"SKYBOX": "Skymap",
"SKYMAP_ALREADY_DOWNLOADED": "This skymap is already downloaded. Select desired action.",
"SPACE_DRAG": "Playspace",
@@ -258,7 +243,6 @@
},
"WHISPER_MODEL": "Speech-to-text model",
"WHISPER_MODEL_HELP": "Whisper model to use.\nLarger models produce more accurate\nresults, but consume more VRAM.",
"TUTORIAL": "Tutorial",
"GRID_OPACITY": "Floor grid opacity",
"GRID_OPACITY_HELP": "Opacity of the floor grid when the skymap is enabled",
"XWAYLAND_BY_DEFAULT": "Run apps in Compatibility mode by default",
@@ -269,7 +253,6 @@
"APPLICATION_STARTED": "Application started",
"APPLICATIONS": "Applications",
"AUDIO": {
"SWITCH_TO_THIS_DEVICE": "Switch to this device",
"AUTO_SWITCH_TO_VR_AUDIO": "Auto-switch to VR audio",
"CARDS": "Cards",
"FAILED_TO_SWITCH_MICROPHONE": "Failed to switch microphone",
@@ -346,12 +329,6 @@
"POPUP_ADD_DISPLAY": {
"RESOLUTION": "Resolution"
},
"POWER_OPTIONS": {
"TITLE": "Power options",
"SHUTDOWN": "Shut down",
"SUSPEND": "Suspend",
"SHUTDOWN_ARE_YOU_SURE": "Are you sure you want to turn off your computer?"
},
"PROCESS": {
"FORCE_KILL": "Force-kill",
"STOP": "Stop"
@@ -372,9 +349,5 @@
"ADD_TO_FAVOURITES": "Add to pinned apps",
"ADD_TO_FAVOURITES_HINT": "Save preset and pin it to the app drawer",
"REMOVE_FROM_FAVOURITES": "Remove from pinned apps",
"EMPTY": "Empty",
"INSTALLED": "Installed",
"NOT_INSTALLED": "Not installed",
"YES": "Yes",
"NO": "No"
"EMPTY": "Empty"
}
+7 -34
View File
@@ -97,10 +97,7 @@
"RIGHT": "Derecha",
"COMPONENT": "Tipo de accionamiento",
"SUBPATH": "Control a accionar",
"THRESHOLD": "Activar al superar el valor superior\nDesactivar al caer bajo el valor inferior",
"CHORD": "Acorde",
"CHORD_ADD_INPUT": "Añadir entrada al acorde",
"CHORD_INPUT_HAND": "Mano de entrada"
"THRESHOLD": "Activar al superar el valor superior\nDesactivar al caer bajo el valor inferior"
},
"BROWSE_ONLINE_CATALOG": "Explorar catálogo en línea...",
"BROWSE_SKYMAPS": "Explorar skymaps",
@@ -197,6 +194,8 @@
"SAVE_PLAYSPACE_CENTER_HELP": "Usa el espacio de juego actual para el comando de reiniciar espacio de juego.",
"RESET_PLAYSPACE_CENTER": "Reiniciar centro del espacio de juego",
"RESET_PLAYSPACE_CENTER_HELP": "Restablece el centro del espacio de juego definido previamente.",
"RESTART_SOFTWARE": "Reiniciar software",
"RESTART_SOFTWARE_HELP": "Aplicar ajustes que requieren reiniciar",
"ROUND_MULTIPLIER": "Redondeo de bordes de la UI",
"SCREEN_RENDER_DOWN": "Renderizar pantalla a resolución menor",
"SCREEN_RENDER_DOWN_HELP": "Ayuda con el aliasing en pantallas de alta resolución",
@@ -206,6 +205,7 @@
"SNAP_ANGLE_DEG_HELP": "Las ventanas agarradas se ajustan a este ángulo en los ejes de cabeceo y balanceo.",
"ENABLE_WATCH": "Habilitar reloj",
"SETS_ON_WATCH": "Sets en el reloj",
"SHOW_WELCOME_SCREEN": "Mostrar pantalla de bienvenida",
"SKYBOX": "Skymap",
"SKYMAP_ALREADY_DOWNLOADED": "Este skymap ya está descargado. Selecciona la acción deseada.",
"SPACE_DRAG": "Espacio de juego",
@@ -247,23 +247,7 @@
"STATISTICS": "Estadísticas",
"ROTATIONS": "Rotaciones del HMD",
"SESSION_TIME": "Tiempo de sesión",
"LENS_SEPARATION_MM": "Separación de lentes (mm)",
"MIDDLE_GRABS_SINGLE": "El modificador de clic central agarra objetos individuales",
"MIDDLE_GRABS_SINGLE_HELP": "Al agarrar con el modificador de clic central (láser púrpura),\nse agarrarán overlays individuales y no el set completo",
"DIAGNOSTICS_REASON": {
"PACTL": "Necesario para el control de altavoces/micrófono y el cambio de tarjeta de audio",
"CAGE": "Recomendado en caso de incompatibilidad con algunas aplicaciones X11 (Modo de compatibilidad)",
"XWAYLAND_SATELLITE": "Recomendado si quieres ejecutar aplicaciones X11 (Xwayland) en modo nativo",
"XDG_OPEN": "Necesario para abrir URLs o juegos de Steam"
},
"DIAGNOSTICS": {
"CPU_ACCESSIBLE_VRAM": "VRAM accesible por CPU: {NUM} MiB",
"TITLE": "Diagnósticos",
"ENABLE_REBAR": "Activa [resizable bar] en la configuración de la BIOS para un rendimiento óptimo de VR"
},
"TUTORIAL": "Tutorial",
"RESTART_WAYVR": "Reiniciar WayVR",
"CLOSE_WAYVR": "Cerrar WayVR"
"LENS_SEPARATION_MM": "Separación de lentes (mm)"
},
"APPLICATION_LAUNCHER": "Lanzador de aplicaciones",
"APPLICATION_STARTED": "Aplicación iniciada",
@@ -280,8 +264,7 @@
"SETTINGS": "Ajustes de audio",
"SPEAKERS": "Altavoces",
"SPEAKERS_SET_SUCCESSFULLY": "Altavoces configurados correctamente",
"VOLUME": "Volumen",
"SWITCH_TO_THIS_DEVICE": "Cambiar a este dispositivo"
"VOLUME": "Volumen"
},
"CLOSE_WINDOW": "Cerrar ventana",
"CREATION_DATE": "Fecha de creación",
@@ -366,15 +349,5 @@
"ADD_TO_FAVOURITES": "Añadir a aplicaciones fijadas",
"ADD_TO_FAVOURITES_HINT": "Guardar ajuste y fijarlo al cajón de aplicaciones",
"REMOVE_FROM_FAVOURITES": "Quitar de aplicaciones fijadas",
"EMPTY": "Vacío",
"INSTALLED": "Instalado",
"NOT_INSTALLED": "No instalado",
"POWER_OPTIONS": {
"TITLE": "Opciones de energía",
"SHUTDOWN": "Apagar",
"SUSPEND": "Suspender",
"SHUTDOWN_ARE_YOU_SURE": "¿Seguro que quieres apagar el ordenador?"
},
"YES": "Sí",
"NO": "No"
"EMPTY": "Vacío"
}
+7 -34
View File
@@ -97,10 +97,7 @@
"RIGHT": "Destro",
"COMPONENT": "Tipo di attuazione",
"SUBPATH": "Comando di attivazione",
"THRESHOLD": "Attiva sopra il valore superiore\nDisattiva sotto il valore inferiore",
"CHORD": "Combinazione",
"CHORD_ADD_INPUT": "Aggiungi input combinazione",
"CHORD_INPUT_HAND": "Mano di input"
"THRESHOLD": "Attiva sopra il valore superiore\nDisattiva sotto il valore inferiore"
},
"BROWSE_ONLINE_CATALOG": "Sfoglia catalogo online...",
"BROWSE_SKYMAPS": "Sfoglia skymap",
@@ -197,6 +194,8 @@
"SAVE_PLAYSPACE_CENTER_HELP": "Usa il playspace corrente per il comando di reset del playspace.",
"RESET_PLAYSPACE_CENTER": "Resetta centro playspace",
"RESET_PLAYSPACE_CENTER_HELP": "Rimuovi il centro del playspace impostato precedentemente.",
"RESTART_SOFTWARE": "Riavvia software",
"RESTART_SOFTWARE_HELP": "Applica le impostazioni che richiedono il riavvio",
"ROUND_MULTIPLIER": "Arrotondamento bordi UI",
"SCREEN_RENDER_DOWN": "Rendering schermo a risoluzione inferiore",
"SCREEN_RENDER_DOWN_HELP": "Riduce l'effetto aliasing sugli schermi ad alta risoluzione",
@@ -206,6 +205,7 @@
"SNAP_ANGLE_DEG_HELP": "Le finestre afferrate si allineano a questo angolo sugli assi di beccheggio e rollio.",
"ENABLE_WATCH": "Attiva orologio",
"SETS_ON_WATCH": "Set sull'orologio",
"SHOW_WELCOME_SCREEN": "Mostra schermata di benvenuto",
"SKYBOX": "Skymap",
"SKYMAP_ALREADY_DOWNLOADED": "Questo skymap è già stato scaricato. Seleziona l'azione desiderata.",
"SPACE_DRAG": "Playspace",
@@ -247,23 +247,7 @@
"STATISTICS": "Statistiche",
"ROTATIONS": "Rotazioni HMD",
"SESSION_TIME": "Tempo sessione",
"LENS_SEPARATION_MM": "Distanza lenti (mm)",
"MIDDLE_GRABS_SINGLE": "Il modificatore click centrale afferra singoli oggetti",
"MIDDLE_GRABS_SINGLE_HELP": "Afferrando con il modificatore click centrale (laser viola),\nsi afferrano i singoli overlay e non l'intero set",
"DIAGNOSTICS_REASON": {
"PACTL": "Necessario per il controllo di altoparlanti/microfono e il cambio della scheda audio",
"CAGE": "Consigliato in caso di incompatibilità con alcune applicazioni X11 (Modalità compatibilità)",
"XWAYLAND_SATELLITE": "Consigliato per eseguire applicazioni X11 (Xwayland) in modalità nativa",
"XDG_OPEN": "Necessario per aprire URL o giochi Steam"
},
"DIAGNOSTICS": {
"CPU_ACCESSIBLE_VRAM": "VRAM accessibile dalla CPU: {NUM} MiB",
"TITLE": "Diagnostica",
"ENABLE_REBAR": "Abilita Resizable BAR nelle impostazioni del BIOS per prestazioni VR ottimali"
},
"TUTORIAL": "Tutorial",
"RESTART_WAYVR": "Riavvia WayVR",
"CLOSE_WAYVR": "Chiudi WayVR"
"LENS_SEPARATION_MM": "Distanza lenti (mm)"
},
"APPLICATION_LAUNCHER": "Avvio applicazioni",
"APPLICATION_STARTED": "Applicazione avviata",
@@ -280,8 +264,7 @@
"SETTINGS": "Impostazioni audio",
"SPEAKERS": "Altoparlanti",
"SPEAKERS_SET_SUCCESSFULLY": "Altoparlanti impostati correttamente",
"VOLUME": "Volume",
"SWITCH_TO_THIS_DEVICE": "Passa a questo dispositivo"
"VOLUME": "Volume"
},
"CLOSE_WINDOW": "Chiudi finestra",
"CREATION_DATE": "Data di creazione",
@@ -366,15 +349,5 @@
"ADD_TO_FAVOURITES": "Aggiungi alle applicazioni fissate",
"ADD_TO_FAVOURITES_HINT": "Salva il preset e fissalo al cassetto delle app",
"REMOVE_FROM_FAVOURITES": "Rimuovi dalle app fissate",
"EMPTY": "Vuoto",
"INSTALLED": "Installato",
"NOT_INSTALLED": "Non installato",
"POWER_OPTIONS": {
"TITLE": "Opzioni di alimentazione",
"SHUTDOWN": "Spegni",
"SUSPEND": "Sospendi",
"SHUTDOWN_ARE_YOU_SURE": "Sei sicuro di voler spegnere il computer?"
},
"YES": "Sì",
"NO": "No"
"EMPTY": "Vuoto"
}
+8 -35
View File
@@ -94,13 +94,10 @@
"SCROLL": "スクロール*"
},
"LEFT": "左手",
"RIGHT": "右手",
"RIGHT": "右",
"COMPONENT": "入力形式",
"SUBPATH": "操作ボタン",
"THRESHOLD": "上の値を超えると有効\n下の値を下回ると無効",
"CHORD": "コード",
"CHORD_ADD_INPUT": "コード入力の追加",
"CHORD_INPUT_HAND": "入力側の手"
"THRESHOLD": "上の値を超えると有効\n下の値を下回ると無効"
},
"BROWSE_ONLINE_CATALOG": "オンラインカタログを見る...",
"BROWSE_SKYMAPS": "スカイマップを選択",
@@ -197,6 +194,8 @@
"SAVE_PLAYSPACE_CENTER_HELP": "現在のプレイスペースを、プレイスペースリセットのバインディングに使用する。",
"RESET_PLAYSPACE_CENTER": "プレイスペースの中心をリセット",
"RESET_PLAYSPACE_CENTER_HELP": "設定済みのプレイスペースの中心点をリセットする。",
"RESTART_SOFTWARE": "ソフトウェアを再起動",
"RESTART_SOFTWARE_HELP": "再起動が必要な設定を適用する",
"ROUND_MULTIPLIER": "UIの角の丸み",
"SCREEN_RENDER_DOWN": "低解像度で画面をレンダリング",
"SCREEN_RENDER_DOWN_HELP": "高解像度ディスプレイでのジャギーを軽減する",
@@ -206,6 +205,7 @@
"SNAP_ANGLE_DEG_HELP": "掴んでいるウィンドウが、ピッチ軸とロール軸でこの角度にスナップします。",
"ENABLE_WATCH": "ウォッチを有効にする",
"SETS_ON_WATCH": "ウォッチにセットを表示",
"SHOW_WELCOME_SCREEN": "ウェルカムスクリーンを表示",
"SKYBOX": "スカイマップ",
"SKYMAP_ALREADY_DOWNLOADED": "このスカイマップはすでにダウンロード済みです。操作を選択してください。",
"SPACE_DRAG": "プレイスペース",
@@ -247,23 +247,7 @@
"STATISTICS": "統計",
"ROTATIONS": "HMD回転数",
"SESSION_TIME": "セッション時間",
"LENS_SEPARATION_MM": "レンズ間隔 (mm)",
"MIDDLE_GRABS_SINGLE": "中クリック修飾キーで個別に掴む",
"MIDDLE_GRABS_SINGLE_HELP": "中クリック修飾キー(紫色のレーザー)で掴むと、\nセット全体ではなく個別のオーバーレイを掴むことができる",
"DIAGNOSTICS_REASON": {
"PACTL": "スピーカー・マイクの制御およびオーディオカードの切り替えに必要",
"CAGE": "一部のX11アプリで互換性問題が発生する場合に推奨(互換モード)",
"XWAYLAND_SATELLITE": "X11 (Xwayland) アプリケーションをネイティブモードで動作させたい場合に推奨",
"XDG_OPEN": "URLやSteamゲームの起動に必要"
},
"DIAGNOSTICS": {
"CPU_ACCESSIBLE_VRAM": "CPUアクセス可能VRAM: {NUM} MiB",
"TITLE": "診断",
"ENABLE_REBAR": "VRパフォーマンスを最適化するため、BIOS設定でresizable BARを有効にして下さい"
},
"TUTORIAL": "チュートリアル",
"RESTART_WAYVR": "WayVRを再起動",
"CLOSE_WAYVR": "WayVRを閉じる"
"LENS_SEPARATION_MM": "レンズ間隔 (mm)"
},
"APPLICATION_LAUNCHER": "アプリランチャー",
"APPLICATION_STARTED": "アプリが起動しました",
@@ -280,8 +264,7 @@
"SETTINGS": "オーディオ設定",
"SPEAKERS": "スピーカー",
"SPEAKERS_SET_SUCCESSFULLY": "スピーカーを設定完了",
"VOLUME": "音量",
"SWITCH_TO_THIS_DEVICE": "このデバイスに切り替え"
"VOLUME": "音量"
},
"CLOSE_WINDOW": "ウィンドウを閉じる",
"CREATION_DATE": "作成日",
@@ -366,15 +349,5 @@
"ADD_TO_FAVOURITES": "固定済みアプリに追加",
"ADD_TO_FAVOURITES_HINT": "プリセットを保存し、アプリドロワーに固定する",
"REMOVE_FROM_FAVOURITES": "固定済みアプリから外す",
"EMPTY": "なし",
"INSTALLED": "インストール済み",
"NOT_INSTALLED": "未インストール",
"POWER_OPTIONS": {
"TITLE": "電源オプション",
"SHUTDOWN": "シャットダウン",
"SUSPEND": "サスペンド",
"SHUTDOWN_ARE_YOU_SURE": "コンピューターをシャットダウンしてもよろしいですか?"
},
"YES": "はい",
"NO": "いいえ"
"EMPTY": "なし"
}
+7 -34
View File
@@ -97,10 +97,7 @@
"RIGHT": "Prawy",
"COMPONENT": "Typ aktywacji",
"SUBPATH": "Aktywujący sterownik",
"THRESHOLD": "Aktywuj powyżej górnej wartości\nDeaktywuj poniżej dolnej wartości",
"CHORD": "Akord",
"CHORD_ADD_INPUT": "Dodaj wejście do akordu",
"CHORD_INPUT_HAND": "Ręka sterująca"
"THRESHOLD": "Aktywuj powyżej górnej wartości\nDeaktywuj poniżej dolnej wartości"
},
"BROWSE_ONLINE_CATALOG": "Przeglądaj katalog online...",
"BROWSE_SKYMAPS": "Przeglądaj skymapy",
@@ -197,6 +194,8 @@
"SAVE_PLAYSPACE_CENTER_HELP": "Użyj aktualnej przestrzeni gry dla przypisania resetowania przestrzeni.",
"RESET_PLAYSPACE_CENTER": "Zresetuj centrum przestrzeni gry",
"RESET_PLAYSPACE_CENTER_HELP": "Resetuj aktualny środek przestrzeni gry.",
"RESTART_SOFTWARE": "Uruchom ponownie program",
"RESTART_SOFTWARE_HELP": "Zastosuj ustawienia wymagające restartu",
"ROUND_MULTIPLIER": "Zaokrąglenie krawędzi UI",
"SCREEN_RENDER_DOWN": "Renderuj ekran w niższej rozdzielczości",
"SCREEN_RENDER_DOWN_HELP": "Pomaga zredukować aliasing na ekranach o wysokiej rozdzielczości",
@@ -206,6 +205,7 @@
"SNAP_ANGLE_DEG_HELP": "Chwycone okna przyciągają się do tego kąta na osi pochylenia i obrotu.",
"ENABLE_WATCH": "Włącz zegarek",
"SETS_ON_WATCH": "Zestawy na zegarku",
"SHOW_WELCOME_SCREEN": "Pokaż ekran powitalny",
"SKYBOX": "Skymap",
"SKYMAP_ALREADY_DOWNLOADED": "Ta skymapa jest już pobrana. Wybierz żądaną akcję.",
"SPACE_DRAG": "Przestrzeń gry",
@@ -247,23 +247,7 @@
"STATISTICS": "Statystyki",
"ROTATIONS": "Obroty HMD",
"SESSION_TIME": "Czas sesji",
"LENS_SEPARATION_MM": "Rozstaw soczewek (mm)",
"MIDDLE_GRABS_SINGLE": "Modyfikator środkowego przycisku chwyta pojedyncze obiekty",
"MIDDLE_GRABS_SINGLE_HELP": "Chwytanie z modyfikatorem środkowego przycisku myszy (fioletowy laser)\npochwyci pojedyncze nakładki, a nie cały zestaw",
"DIAGNOSTICS_REASON": {
"PACTL": "Wymagane do sterowania głośnikami/mikrofonem i przełączania kart dźwiękowych",
"CAGE": "Zalecane w przypadku niekompatybilności z niektórymi aplikacjami X11 (tryb kompatybilności)",
"XWAYLAND_SATELLITE": "Zalecane, jeśli chcesz uruchamiać aplikacje X11 (Xwayland) w trybie natywnym",
"XDG_OPEN": "Wymagane do otwierania linków URL lub gier Steam"
},
"DIAGNOSTICS": {
"CPU_ACCESSIBLE_VRAM": "VRAM dostępny dla CPU: {NUM} MiB",
"TITLE": "Diagnostyka",
"ENABLE_REBAR": "Włącz resizable BAR w ustawieniach BIOS, aby uzyskać optymalną wydajność VR"
},
"TUTORIAL": "Samouczek",
"RESTART_WAYVR": "Zrestartuj WayVR",
"CLOSE_WAYVR": "Zamknij WayVR"
"LENS_SEPARATION_MM": "Rozstaw soczewek (mm)"
},
"APPLICATION_LAUNCHER": "Launcher aplikacji",
"APPLICATION_STARTED": "Uruchomiono aplikację",
@@ -280,8 +264,7 @@
"SETTINGS": "Ustawienia dźwięku",
"SPEAKERS": "Głośniki",
"SPEAKERS_SET_SUCCESSFULLY": "Głośniki ustawione pomyślnie",
"VOLUME": "Głośność",
"SWITCH_TO_THIS_DEVICE": "Przełącz na to urządzenie"
"VOLUME": "Głośność"
},
"CLOSE_WINDOW": "Zamknij okno",
"CREATION_DATE": "Data utworzenia",
@@ -366,15 +349,5 @@
"ADD_TO_FAVOURITES": "Przypnij aplikację",
"ADD_TO_FAVOURITES_HINT": "Zapisz preset i przypnij go do szuflady aplikacji",
"REMOVE_FROM_FAVOURITES": "Usuń z przypiętych aplikacji",
"EMPTY": "Puste",
"INSTALLED": "Zainstalowano",
"NOT_INSTALLED": "Nie zainstalowano",
"POWER_OPTIONS": {
"TITLE": "Opcje zasilania",
"SHUTDOWN": "Wyłącz",
"SUSPEND": "Uśpij",
"SHUTDOWN_ARE_YOU_SURE": "Czy na pewno chcesz wyłączyć komputer?"
},
"YES": "Tak",
"NO": "Nie"
"EMPTY": "Puste"
}
+7 -34
View File
@@ -97,10 +97,7 @@
"RIGHT": "右侧",
"COMPONENT": "触发类型",
"SUBPATH": "激活控件",
"THRESHOLD": "高于上限值时激活\n低于下限值时取消激活",
"CHORD": "组合键",
"CHORD_ADD_INPUT": "添加组合键输入",
"CHORD_INPUT_HAND": "输入手"
"THRESHOLD": "高于上限值时激活\n低于下限值时取消激活"
},
"BROWSE_ONLINE_CATALOG": "浏览在线目录...",
"BROWSE_SKYMAPS": "浏览天空盒",
@@ -197,6 +194,8 @@
"SAVE_PLAYSPACE_CENTER_HELP": "将当前的活动空间设置为重置活动空间的绑定点。",
"RESET_PLAYSPACE_CENTER": "重置活动空间中心",
"RESET_PLAYSPACE_CENTER_HELP": "重置之前设置的活动空间中心。",
"RESTART_SOFTWARE": "重启软件",
"RESTART_SOFTWARE_HELP": "应用需要重启才能生效的设置",
"ROUND_MULTIPLIER": "UI 边缘圆角",
"SCREEN_RENDER_DOWN": "低分辨率渲染屏幕",
"SCREEN_RENDER_DOWN_HELP": "有助于减轻高分辨率屏幕上的锯齿现象",
@@ -206,6 +205,7 @@
"SNAP_ANGLE_DEG_HELP": "抓取窗口在俯仰轴和翻滚轴上按此角度对齐。",
"ENABLE_WATCH": "启用手表",
"SETS_ON_WATCH": "手表显示套装",
"SHOW_WELCOME_SCREEN": "显示欢迎界面",
"SKYBOX": "天空图",
"SKYMAP_ALREADY_DOWNLOADED": "此天空盒已下载。请选择操作。",
"SPACE_DRAG": "活动空间",
@@ -247,23 +247,7 @@
"STATISTICS": "统计数据",
"ROTATIONS": "HMD 旋转次数",
"SESSION_TIME": "会话时间",
"LENS_SEPARATION_MM": "瞳距 (mm)",
"MIDDLE_GRABS_SINGLE": "中键点击修饰键抓取单个对象",
"MIDDLE_GRABS_SINGLE_HELP": "使用中键点击修饰键(紫色激光)抓取时\n将抓取单个叠加层,而非整个集合",
"DIAGNOSTICS_REASON": {
"PACTL": "用于扬声器/麦克风控制及声卡切换",
"CAGE": "若部分 X11 应用程序不兼容,建议开启(兼容模式)",
"XWAYLAND_SATELLITE": "如果你想以原生模式运行 X11 (Xwayland) 应用程序,建议开启此项",
"XDG_OPEN": "打开 URL 或 Steam 游戏所需"
},
"DIAGNOSTICS": {
"CPU_ACCESSIBLE_VRAM": "CPU 可访问 VRAM: {NUM} MiB",
"TITLE": "诊断",
"ENABLE_REBAR": "请在 BIOS 设置中启用 Resizable BAR 以获得最佳 VR 性能"
},
"TUTORIAL": "教程",
"RESTART_WAYVR": "重启 WayVR",
"CLOSE_WAYVR": "关闭 WayVR"
"LENS_SEPARATION_MM": "瞳距 (mm)"
},
"APPLICATION_LAUNCHER": "应用启动器",
"APPLICATION_STARTED": "应用程序已启动",
@@ -280,8 +264,7 @@
"SETTINGS": "音频设置",
"SPEAKERS": "扬声器",
"SPEAKERS_SET_SUCCESSFULLY": "扬声器设置成功",
"VOLUME": "音量",
"SWITCH_TO_THIS_DEVICE": "切换至此设备"
"VOLUME": "音量"
},
"CLOSE_WINDOW": "关闭窗口",
"CREATION_DATE": "创建日期",
@@ -366,15 +349,5 @@
"ADD_TO_FAVOURITES": "添加到固定应用",
"ADD_TO_FAVOURITES_HINT": "保存预设并将其固定到应用抽屉",
"REMOVE_FROM_FAVOURITES": "从固定应用中移除",
"EMPTY": "暂无",
"INSTALLED": "已安装",
"NOT_INSTALLED": "未安装",
"POWER_OPTIONS": {
"TITLE": "电源选项",
"SHUTDOWN": "关机",
"SUSPEND": "暂停",
"SHUTDOWN_ARE_YOU_SURE": "确定要关机吗?"
},
"YES": "确定",
"NO": "否"
"EMPTY": "暂无"
}
+11 -114
View File
@@ -1,11 +1,11 @@
use std::{path::PathBuf, process::Command, rc::Rc};
use std::{path::PathBuf, rc::Rc};
use chrono::Timelike;
use glam::Vec2;
use strum::EnumCount;
use wgui::{
assets::{AssetPathRef, AssetProvider},
components::{ComponentTrait, button::ComponentButton},
components::button::ComponentButton,
event::{CallbackDataCommon, StyleSetRequest},
font_config::WguiFontConfig,
globals::WguiGlobals,
@@ -17,10 +17,7 @@ use wgui::{
task::Tasks,
theme::WguiTheme,
widget::{label::WidgetLabel, rectangle::WidgetRectangle, sprite::WidgetSprite},
windowing::{
context_menu::{self, ContextMenu},
window::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra, WguiWindowPlacement},
},
windowing::window::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra, WguiWindowPlacement},
};
use wlx_common::{
async_executor::AsyncExecutor,
@@ -38,11 +35,10 @@ use crate::{
settings::TabSettings, welcome::TabWelcome,
},
util::{
is_exec_installed,
popup_manager::{MountPopupOnceParams, PopupHolder, PopupManager, PopupManagerParams},
popup_manager::{MountPopupOnceParams, PopupManager, PopupManagerParams},
toast_manager::ToastManager,
},
views::{self, ViewUpdateParams},
views,
};
pub struct FrontendWidgets {
@@ -78,11 +74,9 @@ pub struct Frontend<T> {
toast_manager: ToastManager,
timestep: Timestep,
sounds_to_play: Vec<SoundType>,
context_menu_power: ContextMenu,
window_audio_settings: WguiWindow,
view_audio_settings: Option<views::audio_settings::View>,
popup_dialog_box: PopupHolder<views::dialog_box::View>,
}
pub struct FrontendUpdateParams<'a, T> {
@@ -122,13 +116,11 @@ pub enum FrontendTask {
MountPopupOnce(MountPopupOnceParams),
RefreshPopupManager,
ShowAudioSettings,
ShowPowerSettings(WidgetID /* source widget */),
UpdateAudioSettingsView,
RecenterPlayspace,
PushToast(Translation),
PlaySound(SoundType),
OpenURL(Rc<str>),
RunCommand(String /* program */, Vec<String> /* args */),
HideDashboard,
MarkTutorialGraduated,
}
@@ -210,11 +202,9 @@ impl<T: 'static> Frontend<T> {
popup_manager,
toast_manager,
window_audio_settings: WguiWindow::default(),
context_menu_power: Default::default(),
view_audio_settings: None,
executor: params.executor,
sounds_to_play: Vec::new(),
popup_dialog_box: Default::default(),
};
// init some things first
@@ -230,7 +220,7 @@ impl<T: 'static> Frontend<T> {
self.sounds_to_play.push(sound_type);
}
fn play_sound(&mut self, audio: &mut Box<dyn audio::AudioProvider>, sound_type: SoundType) -> anyhow::Result<()> {
fn play_sound(&mut self, audio_system: &mut audio::AudioSystem, sound_type: SoundType) -> anyhow::Result<()> {
let mut assets = self.globals.assets_builtin();
let path = match sound_type {
@@ -246,7 +236,7 @@ impl<T: 'static> Frontend<T> {
};
let sample = audio::AudioSample::from_mp3(&sound_bytes)?;
audio.play_raw_sample(&sample);
audio_system.play_sample(&sample);
Ok(())
}
@@ -274,88 +264,24 @@ impl<T: 'static> Frontend<T> {
pub fn process_update(
&mut self,
res: FrontendUpdateResult,
audio: &mut Box<dyn audio::AudioProvider>,
audio_system: &mut audio::AudioSystem,
audio_sample_player: &mut audio::SamplePlayer,
) -> anyhow::Result<()> {
for sound_type in res.sounds_to_play {
self.play_sound(audio, sound_type)?;
self.play_sound(audio_system, sound_type)?;
}
audio.play_wgui_samples(res.layout_result.sounds_to_play);
audio_sample_player.play_wgui_samples(audio_system, res.layout_result.sounds_to_play);
Ok(())
}
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("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(FrontendTask::RunCommand(
String::from("loginctl"),
vec![String::from("poweroff")],
));
}
}),
},
);
}
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);
}
"close" => {
self.interface.close(data);
}
"suspend" => {
self.tasks.push(FrontendTask::RunCommand(
String::from("loginctl"),
vec![String::from("suspend")],
));
}
"shutdown" => {
self.action_poweroff_confirm();
}
_ => {
unreachable!()
}
}
}
fn tick(&mut self, params: FrontendUpdateParams<T>) -> anyhow::Result<FrontendUpdateResult> {
// fixme: timer events instead of this thing
if self.ticks.is_multiple_of(1000) {
self.update_time(params.data)?;
}
// TODO: refactor: simplify update function (no configs or T are required in most cases)
let mut config_change_kind = None;
self.popup_dialog_box.update(&mut ViewUpdateParams {
layout: &mut self.layout,
executor: &self.executor,
general_config: self.interface.general_config(params.data),
config_change_kind: &mut config_change_kind,
})?;
if let context_menu::TickResult::Action(action) = self.context_menu_power.tick(&mut self.layout, &mut self.state)? {
self.process_power_action(&action, params.data);
}
{
// always 30 times per second
while self.timestep.on_tick() {
@@ -446,14 +372,12 @@ impl<T: 'static> Frontend<T> {
FrontendTask::MountPopupOnce(popup_params) => self.mount_popup_once(popup_params, params.data)?,
FrontendTask::RefreshPopupManager => self.refresh_popup_manager()?,
FrontendTask::ShowAudioSettings => self.action_show_audio_settings()?,
FrontendTask::ShowPowerSettings(pos) => self.action_show_power_settings(pos)?,
FrontendTask::UpdateAudioSettingsView => self.action_update_audio_settings()?,
FrontendTask::RecenterPlayspace => self.action_recenter_playspace(params.data)?,
FrontendTask::PushToast(content) => self.toast_manager.push(content),
FrontendTask::PlaySound(sound_type) => self.queue_play_sound(sound_type),
FrontendTask::HideDashboard => self.action_hide_dashboard(params.data),
FrontendTask::OpenURL(url) => self.action_open_url(url),
FrontendTask::RunCommand(program, args) => self.action_run_command(program, args),
FrontendTask::MarkTutorialGraduated => self.action_tutorial_graduated(params.data),
};
Ok(())
@@ -580,11 +504,6 @@ impl<T: 'static> Frontend<T> {
FrontendTask::ShowAudioSettings,
);
let btn_power = self.state.fetch_component_as::<ComponentButton>("btn_power")?;
self
.tasks
.handle_button(&btn_power, FrontendTask::ShowPowerSettings(btn_power.base().get_id()));
// "Recenter playspace" bottom bar button
self.tasks.handle_button(
&self.state.fetch_component_as::<ComponentButton>("btn_recenter")?,
@@ -594,18 +513,6 @@ impl<T: 'static> Frontend<T> {
Ok(())
}
fn action_show_power_settings(&mut self, source_id: WidgetID) -> anyhow::Result<()> {
self.context_menu_power.open(context_menu::OpenParams {
on_custom_attribs: None,
position: source_id.into(),
blueprint: context_menu::Blueprint::Template {
template_name: "context_menu_power_options".into(),
template_params: Default::default(),
},
});
Ok(())
}
fn action_show_audio_settings(&mut self) -> anyhow::Result<()> {
self.window_audio_settings.open(&mut WguiWindowParams {
position: Vec2::new(64.0, 64.0),
@@ -670,14 +577,4 @@ impl<T: 'static> Frontend<T> {
url
))));
}
fn action_run_command(&mut self, command: String, args: Vec<String>) {
if let Err(e) = Command::new(command).args(args).spawn() {
self
.tasks
.push(FrontendTask::PushToast(Translation::from_raw_text_string(format!(
"Failed to launch command: {e}"
))))
};
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
use std::{marker::PhantomData, rc::Rc};
use wgui::{
assets::AssetPathRef,
assets::{AssetPathRef},
components::button::ComponentButton,
globals::WguiGlobals,
layout::{Layout, WidgetID},
+2 -2
View File
@@ -1,7 +1,7 @@
use std::{marker::PhantomData, rc::Rc};
use wgui::{
animation::{Animation, AnimationDuration, AnimationEasing},
animation::{Animation, AnimationEasing},
assets::AssetPathRef,
color::{WguiColor, WguiColorName, WguiNamedColor},
components::{ComponentTrait, button::ComponentButton},
@@ -61,7 +61,7 @@ fn get_supporter_anim(
Animation::new_ex(
btn.base().get_id(),
1234,
AnimationDuration::SecondsFixed(8.0),
480,
AnimationEasing::Linear,
Box::new(move |common, data| {
let opacity_in = f32::clamp(data.pos * FADE_SPEED, 0.0, 1.0);
+1 -1
View File
@@ -1,7 +1,7 @@
use std::{collections::HashMap, marker::PhantomData, rc::Rc};
use wgui::{
assets::AssetPathRef,
assets::{AssetPathRef},
components::{
bar_graph::{ComponentBarGraph, ValueCell},
button::ComponentButton,
+4 -47
View File
@@ -49,49 +49,6 @@ pub fn options_stat_row(mp: &mut MacroParams, parent: WidgetID, translation: &st
mp.parser_state.get_widget_id(&id)
}
pub fn options_diagnostics_row(
mp: &mut MacroParams,
parent: WidgetID,
title: &str,
tooltip_str: &str,
checked: bool,
) -> anyhow::Result<()> {
let mut params = TemplateParams::new();
params.insert("title", title);
params.insert("tooltip_str", tooltip_str);
params.insert("color", if checked { "success" } else { "danger" });
params.insert(
"sprite",
if checked {
"@/dashboard/check.svg"
} else {
"@/dashboard/cross.svg"
},
);
mp.parser_state
.instantiate_template(mp.doc_params, "DiagnosticsRow", mp.layout, parent, params)?;
Ok(())
}
pub fn options_diagnostics_drm_card(
mp: &mut MacroParams,
parent: WidgetID,
name: &str,
info: &str,
) -> anyhow::Result<()> {
let mut params = TemplateParams::new();
params.insert("name", name);
params.insert("info", info);
mp.parser_state
.instantiate_template(mp.doc_params, "DiagnosticsDrmCard", mp.layout, parent, params)?;
Ok(())
}
pub fn options_checkbox(mp: &mut MacroParams, parent: WidgetID, setting: SettingType) -> anyhow::Result<()> {
let id = mp.idx.to_string();
mp.idx += 1;
@@ -333,7 +290,7 @@ where
let tasks = mp.tasks.clone();
move |_common, e: ButtonClickEvent| {
tasks.push(Task::OpenContextMenu(
e.mouse_pos_absolute.into(),
e.mouse_pos_absolute.unwrap_or_default(),
EnumType::VARIANTS
.iter()
.filter_map(|item| {
@@ -360,12 +317,12 @@ where
let text = &title.text;
let translated = if title.translated { "1" } else { "0" };
Some(context_menu::Cell::Button(context_menu::Button {
Some(context_menu::Cell {
action_name: Some(format!("{setting_str};{id};{value};{text};{translated}").into()),
title,
tooltip,
..Default::default()
}))
attribs: vec![],
})
})
.collect(),
));
+8 -12
View File
@@ -1,7 +1,8 @@
use glam::Vec2;
use std::{marker::PhantomData, rc::Rc, str::FromStr};
use strum::{AsRefStr, EnumProperty, EnumString};
use wgui::{
assets::AssetPathRef,
assets::{AssetPathRef},
color::WguiColorName,
components::tabs::ComponentTabs,
event::StyleSetRequest,
@@ -34,7 +35,6 @@ use crate::{
mod macros;
mod tab_autostart_apps;
mod tab_controls;
mod tab_diagnostics;
mod tab_features;
mod tab_look_and_feel;
mod tab_misc;
@@ -53,7 +53,6 @@ pub(crate) enum TabNameEnum {
Skybox,
SpaceDrag,
Statistics,
Diagnostics,
Troubleshooting,
}
@@ -68,7 +67,6 @@ impl TabNameEnum {
"skybox" => Some(TabNameEnum::Skybox),
"space_drag" => Some(TabNameEnum::SpaceDrag),
"statistics" => Some(TabNameEnum::Statistics),
"diagnostics" => Some(TabNameEnum::Diagnostics),
"troubleshooting" => Some(TabNameEnum::Troubleshooting),
_ => None,
}
@@ -80,10 +78,11 @@ pub(crate) enum Task {
ClearPipewireTokens,
ClearSavedState,
DeleteAllConfigs,
OpenContextMenu(context_menu::Position, Vec<context_menu::Cell>),
OpenContextMenu(Vec2, Vec<context_menu::Cell>),
RemoveAutostartApp(Rc<str>),
ResetPlayspace,
ResetPlayspaceCenter,
RestartSoftware,
SavePlayspaceCenter,
SetTab(TabNameEnum),
SettingUpdated(SettingType),
@@ -229,6 +228,10 @@ impl<T> Tab<T> for TabSettings<T> {
frontend.interface.playspace_task(data, DashPlayspaceTask::Reset)?;
return Ok(());
}
Task::RestartSoftware => {
frontend.interface.restart(data);
return Ok(());
}
Task::OpenContextMenu(position, cells) => {
self.context_menu.open(context_menu::OpenParams {
on_custom_attribs: None,
@@ -344,7 +347,6 @@ pub(crate) enum SettingType {
NotificationsEnabled,
NotificationsSoundEnabled,
OpaqueBackground,
MiddleGrabsSingle,
MouseAcceleration,
PointerLerpFactor,
MouseSpeed,
@@ -401,7 +403,6 @@ impl SettingType {
Self::NotificationsEnabled => &mut config.notifications_enabled,
Self::NotificationsSoundEnabled => &mut config.notifications_sound_enabled,
Self::OpaqueBackground => &mut config.opaque_background,
Self::MiddleGrabsSingle => &mut config.middle_grabs_single,
Self::MouseAcceleration => &mut config.wvr_mouse_acceleration,
Self::ScreenRenderDown => &mut config.screen_render_down,
Self::SetsOnWatch => &mut config.sets_on_watch,
@@ -552,7 +553,6 @@ impl SettingType {
Self::NotificationsEnabled => Ok("APP_SETTINGS.NOTIFICATIONS_ENABLED"),
Self::NotificationsSoundEnabled => Ok("APP_SETTINGS.NOTIFICATIONS_SOUND_ENABLED"),
Self::OpaqueBackground => Ok("APP_SETTINGS.OPAQUE_BACKGROUND"),
Self::MiddleGrabsSingle => Ok("APP_SETTINGS.MIDDLE_GRABS_SINGLE"),
Self::MouseSpeed => Ok("APP_SETTINGS.POINTER_SPEED"),
Self::MouseAcceleration => Ok("APP_SETTINGS.POINTER_ACCELERATION"),
Self::PointerLerpFactor => Ok("APP_SETTINGS.POINTER_LERP_FACTOR"),
@@ -602,7 +602,6 @@ impl SettingType {
Self::InputEmulationMethod => Some("APP_SETTINGS.INPUT_EMULATION_METHOD_HELP"),
Self::KeyboardMiddleClick => Some("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK_HELP"),
Self::LeftHandedMouse => Some("APP_SETTINGS.LEFT_HANDED_MOUSE_HELP"),
Self::MiddleGrabsSingle => Some("APP_SETTINGS.MIDDLE_GRABS_SINGLE_HELP"),
Self::MouseAcceleration => Some("APP_SETTINGS.POINTER_ACCELERATION_HELP"),
Self::ScreenRenderDown => Some("APP_SETTINGS.SCREEN_RENDER_DOWN_HELP"),
Self::SnapAngleDeg => Some("APP_SETTINGS.SNAP_ANGLE_DEG_HELP"),
@@ -750,9 +749,6 @@ impl<T> TabSettings<T> {
TabNameEnum::Statistics => {
self.current_tab = Some(Box::new(tab_statistics::State::mount(settings_mount_params)?));
}
TabNameEnum::Diagnostics => {
self.current_tab = Some(Box::new(tab_diagnostics::State::mount(settings_mount_params)?));
}
}
Ok(())
@@ -100,7 +100,6 @@ impl State {
options_dropdown::<wlx_common::config::HandsfreeAltTab>(par.mp, c, &SettingType::HandsfreeAltTab)?;
options_checkbox(par.mp, c, SettingType::FocusFollowsMouseMode)?;
options_checkbox(par.mp, c, SettingType::LeftHandedMouse)?;
options_checkbox(par.mp, c, SettingType::MiddleGrabsSingle)?;
options_checkbox(par.mp, c, SettingType::AllowSliding)?;
options_checkbox(par.mp, c, SettingType::InvertScrollDirectionX)?;
options_checkbox(par.mp, c, SettingType::InvertScrollDirectionY)?;
@@ -1,178 +0,0 @@
use wgui::layout::WidgetID;
use crate::{
tab::settings::{
SettingsMountParams, SettingsTab,
macros::{MacroParams, options_category, options_diagnostics_drm_card, options_diagnostics_row},
},
util::is_exec_installed,
};
pub struct State {}
impl SettingsTab for State {}
#[derive(Default)]
struct LspciInfo {
// slot: String,
// class: String,
vendor: String,
device: String,
// s_vendor: String,
}
fn lspci_info(vendor: &str /* hex */, device: &str /* hex */) -> Option<LspciInfo> {
// example output for "lspci -vmmd 1002:747e":
//
// Slot: 0b:00.0
// Class: VGA compatible controller
// Vendor: Advanced Micro Devices, Inc. [AMD/ATI]
// Device: Navi 32 [Radeon RX 7700 XT / 7800 XT]
// SVendor: Sapphire Technology Limited
// SDevice: Device 475a
// Rev: c8
// ProgIf: 00
let mut cmd = std::process::Command::new("lspci");
cmd.arg("-vmmd");
cmd.arg(format!("{}:{}", vendor, device));
let res = cmd.output().ok()?;
if !res.status.success() {
return None;
}
let mut info = LspciInfo::default();
let stdout = str::from_utf8(&res.stdout).ok()?;
for line in stdout.lines() {
let Some((key, value)) = line.split_once(":") else {
continue;
};
if key == "Device" {
info.device = String::from(value.trim());
}
}
Some(info)
}
fn mount_installed_path(
mp: &mut MacroParams,
parent: WidgetID,
exec: &str,
reason_translation: Option<&str>,
) -> anyhow::Result<()> {
let title = if is_exec_installed(exec) {
"INSTALLED"
} else {
"NOT_INSTALLED"
};
let title_translated = mp.layout.state.globals.i18n().translate(title);
let tooltip = if let Some(reason) = reason_translation {
format!(
"{}\n{}",
title_translated,
mp.layout.state.globals.i18n().translate(reason)
)
} else {
title_translated.to_string()
};
options_diagnostics_row(mp, parent, exec, &tooltip, is_exec_installed(exec))?;
Ok(())
}
fn read_sysfs_param(device_path: &str, param: &str) -> anyhow::Result<String> {
let string = std::fs::read_to_string(format!("{}/{}", device_path, param))?;
Ok(string.trim().into())
}
fn read_sysfs_param_int(device_path: &str, param: &str) -> anyhow::Result<u64> {
Ok(read_sysfs_param(device_path, param)?.parse::<u64>()?)
}
fn mount_drm_card(mp: &mut MacroParams, parent: WidgetID, device_path: &str) -> anyhow::Result<()> {
let vendor = read_sysfs_param(device_path, "vendor")?;
let device = read_sysfs_param(device_path, "device")?;
let vram_total = read_sysfs_param_int(device_path, "mem_info_vram_total").ok();
let vis_vram_total = read_sysfs_param_int(device_path, "mem_info_vis_vram_total").ok();
let name = match lspci_info(&vendor, &device) {
Some(info) => {
format!("{} {}", info.vendor, info.device)
}
None => format!("{}:{}", vendor, device),
};
let info = if let Some(vram_total) = vram_total
&& let Some(vis_vram_total) = vis_vram_total
{
let mut s = mp.layout.state.globals.i18n().translate_and_replace(
"APP_SETTINGS.DIAGNOSTICS.CPU_ACCESSIBLE_VRAM",
(
"{NUM}",
&format!("{}/{}", vis_vram_total / 1024 / 1024, vram_total / 1024 / 1024),
),
);
if vis_vram_total != vram_total {
s += " [ALERT!]\nEnable Resizable BAR in the BIOS settings for optimal VR performance";
}
s
} else {
String::from("No info")
};
options_diagnostics_drm_card(mp, parent, &name, &info)?;
Ok(())
}
fn mount_drm_cards(mp: &mut MacroParams, parent: WidgetID) -> anyhow::Result<()> {
for file in std::fs::read_dir("/sys/class/drm/")?.flatten() {
let file_name = file.file_name();
let Some(file_name) = file_name.to_str() else {
continue;
};
if !file_name.starts_with("renderD") {
continue;
}
if let Err(e) = mount_drm_card(mp, parent, &format!("/sys/class/drm/{}/device", file_name)) {
log::error!("failed to mount drm card: {}", e);
}
}
Ok(())
}
impl State {
pub fn mount(par: SettingsMountParams) -> anyhow::Result<Self> {
let c = options_category(
par.mp,
par.id_parent,
"APP_SETTINGS.DIAGNOSTICS.TITLE",
"@/dashboard/diagnostics.svg",
)?;
mount_installed_path(par.mp, c, "pactl", Some("APP_SETTINGS.DIAGNOSTICS_REASON.PACTL"))?;
mount_installed_path(par.mp, c, "cage", Some("APP_SETTINGS.DIAGNOSTICS_REASON.CAGE"))?;
mount_installed_path(
par.mp,
c,
"xwayland-satellite",
Some("APP_SETTINGS.DIAGNOSTICS_REASON.XWAYLAND_SATELLITE"),
)?;
mount_installed_path(par.mp, c, "xdg-open", Some("APP_SETTINGS.DIAGNOSTICS_REASON.XDG_OPEN"))?;
mount_installed_path(par.mp, c, "steam", None)?;
mount_drm_cards(par.mp, c)?;
Ok(State {})
}
}
+15 -16
View File
@@ -328,33 +328,32 @@ fn whisper_models_dropdown(mp: &mut MacroParams, parent: WidgetID) -> anyhow::Re
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
},
})
.map(|(idx, item)| context_menu::Cell {
action_name: Some(format!(";{id};{}", item.file_name).into()),
title: Translation::from_raw_text_string(format!(
"{}{}",
item.display_name,
if downloaded_models.contains(&idx) { " ✅" } else { "" }
)),
tooltip: None,
attribs: vec![],
})
.collect::<Vec<_>>();
cells.insert(
0,
context_menu::Cell::Button(context_menu::Button {
context_menu::Cell {
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));
parent_tasks.push(ParentTask::OpenContextMenu(
e.mouse_pos_absolute.unwrap_or_default(),
cells,
));
Ok(())
}
}));
@@ -36,6 +36,13 @@ impl State {
"@/dashboard/circle.svg",
Task::DeleteAllConfigs,
)?;
options_danger_button(
par.mp,
c,
"APP_SETTINGS.RESTART_SOFTWARE",
"@/dashboard/refresh.svg",
Task::RestartSoftware,
)?;
Ok(State {})
}
}
-13
View File
@@ -7,16 +7,3 @@ pub mod steam_utils;
pub mod toast_manager;
pub mod wgui_simple;
pub mod whisper;
// works good enough
pub fn is_exec_installed(exec: &str) -> bool {
let mut cmd = std::process::Command::new("sh");
cmd.arg("-c");
cmd.arg(format!("command -v {}", exec));
let Ok(res) = cmd.output() else {
return false;
};
res.status.success()
}
+1 -6
View File
@@ -21,9 +21,6 @@ pub struct Volume {
#[serde(rename = "front-right")]
pub front_right: Option<VolumeChannel>,
#[serde(rename = "mono")]
pub mono: Option<VolumeChannel>,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
@@ -210,7 +207,7 @@ pub fn list_sources() -> anyhow::Result<Vec<Source>> {
// exclude all monitor sources
sources.retain(|source| match &source.properties.device_class {
Some(c) => c != "monitor",
None => true, /* unknown, pass it */
None => false,
});
Ok(sources)
@@ -245,8 +242,6 @@ pub fn get_source_volume(source: &Source) -> anyhow::Result<f32> {
front_left
} else if let Some(aux0) = &source.volume.aux0 {
aux0
} else if let Some(mono) = &source.volume.mono {
mono
} else {
return Ok(0.0); // fail silently
}
+10 -52
View File
@@ -3,9 +3,7 @@ use std::{
rc::{Rc, Weak},
};
use glam::Vec2;
use wgui::{
animation::{Animation, AnimationDuration, AnimationEasing},
assets::AssetPathRef,
components::button::ComponentButton,
event::{EventAlterables, StyleSetRequest},
@@ -14,7 +12,7 @@ use wgui::{
layout::{Layout, LayoutTask, LayoutTasks, WidgetID},
parser::{Fetchable, ParseDocumentParams, ParserState},
taffy::Rect,
widget::{WidgetObj, label::WidgetLabel},
widget::label::WidgetLabel,
};
use wlx_common::config::GeneralConfig;
@@ -253,13 +251,6 @@ impl State {
}
}
struct MountPopupPrepareResult {
popup_handle: PopupHandle,
id_content: WidgetID,
id_top_bar_contents: WidgetID,
id_popup_title: WidgetID,
}
impl PopupManager {
pub fn new(params: PopupManagerParams) -> Self {
Self {
@@ -282,7 +273,7 @@ impl PopupManager {
frontend_tasks: &FrontendTasks,
popup_title: &Translation,
popup_padding: PopupPadding,
) -> anyhow::Result<MountPopupPrepareResult> {
) -> anyhow::Result<(PopupHandle, WidgetID /* content widget ID */)> {
let doc_params = &ParseDocumentParams {
globals: globals.clone(),
path: AssetPathRef::BuiltIn("gui/view/popup_window.xml"),
@@ -292,7 +283,6 @@ impl PopupManager {
let id_root = state.get_widget_id("root")?;
let id_content = state.get_widget_id("content")?;
let id_top_bar_contents = state.get_widget_id("top_bar_contents")?;
let padding = match popup_padding {
PopupPadding::Normal => 16.0_f32,
@@ -304,11 +294,10 @@ impl PopupManager {
StyleSetRequest::Padding(Rect::length(padding)),
));
let id_popup_title = {
{
let mut label_title = state.fetch_widget_as::<WidgetLabel>(&layout.state, "popup_title")?;
label_title.set_text_simple(&mut globals.get(), popup_title.clone());
label_title.get_id()
};
}
let but_back = state.fetch_component_as::<ComponentButton>("but_back")?;
@@ -349,13 +338,7 @@ impl PopupManager {
});
frontend_tasks.push(FrontendTask::RefreshPopupManager);
Ok(MountPopupPrepareResult {
popup_handle,
id_content,
id_top_bar_contents,
id_popup_title,
})
Ok((popup_handle, id_content))
}
/// Mount a new popup on top of the existing popup stack.
@@ -373,43 +356,18 @@ impl PopupManager {
anyhow::bail!("mount_popup_once called more than once");
};
let res = self.mount_popup_prepare(globals, layout, frontend_tasks, &params.title, params.extra.padding)?;
// text fade-in
Animation::effect_label_fade_in(
res.id_popup_title,
AnimationDuration::Seconds(0.25),
AnimationEasing::OutQuad,
)
.submit_l(layout);
// top bar slide
Animation::effect_slide(
res.id_top_bar_contents,
AnimationDuration::Seconds(0.5),
AnimationEasing::OutQuint,
Vec2::new(10.0, 0.0),
)
.submit_l(layout);
// content slide
Animation::effect_slide(
res.id_content,
AnimationDuration::Seconds(0.5),
AnimationEasing::OutQuint,
Vec2::new(5.0, 0.0),
)
.submit_l(layout);
let (popup_handle, id_content) =
self.mount_popup_prepare(globals, layout, frontend_tasks, &params.title, params.extra.padding)?;
// mount user-set popup content
let closed_callback = on_content_func(PopupContentFuncData {
layout,
handle: res.popup_handle.clone(),
id_content: res.id_content,
handle: popup_handle.clone(),
id_content,
config,
})?;
res.popup_handle.state.borrow_mut().closed_callback = Some(closed_callback);
popup_handle.state.borrow_mut().closed_callback = Some(closed_callback);
Ok(())
}
+6 -9
View File
@@ -1,7 +1,7 @@
use glam::{Mat4, Vec3};
use std::{cell::RefCell, collections::VecDeque, rc::Rc};
use wgui::{
animation::{Animation, AnimationDuration, AnimationEasing},
animation::{Animation, AnimationEasing},
color::{WguiColor, WguiColorName},
components::tooltip::{TOOLTIP_BORDER_COLOR, TOOLTIP_COLOR},
i18n::Translation,
@@ -45,9 +45,7 @@ impl Drop for MountedToast {
}
}
fn def_toast_duration() -> AnimationDuration {
AnimationDuration::Seconds(2.5)
}
const TOAST_DURATION_TICKS: u32 = 150;
impl ToastManager {
pub fn new() -> Self {
@@ -118,9 +116,9 @@ impl ToastManager {
let (label, _) = layout.add_child(rect.id, label, taffy::Style { ..Default::default() })?;
// show-up animation
Animation::new(
layout.animations.add(Animation::new(
rect.id,
def_toast_duration(),
(TOAST_DURATION_TICKS as f32 * layout.state.theme.animation_mult) as u32,
AnimationEasing::Linear,
Box::new(move |common, data| {
let pos_showup = AnimationEasing::OutQuint.interpolate((data.pos * 4.0).min(1.0));
@@ -145,8 +143,7 @@ impl ToastManager {
);
common.alterables.mark_redraw();
}),
)
.submit_l(layout);
));
state.toast = Some(MountedToast {
id_root: root.id,
@@ -168,7 +165,7 @@ impl ToastManager {
if state.timeout == 0 {
state.toast = None;
state.timeout = def_toast_duration().to_ticks(layout.state.ticks_per_second, layout.state.theme.animation_mult);
state.timeout = TOAST_DURATION_TICKS;
// mount next
if let Some(content) = state.queue.pop_front() {
self.mount_toast(layout, &mut state, content)?;
+4 -5
View File
@@ -1,6 +1,6 @@
use glam::{Mat4, Vec2};
use wgui::{
animation::{Animation, AnimationDuration, AnimationEasing},
animation::{Animation, AnimationEasing},
assets::AssetPathRef,
color::WguiColorName,
components::{self, button::ButtonClickCallback},
@@ -145,9 +145,9 @@ pub fn create_loading(par: CreateLoadingParams) -> anyhow::Result<WidgetID> {
let id_root = data.get_widget_id("root")?;
let id_sprite_loading = data.get_widget_id("sprite_loading")?;
Animation::new(
par.layout.animations.add(Animation::new(
id_sprite_loading,
AnimationDuration::SecondsFixed(120.0), /* spin it for 120 seconds at most */
60 * 30, /* spin it for 30 seconds at most */
AnimationEasing::Linear,
Box::new(move |common, data| {
// spin it
@@ -161,8 +161,7 @@ pub fn create_loading(par: CreateLoadingParams) -> anyhow::Result<WidgetID> {
}
common.alterables.mark_redraw();
}),
)
.submit_l(par.layout);
));
Ok(id_root)
}
+13 -20
View File
@@ -1,7 +1,7 @@
use std::rc::Rc;
use wgui::{
assets::{AssetPathRef, AssetPathSource},
assets::AssetPathRef,
color::{WguiColor, WguiColorName},
components::{
self,
@@ -12,9 +12,9 @@ use wgui::{
globals::WguiGlobals,
i18n::Translation,
layout::{Layout, WidgetID},
parser::{Fetchable, ParseDocumentParams, ParserState, TemplateParams, strip_path_as},
parser::{Fetchable, ParseDocumentParams, ParserState, TemplateParams},
task::Tasks,
widget::{ConstructEssentials, rectangle::WidgetRectangle},
widget::ConstructEssentials,
};
use crate::{
@@ -100,7 +100,7 @@ struct ProfileDisplayName {
struct SelectorCell {
key: String,
display_text: String,
icon_path_builtin: &'static str,
icon_path: &'static str,
}
struct MultiSelectorParams<'a> {
@@ -124,7 +124,7 @@ fn mount_multi_selector(params: MultiSelectorParams) -> anyhow::Result<()> {
params.ess,
components::button::Params {
text: Some(Translation::from_raw_text(&cell.display_text)),
sprite_src: Some(strip_path_as(AssetPathSource::BuiltIn, cell.icon_path_builtin).unwrap()),
sprite_src: Some(AssetPathRef::BuiltIn(cell.icon_path)),
color,
..Default::default()
},
@@ -402,7 +402,7 @@ struct MountDeviceSliderParams<'a> {
checked: bool,
muted: bool,
disp: Option<ProfileDisplayName>,
device_desc: String,
alt_desc: String,
}
fn push_popup_speakers_set_successfully(globals: &WguiGlobals, frontend_tasks: &FrontendTasks, name: &str) {
@@ -775,13 +775,13 @@ impl View {
par.insert("device_name", &disp.name);
par.insert("device_icon", disp.icon_path);
} else {
let icon_path = if params.device_desc.contains("WiVRn") {
let icon_path = if params.alt_desc.contains("WiVRn") {
"@/dashboard/wivrn_head_symbolic.svg"
} else {
"@/dashboard/binary.svg"
};
par.insert("device_name", &params.device_desc);
par.insert("device_name", &params.alt_desc);
par.insert("device_icon", icon_path);
}
@@ -802,13 +802,6 @@ impl View {
par,
)?;
if params.checked {
let rect = data.fetch_widget(&params.layout.state, "rect")?;
let mut rect = rect.widget.cast::<WidgetRectangle>()?;
rect.set_color(&mut params.layout.common(), WguiColorName::BackgroundVariant.into());
rect.set_border_color(&mut params.layout.common(), WguiColorName::Primary.into());
}
let mut common = params.layout.common();
let checkbox = data.fetch_component_as::<ComponentCheckbox>("checkbox")?;
let btn_mute = data.fetch_component_as::<ComponentButton>("btn_mute")?;
@@ -865,7 +858,7 @@ impl View {
false
};
let device_desc = sink.description.clone();
let alt_desc = sink.description.clone();
let muted = sink.mute;
let control = Rc::new(ControlSink::new(self.tasks.clone(), self.on_update.clone(), sink));
@@ -877,7 +870,7 @@ impl View {
self.mount_device_slider(MountDeviceSliderParams {
checked,
disp,
device_desc,
alt_desc,
layout,
control,
muted,
@@ -905,7 +898,7 @@ impl View {
false
};
let device_desc = source.description.clone();
let alt_desc = source.description.clone();
let muted = source.mute;
let control = Rc::new(ControlSource::new(self.tasks.clone(), self.on_update.clone(), source));
@@ -917,7 +910,7 @@ impl View {
self.mount_device_slider(MountDeviceSliderParams {
checked,
disp,
device_desc,
alt_desc,
layout,
control,
muted,
@@ -975,7 +968,7 @@ impl View {
cells.push(SelectorCell {
key: profile_name.clone(),
display_text: disp_name.name,
icon_path_builtin: disp_name.icon_path,
icon_path: disp_name.icon_path,
});
}
+69 -641
View File
@@ -18,7 +18,7 @@ use wgui::{
};
use wlx_common::{
config_io,
openxr_actions::{OneOrMany, OpenXrInputAction, OpenXrInputChordMember, OpenXrInputProfile, load_xr_input_profiles},
openxr_actions::{OneOrMany, OpenXrInputAction, OpenXrInputProfile, load_xr_input_profiles},
openxr_bindings_schema::{
DEFAULT_BUTTON_THRESHOLDS, XrControllerProfile, XrInputComponent, XrInputSide, XrInputSubpathKind,
},
@@ -39,11 +39,8 @@ use crate::{
enum Task {
Save,
Cancel,
OpenContextMenu(context_menu::Position, Vec<context_menu::Cell>),
OpenContextMenu(glam::Vec2, Vec<context_menu::Cell>),
UpdateThreshold(Rc<str>, XrInputSide, usize, f32),
AddChordMember(Rc<str>, XrInputSide),
RemoveChordMember(Rc<str>, XrInputSide, usize),
UpdateChordThreshold(Rc<str>, XrInputSide, usize, usize, f32),
}
pub struct Params<'a> {
@@ -103,113 +100,53 @@ impl ViewTrait for View {
};
threshold[i] = val;
}
Task::AddChordMember(action_name, output_side) => {
if let Some(member) = default_chord_member(self.controller_profile, output_side) {
let cur_profile = &mut self.profiles[self.cur_profile_idx];
let action_mut = get_action_mut(cur_profile, &action_name);
action_chord_mut(action_mut, output_side)
.get_or_insert_default()
.push(member);
self.refresh(par.layout)?;
}
}
Task::RemoveChordMember(action_name, output_side, member_idx) => {
let cur_profile = &mut self.profiles[self.cur_profile_idx];
let action_mut = get_action_mut(cur_profile, &action_name);
let chord = action_chord_mut(action_mut, output_side);
let chord_empty = if let Some(members) = chord.as_mut()
&& member_idx < members.len()
{
members.remove(member_idx);
members.is_empty()
} else {
false
};
if chord_empty {
*chord = None;
}
self.refresh(par.layout)?;
}
Task::UpdateChordThreshold(action_name, output_side, member_idx, i, val) => {
let cur_profile = &mut self.profiles[self.cur_profile_idx];
let action_mut = get_action_mut(cur_profile, &action_name);
if let Some(member) = action_chord_mut(action_mut, output_side)
.as_mut()
.and_then(|members| members.get_mut(member_idx))
{
let mut threshold = member.threshold().unwrap_or(DEFAULT_BUTTON_THRESHOLDS);
threshold[i] = val;
set_chord_member_threshold(member, threshold);
}
log::warn!("UpdateThreshold {action_name} {side:?} {threshold:?}");
}
}
}
// Dropdown handling
if let TickResult::Action(name) = self.context_menu.tick(par.layout, &mut self.parser_state)? {
let parts = name.split(';').collect::<Vec<_>>();
if let TickResult::Action(name) = self.context_menu.tick(par.layout, &mut self.parser_state)?
&& let (Some(action), Some(action_name), Some(side), Some(value)) = {
let mut s = name.splitn(4, ';');
(s.next(), s.next(), s.next(), s.next())
} {
let cur_profile = &mut self.profiles[self.cur_profile_idx];
let action_mut = get_action_mut(cur_profile, action_name);
let side_mut = if side == "right" {
&mut action_mut.right
} else {
&mut action_mut.left
};
match parts.as_slice() {
[action, action_name, side, value] => {
let action_mut = get_action_mut(cur_profile, action_name);
match *action {
"clear" => {
*action_binding_mut(action_mut, side) = None;
}
"subpath" => {
apply_subpath(
action_binding_mut(action_mut, side),
side,
value,
self.controller_profile,
);
}
"comp" => {
apply_comp(action_binding_mut(action_mut, side), side, value);
}
"click" => apply_click_count(action_mut, value),
_ => log::warn!("Unknown action {action}"),
}
self.refresh(par.layout)?;
match action {
"clear" => {
*side_mut = None;
}
[kind, action_name, output_side, member_idx, value] => {
let (Ok(output_side), Ok(member_idx)) = (XrInputSide::try_from(*output_side), member_idx.parse::<usize>())
else {
return Ok(());
};
let action_mut = get_action_mut(cur_profile, action_name);
let Some(member) = action_chord_mut(action_mut, output_side)
.as_mut()
.and_then(|members| members.get_mut(member_idx))
else {
return Ok(());
};
match *kind {
"chord_hand" => {
if let Ok(side) = XrInputSide::try_from(*value) {
apply_chord_hand(member, side, self.controller_profile);
}
}
"chord_subpath" => {
if let Ok(subpath) = XrInputSubpathKind::try_from(*value) {
apply_chord_subpath(member, subpath, self.controller_profile);
}
}
"chord_comp" => {
if let Ok(component) = XrInputComponent::try_from(*value) {
apply_chord_component(member, component);
}
}
_ => log::warn!("Unknown chord action {kind}"),
}
self.refresh(par.layout)?;
"subpath" => {
apply_subpath(side_mut, side, value, self.controller_profile);
}
_ => log::warn!("Malformed bindings context action: {name}"),
"comp" => {
apply_comp(side_mut, side, value);
}
"click" => match value {
"triple" => {
action_mut.triple_click = Some(true);
action_mut.double_click = None;
}
"double" => {
action_mut.triple_click = None;
action_mut.double_click = Some(true);
}
_ => {
action_mut.triple_click = None;
action_mut.double_click = None;
}
},
_ => log::warn!("Unknown action {action}"),
}
self.refresh(par.layout)?;
}
Ok(())
@@ -476,172 +413,6 @@ fn input_controls_for_action(
},
)?;
if &*action != "scroll" {
if is_side_agnostic_action(action.as_ref()) {
chord_controls_for_output(
mp,
parent,
&action,
XrInputSide::Left,
current.left_chord.as_deref(),
click_type,
profile,
false,
)?;
} else {
for output_side in [XrInputSide::Left, XrInputSide::Right] {
if profile.find_userpath(output_side).is_none() {
continue;
}
let chord = if matches!(output_side, XrInputSide::Right) {
current.right_chord.as_deref()
} else {
current.left_chord.as_deref()
};
chord_controls_for_output(mp, parent, &action, output_side, chord, click_type, profile, true)?;
}
}
}
Ok(())
}
fn is_side_agnostic_action(action: &str) -> bool {
matches!(action, "show_hide" | "toggle_dashboard")
}
fn chord_controls_for_output(
mp: &mut MacroParams,
parent: WidgetID,
action: &Rc<str>,
output_side: XrInputSide,
members: Option<&[OpenXrInputChordMember]>,
click_type: ClickType,
profile: &XrControllerProfile,
show_output_side: bool,
) -> anyhow::Result<()> {
let header = horiz_cell(mp.layout, parent)?;
if show_output_side {
wgui_simple::create_icon(
mp.layout,
header,
Vec2::new(24.0, 24.0),
AssetPathRef::BuiltIn(&format!("dashboard/hand_{}.svg", output_side.as_ref())),
)?;
wgui_simple::create_label(mp.layout, header, side_translation(output_side))?;
}
wgui_simple::create_label(
mp.layout,
header,
Translation::from_translation_key("APP_SETTINGS.BINDINGS.CHORD"),
)?;
clicks_dropdown(mp, header, action.clone(), click_type)?;
let tasks = mp.tasks.clone();
let action_for_add = action.clone();
wgui_simple::create_button(wgui_simple::CreateButtonParams {
id_parent: header,
layout: mp.layout,
content: Translation::from_translation_key("APP_SETTINGS.BINDINGS.CHORD_ADD_INPUT"),
icon_builtin: AssetPathRef::BuiltIn("dashboard/add.svg"),
on_click: Rc::new(move |_common, _event| {
tasks.push(Task::AddChordMember(action_for_add.clone(), output_side));
Ok(())
}),
})?;
for (member_idx, member) in members.unwrap_or_default().iter().enumerate() {
chord_member_controls(mp, parent, action, output_side, member_idx, member, profile)?;
}
Ok(())
}
fn chord_member_controls(
mp: &mut MacroParams,
parent: WidgetID,
action: &Rc<str>,
output_side: XrInputSide,
member_idx: usize,
member: &OpenXrInputChordMember,
profile: &XrControllerProfile,
) -> anyhow::Result<()> {
let row = horiz_cell(mp.layout, parent)?;
let parsed = ParsedOpenXrInputPath::try_from(member.path())
.log_warn(member.path())
.ok();
let physical_side = parsed.as_ref().map(|x| x.side).unwrap_or(output_side);
chord_hand_dropdown(mp, row, action.clone(), output_side, member_idx, profile, physical_side)?;
let available_subpaths: Rc<[XrInputSubpathKind]> = profile
.find_userpath(physical_side)
.map(|user_path| {
user_path
.paths
.iter()
.filter(|x| !x.kind.get_bool("Hidden").unwrap_or_default())
.map(|x| x.kind)
.collect::<Vec<_>>()
.into()
})
.unwrap_or_default();
chord_subpath_dropdown(
mp,
row,
action.clone(),
output_side,
member_idx,
available_subpaths,
parsed.as_ref().map(|x| x.subpath),
)?;
let available_components: Rc<[XrInputComponent]> = parsed
.as_ref()
.and_then(|parsed| {
profile
.find_userpath(parsed.side)
.and_then(|user_path| user_path.find_subpath(parsed.subpath))
})
.map(|subpath| subpath.components)
.unwrap_or_default()
.into();
chord_component_dropdown(
mp,
row,
action.clone(),
output_side,
member_idx,
available_components,
parsed.as_ref().map(|x| x.component),
)?;
let tasks = mp.tasks.clone();
let action_for_remove = action.clone();
wgui_simple::create_button(wgui_simple::CreateButtonParams {
id_parent: row,
layout: mp.layout,
content: Translation::from_raw_text(""),
icon_builtin: AssetPathRef::BuiltIn("dashboard/trash.svg"),
on_click: Rc::new(move |_common, _event| {
tasks.push(Task::RemoveChordMember(
action_for_remove.clone(),
output_side,
member_idx,
));
Ok(())
}),
})?;
// put threshold slider on its own row
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())?;
}
Ok(())
}
@@ -730,7 +501,7 @@ fn subpath_dropdown(
mp.layout,
parent,
Vec2::new(32.0, 32.0),
AssetPathRef::BuiltIn(&format!("dashboard/hand_{}.svg", side.as_ref())),
AssetPathRef::BuiltIn(&format!("@/dashboard/hand_{}.svg", side.as_ref())),
)?;
let current_text = current
@@ -781,181 +552,6 @@ fn clicks_dropdown(mp: &mut MacroParams, parent: WidgetID, action: Rc<str>, curr
Ok(())
}
fn side_translation(side: XrInputSide) -> Translation {
Translation::from_translation_key(match side {
XrInputSide::Left => "APP_SETTINGS.BINDINGS.LEFT",
XrInputSide::Right => "APP_SETTINGS.BINDINGS.RIGHT",
})
}
fn chord_hand_dropdown(
mp: &mut MacroParams,
parent: WidgetID,
action: Rc<str>,
output_side: XrInputSide,
member_idx: usize,
profile: &XrControllerProfile,
current: XrInputSide,
) -> anyhow::Result<()> {
let mut params = TemplateParams::new();
params.insert("tooltip", "APP_SETTINGS.BINDINGS.CHORD_INPUT_HAND");
params.insert("min_width", "100");
let cells = [XrInputSide::Left, XrInputSide::Right]
.into_iter()
.filter(|side| profile.find_userpath(*side).is_some())
.map(|side| {
context_menu::Cell::Button(context_menu::Button {
action_name: Some(
format!(
"chord_hand;{};{};{};{}",
action,
output_side.as_ref(),
member_idx,
side.as_ref()
)
.into(),
),
title: side_translation(side),
..Default::default()
})
})
.collect();
create_dropdown_with_cells(mp, parent, params, side_translation(current), cells)
}
fn chord_subpath_dropdown(
mp: &mut MacroParams,
parent: WidgetID,
action: Rc<str>,
output_side: XrInputSide,
member_idx: usize,
available: Rc<[XrInputSubpathKind]>,
current: Option<XrInputSubpathKind>,
) -> anyhow::Result<()> {
let mut params = TemplateParams::new();
params.insert("tooltip", "APP_SETTINGS.BINDINGS.SUBPATH");
params.insert("min_width", "100");
let current_text = current
.map(|subpath| subpath.translation())
.unwrap_or_else(|| Translation::from_translation_key("APP_SETTINGS.OPTION.NONE"));
let cells = available
.iter()
.map(|subpath| {
context_menu::Cell::Button(context_menu::Button {
action_name: Some(
format!(
"chord_subpath;{};{};{};{}",
action,
output_side.as_ref(),
member_idx,
subpath.as_ref()
)
.into(),
),
title: subpath.translation(),
..Default::default()
})
})
.collect();
create_dropdown_with_cells(mp, parent, params, current_text, cells)
}
fn chord_component_dropdown(
mp: &mut MacroParams,
parent: WidgetID,
action: Rc<str>,
output_side: XrInputSide,
member_idx: usize,
available: Rc<[XrInputComponent]>,
current: Option<XrInputComponent>,
) -> anyhow::Result<bool> {
if available.is_empty() {
return Ok(false);
}
let mut params = TemplateParams::new();
params.insert("text", "・");
params.insert("tooltip", "APP_SETTINGS.BINDINGS.COMPONENT");
params.insert("min_width", "100");
let current_text = current
.map(|component| component.translation())
.unwrap_or_else(|| Translation::from_raw_text_rc(Default::default()));
let cells = available
.iter()
.map(|component| {
context_menu::Cell::Button(context_menu::Button {
action_name: Some(
format!(
"chord_comp;{};{};{};{}",
action,
output_side.as_ref(),
member_idx,
component.as_ref()
)
.into(),
),
title: component.translation(),
..Default::default()
})
})
.collect();
create_dropdown_with_cells(mp, parent, params, current_text, cells)?;
Ok(true)
}
fn chord_threshold_slider(
mp: &mut MacroParams,
parent: WidgetID,
action: Rc<str>,
output_side: XrInputSide,
member_idx: usize,
current: Option<[f32; 2]>,
) -> anyhow::Result<()> {
let id = mp.idx.to_string();
mp.idx += 1;
let current = current.unwrap_or(DEFAULT_BUTTON_THRESHOLDS);
let mut params = TemplateParams::new();
params.insert("id", &id);
params.insert("tooltip", "APP_SETTINGS.BINDINGS.THRESHOLD");
params.insert_str("value", format!("{:.2}", current[0]));
params.insert_str("value2", format!("{:.2}", current[1]));
params.insert("min", "0.0");
params.insert("max", "1.0");
params.insert("step", "0.1");
mp.parser_state
.instantiate_template(mp.doc_params, "ThresholdSlider", mp.layout, parent, params)?;
let slider = mp.parser_state.fetch_component_as::<ComponentSlider>(&id)?;
slider.on_value_changed(Box::new({
let tasks = mp.tasks.clone();
move |_common, e| {
let threshold_idx = if matches!(e.index, wgui::components::slider::ValueIndex::Primary) {
0
} else {
1
};
tasks.push(Task::UpdateChordThreshold(
action.clone(),
output_side,
member_idx,
threshold_idx,
e.value,
));
}
}));
Ok(())
}
fn threshold_slider(
mp: &mut MacroParams,
parent: WidgetID,
@@ -998,43 +594,11 @@ fn threshold_slider(
fn create_dropdown<B: 'static + BindingsDropdown>(
mp: &mut MacroParams,
parent: WidgetID,
params: TemplateParams,
mut params: TemplateParams,
action: Rc<str>,
side: XrInputSide,
current_text: Translation,
available: Rc<[B]>,
) -> anyhow::Result<()> {
let mut cells = available
.iter()
.map(|item| {
context_menu::Cell::Button(context_menu::Button {
action_name: Some(item.action_str(&action, side)),
title: item.translation(),
..Default::default()
})
})
.collect::<Vec<_>>();
if let Some(action_str) = B::clear_str(&action, side) {
cells.insert(
0,
context_menu::Cell::Button(context_menu::Button {
action_name: Some(action_str),
title: Translation::from_translation_key("APP_SETTINGS.OPTION.NONE"),
..Default::default()
}),
);
}
create_dropdown_with_cells(mp, parent, params, current_text, cells)
}
fn create_dropdown_with_cells(
mp: &mut MacroParams,
parent: WidgetID,
mut params: TemplateParams,
current_text: Translation,
cells: Vec<context_menu::Cell>,
) -> anyhow::Result<()> {
let id = mp.idx.to_string();
mp.idx += 1;
@@ -1053,9 +617,35 @@ fn create_dropdown_with_cells(
let btn = mp.parser_state.fetch_component_as::<ComponentButton>(&id)?;
btn.on_click(Rc::new({
let tasks = mp.tasks.clone();
let cells = cells.clone();
let available = available.clone();
move |_common, e: ButtonClickEvent| {
tasks.push(Task::OpenContextMenu(e.mouse_pos_absolute.into(), cells.clone()));
let mut cells = available
.iter()
.map(|item| {
let title = item.translation();
context_menu::Cell {
action_name: Some(item.action_str(&action, side)),
title,
tooltip: None,
attribs: vec![],
}
})
.collect::<Vec<_>>();
if let Some(action_str) = B::clear_str(&action, side) {
cells.insert(
0,
context_menu::Cell {
action_name: Some(action_str),
title: Translation::from_translation_key("APP_SETTINGS.OPTION.NONE"),
tooltip: None,
attribs: vec![],
},
);
}
tasks.push(Task::OpenContextMenu(e.mouse_pos_absolute.unwrap_or_default(), cells));
Ok(())
}
}));
@@ -1120,165 +710,3 @@ fn apply_comp(side_mut: &mut Option<OneOrMany<String>>, side: &str, comp: &str)
*side_mut = Some(OneOrMany::One(format!("/user/hand/{side}/input/{subpath}/{comp}")));
}
fn action_chord_mut(
action: &mut OpenXrInputAction,
output_side: XrInputSide,
) -> &mut Option<Vec<OpenXrInputChordMember>> {
match output_side {
XrInputSide::Left => &mut action.left_chord,
XrInputSide::Right => &mut action.right_chord,
}
}
fn action_binding_mut<'a>(action: &'a mut OpenXrInputAction, side: &str) -> &'a mut Option<OneOrMany<String>> {
if side == "right" {
&mut action.right
} else {
&mut action.left
}
}
fn apply_click_count(action: &mut OpenXrInputAction, value: &str) {
match value {
"triple" => {
action.triple_click = Some(true);
action.double_click = None;
}
"double" => {
action.triple_click = None;
action.double_click = Some(true);
}
_ => {
action.triple_click = None;
action.double_click = None;
}
}
}
fn default_chord_member(profile: &XrControllerProfile, preferred_side: XrInputSide) -> Option<OpenXrInputChordMember> {
let other_side = match preferred_side {
XrInputSide::Left => XrInputSide::Right,
XrInputSide::Right => XrInputSide::Left,
};
for side in [preferred_side, other_side] {
let Some(user_path) = profile.find_userpath(side) else {
continue;
};
let Some(subpath) = user_path
.paths
.iter()
.find(|subpath| !subpath.kind.get_bool("Hidden").unwrap_or_default() && !subpath.components.is_empty())
else {
continue;
};
let component = *subpath.components.first()?;
return Some(OpenXrInputChordMember::Path(format!(
"/user/hand/{}/input/{}/{}",
side.as_ref(),
subpath.kind.as_ref(),
component.as_ref()
)));
}
None
}
fn set_chord_member_path(member: &mut OpenXrInputChordMember, path: String, component: XrInputComponent) {
let threshold = if component.is_analog() {
member.threshold()
} else {
None
};
*member = if threshold.is_some() {
OpenXrInputChordMember::Detailed { path, threshold }
} else {
OpenXrInputChordMember::Path(path)
};
}
fn set_chord_member_threshold(member: &mut OpenXrInputChordMember, threshold: [f32; 2]) {
*member = OpenXrInputChordMember::Detailed {
path: member.path().to_owned(),
threshold: Some(threshold),
};
}
fn apply_chord_hand(member: &mut OpenXrInputChordMember, side: XrInputSide, profile: &XrControllerProfile) {
let parsed = ParsedOpenXrInputPath::try_from(member.path()).ok();
let Some(user_path) = profile.find_userpath(side) else {
return;
};
let subpath = parsed
.and_then(|parsed| user_path.find_subpath(parsed.subpath))
.filter(|subpath| !subpath.kind.get_bool("Hidden").unwrap_or_default() && !subpath.components.is_empty())
.or_else(|| {
user_path
.paths
.iter()
.find(|subpath| !subpath.kind.get_bool("Hidden").unwrap_or_default() && !subpath.components.is_empty())
});
let Some(subpath) = subpath else {
return;
};
let component = parsed
.map(|parsed| parsed.component)
.filter(|component| subpath.components.contains(component))
.unwrap_or(subpath.components[0]);
let path = format!(
"/user/hand/{}/input/{}/{}",
side.as_ref(),
subpath.kind.as_ref(),
component.as_ref()
);
set_chord_member_path(member, path, component);
}
fn apply_chord_subpath(
member: &mut OpenXrInputChordMember,
subpath: XrInputSubpathKind,
profile: &XrControllerProfile,
) {
let Ok(parsed) = ParsedOpenXrInputPath::try_from(member.path()) else {
return;
};
let Some(subpath_obj) = profile
.find_userpath(parsed.side)
.and_then(|user_path| user_path.find_subpath(subpath))
else {
return;
};
let Some(first_component) = subpath_obj.components.first().copied() else {
return;
};
let component = if subpath_obj.components.contains(&parsed.component) {
parsed.component
} else {
first_component
};
let path = format!(
"/user/hand/{}/input/{}/{}",
parsed.side.as_ref(),
subpath.as_ref(),
component.as_ref()
);
set_chord_member_path(member, path, component);
}
fn apply_chord_component(member: &mut OpenXrInputChordMember, component: XrInputComponent) {
let Ok(parsed) = ParsedOpenXrInputPath::try_from(member.path()) else {
return;
};
let path = format!(
"/user/hand/{}/input/{}/{}",
parsed.side.as_ref(),
parsed.subpath.as_ref(),
component.as_ref()
);
set_chord_member_path(member, path, component);
}
+3 -22
View File
@@ -32,21 +32,6 @@ enum Task {
ActionClicked(&'static str),
}
pub fn get_entries_yes_no() -> Vec<ButtonEntry> {
vec![
ButtonEntry {
content: Translation::from_translation_key("YES"),
icon: "@/dashboard/check.svg",
action: "yes",
},
ButtonEntry {
content: Translation::from_translation_key("NO"),
icon: "@/dashboard/cancel.svg",
action: "no",
},
]
}
pub struct View {
tasks: Tasks<Task>,
@@ -67,13 +52,6 @@ fn doc_params(globals: &WguiGlobals) -> ParseDocumentParams<'_> {
impl ViewTrait for View {
fn update(&mut self, _par: &mut ViewUpdateParams) -> anyhow::Result<()> {
self.tick();
Ok(())
}
}
impl View {
pub fn tick(&mut self) {
for task in self.tasks.drain() {
match task {
Task::ActionClicked(action) => {
@@ -87,8 +65,11 @@ impl View {
}
}
}
Ok(())
}
}
impl View {
pub fn new(
layout: &mut Layout,
id_parent: WidgetID,
+2
View File
@@ -17,5 +17,7 @@ glam.workspace = true
log.workspace = true
rust-embed.workspace = true
tracing-subscriber.workspace = true
vulkano.workspace = true
vulkano-shaders.workspace = true
winit.workspace = true
ureq = "3.3.0"
+144 -45
View File
@@ -1,14 +1,25 @@
use glam::{Vec2, vec2};
use std::rc::Rc;
use std::{rc::Rc, sync::Arc};
use testbed::{Testbed, testbed_any::TestbedAny};
use tracing_subscriber::EnvFilter;
use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use vulkan::init_window;
use vulkano::{
Validated, VulkanError,
command_buffer::CommandBufferUsage,
format::Format,
image::{ImageUsage, view::ImageView},
swapchain::{
ColorSpace, CompositeAlpha, PresentMode, Surface, SurfaceInfo, Swapchain, SwapchainCreateInfo,
SwapchainPresentInfo, acquire_next_image,
},
sync::GpuFuture,
};
use wgui::{
event::{MouseButtonEvent, MouseButtonIndex, MouseMotionEvent, MouseWheelEvent},
gfx::{CommandBufferUsage, PresentStatus, SwapchainError, WGfxClearMode},
gfx::{WGfx, cmd::WGfxClearMode},
renderer_vk::{self},
};
use winit::{
@@ -48,9 +59,9 @@ fn init_logging() {
.init();
}
fn load_testbed(audio: &mut Box<dyn audio::AudioProvider>) -> anyhow::Result<Box<dyn Testbed>> {
fn load_testbed(audio_sample_player: &mut audio::SamplePlayer) -> anyhow::Result<Box<dyn Testbed>> {
let mut assets = Box::new(assets::Asset {});
audio.register_wgui_samples(assets.as_mut())?;
audio_sample_player.register_wgui_samples(assets.as_mut())?;
let name = std::env::var("TESTBED").unwrap_or_default();
Ok(match name.as_str() {
@@ -63,19 +74,41 @@ fn load_testbed(audio: &mut Box<dyn audio::AudioProvider>) -> anyhow::Result<Box
fn main() -> Result<(), Box<dyn std::error::Error>> {
init_logging();
let (gfx, event_loop, window) =
let (gfx, event_loop, window, surface, color_space) =
init_window("[-/=]: gui scale, F10: debug draw, F11: print tree")?;
let inner_size = window.inner_size();
let mut swapchain_size = [inner_size.width, inner_size.height];
let mut swapchain = gfx.create_swapchain(swapchain_size)?;
swapchain_size = swapchain.extent();
let mut swapchain_create_info = swapchain_create_info(
&gfx,
gfx.surface_format,
color_space,
surface.clone(),
swapchain_size,
);
let (mut swapchain, mut images) = {
let (swapchain, images) = Swapchain::new(
gfx.device.clone(),
surface.clone(),
swapchain_create_info.clone(),
)?;
let image_views = images
.into_iter()
.map(|image| ImageView::new_default(image).unwrap())
.collect::<Vec<_>>();
(swapchain, image_views)
};
let mut recreate = false;
let mut scale = window.scale_factor() as f32;
let mut audio: Box<dyn audio::AudioProvider> = Box::new(audio::RealAudioProvider::new());
let mut testbed = load_testbed(&mut audio)?;
let mut audio_system = audio::AudioSystem::new();
let mut audio_sample_player = audio::SamplePlayer::new();
let mut testbed = load_testbed(&mut audio_sample_player)?;
let mut mouse = Vec2::ZERO;
@@ -248,21 +281,20 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
} => {
if recreate {
let inner_size = window.inner_size();
if inner_size.width == 0 || inner_size.height == 0 {
return;
}
swapchain_size = [inner_size.width, inner_size.height];
let requested_size = [inner_size.width, inner_size.height];
match swapchain.recreate(requested_size) {
Ok(new_swapchain) => {
swapchain = new_swapchain;
swapchain_size = swapchain.extent();
}
Err(e) => {
log::error!("failed to recreate swapchain: {e:#}");
return;
}
}
swapchain_create_info.image_extent = swapchain_size;
(swapchain, images) = {
let (swapchain, images) = swapchain.recreate(swapchain_create_info.clone()).unwrap();
let image_views = images
.into_iter()
.map(|image| ImageView::new_default(image).unwrap())
.collect::<Vec<_>>();
(swapchain, image_views)
};
render_context
.update_viewport(&mut shared_context, swapchain_size, scale)
@@ -282,7 +314,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
width: (swapchain_size[0] as f32 / scale) as _,
height: (swapchain_size[1] as f32 / scale) as _,
timestep_alpha: timestep.alpha,
audio: &mut audio,
audio_system: &mut audio_system,
audio_sample_player: &mut audio_sample_player,
})
.unwrap();
@@ -299,19 +332,20 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
profiler.start();
{
let frame = match swapchain.acquire() {
Ok(frame) => frame,
Err(SwapchainError::OutOfDate) => {
recreate = true;
return;
}
Err(e) => {
log::error!("failed to acquire next image: {e}");
return;
}
};
let (image_index, _, acquire_future) =
match acquire_next_image(swapchain.clone(), None).map_err(Validated::unwrap) {
Ok(r) => r,
Err(VulkanError::OutOfDate) => {
recreate = true;
return;
}
Err(e) => {
log::error!("failed to acquire next image: {e}");
return;
}
};
let tgt = frame.image_view().clone();
let tgt = images[image_index as usize].clone();
let mut cmd_buf = gfx
.create_gfx_command_buffer(CommandBufferUsage::OneTimeSubmit)
@@ -354,15 +388,17 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let cmd_buf = cmd_buf.build().unwrap();
match frame.present(&cmd_buf) {
Ok(PresentStatus::Optimal) => {}
Ok(PresentStatus::Suboptimal) | Err(SwapchainError::OutOfDate) => {
recreate = true;
}
Err(e) => {
log::error!("failed to present swapchain image: {e}");
}
}
acquire_future
.then_execute(gfx.queue_gfx.clone(), cmd_buf)
.unwrap()
.then_swapchain_present(
gfx.queue_gfx.clone(),
SwapchainPresentInfo::swapchain_image_index(swapchain.clone(), image_index),
)
.then_signal_fence_and_flush()
.unwrap()
.wait(None)
.unwrap();
}
profiler.end();
@@ -378,3 +414,66 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
fn swapchain_create_info(
graphics: &WGfx,
format: Format,
color_space: ColorSpace,
surface: Arc<Surface>,
extent: [u32; 2],
) -> SwapchainCreateInfo {
let surface_capabilities = graphics
.device
.physical_device()
.surface_capabilities(&surface, SurfaceInfo::default())
.unwrap(); // want panic
let mut composite_alpha = None;
for c in [
CompositeAlpha::PreMultiplied,
CompositeAlpha::PostMultiplied,
CompositeAlpha::Opaque,
] {
if surface_capabilities
.supported_composite_alpha
.contains_enum(c)
{
composite_alpha = Some(c);
break;
}
log::warn!(
"GPU driver doesn't support {c:?} compositeAlpha! Desktop window will be blended using a fallback method and may look different than in VR."
);
}
let composite_alpha = composite_alpha
.expect("GPU driver issue: VkSurfaceCapabilitiesKHR has empty supportedCompositeAlpha.");
let present_modes = graphics
.device
.physical_device()
.surface_present_modes(&surface, SurfaceInfo::default())
.expect("Could not get GPU present modes for VKSurface.");
let mut present_mode = None;
for pm in [PresentMode::Mailbox, PresentMode::Fifo] {
if present_modes.contains(&pm) {
present_mode = Some(pm);
break;
}
log::warn!(
"GPU driver doesn't support {pm:?} presentMode! Desktop window may have a higher latency and/or glitch during grab/resize."
);
}
let present_mode = present_mode.expect("GPU driver issue: VkPresentModeKHR FIFO is not supported even though it's required by Vulkan spec.");
SwapchainCreateInfo {
min_image_count: surface_capabilities.min_image_count.max(2),
present_mode,
image_format: format,
image_extent: extent,
image_usage: ImageUsage::COLOR_ATTACHMENT,
image_color_space: color_space,
composite_alpha,
..Default::default()
}
}
+5 -2
View File
@@ -9,12 +9,15 @@ pub struct TestbedUpdateParams<'a> {
pub width: f32,
pub height: f32,
pub timestep_alpha: f32,
pub audio: &'a mut Box<dyn audio::AudioProvider>,
pub audio_system: &'a mut audio::AudioSystem,
pub audio_sample_player: &'a mut audio::SamplePlayer,
}
impl<'a> TestbedUpdateParams<'a> {
pub fn process_layout_result(&mut self, res: LayoutUpdateResult) {
self.audio.play_wgui_samples(res.sounds_to_play);
self
.audio_sample_player
.play_wgui_samples(self.audio_system, res.sounds_to_play);
}
}
+3 -1
View File
@@ -41,7 +41,9 @@ impl Testbed for TestbedDashboard {
height: params.height,
timestep_alpha: params.timestep_alpha,
})?;
self.frontend.process_update(res, params.audio)?;
self
.frontend
.process_update(res, params.audio_system, params.audio_sample_player)?;
while self.executor.try_tick() {}
Ok(())
+4 -6
View File
@@ -33,7 +33,7 @@ use wlx_common::locale::WayVRLangProvider;
#[derive(Clone)]
pub enum TestbedTask {
ShowPopup,
ShowContextMenu(context_menu::Position),
ShowContextMenu(Vec2),
}
struct Data {
@@ -196,7 +196,7 @@ impl TestbedGeneric {
button_context_menu.on_click({
let tasks = testbed.tasks.clone();
Rc::new(move |_common, m| {
tasks.push(TestbedTask::ShowContextMenu(m.boundary.into()));
tasks.push(TestbedTask::ShowContextMenu(m.boundary.bottom_left()));
Ok(())
})
});
@@ -212,9 +212,7 @@ impl TestbedGeneric {
) -> anyhow::Result<()> {
match task {
TestbedTask::ShowPopup => self.show_popup(params, data)?,
TestbedTask::ShowContextMenu(position) => {
self.show_context_menu(params, data, position.clone())
}
TestbedTask::ShowContextMenu(position) => self.show_context_menu(params, data, *position),
}
Ok(())
@@ -253,7 +251,7 @@ impl TestbedGeneric {
&mut self,
_params: &mut TestbedUpdateParams,
data: &mut Data,
position: context_menu::Position,
position: Vec2,
) {
data.context_menu.open(context_menu::OpenParams {
on_custom_attribs: Some(Rc::new(move |custom_attribs| {
+197 -13
View File
@@ -1,11 +1,46 @@
use std::sync::Arc;
use anyhow::Context;
use std::sync::{Arc, OnceLock};
use vulkano::{
device::{
Device, DeviceCreateInfo, DeviceExtensions, DeviceFeatures, Queue, QueueCreateInfo, QueueFlags,
physical::{PhysicalDevice, PhysicalDeviceType},
},
instance::{Instance, InstanceCreateInfo},
swapchain::ColorSpace,
};
use wgui::gfx::WGfx;
use wgui::gfx::{WGfx, WGfxCreateInfo};
use winit::{event_loop::EventLoop, window::Window};
static VULKAN_LIBRARY: OnceLock<Arc<vulkano::VulkanLibrary>> = OnceLock::new();
fn get_vulkan_library() -> &'static Arc<vulkano::VulkanLibrary> {
VULKAN_LIBRARY.get_or_init(|| vulkano::VulkanLibrary::new().unwrap()) // want panic
}
#[allow(clippy::type_complexity)]
pub fn init_window(title: &str) -> anyhow::Result<(Arc<WGfx>, EventLoop<()>, Arc<Window>)> {
pub fn init_window(
title: &str,
) -> anyhow::Result<(
Arc<WGfx>,
winit::event_loop::EventLoop<()>,
Arc<winit::window::Window>,
Arc<vulkano::swapchain::Surface>,
ColorSpace,
)> {
use vulkano::{instance::InstanceCreateFlags, swapchain::Surface};
use winit::{event_loop::EventLoop, window::Window};
let event_loop = EventLoop::new().unwrap(); // want panic
let mut vk_instance_extensions = Surface::required_extensions(&event_loop).unwrap();
vk_instance_extensions.khr_get_physical_device_properties2 = true;
log::debug!("Instance exts for runtime: {:?}", vk_instance_extensions);
let instance = Instance::new(
get_vulkan_library().clone(),
InstanceCreateInfo {
flags: InstanceCreateFlags::ENUMERATE_PORTABILITY,
enabled_extensions: vk_instance_extensions,
..Default::default()
},
)?;
#[allow(deprecated)]
let window = Arc::new(
@@ -17,20 +52,169 @@ pub fn init_window(title: &str) -> anyhow::Result<(Arc<WGfx>, EventLoop<()>, Arc
)
.unwrap(), // want panic
);
let surface = Surface::from_window(instance.clone(), window.clone())?;
let gfx = WGfx::new_for_window(
window.as_ref(),
&WGfxCreateInfo {
application_name: "uidev".to_owned(),
engine_name: "wgui".to_owned(),
let mut device_extensions = DeviceExtensions::empty();
device_extensions.khr_swapchain = true;
log::debug!("Device exts for app: {:?}", device_extensions);
let (physical_device, mut my_extensions, queue_families) = instance
.enumerate_physical_devices()?
.filter_map(|p| {
if p.supported_extensions().contains(&device_extensions) {
Some((p, device_extensions))
} else {
log::debug!(
"Not using {} because it does not implement the following device extensions:",
p.properties().device_name,
);
for (ext, missing) in p.supported_extensions().difference(&device_extensions) {
if missing {
log::debug!(" {ext}");
}
}
None
}
})
.filter_map(|(p, my_extensions)| {
try_all_queue_families(p.as_ref()).map(|families| (p, my_extensions, families))
})
.min_by_key(|(p, _, _)| prio_from_device_type(p))
.expect("no suitable physical device found");
log::info!(
"Using vkPhysicalDevice: {}",
physical_device.properties().device_name,
);
if physical_device.supported_extensions().img_filter_cubic {
my_extensions.img_filter_cubic = true;
log::info!("img_filter_cubic!");
}
let (format, color_space) = physical_device
.surface_formats(&surface, vulkano::swapchain::SurfaceInfo::default())?
.into_iter()
.find(|(format, _)| !format.components().iter().any(|b| *b > 8))
.context("Could not read or no suitable surface formats for PhysicalDevice")?;
let (device, queues) = Device::new(
physical_device,
DeviceCreateInfo {
enabled_extensions: my_extensions,
enabled_features: DeviceFeatures {
dynamic_rendering: true,
descriptor_binding_sampled_image_update_after_bind: true,
..DeviceFeatures::empty()
},
queue_create_infos: queue_families
.iter()
.map(|fam| QueueCreateInfo {
queue_family_index: fam.queue_family_index,
queues: fam.priorities.clone(),
..Default::default()
})
.collect::<Vec<_>>(),
..Default::default()
},
)?;
log::info!("Using vkPhysicalDevice: {}", gfx.device_info().name);
if gfx.capabilities().filter_cubic {
log::info!("cubic filtering available");
let (queue_gfx, queue_xfer, _) = unwrap_queues(queues.collect());
let me = WGfx::new_from_raw(instance, device, queue_gfx, queue_xfer, format);
Ok((me, event_loop, window, surface, color_space))
}
#[derive(Debug)]
struct QueueFamilyLayout {
queue_family_index: u32,
priorities: Vec<f32>,
}
fn prio_from_device_type(physical_device: &PhysicalDevice) -> u32 {
match physical_device.properties().device_type {
PhysicalDeviceType::DiscreteGpu => 0,
PhysicalDeviceType::IntegratedGpu => 1,
PhysicalDeviceType::VirtualGpu => 2,
PhysicalDeviceType::Cpu => 3,
_ => 4,
}
}
fn unwrap_queues(queues: Vec<Arc<Queue>>) -> (Arc<Queue>, Arc<Queue>, Option<Arc<Queue>>) {
match queues[..] {
[ref g, ref t, ref c] => (g.clone(), t.clone(), Some(c.clone())),
[ref gt, ref c] => (gt.clone(), gt.clone(), Some(c.clone())),
[ref gt] => (gt.clone(), gt.clone(), None),
_ => unreachable!(),
}
}
fn try_all_queue_families(physical_device: &PhysicalDevice) -> Option<Vec<QueueFamilyLayout>> {
queue_families_priorities(
physical_device,
vec![
// main-thread graphics + uploads
QueueFlags::GRAPHICS | QueueFlags::TRANSFER,
// capture-thread uploads
QueueFlags::TRANSFER,
],
)
.or_else(|| {
queue_families_priorities(
physical_device,
vec![
// main thread graphics
QueueFlags::GRAPHICS,
// main thread uploads
QueueFlags::TRANSFER,
// capture thread uploads
QueueFlags::TRANSFER,
],
)
})
.or_else(|| {
queue_families_priorities(
physical_device,
// main thread-only. software capture not supported.
vec![QueueFlags::GRAPHICS | QueueFlags::TRANSFER],
)
})
}
fn queue_families_priorities(
physical_device: &PhysicalDevice,
mut requested_queues: Vec<QueueFlags>,
) -> Option<Vec<QueueFamilyLayout>> {
let mut result = Vec::with_capacity(3);
for (idx, props) in physical_device.queue_family_properties().iter().enumerate() {
let mut remaining = props.queue_count;
let mut want = 0usize;
requested_queues.retain(|requested| {
if props.queue_flags.intersects(*requested) && remaining > 0 {
remaining -= 1;
want += 1;
false
} else {
true
}
});
if want > 0 {
result.push(QueueFamilyLayout {
queue_family_index: idx as u32,
priorities: std::iter::repeat_n(1.0, want).collect(),
});
}
}
Ok((gfx, event_loop, window))
if requested_queues.is_empty() {
log::debug!("Selected GPU queue families: {result:?}");
Some(result)
} else {
None
}
}
+5 -65
View File
@@ -321,34 +321,21 @@ impl WayVRClient {
}
}
pub async fn fn_wlx_overlay_list(
pub async fn fn_wvr_window_list(
client: WayVRClientMutex,
serial: Serial,
params: packet_client::WlxOverlayListParams,
) -> anyhow::Result<Vec<String>> {
) -> anyhow::Result<Option<Vec<packet_server::WvrWindow>>> {
Ok(
send_and_wait!(
client,
serial,
&PacketClient::WlxOverlayList(serial, params),
WlxOverlayListResponse
&PacketClient::WvrWindowList(serial),
WvrWindowListResponse
)
.list,
.map(|res| res.list),
)
}
pub async fn fn_wlx_overlay_set_visible(
client: WayVRClientMutex,
overlay: String,
visible: bool,
) -> anyhow::Result<()> {
send_only!(
client,
&PacketClient::WlxOverlaySetVisible(overlay, visible)
);
Ok(())
}
pub async fn fn_wvr_window_set_visible(
client: WayVRClientMutex,
handle: packet_server::WvrWindowHandle,
@@ -462,53 +449,6 @@ impl WayVRClient {
send_only!(client, &PacketClient::WlxModifyPanel(params));
Ok(())
}
pub async fn fn_wlx_window_state_get(
client: WayVRClientMutex,
serial: Serial,
params: packet_client::WlxWindowStateGetParams,
) -> anyhow::Result<Result<packet_client::WlxWindowStateValue, String>> {
Ok(send_and_wait!(
client,
serial,
&PacketClient::WlxWindowStateGet(serial, params),
WlxWindowStateGetResponse
))
}
pub async fn fn_wlx_window_state_set(
client: WayVRClientMutex,
params: packet_client::WlxWindowStateSetParams,
) -> anyhow::Result<()> {
send_only!(client, &PacketClient::WlxWindowStateSet(params));
Ok(())
}
pub async fn fn_wlx_window_attrib_get(
client: WayVRClientMutex,
serial: Serial,
params: packet_client::WlxWindowAttribGetParams,
) -> anyhow::Result<Result<packet_client::WlxWindowAttribValue, String>> {
Ok(send_and_wait!(
client,
serial,
&PacketClient::WlxWindowAttribGet(serial, params),
WlxWindowAttribGetResponse
))
}
pub async fn fn_wlx_window_attrib_set(
client: WayVRClientMutex,
serial: Serial,
params: packet_client::WlxWindowAttribSetParams,
) -> anyhow::Result<Result<(), String>> {
Ok(send_and_wait!(
client,
serial,
&PacketClient::WlxWindowAttribSet(serial, params),
WlxWindowAttribSetResponse
))
}
}
impl Drop for WayVRClient {
+1 -1
View File
@@ -22,7 +22,7 @@ impl SerialGenerator {
}
}
pub const PROTOCOL_VERSION: u32 = 5;
pub const PROTOCOL_VERSION: u32 = 4;
pub const CONNECTION_MAGIC: &str = "wayvr_ipc";
pub fn data_encode<T>(data: &T) -> Vec<u8>
+1 -126
View File
@@ -81,130 +81,10 @@ pub struct WlxModifyPanelParams {
pub command: WlxModifyPanelCommand,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum WlxHand {
Left,
Right,
}
// see wlx_common::windowing::Positioning
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum WlxPositioning {
Floating,
Anchored,
Static,
FollowHead {
#[serde(default)]
lerp: f32,
},
FollowHand {
hand: WlxHand,
#[serde(default)]
lerp: f32,
},
}
// see wlx_common::windowing::OverlayWindowState
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum WlxWindowStateField {
Alpha,
Grabbable,
Interactable,
Positioning,
Curvature,
Additive,
BlockInput,
AlignToHmd,
/// see wlx_common::windowing::OverlayWindowConfig::global
Global,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum WlxWindowStateValue {
Bool(bool),
Float(f32),
Positioning(WlxPositioning),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WlxWindowStateGetParams {
pub overlay: String,
pub field: WlxWindowStateField,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WlxWindowStateSetParams {
pub overlay: String,
pub field: WlxWindowStateField,
pub value: WlxWindowStateValue,
}
// see wlx_common::overlays::BackendAttrib
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum WlxWindowAttrib {
Stereo,
StereoFullFrame,
StereoAdjustMouse,
MouseTransform,
WindowSize,
}
// see wlx_common::overlays::StereoMode
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum WlxStereoMode {
None,
LeftRight,
RightLeft,
TopBottom,
BottomTop,
}
// see wlx_common::overlays::MouseTransform
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum WlxMouseTransform {
Default,
Normal,
Rotated90,
Rotated180,
Rotated270,
Flipped,
Flipped90,
Flipped180,
Flipped270,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum WlxWindowAttribValue {
Stereo(WlxStereoMode),
StereoFullFrame(bool),
StereoAdjustMouse(bool),
MouseTransform(WlxMouseTransform),
WindowSize([u32; 2]),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WlxWindowAttribGetParams {
pub overlay: String,
pub attrib: WlxWindowAttrib,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WlxWindowAttribSetParams {
pub overlay: String,
pub attrib: WlxWindowAttrib,
pub value: WlxWindowAttribValue,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct WlxOverlayListParams {
pub visible: bool,
pub hidden: bool,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum PacketClient {
Handshake(Handshake),
WlxOverlayList(Serial, WlxOverlayListParams),
WvrWindowList(Serial),
WvrWindowSetVisible(packet_server::WvrWindowHandle, bool),
WvrProcessGet(Serial, packet_server::WvrProcessHandle),
WvrProcessLaunch(Serial, WvrProcessLaunchParams),
@@ -217,9 +97,4 @@ pub enum PacketClient {
WlxShowHide,
WlxSwitchSet(Option<usize>),
WlxHandsfree(HandsfreeParams),
WlxWindowStateGet(Serial, WlxWindowStateGetParams),
WlxWindowStateSet(WlxWindowStateSetParams),
WlxWindowAttribGet(Serial, WlxWindowAttribGetParams),
WlxWindowAttribSet(Serial, WlxWindowAttribSetParams),
WlxOverlaySetVisible(String, bool),
}
+5 -11
View File
@@ -4,7 +4,7 @@ use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use super::{ipc::Serial, packet_client};
use super::ipc::Serial;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerInfo {}
@@ -39,8 +39,8 @@ pub struct WvrWindow {
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WlxOverlayList {
pub list: Vec<String>,
pub struct WvrWindowList {
pub list: Vec<WvrWindow>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -98,13 +98,10 @@ pub enum PacketServer {
Disconnect(Disconnect),
HandshakeSuccess(HandshakeSuccess),
WlxInputStateResponse(Serial, WlxInputState),
WlxOverlayListResponse(Serial, WlxOverlayList),
WvrWindowListResponse(Serial, Option<WvrWindowList>),
WvrProcessGetResponse(Serial, Option<WvrProcess>),
WvrProcessLaunchResponse(Serial, Result<WvrProcessHandle, String>),
WvrProcessListResponse(Serial, WvrProcessList),
WlxWindowStateGetResponse(Serial, Result<packet_client::WlxWindowStateValue, String>),
WlxWindowAttribGetResponse(Serial, Result<packet_client::WlxWindowAttribValue, String>),
WlxWindowAttribSetResponse(Serial, Result<(), String>),
WvrStateChanged(WvrStateChanged),
}
@@ -114,13 +111,10 @@ impl PacketServer {
PacketServer::Disconnect(_) => None,
PacketServer::HandshakeSuccess(_) => None,
PacketServer::WlxInputStateResponse(serial, _) => Some(serial),
PacketServer::WlxOverlayListResponse(serial, _) => Some(serial),
PacketServer::WvrWindowListResponse(serial, _) => Some(serial),
PacketServer::WvrProcessGetResponse(serial, _) => Some(serial),
PacketServer::WvrProcessLaunchResponse(serial, _) => Some(serial),
PacketServer::WvrProcessListResponse(serial, _) => Some(serial),
PacketServer::WlxWindowStateGetResponse(serial, _) => Some(serial),
PacketServer::WlxWindowAttribGetResponse(serial, _) => Some(serial),
PacketServer::WlxWindowAttribSetResponse(serial, _) => Some(serial),
PacketServer::WvrStateChanged(_) => None,
}
}
+4 -2
View File
@@ -53,12 +53,15 @@ smol = { workspace = true }
strum.workspace = true
tracing-subscriber.workspace = true
uuid = { workspace = true }
vulkano.workspace = true
vulkano-shaders.workspace = true
wayland-client = { workspace = true }
wayland-protocols = { workspace = true }
wayland-protocols-misc = { workspace = true }
winit = { workspace = true, optional = true }
xdg.workspace = true
ash = "^0.38.0" # must match vulkano
bytes = { workspace = true }
chrono-tz = "0.10.4"
config = "0.15.25"
@@ -83,7 +86,7 @@ mint = "0.5.9"
openxr = { version = "0.21.1", features = ["linked", "mint"], optional = true }
ovr_overlay = {
git = "https://github.com/galister/ovr_overlay_oyasumi",
rev = "3b7d24cee24aa2b1ad7dffc5d5c8568508049bdd",
rev = "e477bd2a9e04293ea68c1e7529ef2cb131f32acc",
features = [
"ovr_input",
"ovr_system",
@@ -127,7 +130,6 @@ evdev = "0.13.2"
[build-dependencies]
regex.workspace = true
shaderc = "0.8"
[features]
default = ["openvr", "openxr", "osc", "wayland", "whisper", "x11"]
+2 -38
View File
@@ -1,21 +1,7 @@
use regex::Regex;
use shaderc::{Compiler, EnvVersion, ShaderKind, TargetEnv};
use std::{path::PathBuf, process::Command};
const SHADERS: &[(&str, ShaderKind)] = &[
("quad.vert", ShaderKind::Vertex),
("color.frag", ShaderKind::Fragment),
("grid.frag", ShaderKind::Fragment),
("screen.frag", ShaderKind::Fragment),
("simple.frag", ShaderKind::Fragment),
("wayland.frag", ShaderKind::Fragment),
("srgb.frag", ShaderKind::Fragment),
("sky.frag", ShaderKind::Fragment),
];
use std::process::Command;
fn main() {
compile_shaders();
let mut wlx_build = get_version().unwrap_or(format!("{}-unknown", env!("CARGO_PKG_VERSION")));
match std::env::var("GITHUB_JOB").as_deref() {
@@ -27,29 +13,7 @@ fn main() {
}
_ => {}
}
println!("cargo:rustc-env=WLX_BUILD={wlx_build}");
}
fn compile_shaders() {
let shader_dir = PathBuf::from("src/shaders");
let out_dir = PathBuf::from(std::env::var_os("OUT_DIR").expect("OUT_DIR is set by Cargo"));
let compiler = Compiler::new().expect("shaderc compiler is available");
let mut options = shaderc::CompileOptions::new().expect("shaderc options are available");
options.set_target_env(TargetEnv::Vulkan, EnvVersion::Vulkan1_3 as u32);
for (name, kind) in SHADERS {
let path = shader_dir.join(name);
println!("cargo:rerun-if-changed={}", path.display());
let source = std::fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("failed to read {}: {e}", path.display()));
let artifact = compiler
.compile_into_spirv(&source, *kind, name, "main", Some(&options))
.unwrap_or_else(|e| panic!("failed to compile {}: {e}", path.display()));
std::fs::write(out_dir.join(format!("{name}.spv")), artifact.as_binary_u8())
.unwrap_or_else(|e| panic!("failed to write compiled shader {name}: {e}"));
}
println!("cargo:rustc-env=WLX_BUILD={}", wlx_build);
}
fn get_version() -> Result<String, Box<dyn std::error::Error>> {
+39
View File
@@ -5984,6 +5984,45 @@
"dest": "cargo/vendor/vk-parse-0.15.0",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/vulkano/vulkano-0.35.1.crate",
"sha256": "08840c2b51759a6f88f26f5ea378bc8b5c199a5b4760ddda292304be087249c4",
"dest": "cargo/vendor/vulkano-0.35.1"
},
{
"type": "inline",
"contents": "{\"package\": \"08840c2b51759a6f88f26f5ea378bc8b5c199a5b4760ddda292304be087249c4\", \"files\": {}}",
"dest": "cargo/vendor/vulkano-0.35.1",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/vulkano-macros/vulkano-macros-0.35.0.crate",
"sha256": "1dc929c42c9336fd082079ac3ea30126e4a0dfe36fd2e2b3581303f7d140d20f",
"dest": "cargo/vendor/vulkano-macros-0.35.0"
},
{
"type": "inline",
"contents": "{\"package\": \"1dc929c42c9336fd082079ac3ea30126e4a0dfe36fd2e2b3581303f7d140d20f\", \"files\": {}}",
"dest": "cargo/vendor/vulkano-macros-0.35.0",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/vulkano-shaders/vulkano-shaders-0.35.0.crate",
"sha256": "3bf501461be7cef2893c0e62c50945add9763cc482051d29053f6157089d5ea9",
"dest": "cargo/vendor/vulkano-shaders-0.35.0"
},
{
"type": "inline",
"contents": "{\"package\": \"3bf501461be7cef2893c0e62c50945add9763cc482051d29053f6157089d5ea9\", \"files\": {}}",
"dest": "cargo/vendor/vulkano-shaders-0.35.0",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
+2 -1
View File
@@ -3,5 +3,6 @@ use wgui::layout::LayoutUpdateResult;
use crate::state::AppState;
pub fn process_layout_result(app: &mut AppState, res: LayoutUpdateResult) {
app.audio.play_wgui_samples(res.sounds_to_play);
app.audio_sample_player
.play_wgui_samples(&mut app.audio_system, res.sounds_to_play);
}
+2 -2
View File
@@ -180,10 +180,10 @@
<blueprint name="menu_handsfree">
<context_menu >
<cell translation="BAR.HANDSFREE.NONE" _press="::HandsfreeMode None" _press2="::ContextMenuClose" />
<cell translation="BAR.HANDSFREE.HMD" _press="::HandsfreeMode Hmd" _press2="::ContextMenuClose" skip="${openvr}" />
<cell translation="BAR.HANDSFREE.HMD" _press="::HandsfreeMode Hmd" _press2="::ContextMenuClose" />
<cell translation="BAR.HANDSFREE.HMD_ONLY" _press="::HandsfreeMode HmdOnly" _press2="::ContextMenuClose" />
<cell translation="BAR.HANDSFREE.EYE_TRACKING" _press="::HandsfreeMode EyeTracking" _press2="::ContextMenuClose" skip="${openvr}" />
<cell translation="BAR.HANDSFREE.EYE_ONLY" _press="::HandsfreeMode EyeTrackingOnly" _press2="::ContextMenuClose" />
<cell translation="BAR.HANDSFREE.EYE_ONLY" _press="::HandsfreeMode EyeTrackingOnly" _press2="::ContextMenuClose" skip="${openvr}" />
</context_menu>
</blueprint>
+7 -9
View File
@@ -1,27 +1,25 @@
<layout version="2">
<elements>
<!-- Provides a larger hitbox for eye gaze -->
<div padding="60">
<div interactable="0">
<rectangle
max_width="500"
padding="16"
flex_direction="column"
gap="8"
color="background" border_color="outline" border="2" round="8"
id="rect">
color="background" border_color="outline" border="2" round="8">
<!-- Toast title -->
<div flex_direction="row" align_items="center" gap="8" id="div_title">
<sprite id="sprite_bell" src="@/icons/bell.svg" color="primary" min_width="32" min_height="32" max_width="32" max_height="32" flex_grow="1" />
<label wrap="1" id="label_title" color="primary" weight="bold" size="25" padding_left="16" padding_right="16" />
<div flex_direction="row" align_items="center" gap="8">
<sprite src="@/icons/bell.svg" color="primary" min_width="32" min_height="32" max_width="32" max_height="32" flex_grow="1" />
<label wrap="1" id="toast_title" color="primary" weight="bold" size="25" padding_left="16" padding_right="16" />
</div>
<!-- A simple separator -->
<rectangle id="rect_separator" width="100%" height="2" color="outline" />
<rectangle width="100%" height="2" color="outline" />
<div>
<!-- Toast message -->
<label id="label_body" color="on_background" wrap="1" size="20" padding_left="16" padding_right="16" />
<label id="toast_body" color="on_background" wrap="1" size="20" padding_left="16" padding_right="16" />
</div>
</rectangle>
</div>
+2 -5
View File
@@ -108,8 +108,7 @@
"TOGGLE_CAPTURE": "Kontrolle an Desktop zurückgeben",
"FOCUS_MODE": "Fokus-Modus (Super halten)",
"CLOSE": "Fokussierte App schließen",
"MOUSE": "Echte Mausklicks & Scrollen wirken ebenfalls als VR-Pointer",
"KEYBOARD": "Tastatur umschalten"
"MOUSE": "Echte Mausklicks & Scrollen wirken ebenfalls als VR-Pointer"
},
"GRAB": {
"ADJUST_DISTANCE": "Entfernung anpassen",
@@ -177,7 +176,5 @@
"SELECT_SCREEN_CAST_FOR": "Screen-Cast für Display {DISPLAY} auswählen",
"CANNOT_CAPTURE_KBD_MOUSE": "Tastatur/Maus-Erfassung konnte nicht initialisiert werden!\nPrüfe, ob dein Benutzer in der Gruppe 'input' ist.",
"WE_ARE_GRABBING": "WayVR steuert jetzt deine Tastatur und Maus!\nDrücke Super+Z, um die Steuerung freizugeben."
},
"STEAMVR_DASH_TOAST_TITLE": "SteamVR Dashboard geöffnet",
"STEAMVR_DASH_TOAST_TEXT": "Schließe das SteamVR-Dashboard, um WayVR zu nutzen."
}
}
-3
View File
@@ -88,7 +88,6 @@ ALTTAB.GRABALL,Move entire set,Key binding action to grab and move the entire se
ALTTAB.GRAB,Move individual overlay,Key binding action to grab and move a single overlay. “Overlay” was previously translated as “{$DEFINITIONS.OVERLAY}”
ALTTAB.DASH,Toggle dashboard,Key binding action that toggles the dashboard. “Dashboard” was previously translated as “${DEFINITIONS.DASHBOARD}”
ALTTAB.EDIT,Toggle edit mode,Key binding action that toggles edit mode. “Edit mode” was previously translated as “${DEFINITIONS.EDIT_MODE}”
ALTTAB.KEYBOARD,Toggle keyboard,Key binding action that toggles the keyboard. “Keyboard” was previously translated as “${DEFINITIONS.KEYBOARD}”
ALTTAB.SET,Switch set,Key binding action to switch between sets. “Set” was previously translated as “${DEFINITIONS.SET}”
ALTTAB.RECENTER,Show/hide/recenter,"Key binding action to show, hide, or recenter the current set. “Set” was previously translated as “${DEFINITIONS.SET}”"
ALTTAB.GENERAL_BINDS,General bindings,Title for key bindings that don’t need any specific criteria
@@ -114,8 +113,6 @@ SCREENCAST.FAIL,Could not initialize screen cast source.,Error message on a wind
TOAST.TITLE_SCREENCAST_FAIL,Could not start screen cast source!,Error message on a window that normally displays screen casts.
TOAST.CANNOT_ADD_SET,Cannot add set!,Error message telling the user the set could not be added. “Set” was previously translated as “${DEFINITIONS.SET}”
TOAST.CANNOT_REMOVE_SET,Cannot remove set!,Error message telling the user the set could not be removed. “Set” was previously translated as “${DEFINITIONS.SET}”
STEAMVR_DASH_TOAST_TITLE,SteamVR Dashboard Open,Title of toast shown when SteamVR dashboard is visible
STEAMVR_DASH_TOAST_TEXT,Close SteamVR dashboard to start using WayVR.,Body of toast shown when SteamVR dashboard is visible
TOAST.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”.
88 ALTTAB.EDIT Toggle edit mode Key binding action that toggles edit mode. “Edit mode” was previously translated as “${DEFINITIONS.EDIT_MODE}”
89 ALTTAB.KEYBOARD ALTTAB.SET Toggle keyboard Switch set Key binding action that toggles the keyboard. “Keyboard” was previously translated as “${DEFINITIONS.KEYBOARD}” Key binding action to switch between sets. “Set” was previously translated as “${DEFINITIONS.SET}”
90 ALTTAB.SET ALTTAB.RECENTER Switch set Show/hide/recenter Key binding action to switch between sets. “Set” was previously translated as “${DEFINITIONS.SET}” Key binding action to show, hide, or recenter the current set. “Set” was previously translated as “${DEFINITIONS.SET}”
ALTTAB.RECENTER Show/hide/recenter Key binding action to show, hide, or recenter the current set. “Set” was previously translated as “${DEFINITIONS.SET}”
91 ALTTAB.GENERAL_BINDS General bindings Title for key bindings that don’t need any specific criteria
92 ALTTAB.TOGGLE_CAPTURE Give control back to desktop Key binding action that releases exclusive input capture
93 ALTTAB.FOCUS_MODE Focus mode (hold Super) Key binding action to enter into a task-switcher kind of mode, like Alt-Tab.
113 STEAMVR_DASH_TOAST_TITLE TOAST.DEFAULT_TITLE SteamVR Dashboard Open Notification Title of toast shown when SteamVR dashboard is visible Default title of notifications
114 STEAMVR_DASH_TOAST_TEXT TOAST.EMPTY_SET Close SteamVR dashboard to start using WayVR. Empty set! Body of toast shown when SteamVR dashboard is visible Title of a pop-up when the user switches to an empty set. “Set” was previously translated as “${DEFINITIONS.SET}”
115 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
116 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.
117 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}”
118 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}”
-3
View File
@@ -102,7 +102,6 @@
"GRAB": "Move individual overlay",
"DASH": "Toggle dashboard",
"EDIT": "Toggle edit mode",
"KEYBOARD": "Toggle keyboard",
"SET": "Switch set",
"RECENTER": "Show/hide/recenter",
"GENERAL_BINDS": "General bindings",
@@ -145,8 +144,6 @@
"NO_SET_SELECTED": "No set is selected.",
"ONE_CONTROLLER_ON_FLOOR": "Place one controller on the floor!"
},
"STEAMVR_DASH_TOAST_TITLE": "SteamVR Dashboard Open",
"STEAMVR_DASH_TOAST_TEXT": "Close SteamVR dashboard to start using WayVR.",
"WATCH": {
"ADD_NEW_SET": "Add a new set",
"CLEANUP_MIRRORS": "Remove mirrors that are\nnot currently visible",
+2 -5
View File
@@ -108,8 +108,7 @@
"TOGGLE_CAPTURE": "Devolver el control al escritorio",
"FOCUS_MODE": "Modo enfoque (mantén Super)",
"CLOSE": "Cerrar app enfocada",
"MOUSE": "Los clics y el scroll del ratón real también actúan como el puntero de VR",
"KEYBOARD": "Alternar teclado"
"MOUSE": "Los clics y el scroll del ratón real también actúan como el puntero de VR"
},
"GRAB": {
"ADJUST_DISTANCE": "Ajustar distancia",
@@ -177,7 +176,5 @@
"SELECT_SCREEN_CAST_FOR": "Seleccionar transmisión de pantalla para la pantalla {DISPLAY}",
"CANNOT_CAPTURE_KBD_MOUSE": "¡No se pudo inicializar la captura de teclado/ratón!\nComprueba que tu usuario esté en el grupo 'input'.",
"WE_ARE_GRABBING": "¡WayVR tiene el control de tu teclado y ratón!\nPulsa Super+Z para liberarlos."
},
"STEAMVR_DASH_TOAST_TITLE": "Panel de SteamVR abierto",
"STEAMVR_DASH_TOAST_TEXT": "Cierra el tablero de SteamVR para empezar a usar WayVR."
}
}
+2 -5
View File
@@ -108,8 +108,7 @@
"TOGGLE_CAPTURE": "Rendi il controllo al desktop",
"FOCUS_MODE": "Modalità focus (tieni premuto Super)",
"CLOSE": "Chiudi app selezionata",
"MOUSE": "I clic e lo scorrimento del mouse reale influenzano anche il puntatore VR",
"KEYBOARD": "Attiva/disattiva tastiera"
"MOUSE": "I clic e lo scorrimento del mouse reale influenzano anche il puntatore VR"
},
"GRAB": {
"ADJUST_DISTANCE": "Regola distanza",
@@ -177,7 +176,5 @@
"SELECT_SCREEN_CAST_FOR": "Seleziona la trasmissione schermo per il display {DISPLAY}",
"CANNOT_CAPTURE_KBD_MOUSE": "Impossibile inizializzare la cattura di tastiera/mouse!\nControlla che il tuo utente sia nel gruppo 'input'.",
"WE_ARE_GRABBING": "WayVR sta controllando tastiera e mouse!\nPremi Super+Z per rilasciare il controllo."
},
"STEAMVR_DASH_TOAST_TITLE": "Dashboard di SteamVR aperta",
"STEAMVR_DASH_TOAST_TEXT": "Chiudi il pannello di SteamVR per iniziare a usare WayVR."
}
}
+3 -6
View File
@@ -108,8 +108,7 @@
"TOGGLE_CAPTURE": "デスクトップに操作を戻す",
"FOCUS_MODE": "フォーカスモード (Superキーを長押し)",
"CLOSE": "フォーカス中のアプリを閉じる",
"MOUSE": "実マウスのクリックとスクロールもVRポインターに反映される",
"KEYBOARD": "キーボードの切り替え"
"MOUSE": "実マウスのクリックとスクロールもVRポインターに反映される"
},
"GRAB": {
"ADJUST_DISTANCE": "距離を調整",
@@ -177,7 +176,5 @@
"SELECT_SCREEN_CAST_FOR": "ディスプレイ {DISPLAY} のスクリーンキャストを選択してください",
"CANNOT_CAPTURE_KBD_MOUSE": "キーボード/マウスのキャプチャを初期化できませんでした!\nユーザーが 'input' グループに所属しているか確認してください。",
"WE_ARE_GRABBING": "WayVR がキーボードとマウスを制御しています。\n解除するには、Super+Zを押してください。"
},
"STEAMVR_DASH_TOAST_TITLE": "SteamVRダッシュボード展開中",
"STEAMVR_DASH_TOAST_TEXT": "WayVRを使用するには、SteamVRダッシュボードを閉じてください。"
}
}
}
+2 -5
View File
@@ -108,8 +108,7 @@
"TOGGLE_CAPTURE": "Oddaj kontrolę do pulpitu",
"FOCUS_MODE": "Tryb skupienia (przytrzymaj Super)",
"CLOSE": "Zamknij aktywną aplikację",
"MOUSE": "Kliknięcia i przewijanie myszką przenoszą się na wskaźnik VR",
"KEYBOARD": "Przełącz klawiaturę"
"MOUSE": "Kliknięcia i przewijanie myszką przenoszą się na wskaźnik VR"
},
"GRAB": {
"ADJUST_DISTANCE": "Dostosuj odległość",
@@ -177,7 +176,5 @@
"SELECT_SCREEN_CAST_FOR": "Wybierz przesyłanie ekranu dla wyświetlacza {DISPLAY}",
"CANNOT_CAPTURE_KBD_MOUSE": "Nie można zainicjować przechwytywania klawiatury/myszy!\nSprawdź, czy Twój użytkownik należy do grupy 'input'.",
"WE_ARE_GRABBING": "WayVR przejął kontrolę nad klawiaturą i myszą!\nNaciśnij Super+Z, aby zwolnić."
},
"STEAMVR_DASH_TOAST_TITLE": "Panel SteamVR otwarty",
"STEAMVR_DASH_TOAST_TEXT": "Zamknij pulpit SteamVR, aby zacząć korzystać z WayVR."
}
}
+2 -5
View File
@@ -108,8 +108,7 @@
"TOGGLE_CAPTURE": "将控制权还给桌面",
"FOCUS_MODE": "焦点模式 (按住 Super 键)",
"CLOSE": "关闭当前应用",
"MOUSE": "实体鼠标点击与滚动同样可转换为 VR 指针",
"KEYBOARD": "切换键盘"
"MOUSE": "实体鼠标点击与滚动同样可转换为 VR 指针"
},
"GRAB": {
"ADJUST_DISTANCE": "调整距离",
@@ -177,7 +176,5 @@
"SELECT_SCREEN_CAST_FOR": "为显示器 {DISPLAY} 选择屏幕投射",
"CANNOT_CAPTURE_KBD_MOUSE": "无法初始化键盘/鼠标捕获!\n请检查您的用户是否在 'input' 组中。",
"WE_ARE_GRABBING": "WayVR 正在接管你的键盘和鼠标!\n按 Super+Z 释放。"
},
"STEAMVR_DASH_TOAST_TITLE": "SteamVR 控制面板已开启",
"STEAMVR_DASH_TOAST_TEXT": "关闭 SteamVR 仪表板以开始使用 WayVR。"
}
}
+105 -29
View File
@@ -1,8 +1,9 @@
use std::f32::consts::PI;
use std::process::{Child, Command};
use std::sync::Arc;
use std::time::{Duration, Instant};
use glam::{Affine3A, Quat, Vec2, Vec3A};
use glam::{Affine3A, Quat, Vec2, Vec3A, Vec3Swizzles};
use idmap_derive::IntegerId;
use smallvec::{SmallVec, smallvec};
@@ -20,7 +21,7 @@ use crate::subsystem::input::InputFocus;
use crate::windowing::backend::OverlayEventData;
use crate::windowing::manager::OverlayWindowManager;
use crate::windowing::window::{self, OverlayCategory, OverlayWindowData, realign, scalar_scale};
use crate::windowing::{OverlayID, OverlaySelector, raycast_overlay};
use crate::windowing::{OverlayID, OverlaySelector};
use super::task::TaskType;
@@ -68,7 +69,6 @@ pub enum FocusPickState {
pub struct InputState {
pub hmd: Affine3A,
pub ipd: f32,
pub eye_gaze: Option<Affine3A>,
pub pointers: [Pointer; 2],
pub devices: Vec<TrackedDevice>,
pub handsfree_state: PointerState,
@@ -84,7 +84,6 @@ impl InputState {
Self {
hmd: Affine3A::IDENTITY,
ipd: 0.0,
eye_gaze: None,
pointers: [Pointer::new(0), Pointer::new(1)],
devices: Vec::new(),
processes: Vec::new(),
@@ -349,8 +348,6 @@ pub struct Pointer {
pub(super) interaction: InteractionState,
pub tracked: bool,
pub handsfree: bool,
/// distance of the most recent ray hit on a wayland client window, used for drag n drop
pub last_wvr_hit_distance: Option<f32>,
}
impl Pointer {
@@ -367,7 +364,6 @@ impl Pointer {
interaction: InteractionState::default(),
tracked: false,
handsfree: false,
last_wvr_hit_distance: None,
}
}
@@ -430,7 +426,7 @@ pub struct GrabData {
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
#[derive(Debug, Clone, Copy, Default)]
pub enum PointerMode {
#[default]
Left,
@@ -518,8 +514,6 @@ where
);
}
crate::overlays::drag_item::update_position(overlays, app);
[hits[0].1, hits[1].1]
}
@@ -554,24 +548,10 @@ where
let hovered_id = pointer.interaction.hovered_id.take();
let (Some((mut hit, raw_hit)), haptics) = get_nearest_hit(idx, overlays, app) else {
if let Some(wvr_server) = app.wvr_server.as_mut() {
wvr_server.clear_dnd_focus(idx);
}
handle_no_hit(idx, hovered_id, overlays, app);
return (None, pending_haptics); // no hit
};
let hit_is_wayvr = overlays
.iter()
.find(|(id, _)| *id == hit.overlay)
.is_some_and(|(_, overlay)| overlay.config.category == OverlayCategory::WayVR);
if hit_is_wayvr {
app.input_state.pointers[idx].last_wvr_hit_distance = Some(raw_hit.dist);
} else if let Some(wvr_server) = app.wvr_server.as_mut() {
wvr_server.clear_dnd_focus(idx);
}
// focus change
if let Some(hovered_id) = hovered_id
&& hovered_id != hit.overlay
@@ -802,7 +782,7 @@ where
let Some(overlay_state) = overlay.config.active_state.as_ref() else {
continue;
};
if (!overlay_state.interactable || overlay_state.alpha < 0.025) && !edit_mode {
if !overlay_state.interactable && !edit_mode {
continue;
}
@@ -877,9 +857,7 @@ fn start_grab(par: StartGrabParams) {
let grab_anchor = !par.edit_mode
&& !par.grab_float
&& !app.anchor_grabbed
&& matches!(state.positioning, Positioning::Anchored)
&& !(app.session.config.middle_grabs_single
&& pointer.interaction.mode == PointerMode::Middle);
&& matches!(state.positioning, Positioning::Anchored);
let relative_grab_transform = if grab_anchor {
app.anchor
@@ -1121,7 +1099,17 @@ fn ray_test(
overlay_pose: &Affine3A,
curvature: Option<&f32>,
) -> Option<RayHit> {
let (dist, local_pos) = raycast_overlay(ray_origin, overlay_pose, curvature.copied())?;
let (dist, local_pos) = curvature.map_or_else(
|| {
Some(raycast_plane(
ray_origin,
Vec3A::NEG_Z,
overlay_pose,
Vec3A::NEG_Z,
))
},
|curvature| raycast_cylinder(ray_origin, Vec3A::NEG_Z, overlay_pose, *curvature),
)?;
if dist < 0.0 {
// hit is behind us
@@ -1135,3 +1123,91 @@ fn ray_test(
dist,
})
}
fn raycast_plane(
source: &Affine3A,
source_fwd: Vec3A,
plane: &Affine3A,
plane_norm: Vec3A,
) -> (f32, Vec2) {
let plane_normal = plane.transform_vector3a(plane_norm);
let ray_dir = source.transform_vector3a(source_fwd);
let d = plane.translation.dot(-plane_normal);
let mut dist = -(d + source.translation.dot(plane_normal)) / ray_dir.dot(plane_normal);
let hit_local = plane
.inverse()
.transform_point3a(source.translation + ray_dir * dist)
.xy();
// hitting the backside of the plane, make the hit invalid
if ray_dir.dot(plane_normal) < 0.0 && dist.is_sign_positive() {
dist = -dist;
}
(dist, hit_local)
}
fn raycast_cylinder(
source: &Affine3A,
source_fwd: Vec3A,
plane: &Affine3A,
curvature: f32,
) -> Option<(f32, Vec2)> {
// this is solved locally; (0,0) is the center of the cylinder, and the cylinder is aligned with the Y axis
let size = plane.x_axis.length();
let to_local = Affine3A {
matrix3: plane.matrix3.mul_scalar(1.0 / size),
translation: plane.translation,
}
.inverse();
let radius = size / (2.0 * PI * curvature);
let ray_dir = to_local.transform_vector3a(source.transform_vector3a(source_fwd));
let ray_origin = to_local.transform_point3a(source.translation) + Vec3A::NEG_Z * radius;
let v_dir = ray_dir.xz();
let v_pos = ray_origin.xz();
let l_dir = v_dir.dot(v_dir);
let l_pos = v_dir.dot(v_pos);
let c = radius.mul_add(-radius, v_pos.dot(v_pos));
let d = l_pos.mul_add(l_pos, -(l_dir * c));
if d < f32::EPSILON {
return None;
}
let sqrt_d = d.sqrt();
let t1 = (-l_pos - sqrt_d) / l_dir;
let t2 = (-l_pos + sqrt_d) / l_dir;
let mut t = t1.max(t2);
if t < f32::EPSILON {
return None;
}
let mut hit_local = ray_origin + ray_dir * t;
if hit_local.z > 0.0 {
// hitting the opposite half of the cylinder
return None;
}
let normal = Vec3A::new(hit_local.x, 0.0, hit_local.z).normalize();
// If hitting from the outside, flip t
if ray_dir.dot(normal) < 0.0 && t.is_sign_positive() {
t = -t;
}
let max_angle = 2.0 * (size / (2.0 * radius));
let x_angle = (hit_local.x / radius).asin();
hit_local.x = x_angle / max_angle;
hit_local.y /= size;
Some((t, hit_local.xy()))
}
-1
View File
@@ -37,5 +37,4 @@ pub enum BackendError {
pub struct RunParams {
pub wgui_globals: WguiGlobals,
pub config: GeneralConfig,
pub no_audio: bool,
}
+77 -239
View File
@@ -1,7 +1,6 @@
use std::{array, fs::File, io::Write, time::Duration};
use anyhow::bail;
use glam::{Affine3A, FloatExt, Quat, Vec3};
use ovr_overlay::{
TrackedDeviceIndex,
input::{ActionHandle, ActionSetHandle, ActiveActionSet, InputManager, InputValueHandle},
@@ -12,10 +11,10 @@ use ovr_overlay::{
},
system::SystemManager,
};
use wlx_common::{config::HandsfreePointer, config_io};
use wlx_common::config_io;
use crate::{
backend::input::{Haptics, Pointer, PointerState, TrackedDevice, TrackedDeviceRole},
backend::input::{Haptics, TrackedDevice, TrackedDeviceRole},
state::AppState,
};
@@ -23,7 +22,6 @@ use super::helpers::{Affine3AConvert, OVRError};
const SET_DEFAULT: &str = "/actions/default";
const INPUT_SOURCES: [&str; 2] = ["/user/hand/left", "/user/hand/right"];
const INPUT_HEAD: &str = "/user/head";
const PATH_POSES: [&str; 2] = [
"/actions/default/in/LeftHand",
"/actions/default/in/RightHand",
@@ -34,7 +32,6 @@ const PATH_HAPTICS: [&str; 2] = [
];
const PATH_ALT_CLICK: &str = "/actions/default/in/AltClick";
const PATH_EYE_TRACKING: &str = "/actions/default/in/EyeTracking";
const PATH_CLICK_MODIFIER_MIDDLE: &str = "/actions/default/in/ClickModifierMiddle";
const PATH_CLICK_MODIFIER_RIGHT: &str = "/actions/default/in/ClickModifierRight";
const PATH_CLICK: &str = "/actions/default/in/Click";
@@ -51,9 +48,7 @@ const INPUT_ANY: InputValueHandle = InputValueHandle(ovr_overlay::sys::k_ulInval
pub(super) struct OpenVrInputSource {
hands: [OpenVrHandSource; 2],
head_hnd: InputValueHandle,
set_hnd: ActionSetHandle,
eye_tracking_hnd: ActionHandle,
click_hnd: ActionHandle,
grab_hnd: ActionHandle,
scroll_hnd: ActionHandle,
@@ -79,9 +74,7 @@ pub(super) struct OpenVrHandSource {
impl OpenVrInputSource {
pub fn new(input: &mut InputManager) -> Result<Self, OVRError> {
let set_hnd = input.get_action_set_handle(SET_DEFAULT)?;
let head_hnd = input.get_input_source_handle(INPUT_HEAD)?;
let eye_tracking_hnd = input.get_action_handle(PATH_EYE_TRACKING)?;
let click_hnd = input.get_action_handle(PATH_CLICK)?;
let grab_hnd = input.get_action_handle(PATH_GRAB)?;
let scroll_hnd = input.get_action_handle(PATH_SCROLL)?;
@@ -120,9 +113,7 @@ impl OpenVrInputSource {
Ok(Self {
hands,
head_hnd,
set_hnd,
eye_tracking_hnd,
click_hnd,
grab_hnd,
scroll_hnd,
@@ -191,188 +182,90 @@ impl OpenVrInputSource {
},
});
let aas_head = ActiveActionSet(ovr_overlay::sys::VRActiveActionSet_t {
ulActionSet: self.set_hnd.0,
ulRestrictedToDevice: self.head_hnd.0,
ulSecondaryActionSet: 0,
unPadding: 0,
nPriority: 0x0,
});
let _ = input.update_actions(&mut [aas_left, aas_right, aas_head]);
let eye_gaze = input
.get_eye_tracking_data_relative_to_now(self.eye_tracking_hnd, universe.clone(), 0.005)
.ok()
.and_then(|data| eye_tracking_pose(&data.0));
app.input_state.eye_gaze = eye_gaze;
let _ = input.update_actions(&mut [aas_left, aas_right]);
let devices = system.get_device_to_absolute_tracking_pose(universe.clone(), 0.005);
let hmd = devices[0].mDeviceToAbsoluteTracking.to_affine();
let hmd_tracked = devices[0].bPoseIsValid;
if hmd_tracked {
app.input_state.hmd = hmd;
if devices[0].bPoseIsValid {
app.input_state.hmd = devices[0].mDeviceToAbsoluteTracking.to_affine();
}
let picking_focus = app.input_state.picking_focus;
let mut any_tracked = false;
for i in 0..2 {
let hand = &mut self.hands[i];
let app_hand = &mut app.input_state.pointers[i];
if picking_focus.is_none() {
for i in 0..2 {
let hand = &mut self.hands[i];
let app_hand = &mut app.input_state.pointers[i];
app_hand.handsfree = false;
if let Some(device) = hand.device.filter(|_| !overlay.is_dashboard_visible()) {
app_hand.raw_pose = devices[device.0 as usize]
.mDeviceToAbsoluteTracking
.to_affine();
app_hand.tracked = devices[device.0 as usize].bPoseIsValid;
} else {
app_hand.tracked = false;
}
any_tracked |= app_hand.tracked;
hand.has_pose = false;
let _ = input
.get_pose_action_data_relative_to_now(
hand.pose_hnd,
universe.clone(),
0.005,
INPUT_ANY,
)
.map(|pose| {
app_hand.pose = pose.0.pose.mDeviceToAbsoluteTracking.to_affine();
hand.has_pose = true;
});
app_hand.now.click = input
.get_digital_action_data(self.click_hnd, hand.input_hnd)
.is_ok_and(|x| x.0.bState);
app_hand.now.grab = input
.get_digital_action_data(self.grab_hnd, hand.input_hnd)
.is_ok_and(|x| x.0.bState);
app_hand.now.alt_click = input
.get_digital_action_data(self.alt_click_hnd, hand.input_hnd)
.is_ok_and(|x| x.0.bState);
app_hand.now.show_hide = input
.get_digital_action_data(self.show_hide_hnd, hand.input_hnd)
.is_ok_and(|x| x.0.bState);
app_hand.now.toggle_dashboard = input
.get_digital_action_data(self.toggle_dashboard_hnd, hand.input_hnd)
.is_ok_and(|x| x.0.bState);
app_hand.now.space_drag = input
.get_digital_action_data(self.space_drag_hnd, hand.input_hnd)
.is_ok_and(|x| x.0.bState);
app_hand.now.space_rotate = input
.get_digital_action_data(self.space_rotate_hnd, hand.input_hnd)
.is_ok_and(|x| x.0.bState);
app_hand.now.space_reset = input
.get_digital_action_data(self.space_reset_hnd, hand.input_hnd)
.is_ok_and(|x| x.0.bState);
app_hand.now.click_modifier_right = input
.get_digital_action_data(self.click_modifier_right_hnd, hand.input_hnd)
.is_ok_and(|x| x.0.bState);
app_hand.now.click_modifier_middle = input
.get_digital_action_data(self.click_modifier_middle_hnd, hand.input_hnd)
.is_ok_and(|x| x.0.bState);
app_hand.now.move_mouse = input
.get_digital_action_data(self.move_mouse_hnd, hand.input_hnd)
.is_ok_and(|x| x.0.bState);
let scroll = input
.get_analog_action_data(self.scroll_hnd, hand.input_hnd)
.map_or((0.0, 0.0), |x| (x.0.x, x.0.y));
app_hand.now.scroll_x = scroll.0;
app_hand.now.scroll_y = scroll.1;
}
} else {
app.input_state.handsfree_state.scroll_x =
app.input_state.handsfree_state.scroll_x.lerp(0.0, 0.7);
app.input_state.handsfree_state.scroll_y =
app.input_state.handsfree_state.scroll_y.lerp(0.0, 0.7);
for pointer in &mut app.input_state.pointers {
pointer.before = pointer.now;
pointer.now = PointerState::default();
pointer.tracked = false;
pointer.handsfree = false;
}
}
if !picking_focus.is_none() || !any_tracked {
let mode: HandsfreePointer = if picking_focus.is_none() {
app.session.config.handsfree_pointer
if let Some(device) = hand.device.filter(|_| !overlay.is_dashboard_visible()) {
app_hand.raw_pose = devices[device.0 as usize]
.mDeviceToAbsoluteTracking
.to_affine();
app_hand.tracked = devices[device.0 as usize].bPoseIsValid;
} else {
app.session.config.handsfree_alt_tab.into()
};
let handsfree_state = app.input_state.handsfree_state;
let delta_time = app.delta_time;
let pointer_lerp_factor = app.session.config.pointer_lerp_factor;
Self::update_handsfree(
&mut app.input_state.pointers[0],
mode,
hmd,
hmd_tracked,
eye_gaze,
handsfree_state,
delta_time,
pointer_lerp_factor,
);
}
}
fn update_handsfree(
pointer: &mut Pointer,
mode: HandsfreePointer,
hmd: Affine3A,
hmd_tracked: bool,
eye_gaze: Option<Affine3A>,
handsfree_state: PointerState,
delta_time: f32,
pointer_lerp_factor: f32,
) {
let (raw_pose, tracked, lerp_scale) = match mode {
HandsfreePointer::None => return,
HandsfreePointer::Hmd | HandsfreePointer::HmdOnly => (hmd, hmd_tracked, 1.0),
HandsfreePointer::EyeTracking | HandsfreePointer::EyeTrackingOnly => {
let Some(gaze) = eye_gaze else {
pointer.tracked = false;
pointer.handsfree = false;
pointer.now = handsfree_pointer_state(handsfree_state);
return;
};
// Match the OpenXR backend's stronger smoothing for eye gaze.
(gaze, true, 0.5)
app_hand.tracked = false;
}
};
let cur_quat = Quat::from_affine3(&pointer.pose);
let cur_pos = Vec3::from(pointer.pose.translation);
let new_quat = Quat::from_affine3(&raw_pose);
let new_pos = Vec3::from(raw_pose.translation);
let lerp_factor = (delta_time * 100.0 * pointer_lerp_factor * lerp_scale).clamp(0.1, 1.0);
hand.has_pose = false;
pointer.raw_pose = raw_pose;
pointer.pose = Affine3A::from_rotation_translation(
cur_quat.lerp(new_quat, lerp_factor),
cur_pos.lerp(new_pos, lerp_factor),
);
pointer.tracked = tracked;
pointer.handsfree = tracked;
pointer.now = handsfree_pointer_state(handsfree_state);
let _ = input
.get_pose_action_data_relative_to_now(
hand.pose_hnd,
universe.clone(),
0.005,
INPUT_ANY,
)
.map(|pose| {
app_hand.pose = pose.0.pose.mDeviceToAbsoluteTracking.to_affine();
hand.has_pose = true;
});
app_hand.now.click = input
.get_digital_action_data(self.click_hnd, hand.input_hnd)
.is_ok_and(|x| x.0.bState);
app_hand.now.grab = input
.get_digital_action_data(self.grab_hnd, hand.input_hnd)
.is_ok_and(|x| x.0.bState);
app_hand.now.alt_click = input
.get_digital_action_data(self.alt_click_hnd, hand.input_hnd)
.is_ok_and(|x| x.0.bState);
app_hand.now.show_hide = input
.get_digital_action_data(self.show_hide_hnd, hand.input_hnd)
.is_ok_and(|x| x.0.bState);
app_hand.now.toggle_dashboard = input
.get_digital_action_data(self.toggle_dashboard_hnd, hand.input_hnd)
.is_ok_and(|x| x.0.bState);
app_hand.now.space_drag = input
.get_digital_action_data(self.space_drag_hnd, hand.input_hnd)
.is_ok_and(|x| x.0.bState);
app_hand.now.space_rotate = input
.get_digital_action_data(self.space_rotate_hnd, hand.input_hnd)
.is_ok_and(|x| x.0.bState);
app_hand.now.space_reset = input
.get_digital_action_data(self.space_reset_hnd, hand.input_hnd)
.is_ok_and(|x| x.0.bState);
app_hand.now.click_modifier_right = input
.get_digital_action_data(self.click_modifier_right_hnd, hand.input_hnd)
.is_ok_and(|x| x.0.bState);
app_hand.now.click_modifier_middle = input
.get_digital_action_data(self.click_modifier_middle_hnd, hand.input_hnd)
.is_ok_and(|x| x.0.bState);
app_hand.now.move_mouse = input
.get_digital_action_data(self.move_mouse_hnd, hand.input_hnd)
.is_ok_and(|x| x.0.bState);
let scroll = input
.get_analog_action_data(self.scroll_hnd, hand.input_hnd)
.map_or((0.0, 0.0), |x| (x.0.x, x.0.y));
app_hand.now.scroll_x = scroll.0;
app_hand.now.scroll_y = scroll.1;
}
}
pub fn update_devices(&mut self, system: &mut SystemManager, app: &mut AppState) -> bool {
@@ -424,49 +317,6 @@ impl OpenVrInputSource {
}
}
fn handsfree_pointer_state(state: PointerState) -> PointerState {
PointerState {
click: state.click,
grab: state.grab,
grab_float: state.grab_float,
click_modifier_right: state.click_modifier_right,
click_modifier_middle: state.click_modifier_middle,
scroll_x: state.scroll_x,
scroll_y: state.scroll_y,
..PointerState::default()
}
}
fn eye_tracking_pose(data: &ovr_overlay::sys::VREyeTrackingData_t) -> Option<Affine3A> {
if !(data.bActive && data.bValid && data.bTracked) {
return None;
}
let origin = Vec3::new(
data.vGazeOrigin.v[0],
data.vGazeOrigin.v[1],
data.vGazeOrigin.v[2],
);
let target = Vec3::new(
data.vGazeTarget.v[0],
data.vGazeTarget.v[1],
data.vGazeTarget.v[2],
);
if !origin.is_finite() || !target.is_finite() {
return None;
}
let direction = target - origin;
if !direction.is_finite() || direction.length_squared() <= f32::EPSILON {
return None;
}
Some(Affine3A::from_rotation_translation(
Quat::from_rotation_arc(Vec3::NEG_Z, direction.normalize()),
origin,
))
}
fn get_tracked_device(
system: &mut SystemManager,
index: TrackedDeviceIndex,
@@ -545,18 +395,6 @@ pub fn set_action_manifest(input: &mut InputManager) -> anyhow::Result<()> {
.write_all(include_bytes!("../../res/actions_binding_oculus.json"))?;
}
let binding_path = config_io::get_config_root().join("actions_binding_frame.json");
if !binding_path.is_file() {
File::create(&binding_path)?
.write_all(include_bytes!("../../res/actions_binding_frame.json"))?;
}
let binding_path = config_io::get_config_root().join("actions_binding_generic_hmd.json");
if !binding_path.is_file() {
File::create(&binding_path)?
.write_all(include_bytes!("../../res/actions_binding_generic_hmd.json"))?;
}
if let Err(e) = input.set_action_manifest(action_path.as_path()) {
bail!("Failed to set action manifest: {e}");
}
+78 -3
View File
@@ -2,14 +2,29 @@ use std::f32::consts::PI;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use ash::vk::SubmitInfo;
use glam::{Affine3A, Quat, Vec3, Vec3A, Vec4};
use idmap::IdMap;
use ovr_overlay::overlay::OverlayManager;
use ovr_overlay::sys::ETrackingUniverseOrigin;
use wgui::gfx::{CommandBufferUsage, Format, ImageView, WGfx};
use vulkano::{
VulkanObject,
command_buffer::{
CommandBufferBeginInfo, CommandBufferLevel, CommandBufferUsage, RecordingCommandBuffer,
},
format::Format,
image::view::ImageView,
image::{Image, ImageLayout},
sync::{
AccessFlags, DependencyInfo, ImageMemoryBarrier, PipelineStages,
fence::{Fence, FenceCreateInfo},
},
};
use wgui::gfx::WGfx;
use wlx_common::overlays::{BackendAttrib, BackendAttribValue};
use crate::backend::input::{HoverResult, PointerHit};
use crate::graphics::ExtentExt;
use crate::state::AppState;
use crate::subsystem::hid::WheelDelta;
use crate::windowing::Z_ORDER_LINES;
@@ -38,7 +53,15 @@ impl LinePool {
let texture = command_buffer.upload_image(2, 2, Format::R8G8B8A8_UNORM, &buf)?;
command_buffer.build_and_execute_now()?;
let view = graphics.create_image_view(texture)?;
transition_layout(
&graphics,
texture.clone(),
ImageLayout::ShaderReadOnlyOptimal,
ImageLayout::TransferSrcOptimal,
)?
.wait(None)?;
let view = ImageView::new_default(texture)?;
Ok(Self {
lines: IdMap::new(),
@@ -199,7 +222,7 @@ impl OverlayBackend for LineBackend {
}
fn frame_meta(&mut self) -> Option<FrameMeta> {
Some(FrameMeta {
extent: self.view.extent_2d(),
extent: self.view.extent_u32arr(),
..Default::default()
})
}
@@ -224,3 +247,55 @@ impl OverlayBackend for LineBackend {
false
}
}
pub fn transition_layout(
gfx: &WGfx,
image: Arc<Image>,
old_layout: ImageLayout,
new_layout: ImageLayout,
) -> anyhow::Result<Fence> {
let barrier = ImageMemoryBarrier {
src_stages: PipelineStages::ALL_TRANSFER,
src_access: AccessFlags::TRANSFER_WRITE,
dst_stages: PipelineStages::ALL_TRANSFER,
dst_access: AccessFlags::TRANSFER_READ,
old_layout,
new_layout,
subresource_range: image.subresource_range(),
..ImageMemoryBarrier::image(image)
};
let command_buffer = unsafe {
let mut builder = RecordingCommandBuffer::new(
gfx.command_buffer_allocator.clone(),
gfx.queue_gfx.queue_family_index(),
CommandBufferLevel::Primary,
CommandBufferBeginInfo {
usage: CommandBufferUsage::OneTimeSubmit,
inheritance_info: None,
..Default::default()
},
)?;
builder.pipeline_barrier(&DependencyInfo {
image_memory_barriers: smallvec::smallvec![barrier],
..Default::default()
})?;
builder.end()?
};
let fence = Fence::new(gfx.device.clone(), FenceCreateInfo::default())?;
let fns = gfx.device.fns();
unsafe {
(fns.v1_0.queue_submit)(
gfx.queue_gfx.handle(),
1,
[SubmitInfo::default().command_buffers(&[command_buffer.handle()])].as_ptr(),
fence.handle(),
)
}
.result()?;
Ok(fence)
}
+14 -44
View File
@@ -11,6 +11,7 @@ use ovr_overlay::{
sys::{ETrackedDeviceProperty, EVRApplicationType, EVREventType},
};
use smallvec::smallvec;
use vulkano::{Handle, VulkanObject, device::physical::PhysicalDevice};
use wgui::i18n::Translation;
use wlx_common::{dash_interface::InterfaceFeats, overlays::ToastTopic};
@@ -76,12 +77,16 @@ pub fn openvr_run(args: &Args, params: RunParams) -> Result<(), BackendError> {
let mut chaperone_mgr = context.chaperone_setup_mngr();
let mut compositor_mgr = context.compositor_mngr();
let device_extensions_fn = |physical_device_handle: u64| {
compositor_mgr.get_vulkan_device_extensions_required(physical_device_handle)
let device_extensions_fn = |device: &PhysicalDevice| {
let names = compositor_mgr.get_vulkan_device_extensions_required(device.handle().as_raw());
names.iter().map(std::string::String::as_str).collect()
};
let mut compositor_mgr = context.compositor_mngr();
let instance_extensions = compositor_mgr.get_vulkan_instance_extensions_required();
let instance_extensions = {
let names = compositor_mgr.get_vulkan_instance_extensions_required();
names.iter().map(std::string::String::as_str).collect()
};
let feats = InterfaceFeats::default_for_backend(XrBackend::OpenVR);
@@ -116,10 +121,6 @@ pub fn openvr_run(args: &Args, params: RunParams) -> Result<(), BackendError> {
let mut overlays = OverlayWindowManager::<OpenVrOverlayData>::new(&mut app, args.headless)?;
let mut dashboard_hidden = false;
let mut dash_toast_shown = false;
let mut dash_toast_id = None;
let mut playspace = playspace::PlayspaceMover::new();
playspace.playspace_changed(&mut compositor_mgr, &mut chaperone_mgr);
@@ -208,29 +209,6 @@ pub fn openvr_run(args: &Args, params: RunParams) -> Result<(), BackendError> {
}
}
if !dashboard_hidden {
if overlay_mgr.is_dashboard_visible() {
if !dash_toast_shown {
let id = Toast::new(
ToastTopic::Priority,
Some(Translation::from_translation_key(
"STEAMVR_DASH_TOAST_TITLE",
)),
Translation::from_translation_key("STEAMVR_DASH_TOAST_TEXT"),
)
.with_timeout(300.0) // we'll zero this if dash gets hidden
.submit(&mut app);
dash_toast_id = Some(id);
dash_toast_shown = true;
}
} else {
dashboard_hidden = true;
if let Some(id) = dash_toast_id {
Toast::expire_now(&mut app, id);
}
}
}
if next_device_update <= Instant::now() {
let changed = input_source.update_devices(&mut system_mgr, &mut app);
if changed {
@@ -302,7 +280,7 @@ pub fn openvr_run(args: &Args, params: RunParams) -> Result<(), BackendError> {
.enqueue(TaskType::Overlay(OverlayTask::ToggleDashboard));
}
overlays.tick(&mut app);
overlays.values_mut().for_each(|o| o.config.tick(&mut app));
playspace.update(&mut chaperone_mgr, &mut overlays, &mut app);
@@ -310,9 +288,7 @@ pub fn openvr_run(args: &Args, params: RunParams) -> Result<(), BackendError> {
let haptics = interact(&mut overlays, &mut app, &mut current_lines);
for (idx, haptics) in haptics.iter().enumerate() {
if let Some(haptics) = haptics
&& !app.input_state.pointers[idx].handsfree
{
if let Some(haptics) = haptics {
input_source.haptics(&mut input_mgr, idx, haptics);
}
}
@@ -350,9 +326,6 @@ pub fn openvr_run(args: &Args, params: RunParams) -> Result<(), BackendError> {
for o in overlays.values_mut() {
if o.data.visible {
if !(dashboard_hidden || o.data.always_visible) {
continue;
}
let ShouldRender::Should = o.should_render(&mut app)? else {
continue;
};
@@ -363,19 +336,16 @@ pub fn openvr_run(args: &Args, params: RunParams) -> Result<(), BackendError> {
let mut rdr = RenderResources::new(app.gfx.clone(), tgt, &meta)?;
o.render(&mut app, &mut rdr)?;
o.data.image_dirty = true;
futures.execute_results(rdr.end()?);
futures.execute_results(rdr.end()?)?;
}
}
log::trace!("Rendering overlays");
futures.wait()?;
overlays.values_mut().for_each(|o| {
if !(dashboard_hidden || o.data.always_visible) {
return;
}
o.after_render(universe.clone(), &mut overlay_mgr, &app.gfx);
});
overlays
.values_mut()
.for_each(|o| o.after_render(universe.clone(), &mut overlay_mgr, &app.gfx));
lines.reset();
+19 -23
View File
@@ -7,9 +7,13 @@ use ovr_overlay::{
pose::Matrix3x4,
sys::{ETrackingUniverseOrigin, VRVulkanTextureData_t},
};
use wgui::gfx::{ImageLayout, ImageUsage, ImageView, WGfx};
use vulkano::{
Handle, VulkanObject,
image::{ImageUsage, view::ImageView},
};
use wgui::gfx::WGfx;
use crate::{overlays::toast::TOAST_NAME, state::AppState, windowing::window::OverlayWindowData};
use crate::{graphics::ExtentExt, state::AppState, windowing::window::OverlayWindowData};
use super::helpers::Affine3AConvert;
@@ -17,7 +21,6 @@ use super::helpers::Affine3AConvert;
pub(super) struct OpenVrOverlayData {
pub(super) handle: Option<OverlayHandle>,
pub(super) visible: bool,
pub(super) always_visible: bool,
pub(super) color: Vec4,
pub(crate) width: f32,
pub(super) override_width: bool,
@@ -41,7 +44,6 @@ impl OverlayWindowData<OpenVrOverlayData> {
};
log::debug!("{}: initialize", self.config.name);
self.data.always_visible = self.config.name.as_ref() == TOAST_NAME.as_ref();
self.data.handle = Some(handle);
self.data.color = Vec4::ONE;
@@ -66,7 +68,7 @@ impl OverlayWindowData<OpenVrOverlayData> {
extent: [u32; 2],
) -> anyhow::Result<Arc<ImageView>> {
if let Some(image_view) = self.data.image_view.as_ref()
&& image_view.extent_2d() == extent
&& image_view.extent_u32arr() == extent
{
return Ok(image_view.clone());
}
@@ -81,10 +83,10 @@ impl OverlayWindowData<OpenVrOverlayData> {
let image = app.gfx.new_image(
extent[0],
extent[1],
app.gfx.surface_format(),
app.gfx.surface_format,
ImageUsage::TRANSFER_SRC | ImageUsage::COLOR_ATTACHMENT | ImageUsage::SAMPLED,
)?;
let image_view = app.gfx.create_image_view(image)?;
let image_view = ImageView::new_default(image)?;
self.data.image_view = Some(image_view.clone());
Ok(image_view)
}
@@ -291,26 +293,20 @@ impl OverlayWindowData<OpenVrOverlayData> {
}
}
if let Err(e) = graphics.transition_image_now(&image, ImageLayout::TransferSrc) {
log::error!(
"{}: Failed to prepare overlay image: {e:#}",
self.config.name
);
return;
}
let raw_image = image.handle().as_raw();
let format = image.format();
let mut texture = VRVulkanTextureData_t {
m_nImage: image.raw_handle(),
m_nFormat: graphics.raw_format(format) as _,
m_nImage: raw_image,
m_nFormat: format as _,
m_nWidth: dimensions[0],
m_nHeight: dimensions[1],
m_nSampleCount: 1,
m_pDevice: graphics.raw_device_handle() as *mut _,
m_pPhysicalDevice: graphics.raw_physical_device_handle() as *mut _,
m_pInstance: graphics.raw_instance_handle() as *mut _,
m_pQueue: graphics.raw_graphics_queue_handle() as *mut _,
m_nQueueFamilyIndex: graphics.graphics_queue_family_index(),
m_nSampleCount: image.samples() as u32,
m_pDevice: graphics.device.handle().as_raw() as *mut _,
m_pPhysicalDevice: graphics.device.physical_device().handle().as_raw() as *mut _,
m_pInstance: graphics.instance.handle().as_raw() as *mut _,
m_pQueue: graphics.queue_gfx.handle().as_raw() as *mut _,
m_nQueueFamilyIndex: graphics.queue_gfx.queue_family_index(),
};
log::trace!(
"{}: UploadTex {:?}, {}x{}, {:?}",
+8 -35
View File
@@ -19,15 +19,8 @@ pub(crate) use next_chain_insert;
use crate::state::AppState;
#[derive(Default)]
pub(super) struct ExtraExts {
pub mndx_composition_layer_alpha_blend_system_ui: bool,
pub ext_eye_gaze_interaction: bool,
}
pub(super) fn init_xr() -> Result<(xr::Instance, xr::SystemId, ExtraExts), anyhow::Error> {
pub(super) fn init_xr() -> Result<(xr::Instance, xr::SystemId), anyhow::Error> {
let entry = xr::Entry::linked();
let mut extra_exts = ExtraExts::default();
let Ok(available_extensions) = entry.enumerate_extensions() else {
bail!("Failed to enumerate OpenXR extensions.");
@@ -50,6 +43,11 @@ pub(super) fn init_xr() -> Result<(xr::Instance, xr::SystemId, ExtraExts), anyho
} else {
log::warn!("Missing EXT_dpad_binding extension.");
}
if available_extensions.fb_composition_layer_alpha_blend {
enabled_extensions.fb_composition_layer_alpha_blend = true;
} else {
log::warn!("Missing XR_FB_composition_layer_alpha_blend extension.");
}
if available_extensions.ext_samsung_odyssey_controller {
enabled_extensions.ext_samsung_odyssey_controller = true;
@@ -68,7 +66,6 @@ pub(super) fn init_xr() -> Result<(xr::Instance, xr::SystemId, ExtraExts), anyho
}
if available_extensions.ext_eye_gaze_interaction {
enabled_extensions.ext_eye_gaze_interaction = true;
extra_exts.ext_eye_gaze_interaction = true;
} else {
log::warn!("Missing EXT_eye_gaze_interaction extension.");
}
@@ -88,14 +85,6 @@ pub(super) fn init_xr() -> Result<(xr::Instance, xr::SystemId, ExtraExts), anyho
log::warn!("Missing XR_KHR_composition_layer_color_scale_bias extension.");
}
let xr_extension = b"XR_MNDX_composition_layer_alpha_blend_system_ui\0".to_vec();
if available_extensions.other.contains(&xr_extension) {
enabled_extensions.other.push(xr_extension);
extra_exts.mndx_composition_layer_alpha_blend_system_ui = true;
} else {
log::warn!("Missing XR_MNDX_composition_layer_alpha_blend_system_ui extension.");
}
let xr_extension = b"XR_MNDX_system_buttons\0".to_vec();
if available_extensions.other.contains(&xr_extension) {
enabled_extensions.other.push(xr_extension);
@@ -142,22 +131,6 @@ pub(super) fn init_xr() -> Result<(xr::Instance, xr::SystemId, ExtraExts), anyho
instance_props.runtime_version
);
let available_extensions = available_extensions
.names()
.into_iter()
.filter_map(|name| {
Some(
CStr::from_bytes_with_nul(name)
.ok()?
.to_string_lossy()
.into_owned(),
)
})
.collect::<Vec<_>>()
.join("\n");
log::debug!("Available OpenXR extensions:\n{available_extensions}");
let enabled_extensions = enabled_extensions
.names()
.into_iter()
@@ -172,7 +145,7 @@ pub(super) fn init_xr() -> Result<(xr::Instance, xr::SystemId, ExtraExts), anyho
.collect::<Vec<_>>()
.join("\n");
log::debug!("Enabled OpenXR extensions:\n{enabled_extensions}");
log::info!("OpenXR extensions:\n{enabled_extensions}");
let Ok(system) = xr_instance.system(xr::FormFactor::HEAD_MOUNTED_DISPLAY) else {
bail!("Failed to access OpenXR HMD system.");
@@ -194,7 +167,7 @@ pub(super) fn init_xr() -> Result<(xr::Instance, xr::SystemId, ExtraExts), anyho
);
}
Ok((xr_instance, system, extra_exts))
Ok((xr_instance, system))
}
pub(super) unsafe fn create_overlay_session(
File diff suppressed because it is too large. Load diff
+13 -6
View File
@@ -11,13 +11,20 @@ use std::{
};
use wgui::gfx::{
Buffer, BufferUsage, CommandBufferUsage, Scissor, Vert2Uv,
cmd::WGfxClearMode,
pass::WGfxPass,
pipeline::{WGfxPipeline, WPipelineCreateInfo},
};
use crate::{backend::openxr::helpers, graphics::GpuFutures, state::AppState};
use crate::{
backend::openxr::helpers,
graphics::{GpuFutures, Vert2Uv},
state::AppState,
};
use vulkano::{
buffer::{BufferUsage, Subbuffer},
command_buffer::CommandBufferUsage,
};
use super::{
CompositionLayer, XrState,
@@ -48,7 +55,7 @@ impl LinePool {
let pipeline = app.gfx.create_pipeline(
app.gfx_extras.shaders.get("vert_quad").unwrap(), // want panic
app.gfx_extras.shaders.get("frag_color").unwrap(), // want panic
WPipelineCreateInfo::new(app.gfx.surface_format()),
WPipelineCreateInfo::new(app.gfx.surface_format),
)?;
Ok(Self {
@@ -73,7 +80,7 @@ impl LinePool {
0..4,
0..1,
vec![set0],
Scissor::from_viewport([1.0, 1.0], [0.0, 0.0]),
&Default::default(),
)?;
let srd = create_swapchain(xr, app.gfx.clone(), [1, 1], 1, SwapchainOpts::new())?;
@@ -175,7 +182,7 @@ impl LinePool {
cmd_buffer.run_ref(&line.pass)?;
cmd_buffer.end_rendering()?;
futures.execute(cmd_buffer.build()?);
futures.execute(cmd_buffer.queue.clone(), cmd_buffer.build()?)?;
}
}
@@ -218,7 +225,7 @@ pub(super) struct Line {
struct LineContainer {
swapchain: WlxSwapchain,
buf_color: Arc<Buffer<f32>>,
buf_color: Subbuffer<[f32]>,
pass: WGfxPass<Vert2Uv>,
maybe_line: Option<Line>,
}
+26 -66
View File
@@ -9,6 +9,7 @@ use glam::{Affine3A, Vec3};
use input::OpenXrInputSource;
use openxr as xr;
use skybox::{Skybox, create_skybox};
use vulkano::{Handle, VulkanObject};
use wgui::i18n::Translation;
use wlx_common::{
dash_interface::InterfaceFeats,
@@ -20,12 +21,7 @@ use crate::{
backend::{
BackendError, RunParams,
input::interact,
openxr::{
helpers::{ExtraExts, try_apply_chroma_key},
lines::LinePool,
overlay::OpenXrOverlayData,
swapchain::SwapchainRetirementQueue,
},
openxr::{helpers::try_apply_chroma_key, lines::LinePool, overlay::OpenXrOverlayData},
task::{OpenXrTask, OverlayTask, TaskType},
},
config::{save_settings, save_state},
@@ -57,25 +53,12 @@ struct XrState {
fps: f32,
stage: Arc<xr::Space>,
view: Arc<xr::Space>,
extra_exts: ExtraExts,
}
/// XR runtime using our vk device/queue can have submissions in flight after last xrEndFrame/xrReleaseSwapchainImage
/// This guard is dropped first, draining shared device before swapchain/session teardown begins
struct OpenXrGpuIdleGuard(Arc<wgui::gfx::WGfx>);
impl Drop for OpenXrGpuIdleGuard {
fn drop(&mut self) {
if let Err(e) = self.0.wait_idle() {
log::error!("Failed waiting for Vulkan device idle during OpenXR shutdown: {e:#}");
}
}
}
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
pub fn openxr_run(args: &Args, params: RunParams) -> Result<(), BackendError> {
let (xr_instance, system, extra_exts) = match helpers::init_xr() {
Ok((xr_instance, system, extra_exts)) => (xr_instance, system, extra_exts),
let (xr_instance, system) = match helpers::init_xr() {
Ok((xr_instance, system)) => (xr_instance, system),
Err(e) => {
if !args.wait {
log::warn!("Will not use OpenXR: {e}");
@@ -85,7 +68,10 @@ pub fn openxr_run(args: &Args, params: RunParams) -> Result<(), BackendError> {
};
let feats = InterfaceFeats {
passthru: extra_exts.mndx_composition_layer_alpha_blend_system_ui,
passthru: xr_instance
.exts()
.fb_composition_layer_alpha_blend
.is_some(),
..InterfaceFeats::default_for_backend(XrBackend::OpenXR)
};
@@ -97,7 +83,6 @@ pub fn openxr_run(args: &Args, params: RunParams) -> Result<(), BackendError> {
app.session.no_autostart = args.no_autostart;
let modes = xr_instance.enumerate_environment_blend_modes(system, VIEW_TYPE)?;
log::debug!("Env blend modes: {modes:?}");
if args.show {
app.session.config.tutorial_graduated = true;
@@ -127,11 +112,11 @@ pub fn openxr_run(args: &Args, params: RunParams) -> Result<(), BackendError> {
&xr_instance,
system,
&xr::vulkan::SessionCreateInfo {
instance: app.gfx.raw_instance_handle() as _,
physical_device: app.gfx.raw_physical_device_handle() as _,
device: app.gfx.raw_device_handle() as _,
queue_family_index: app.gfx.graphics_queue_family_index(),
queue_index: app.gfx.graphics_queue_index(),
instance: app.gfx.instance.handle().as_raw() as _,
physical_device: app.gfx.device.physical_device().handle().as_raw() as _,
device: app.gfx.device.handle().as_raw() as _,
queue_family_index: app.gfx.queue_gfx.queue_family_index(),
queue_index: 0,
},
)?;
xr::Session::from_raw(xr_instance.clone(), raw_session, Box::new(()))
@@ -149,7 +134,6 @@ pub fn openxr_run(args: &Args, params: RunParams) -> Result<(), BackendError> {
fps: 30.0,
stage: Arc::new(stage),
view: Arc::new(view),
extra_exts,
};
let mut skybox: Option<Skybox> = None;
@@ -174,10 +158,6 @@ pub fn openxr_run(args: &Args, params: RunParams) -> Result<(), BackendError> {
let mut main_session_visible = false;
let mut environment_blend_mode = modes[0];
let mut last_frame_time = Instant::now();
let mut retired_swapchains = SwapchainRetirementQueue::default();
// declared last so rust drops this before all long-lived XR objects
let _gpu_idle_before_xr_drop = OpenXrGpuIdleGuard(app.gfx.clone());
'main_loop: loop {
let now = Instant::now();
@@ -212,7 +192,6 @@ pub fn openxr_run(args: &Args, params: RunParams) -> Result<(), BackendError> {
&xr_state,
&modes,
&mut skybox,
&mut retired_swapchains,
&mut environment_blend_mode,
main_session_visible,
);
@@ -240,7 +219,6 @@ pub fn openxr_run(args: &Args, params: RunParams) -> Result<(), BackendError> {
&xr_state,
&modes,
&mut skybox,
&mut retired_swapchains,
&mut environment_blend_mode,
main_session_visible,
);
@@ -263,9 +241,6 @@ pub fn openxr_run(args: &Args, params: RunParams) -> Result<(), BackendError> {
continue 'main_loop;
}
// poll only, never wait for GPU
retired_swapchains.collect(&app.gfx);
log::trace!("xrWaitFrame");
let xr_frame_state = frame_wait.wait()?;
log::trace!("xrBeginFrame");
@@ -345,7 +320,7 @@ pub fn openxr_run(args: &Args, params: RunParams) -> Result<(), BackendError> {
)?;
let ipd = helpers::ipd_from_views(&views);
if (app.input_state.ipd - ipd).abs() > 0.5 {
if (app.input_state.ipd - ipd).abs() > 0.05 {
log::info!("IPD changed: {} -> {}", app.input_state.ipd, ipd);
app.input_state.ipd = ipd;
Toast::new(
@@ -356,7 +331,7 @@ pub fn openxr_run(args: &Args, params: RunParams) -> Result<(), BackendError> {
.submit(&mut app);
}
overlays.tick(&mut app);
overlays.values_mut().for_each(|o| o.config.tick(&mut app));
current_lines.clear();
@@ -431,17 +406,11 @@ pub fn openxr_run(args: &Args, params: RunParams) -> Result<(), BackendError> {
log::trace!("{}: render new frame", o.config.name);
let meta = o.config.backend.frame_meta().unwrap(); // want panic
let stereo = !matches!(meta.stereo, StereoMode::None);
let wsi = o.ensure_swapchain_acquire(
&app,
&xr_state,
&mut retired_swapchains,
meta.extent,
stereo,
)?;
let wsi = o.ensure_swapchain_acquire(&app, &xr_state, meta.extent, stereo)?;
let tgt = RenderTarget { views: wsi.views };
let mut rdr = RenderResources::new(app.gfx.clone(), tgt, &meta)?;
o.render(&mut app, &mut rdr)?;
futures.execute_results(rdr.end()?);
futures.execute_results(rdr.end()?)?;
} else if o.data.swapchain.is_none() {
log::trace!("{}: not showing due to missing swapchain", o.config.name);
continue;
@@ -533,7 +502,6 @@ pub fn openxr_run(args: &Args, params: RunParams) -> Result<(), BackendError> {
&xr_state,
&modes,
&mut skybox,
&mut retired_swapchains,
&mut environment_blend_mode,
main_session_visible,
);
@@ -544,14 +512,10 @@ pub fn openxr_run(args: &Args, params: RunParams) -> Result<(), BackendError> {
}
}
while let Some(mut o) = overlays.pop_dropped() {
if let Some(swapchain) = o.data.swapchain.take() {
retired_swapchains.retire(swapchain, &app.gfx);
}
while let Some(o) = overlays.pop_dropped() {
delete_queue.push((o, cur_frame + 5));
}
retired_swapchains.collect(&app.gfx);
delete_queue.retain(|(_, frame)| *frame > cur_frame);
//FIXME: Temporary workaround for Monado bug
@@ -595,7 +559,6 @@ fn reconfigure_environment_blend(
xr_state: &XrState,
modes: &[xr::EnvironmentBlendMode],
skybox: &mut Option<Skybox>,
retired_swapchains: &mut SwapchainRetirementQueue,
environment_blend_mode: &mut xr::EnvironmentBlendMode,
main_session_visible: bool,
) {
@@ -616,21 +579,18 @@ fn reconfigure_environment_blend(
&& !main_session_visible;
if want_skybox {
if skybox
.as_ref()
.is_some_and(|curr_skybox| curr_skybox.needs_recreate(app))
{
if let Some(old_skybox) = skybox.take() {
old_skybox.retire(retired_swapchains, app);
if let Some(curr_skybox) = skybox.as_ref() {
if curr_skybox.needs_recreate(app) {
*skybox = None;
log::debug!("Allocating skybox.");
*skybox = create_skybox(xr_state, app);
}
log::debug!("Allocating skybox.");
*skybox = create_skybox(xr_state, app);
} else if skybox.is_none() {
} else {
log::debug!("Allocating skybox.");
*skybox = create_skybox(xr_state, app);
}
} else if let Some(old_skybox) = skybox.take() {
} else {
log::debug!("Destroying skybox.");
old_skybox.retire(retired_swapchains, app);
*skybox = None;
}
}
+7 -6
View File
@@ -8,7 +8,7 @@ use super::{CompositionLayer, XrState, helpers, swapchain::WlxSwapchain};
use crate::{
backend::openxr::{
helpers::next_chain_insert,
swapchain::{SwapchainOpts, SwapchainRetirementQueue, WlxSwapchainImage, create_swapchain},
swapchain::{SwapchainOpts, WlxSwapchainImage, create_swapchain},
},
state::AppState,
windowing::window::{OverlayCategory, OverlayWindowData},
@@ -29,7 +29,6 @@ impl OverlayWindowData<OpenXrOverlayData> {
&'a mut self,
app: &AppState,
xr: &'a XrState,
retired_swapchains: &mut SwapchainRetirementQueue,
extent: [u32; 2],
stereo: bool,
) -> anyhow::Result<WlxSwapchainImage> {
@@ -57,9 +56,7 @@ impl OverlayWindowData<OpenXrOverlayData> {
SwapchainOpts::new(),
)?;
let tgt = swapchain.acquire_wait_image()?;
if let Some(old_swapchain) = self.data.swapchain.replace(swapchain) {
retired_swapchains.retire(old_swapchain, &app.gfx);
}
self.data.swapchain = Some(swapchain);
Ok(tgt)
}
@@ -114,7 +111,11 @@ impl OverlayWindowData<OpenXrOverlayData> {
flags |= CompositionLayerFlags::UNPREMULTIPLIED_ALPHA;
}
if xr.extra_exts.mndx_composition_layer_alpha_blend_system_ui
if xr
.instance
.exts()
.fb_composition_layer_alpha_blend
.is_some()
&& self.config.category == OverlayCategory::Passthru
&& self.data.layer_alpha_blend_fb.is_none()
&& !self.config.editing
+18 -30
View File
@@ -2,18 +2,19 @@ use std::{f32::consts::PI, fs::File, sync::Arc};
use glam::{Quat, Vec3A};
use openxr as xr;
use wgui::gfx::{
BLEND_ALPHA, CommandBufferUsage, ImageView, Scissor, cmd::WGfxClearMode,
pipeline::WPipelineCreateInfo,
use vulkano::{
command_buffer::CommandBufferUsage, image::view::ImageView,
pipeline::graphics::color_blend::AttachmentBlend,
};
use wgui::gfx::{cmd::WGfxClearMode, pipeline::WPipelineCreateInfo};
use wlx_common::config_io;
use crate::{
backend::openxr::{
helpers::{next_chain_insert, translation_rotation_to_posef},
swapchain::{SwapchainOpts, SwapchainRetirementQueue},
swapchain::SwapchainOpts,
},
graphics::{GpuFutures, dds::WlxCommandBufferDds},
graphics::{ExtentExt, GpuFutures, dds::WlxCommandBufferDds},
state::AppState,
};
@@ -75,7 +76,7 @@ impl Skybox {
let view = if let Some(image) = maybe_image {
command_buffer.build_and_execute_now()?;
Some(app.gfx.create_image_view(image)?)
Some(ImageView::new_default(image)?)
} else {
None
};
@@ -112,19 +113,6 @@ impl Skybox {
*self.current_skybox != *app.session.config.skybox_texture
}
pub(super) fn retire(
mut self,
retired_swapchains: &mut SwapchainRetirementQueue,
app: &AppState,
) {
if let Some(swapchain) = self.sky.take() {
retired_swapchains.retire(swapchain, &app.gfx);
}
if let Some(swapchain) = self.grid.take() {
retired_swapchains.retire(swapchain, &app.gfx);
}
}
fn prepare_sky<'a>(
&'a mut self,
xr: &'a XrState,
@@ -139,7 +127,7 @@ impl Skybox {
let extent = self
.view
.as_ref()
.map_or([4096, 4096], |view| view.extent_2d());
.map_or([4096, 4096], ExtentExt::extent_u32arr);
let mut swapchain = create_swapchain(xr, app.gfx.clone(), extent, 1, opts)?;
let tgt = swapchain
@@ -153,11 +141,11 @@ impl Skybox {
let pipeline = app.gfx.create_pipeline(
app.gfx_extras.shaders.get("vert_quad").unwrap(), // want panic
app.gfx_extras.shaders.get("frag_srgb").unwrap(), // want panic
WPipelineCreateInfo::new(app.gfx.surface_format()),
WPipelineCreateInfo::new(app.gfx.surface_format),
)?;
let set0 = pipeline.uniform_sampler(0, view.clone(), app.gfx.texture_filter())?;
let set1 = pipeline.uniform_buffer_upload(1, &[1f32])?;
let set0 = pipeline.uniform_sampler(0, view.clone(), app.gfx.texture_filter)?;
let set1 = pipeline.uniform_buffer_upload(1, vec![1f32])?;
pipeline.create_pass(
tgt.extent_f32(),
[0.0, 0.0],
@@ -165,13 +153,13 @@ impl Skybox {
0..4,
0..1,
vec![set0, set1],
Scissor::new([0, 0], tgt.extent_2d()),
&Default::default(),
)?
} else {
let pipeline = app.gfx.create_pipeline(
app.gfx_extras.shaders.get("vert_quad").unwrap(), // want panic
app.gfx_extras.shaders.get("frag_sky").unwrap(), // want panic
WPipelineCreateInfo::new(app.gfx.surface_format()),
WPipelineCreateInfo::new(app.gfx.surface_format),
)?;
pipeline.create_pass(
@@ -181,7 +169,7 @@ impl Skybox {
0..4,
0..1,
vec![],
Scissor::new([0, 0], tgt.extent_2d()),
&Default::default(),
)?
};
@@ -192,7 +180,7 @@ impl Skybox {
cmd_buffer.run_ref(&pass)?;
cmd_buffer.end_rendering()?;
futures.execute(cmd_buffer.build()?);
futures.execute(cmd_buffer.queue.clone(), cmd_buffer.build()?)?;
self.sky = Some(swapchain);
Ok(())
@@ -219,7 +207,7 @@ impl Skybox {
let pipeline = app.gfx.create_pipeline(
app.gfx_extras.shaders.get("vert_quad").unwrap(), // want panic
app.gfx_extras.shaders.get("frag_grid").unwrap(), // want panic
WPipelineCreateInfo::new(app.gfx.surface_format()).use_blend(BLEND_ALPHA),
WPipelineCreateInfo::new(app.gfx.surface_format).use_blend(AttachmentBlend::alpha()),
)?;
let tgt = swapchain
@@ -235,7 +223,7 @@ impl Skybox {
0..4,
0..1,
vec![],
Scissor::new([0, 0], tgt.extent_2d()),
&Default::default(),
)?;
let mut cmd_buffer = app
@@ -245,7 +233,7 @@ impl Skybox {
cmd_buffer.run_ref(&pass)?;
cmd_buffer.end_rendering()?;
futures.execute(cmd_buffer.build()?);
futures.execute(cmd_buffer.queue.clone(), cmd_buffer.build()?)?;
self.grid = Some(swapchain);
Ok(())
+29 -100
View File
@@ -1,12 +1,18 @@
use std::sync::Arc;
use ash::vk;
use openxr as xr;
use smallvec::SmallVec;
use wgui::gfx::{
GpuCompletionMarker, ImageCreateInfo, ImageLayout, ImageUsage, ImageView, ImageViewCreateInfo,
WGfx,
use vulkano::{
Handle,
image::{
ImageCreateInfo, ImageUsage,
sys::RawImage,
view::{ImageView, ImageViewCreateInfo},
},
};
use wgui::gfx::WGfx;
use super::XrState;
@@ -42,7 +48,7 @@ pub(super) fn create_swapchain(
let swapchain = xr.session.create_swapchain(&xr::SwapchainCreateInfo {
create_flags,
usage_flags: xr::SwapchainUsageFlags::COLOR_ATTACHMENT | xr::SwapchainUsageFlags::SAMPLED,
format: gfx.raw_format(gfx.surface_format()) as _,
format: gfx.surface_format as _,
sample_count: 1,
width: extent[0],
height: extent[1],
@@ -55,35 +61,28 @@ pub(super) fn create_swapchain(
.enumerate_images()?
.into_iter()
.map(|handle| {
// SAFETY: XR runtime owns the swapchain image & keeps it alive until explicitly destroyed
let image = unsafe {
gfx.wrap_external_image_with_layout(
handle,
let vk_image = vk::Image::from_raw(handle);
// thanks @yshui
let raw_image = unsafe {
RawImage::from_handle_borrowed(
gfx.device.clone(),
vk_image,
ImageCreateInfo {
format: gfx.surface_format(),
format: gfx.surface_format as _,
extent: [extent[0], extent[1], 1],
array_layers: array_size,
usage: ImageUsage::COLOR_ATTACHMENT | ImageUsage::SAMPLED,
..ImageCreateInfo::new_2d(
extent[0],
extent[1],
gfx.surface_format(),
ImageUsage::COLOR_ATTACHMENT | ImageUsage::SAMPLED,
)
},
ImageLayout::ColorAttachment,
)?
};
let mut wsi = WlxSwapchainImage::default();
for d in 0..array_size {
wsi.views.push(gfx.create_image_view_with_info(
image.clone(),
ImageViewCreateInfo {
base_array_layer: d,
array_layer_count: 1,
usage: ImageUsage::COLOR_ATTACHMENT,
..Default::default()
},
)?);
)?
};
// SAFETY: OpenXR guarantees that the image is a swapchain image, thus has memory backing it.
let image = Arc::new(unsafe { raw_image.assume_bound() });
let mut wsi = WlxSwapchainImage::default();
for d in 0..array_size {
let mut create_info = ImageViewCreateInfo::from_image(&image);
create_info.subresource_range.array_layers = d..d + 1;
wsi.views.push(ImageView::new(image.clone(), create_info)?);
}
Ok(wsi)
})
@@ -92,8 +91,8 @@ pub(super) fn create_swapchain(
Ok(WlxSwapchain {
acquired: false,
ever_acquired: false,
images,
swapchain,
images,
extent,
array_size,
})
@@ -107,11 +106,10 @@ pub(super) struct WlxSwapchainImage {
pub(super) struct WlxSwapchain {
acquired: bool,
pub(super) ever_acquired: bool,
// drop image views before destroying parent swapchain
pub(super) images: SmallVec<[WlxSwapchainImage; 4]>,
pub(super) swapchain: xr::Swapchain<xr::Vulkan>,
pub(super) extent: [u32; 2],
pub(super) array_size: u32,
pub(super) images: SmallVec<[WlxSwapchainImage; 4]>,
}
impl WlxSwapchain {
@@ -145,72 +143,3 @@ impl WlxSwapchain {
.image_array_index(array_index)
}
}
struct RetiredSwapchain {
// keep alive until completion is signaled
_swapchain: WlxSwapchain,
completion: Option<GpuCompletionMarker>,
}
/// Keeps destroyed xrswapchains alive until work submitted by runtime from has finished
#[derive(Default)]
pub(super) struct SwapchainRetirementQueue {
retired: Vec<RetiredSwapchain>,
}
impl SwapchainRetirementQueue {
/// queue the swapchain for non-blocking retirement
pub(super) fn retire(&mut self, swapchain: WlxSwapchain, gfx: &Arc<WGfx>) {
self.retired.push(RetiredSwapchain {
_swapchain: swapchain,
completion: None,
});
let idx = self.retired.len() - 1;
if let Err(e) = Self::arm(&mut self.retired[idx], gfx) {
log::warn!("Failed arming retired OpenXR swapchain: {e:#}");
}
}
fn arm(retired: &mut RetiredSwapchain, gfx: &Arc<WGfx>) -> anyhow::Result<()> {
if retired.completion.is_some() {
return Ok(());
}
retired._swapchain.ensure_image_released()?;
retired.completion = Some(gfx.submit_graphics_completion_marker()?);
Ok(())
}
pub(super) fn collect(&mut self, gfx: &Arc<WGfx>) {
let mut idx = 0;
while idx < self.retired.len() {
if self.retired[idx].completion.is_none() {
if let Err(e) = Self::arm(&mut self.retired[idx], gfx) {
log::warn!("Failed re-arming retired OpenXR swapchain: {e:#}");
idx += 1;
continue;
}
}
let complete = match self.retired[idx]
.completion
.as_ref()
.expect("retired swapchain completion marker missing after arm")
.is_complete()
{
Ok(complete) => complete,
Err(e) => {
log::warn!("Failed polling retired OpenXR swapchain: {e:#}");
false
}
};
if complete {
self.retired.swap_remove(idx);
} else {
idx += 1;
}
}
}
}
+1 -3
View File
@@ -12,7 +12,7 @@ use crate::{
backend::input,
state::AppState,
windowing::{
OverlayID, OverlaySelector,
OverlaySelector,
window::{OverlayCategory, OverlayWindowConfig},
},
};
@@ -135,8 +135,6 @@ pub enum OverlayTask {
Spawn(OverlaySelector, SpawnPos, Box<CreateOverlayTask>),
ModifyPanel(ModifyPanelTask),
Drop(OverlaySelector),
DropImmediate(OverlaySelector),
DropExpired(OverlayID),
}
pub type GlobalTask = dyn FnOnce(&mut AppState) + Send;
+25 -111
View File
@@ -1,5 +1,4 @@
use anyhow::Context;
use smithay::backend::allocator::Buffer;
use smithay::backend::allocator::dmabuf::Dmabuf;
use smithay::backend::renderer::{BufferType, buffer_type};
use smithay::desktop::{
@@ -15,9 +14,7 @@ use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
use smithay::reexports::wayland_protocols_misc::server_decoration::server::org_kde_kwin_server_decoration;
use smithay::reexports::wayland_server::Resource;
use smithay::reexports::wayland_server::backend::ObjectId;
use smithay::reexports::wayland_server::protocol::{
wl_buffer, wl_callback, wl_data_source, wl_output, wl_seat,
};
use smithay::reexports::wayland_server::protocol::{wl_buffer, wl_callback, wl_output, wl_seat};
use smithay::reexports::wayland_server::{self, DisplayHandle};
use smithay::utils::{Logical, Rectangle, Serial, Size};
use smithay::wayland::buffer::BufferHandler;
@@ -43,7 +40,7 @@ use smithay::wayland::shell::xdg::decoration::{XdgDecorationHandler, XdgDecorati
use smithay::wayland::shell::xdg::{
PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,
};
use smithay::wayland::shm::{ShmHandler, ShmState, shm_format_to_fourcc, with_buffer_contents};
use smithay::wayland::shm::{ShmHandler, ShmState, with_buffer_contents};
use smithay::wayland::single_pixel_buffer::get_single_pixel_buffer;
use smithay::wayland::viewporter::ViewporterState;
use smithay::{
@@ -60,10 +57,10 @@ use std::sync::{Arc, Mutex};
use wayland_server::Client;
use wayland_server::backend::{ClientData, ClientId, DisconnectReason};
use wayland_server::protocol::wl_surface::WlSurface;
use wlx_capture::DrmFourcc;
use crate::backend::wayvr::image_importer::ImageImporter;
use crate::backend::wayvr::{SurfaceBufWithImage, WAYVR_SCREEN_RES, time};
use crate::graphics::ExtentExt;
use crate::ipc::event_queue::SyncEventQueue;
use super::WayVRTask;
@@ -92,15 +89,8 @@ pub struct Application {
pub viewporter: ViewporterState,
pub display_handle: DisplayHandle,
pub redraw_requests: HashSet<ObjectId>,
pub(super) no_server_decorations: HashSet<ObjectId>,
pub pending_frame_callbacks: HashMap<ObjectId, Vec<wl_callback::WlCallback>>,
pub cursor_image: CursorImageStatus,
/// pointer that last drove the wvr_server mouse. used for DND
pub vr_pointer: usize,
/// pointer that produced the button press that correlates to DND serial
pub pointer_button_owner: Option<usize>,
/// pointer currently owning a DND grab
pub dnd_pointer: Option<usize>,
}
impl Application {
@@ -182,23 +172,6 @@ impl Application {
self.redraw_requests.remove(surface_id)
}
pub fn has_server_side_decorations(&self, surface: &WlSurface) -> bool {
!self.no_server_decorations.contains(&surface.id())
}
fn set_server_side_decorations(&mut self, surface: &WlSurface, enabled: bool) {
let id = surface.id();
let changed = if enabled {
self.no_server_decorations.remove(&id)
} else {
self.no_server_decorations.insert(id.clone())
};
if changed {
self.redraw_requests.insert(id);
}
}
pub fn output_logical_size(&self) -> Size<i32, Logical> {
self.output.current_mode().map_or_else(
|| Size::new(WAYVR_SCREEN_RES[0], WAYVR_SCREEN_RES[1]),
@@ -208,7 +181,12 @@ impl Application {
fn surface_logical_size(surface: &WlSurface) -> Option<Size<i32, Logical>> {
smithay::wayland::compositor::with_states(surface, |states| {
SurfaceBufWithImage::get_from_surface(states).map(|buf| buf.logical_size(states))
SurfaceBufWithImage::get_from_surface(states).map(|buf| {
let extent = buf.image.extent_u32arr();
let scale = buf.scale.max(1) as u32;
Size::new((extent[0] / scale) as i32, (extent[1] / scale) as i32)
})
})
}
@@ -235,13 +213,6 @@ impl Application {
}
}
fn drm_format_has_alpha(format: DrmFourcc) -> bool {
matches!(
format,
DrmFourcc::Abgr8888 | DrmFourcc::Argb8888 | DrmFourcc::Abgr2101010
)
}
impl compositor::CompositorHandler for Application {
fn compositor_state(&mut self) -> &mut compositor::CompositorState {
&mut self.compositor
@@ -268,12 +239,8 @@ impl compositor::CompositorHandler for Application {
Some(BufferType::Dma) => {
let dmabuf = get_dmabuf(&buffer).unwrap(); // always Ok due to buffer_type
if let Ok(image) = self
.image_importer
.get_or_import_dmabuf(dmabuf.clone())
.inspect_err(|e| {
log::warn!("wayland_server failed to import DMA-buf: {e:?}");
})
if let Ok(image) =
self.image_importer.get_or_import_dmabuf(dmabuf.clone())
{
let sbwi = SurfaceBufWithImage {
image,
@@ -282,7 +249,6 @@ impl compositor::CompositorHandler for Application {
),
scale: attrs.buffer_scale,
dmabuf: true,
has_alpha: drm_format_has_alpha(dmabuf.format().code),
};
if let Some(old_buffer) =
@@ -310,8 +276,6 @@ impl compositor::CompositorHandler for Application {
),
scale: attrs.buffer_scale,
dmabuf: false,
has_alpha: shm_format_to_fourcc(buf.format)
.is_some_and(drm_format_has_alpha),
};
sbwi.apply_to_surface(states, None);
}
@@ -333,7 +297,6 @@ impl compositor::CompositorHandler for Application {
),
scale: attrs.buffer_scale,
dmabuf: false,
has_alpha: true,
};
sbwi.apply_to_surface(states, None);
}
@@ -355,19 +318,6 @@ impl compositor::CompositorHandler for Application {
}
});
// validate wp_viewport against currently attached backing buffer
smithay::wayland::compositor::with_states(surface, |states| {
if let Some(buf) = SurfaceBufWithImage::get_from_surface(states) {
let extent = buf.image.extent_2d();
let scale = buf.scale.max(1) as u32;
let buffer_size = Size::new(
(extent[0] / scale).max(1) as i32,
(extent[1] / scale).max(1) as i32,
);
smithay::wayland::viewporter::ensure_viewport_valid(states, buffer_size);
}
});
self.redraw_requests.insert(surface.id());
}
}
@@ -397,25 +347,7 @@ impl BufferHandler for Application {
fn buffer_destroyed(&mut self, _buffer: &wl_buffer::WlBuffer) {}
}
impl ClientDndGrabHandler for Application {
fn started(
&mut self,
_source: Option<wl_data_source::WlDataSource>,
icon: Option<WlSurface>,
_seat: Seat<Self>,
) {
let pointer = self.pointer_button_owner.unwrap_or(self.vr_pointer);
self.dnd_pointer = Some(pointer);
self.wayvr_tasks
.send(WayVRTask::DndStarted { icon, pointer });
}
fn dropped(&mut self, _target: Option<WlSurface>, _validated: bool, _seat: Seat<Self>) {
self.dnd_pointer = None;
self.pointer_button_owner = None;
self.wayvr_tasks.send(WayVRTask::DndDropped);
}
}
impl ClientDndGrabHandler for Application {}
impl ServerDndGrabHandler for Application {
fn send(&mut self, _mime_type: String, _fd: OwnedFd, _seat: Seat<Self>) {}
@@ -498,8 +430,6 @@ impl XdgShellHandler for Application {
fn toplevel_destroyed(&mut self, surface: ToplevelSurface) {
self.output.leave(surface.wl_surface());
self.no_server_decorations
.remove(&surface.wl_surface().id());
if let Some(client) = surface.wl_surface().client() {
self.wayvr_tasks
@@ -680,16 +610,10 @@ impl DmabufHandler for Application {
dmabuf: Dmabuf,
notifier: ImportNotifier,
) {
match self.image_importer.get_or_import_dmabuf(dmabuf) {
Ok(_) => {
log::info!("wayland_server DMA-buf import complete; notifying client");
let _ = notifier.successful::<Self>();
log::info!("wayland_server DMA-buf success notification returned");
}
Err(e) => {
log::warn!("wayland_server rejected DMA-buf import: {e:?}");
notifier.failed();
}
if self.image_importer.get_or_import_dmabuf(dmabuf).is_ok() {
let _ = notifier.successful::<Self>();
} else {
notifier.failed();
}
}
}
@@ -713,26 +637,24 @@ impl selection_ext::DataControlHandler for Application {
}
impl XdgDecorationHandler for Application {
fn new_decoration(&mut self, toplevel: ToplevelSurface) {
self.set_server_side_decorations(toplevel.wl_surface(), true);
toplevel.with_pending_state(|state| {
state.decoration_mode = Some(zxdg_toplevel_decoration_v1::Mode::ServerSide);
});
toplevel.send_configure();
}
fn request_mode(&mut self, toplevel: ToplevelSurface, mode: zxdg_toplevel_decoration_v1::Mode) {
self.set_server_side_decorations(
toplevel.wl_surface(),
matches!(mode, zxdg_toplevel_decoration_v1::Mode::ServerSide),
);
fn request_mode(
&mut self,
toplevel: ToplevelSurface,
_mode: zxdg_toplevel_decoration_v1::Mode,
) {
toplevel.with_pending_state(|state| {
state.decoration_mode = Some(mode);
state.decoration_mode = Some(zxdg_toplevel_decoration_v1::Mode::ServerSide);
});
toplevel.send_configure();
}
fn unset_mode(&mut self, toplevel: ToplevelSurface) {
self.set_server_side_decorations(toplevel.wl_surface(), true);
toplevel.with_pending_state(|state| {
state.decoration_mode = Some(zxdg_toplevel_decoration_v1::Mode::ServerSide);
});
@@ -747,19 +669,11 @@ impl KdeDecorationHandler for Application {
fn request_mode(
&mut self,
surface: &WlSurface,
_surface: &WlSurface,
decoration: &org_kde_kwin_server_decoration::OrgKdeKwinServerDecoration,
mode: wayland_server::WEnum<org_kde_kwin_server_decoration::Mode>,
_mode: wayland_server::WEnum<org_kde_kwin_server_decoration::Mode>,
) {
let wayland_server::WEnum::Value(mode) = mode else {
return;
};
self.set_server_side_decorations(
surface,
matches!(mode, org_kde_kwin_server_decoration::Mode::Server),
);
decoration.mode(mode);
decoration.mode(org_kde_kwin_server_decoration::Mode::Server);
}
}
+14 -29
View File
@@ -12,7 +12,9 @@ use smithay::{
},
};
use std::{ops::RangeInclusive, rc::Rc, sync::Arc};
use wgui::gfx::ImageView;
use vulkano::image::view::ImageView;
use crate::graphics::ExtentExt;
use crate::backend::input::PointerHit;
@@ -34,9 +36,6 @@ pub struct RenderedSurface {
pub image: Arc<ImageView>,
pub pos: Vec2,
pub size: Vec2,
pub has_alpha: bool,
/// normalized source rect in backing wl_buffer
pub source_uv: [f32; 4],
}
#[derive(Clone)]
@@ -209,17 +208,15 @@ pub fn collect_rendered_surface_tree_at(
let pos = *parent_pos + surface_location(states);
if let Some(surf) = SurfaceBufWithImage::get_from_surface(states) {
let logical_size = surf.logical_size(states);
let source_uv = surf.source_uv(states);
let extent = surf.image.extent_f32();
let scale = surf.scale.max(1) as f32;
out.push(RenderedSurface {
surface: surface.clone(),
surface_id: surface.id(),
image: surf.image,
pos: Vec2::new(pos.x as f32, pos.y as f32),
size: Vec2::new(logical_size.w as f32, logical_size.h as f32),
has_alpha: surf.has_alpha,
source_uv,
size: Vec2::new(extent[0] / scale, extent[1] / scale),
});
}
},
@@ -277,8 +274,10 @@ fn surface_accepts_input_states(
// fallback for normal rendered surfaces
if let Some(surf) = SurfaceBufWithImage::get_from_surface(states) {
let size = surf.logical_size(states);
return local.x < size.w as f32 && local.y < size.h as f32;
let extent = surf.image.extent_f32();
let scale = surf.scale.max(1) as f32;
return local.x < extent[0] / scale && local.y < extent[1] / scale;
}
false
@@ -329,20 +328,11 @@ pub fn rendered_surfaces_dirty(old: &[RenderedSurface], new: &[RenderedSurface])
a.surface_id != b.surface_id
|| a.pos != b.pos
|| a.size != b.size
|| a.has_alpha != b.has_alpha
|| a.source_uv != b.source_uv
|| !Arc::ptr_eq(a.image.image(), b.image.image())
|| *a.image.image() != *b.image.image()
})
}
pub fn compute_transforms(
inner_extent: [u32; 2],
server_side_decorations: bool,
) -> (Affine2, RangeInclusive<f32>) {
if !server_side_decorations {
return (Affine2::IDENTITY, 0.0..=1.0);
}
pub fn compute_transforms(inner_extent: [u32; 2]) -> (Affine2, RangeInclusive<f32>) {
let ix = inner_extent[0].max(1) as f32;
let iy = inner_extent[1].max(1) as f32;
@@ -369,14 +359,9 @@ pub fn build_hit_context(
toplevel: &WlSurface,
_popup_manager: &PopupManager,
inner_extent: [u32; 2],
server_side_decorations: bool,
) -> WvrHitContext {
let (mouse_transform, uv_range) = compute_transforms(inner_extent, server_side_decorations);
let panel_height = if server_side_decorations {
BORDER_SIZE * 2 + BAR_SIZE
} else {
0
};
let (mouse_transform, uv_range) = compute_transforms(inner_extent);
let panel_height = BORDER_SIZE * 2 + BAR_SIZE;
let surfaces = collect_rendered_surface_tree(toplevel);
+12 -13
View File
@@ -11,7 +11,8 @@ use smithay::{
single_pixel_buffer::SinglePixelBufferUserData,
},
};
use wgui::gfx::{CommandBufferUsage, Format, ImageView, WGfx};
use vulkano::{format::Format, image::view::ImageView};
use wgui::gfx::WGfx;
use wlx_capture::frame::{DmabufFrame, FrameFormat, Transform};
use crate::graphics::dmabuf::{WGfxDmabuf, fourcc_to_vk};
@@ -33,16 +34,17 @@ impl ImageImporter {
&mut self,
spb: &SinglePixelBufferUserData,
) -> anyhow::Result<Arc<ImageView>> {
let mut cmd_buf = self
.gfx
.create_xfer_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
let mut cmd_buf = self.gfx.create_xfer_command_buffer(
vulkano::command_buffer::CommandBufferUsage::OneTimeSubmit,
)?;
let rgba = spb.rgba8888();
let image = cmd_buf.upload_image(1, 1, Format::R8G8B8A8_UNORM, &rgba)?;
cmd_buf.build_and_execute_now()?; //TODO: async
self.gfx.create_image_view(image)
let image_view = ImageView::new_default(image)?;
Ok(image_view)
}
pub fn import_shm(
@@ -51,9 +53,9 @@ impl ImageImporter {
size: usize,
bd: BufferData,
) -> anyhow::Result<Arc<ImageView>> {
let mut cmd_buf = self
.gfx
.create_xfer_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
let mut cmd_buf = self.gfx.create_xfer_command_buffer(
vulkano::command_buffer::CommandBufferUsage::OneTimeSubmit,
)?;
let fourcc = shm_format_to_fourcc(bd.format)
.with_context(|| format!("Could not convert {:?} to fourcc", bd.format))?;
@@ -94,7 +96,7 @@ impl ImageImporter {
let image = cmd_buf.upload_image(bd.width as _, bd.height as _, format, &packed)?;
cmd_buf.build_and_execute_now()?;
self.gfx.create_image_view(image)
Ok(ImageView::new_default(image)?)
}
pub fn get_or_import_dmabuf(&mut self, dmabuf: Dmabuf) -> anyhow::Result<Arc<ImageView>> {
@@ -130,11 +132,8 @@ impl ImageImporter {
}
let image = self.gfx.dmabuf_texture(frame)?;
log::info!("DMA-buf import: creating image view");
let image_view = self.gfx.create_image_view(image)?;
log::info!("DMA-buf import: image view created");
let image_view = ImageView::new_default(image)?;
self.dmabufs.insert(key, image_view.clone());
log::info!("DMA-buf import: cached ImageView");
Ok(image_view)
}
+10 -55
View File
@@ -182,16 +182,11 @@ impl RestrictedState {
if entry.device.is_grabbed() {
Ok(false)
} else {
match entry.device.grab() {
Ok(()) => {
log::debug!(
"exclusively grabbed input device: {}",
evdev_device_desc(&entry.path, &entry.device)
);
Ok(true)
}
Err(error) => Err(with_device_context("grab", &entry.path, error)),
}
entry
.device
.grab()
.map(|()| true)
.map_err(|error| with_device_context("grab", &entry.path, error))
}
};
@@ -223,18 +218,10 @@ impl RestrictedState {
continue;
}
match entry.device.ungrab() {
Ok(()) => {
log::debug!(
"ungrabbed input device: {}",
evdev_device_desc(&entry.path, &entry.device)
);
}
Err(error) => {
if first_error.is_none() {
first_error = Some(with_device_context("ungrab", &entry.path, error));
}
}
if let Err(error) = entry.device.ungrab()
&& first_error.is_none()
{
first_error = Some(with_device_context("ungrab", &entry.path, error));
}
}
@@ -274,15 +261,6 @@ impl LibinputInterface for RestrictedInterface {
if state.desired_grabbed {
device.grab().map_err(io_errno)?;
log::debug!(
"exclusively grabbed input device on open: {}",
evdev_device_desc(path, &device)
);
} else {
log::debug!(
"opened input device (non-exclusive): {}",
evdev_device_desc(path, &device)
);
}
state.devices.insert(
@@ -297,13 +275,7 @@ impl LibinputInterface for RestrictedInterface {
}
fn close_restricted(&mut self, fd: OwnedFd) {
let removed = self.state.borrow_mut().devices.remove(&fd.as_raw_fd());
if let Some(removed) = removed {
log::debug!(
"closed input device (releasing grab): {}",
evdev_device_desc(&removed.path, &removed.device)
);
}
self.state.borrow_mut().devices.remove(&fd.as_raw_fd());
drop(fd);
}
}
@@ -344,8 +316,6 @@ fn worker_main(
return;
}
log::debug!("input capture worker started");
let mut grabbed = false;
let mut pending_grab = false;
let mut watchdog = Instant::now() + WATCHDOG_TIMEOUT;
@@ -593,11 +563,9 @@ fn process_libinput_event(par: ProcessLibinputEventParams) -> ProcessResult {
if !par.grabbed {
// while ungrabbed, arm an automatic grab that triggers after release
if par.allow_deferred_grab
&& !*par.pending_grab
&& key_combo_is_pressed(KeyCombo::GrabRelease, &state.pressed_keys)
{
*par.pending_grab = true;
log::debug!("key combo triggered: GrabRelease (grab deferred until release)");
}
return ProcessResult::Continue;
@@ -623,8 +591,6 @@ fn process_libinput_event(par: ProcessLibinputEventParams) -> ProcessResult {
continue;
}
log::debug!("key combo triggered: {combo:?} pressed={combo_pressed}");
if combo_pressed {
state.active_combos.insert(combo);
} else {
@@ -942,17 +908,6 @@ fn io_errno(error: io::Error) -> i32 {
error.raw_os_error().unwrap_or(libc::EIO)
}
fn evdev_device_desc(path: &Path, device: &EvdevDevice) -> String {
let id = device.input_id();
format!(
"{} (vendor {:04x}, product {:04x}) {}",
device.name().unwrap_or("<unknown>"),
id.vendor(),
id.product(),
path.display()
)
}
fn with_device_context(action: &str, path: &Path, error: io::Error) -> io::Error {
io::Error::new(
error.kind(),
+12 -134
View File
@@ -51,16 +51,13 @@ use std::{
sync::Arc,
time::{Duration, Instant},
};
use vulkano::image::view::ImageView;
use wayland_protocols::xdg::shell::server::xdg_toplevel;
use wayvr_ipc::packet_client::PositionMode;
use wgui::{
gfx::{ImageView, WGfx},
globals::WguiGlobals,
log::LogErr,
};
use wgui::{gfx::WGfx, globals::WguiGlobals, log::LogErr};
use wlx_capture::frame::Transform;
use wlx_common::{
audio,
audio::{AudioSystem, SamplePlayer},
config::{DefaultPositioning, GeneralConfig, InputCaptureMethod},
desktop_finder::DesktopFinder,
};
@@ -77,11 +74,10 @@ use crate::{
window::CreateWindowParams,
},
},
graphics::WGfxExtras,
graphics::{ExtentExt, WGfxExtras},
ipc::{event_queue::SyncEventQueue, ipc_server},
overlays::{
anchor::ALTTAB_HELP_NAME,
drag_item::{DRAG_ITEM_NAME, create_drag_item},
keyboard::KEYBOARD_NAME,
wayvr::{WvrCommand, create_wl_window_overlay},
},
@@ -136,11 +132,6 @@ pub enum WayVRTask {
NewExternalProcess(ExternalProcessRequest),
ProcessTerminationRequest(process::ProcessHandle, KillSignal),
CloseWindowRequest(window::WindowHandle),
DndStarted {
icon: Option<WlSurface>,
pointer: usize,
},
DndDropped,
}
pub struct WvrServerState {
@@ -177,7 +168,7 @@ pub enum TickTask {
NewExternalProcess(ExternalProcessRequest), // Call WayVRCompositor::add_client after receiving this message
}
const KEY_REPEAT_DELAY: i32 = 500;
const KEY_REPEAT_DELAY: i32 = 200;
const KEY_REPEAT_RATE: i32 = 50;
const WAYVR_SCREEN_RES: [i32; 2] = [2560, 1440];
@@ -297,12 +288,8 @@ impl WvrServerState {
popup_manager: PopupManager::default(),
viewporter,
redraw_requests: HashSet::new(),
no_server_decorations: HashSet::new(),
pending_frame_callbacks: HashMap::new(),
cursor_image: CursorImageStatus::default_named(),
vr_pointer: 0,
pointer_button_owner: None,
dnd_pointer: None,
};
Ok(Self {
@@ -651,37 +638,6 @@ impl WvrServerState {
);
}
}
WayVRTask::DndStarted { icon, pointer } => {
// don't freeze on drag start
wvr_server.mouse_freeze = Instant::now();
let selector = OverlaySelector::Name(DRAG_ITEM_NAME.clone());
app.tasks
.enqueue(TaskType::Overlay(OverlayTask::DropImmediate(
selector.clone(),
)));
if let Some(icon) = icon {
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Spawn(
selector,
SpawnPos::FixedNoRealign,
Box::new(move |app| {
create_drag_item(app, icon, pointer)
.inspect_err(|e| {
log::warn!("Could not create drag item overlay: {e:?}")
})
.ok()
}),
)));
}
}
WayVRTask::DndDropped => {
app.tasks
.enqueue(TaskType::Overlay(OverlayTask::DropImmediate(
OverlaySelector::Name(DRAG_ITEM_NAME.clone()),
)));
}
}
}
@@ -696,7 +652,8 @@ impl WvrServerState {
&mut app.input_state,
&mut app.tasks,
&mut app.hid_provider,
&mut app.audio,
&mut app.audio_sample_player,
&mut app.audio_system,
&app.wgui_globals,
&app.session.config,
);
@@ -817,7 +774,8 @@ impl WvrServerState {
input_state: &mut InputState,
tasks: &mut TaskContainer,
hid_wrapper: &mut HidWrapper,
audio: &mut Box<dyn audio::AudioProvider>,
audio_sample_player: &mut SamplePlayer,
audio_system: &mut AudioSystem,
globals: &WguiGlobals,
config: &GeneralConfig,
) {
@@ -966,7 +924,7 @@ impl WvrServerState {
}
}
input_capture::CapturedEvent::Grabbed => {
audio.play_sample("input_grab");
audio_sample_player.play_sample(audio_system, "input_grab");
if !self.grab_toast_sent {
self.grab_toast_sent = true;
let text = &globals.i18n().translate("NOTIFICATION.WE_ARE_GRABBING");
@@ -979,7 +937,7 @@ impl WvrServerState {
self.manager.release_all_keys();
self.has_input_focus = false;
self.wm.keyboard_focus = None;
audio.play_sample("input_ungrab");
audio_sample_player.play_sample(audio_system, "input_ungrab");
}
input_capture::CapturedEvent::KeyCombo { combo, pressed } => match combo {
input_capture::KeyCombo::CloseApp if pressed => {
@@ -1034,14 +992,13 @@ impl WvrServerState {
let toplevel = window.toplevel.wl_surface().clone();
let inner_extent = with_states(&toplevel, |states| {
SurfaceBufWithImage::get_from_surface(states)
.map_or([1, 1], |s| s.image.extent_2d())
.map_or([1, 1], |s| s.image.extent_u32arr())
});
let hit_ctx = build_hit_context(
&toplevel,
&self.manager.state.popup_manager,
inner_extent,
self.manager.state.has_server_side_decorations(&toplevel),
);
let new_x = (hover.pos.x + mouse_delta.x).clamp(0., inner_extent[0] as f64);
@@ -1226,47 +1183,6 @@ impl WvrServerState {
self.manager.send_pointer_button(index, pressed);
}
pub fn set_vr_pointer(&mut self, pointer: usize) {
self.manager.state.vr_pointer = pointer;
}
pub fn set_vr_pointer_button(&mut self, pointer: usize, pressed: bool) {
self.manager.state.vr_pointer = pointer;
if pressed {
self.manager.state.pointer_button_owner = Some(pointer);
} else if self.manager.state.dnd_pointer.is_none()
&& self.manager.state.pointer_button_owner == Some(pointer)
{
self.manager.state.pointer_button_owner = None;
}
}
pub fn dnd_active(&self) -> bool {
self.manager.state.dnd_pointer.is_some()
}
pub fn dnd_active_for_pointer(&self, pointer: usize) -> bool {
self.manager.state.dnd_pointer == Some(pointer)
}
pub fn clear_dnd_focus(&mut self, pointer: usize) {
if !self.dnd_active_for_pointer(pointer) {
return;
}
let pos = self
.wm
.mouse
.as_ref()
.map_or(DVec2::ZERO, |mouse| mouse.pos);
self.manager
.send_mouse_move(None, pos, DVec2::ZERO, DVec2::ZERO);
}
pub fn send_dnd_release(&mut self, index: MouseIndex) {
self.manager.send_pointer_button(index, false);
}
pub fn send_mouse_scroll(
&mut self,
hover_window: window::WindowHandle,
@@ -1438,47 +1354,9 @@ pub struct SurfaceBufWithImage {
pub transform: Transform,
pub scale: i32,
pub dmabuf: bool,
pub has_alpha: bool,
}
impl SurfaceBufWithImage {
/// Logical size of wl_surface after buffer scaling and wp_viewport
pub fn logical_size(&self, surface_data: &SurfaceData) -> Size<i32, Logical> {
use smithay::wayland::viewporter::ViewportCachedState;
let extent = self.image.extent_2d();
let scale = self.scale.max(1) as u32;
let buffer_size = Size::new(
(extent[0] / scale).max(1) as i32,
(extent[1] / scale).max(1) as i32,
);
let mut guard = surface_data.cached_state.get::<ViewportCachedState>();
guard.current().size().unwrap_or(buffer_size)
}
/// Normalized source rectangle in backing image cropped by wp_viewport
pub fn source_uv(&self, surface_data: &SurfaceData) -> [f32; 4] {
use smithay::wayland::viewporter::ViewportCachedState;
let extent = self.image.extent_2d();
let scale = self.scale.max(1) as f64;
let buffer_w = (extent[0] as f64 / scale).max(1.0);
let buffer_h = (extent[1] as f64 / scale).max(1.0);
let mut guard = surface_data.cached_state.get::<ViewportCachedState>();
let viewport = *guard.current();
let Some(src) = viewport.src else {
return [0.0, 0.0, 1.0, 1.0];
};
let u0 = (src.loc.x / buffer_w).clamp(0.0, 1.0) as f32;
let v0 = (src.loc.y / buffer_h).clamp(0.0, 1.0) as f32;
let u1 = ((src.loc.x + src.size.w) / buffer_w).clamp(0.0, 1.0) as f32;
let v1 = ((src.loc.y + src.size.h) / buffer_h).clamp(0.0, 1.0) as f32;
[u0, v0, u1, v1]
}
fn apply_to_surface(
self,
surface_data: &SurfaceData,
+9 -30
View File
@@ -2,18 +2,12 @@ use std::rc::Rc;
use glam::DVec2;
use slotmap::{DenseSlotMap, new_key_type};
use smithay::utils::{Logical, Serial, Size};
use smithay::utils::{Logical, Size};
use smithay::wayland::shell::xdg::ToplevelSurface;
use wayvr_ipc::packet_server;
use crate::backend::wayvr::process;
#[derive(Debug, Clone, Copy)]
pub struct PendingConfigure {
pub size: Size<i32, Logical>,
pub serial: Serial,
}
#[derive(Debug)]
pub struct Window {
pub min_size: Size<i32, Logical>,
@@ -25,7 +19,7 @@ pub struct Window {
pub toplevel: Rc<ToplevelSurface>,
pub process: process::ProcessHandle,
pub pending_configure: Option<PendingConfigure>,
pub pending_configure_size: Option<Size<i32, Logical>>,
}
impl Window {
@@ -45,7 +39,7 @@ impl Window {
visible: true,
toplevel,
process,
pending_configure: None,
pending_configure_size: None,
}
}
@@ -74,10 +68,7 @@ impl Window {
fn send_size_configure(&mut self, size: Size<i32, Logical>, bounds: Size<i32, Logical>) {
let clamped_size = self.clamp_configure_size(size, bounds);
if self
.pending_configure
.is_some_and(|pending| pending.size == clamped_size)
{
if self.pending_configure_size == Some(clamped_size) {
return;
}
@@ -86,13 +77,10 @@ impl Window {
state.size = Some(clamped_size);
});
let serial = self.toplevel.send_configure();
self.toplevel.send_configure();
self.bounds = bounds;
self.pending_configure = Some(PendingConfigure {
size: clamped_size,
serial,
});
self.pending_configure_size = Some(clamped_size);
}
pub fn checked_configure_size(&mut self, size: Size<i32, Logical>) {
@@ -103,11 +91,7 @@ impl Window {
self.send_size_configure(size, bounds);
}
pub fn remember_committed_size(
&mut self,
size: Size<i32, Logical>,
configure_serial: Option<Serial>,
) -> bool {
pub fn remember_committed_size(&mut self, size: Size<i32, Logical>) -> bool {
let size_x = size.w.max(1) as u32;
let size_y = size.h.max(1) as u32;
@@ -116,12 +100,7 @@ impl Window {
self.size_x = size_x;
self.size_y = size_y;
if self
.pending_configure
.is_some_and(|pending| Some(pending.serial) == configure_serial)
{
self.pending_configure = None;
}
self.pending_configure_size = None;
changed
}
@@ -176,7 +155,7 @@ impl WindowManager {
par.min_size,
par.max_size,
);
window.remember_committed_size(Size::new(par.size_x as i32, par.size_y as i32), None);
window.remember_committed_size(Size::new(par.size_x as i32, par.size_y as i32));
self.windows.insert(window)
}
-2
View File
@@ -198,7 +198,6 @@ pub struct AutoSettings {
pub default_curvature: f32,
pub default_opacity: f32,
pub default_positioning: DefaultPositioning,
pub middle_grabs_single: bool,
}
fn get_settings_path() -> PathBuf {
@@ -274,7 +273,6 @@ pub fn save_settings(config: &GeneralConfig) -> anyhow::Result<()> {
default_curvature: config.default_curvature,
default_opacity: config.default_opacity,
default_positioning: config.default_positioning,
middle_grabs_single: config.middle_grabs_single,
};
let json = serde_json::to_string_pretty(&conf).unwrap(); // want panic
+43 -9
View File
@@ -1,6 +1,14 @@
use image_dds::{ImageFormat, Surface};
use std::{io::Read, sync::Arc};
use wgui::gfx::{Format, Image, cmd::XferCommandBufferBuilder};
use vulkano::{
DeviceSize,
buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer},
command_buffer::CopyBufferToImageInfo,
format::Format,
image::{Image, ImageCreateInfo, ImageType, ImageUsage},
memory::allocator::{AllocationCreateInfo, MemoryTypeFilter},
};
use wgui::gfx::cmd::XferCommandBuffer;
pub trait WlxCommandBufferDds {
fn upload_image_dds<R>(&mut self, r: R) -> anyhow::Result<Arc<Image>>
@@ -8,7 +16,7 @@ pub trait WlxCommandBufferDds {
R: Read;
}
impl WlxCommandBufferDds for XferCommandBufferBuilder {
impl WlxCommandBufferDds for XferCommandBuffer {
fn upload_image_dds<R>(&mut self, r: R) -> anyhow::Result<Arc<Image>>
where
R: Read,
@@ -25,16 +33,42 @@ impl WlxCommandBufferDds for XferCommandBufferBuilder {
anyhow::bail!("Not a 2D texture.")
}
self.upload_image(
surface.width,
surface.height,
dds_to_wgfx(surface.image_format)?,
surface.data,
)
let image = Image::new(
self.graphics.memory_allocator.clone(),
ImageCreateInfo {
image_type: ImageType::Dim2d,
format: dds_to_vk(surface.image_format)?,
extent: [surface.width, surface.height, surface.depth],
usage: ImageUsage::TRANSFER_DST | ImageUsage::TRANSFER_SRC | ImageUsage::SAMPLED,
..Default::default()
},
AllocationCreateInfo::default(),
)?;
let buffer: Subbuffer<[u8]> = Buffer::new_slice(
self.graphics.memory_allocator.clone(),
BufferCreateInfo {
usage: BufferUsage::TRANSFER_SRC,
..Default::default()
},
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_HOST
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
},
surface.data.len() as DeviceSize,
)?;
buffer.write()?.copy_from_slice(surface.data);
self.command_buffer
.copy_buffer_to_image(CopyBufferToImageInfo::buffer_image(buffer, image.clone()))?;
Ok(image)
}
}
pub fn dds_to_wgfx(dds_fmt: ImageFormat) -> anyhow::Result<Format> {
pub fn dds_to_vk(dds_fmt: ImageFormat) -> anyhow::Result<Format> {
match dds_fmt {
ImageFormat::R8Unorm => Ok(Format::R8_UNORM),
ImageFormat::Rgba8Unorm => Ok(Format::R8G8B8A8_UNORM),
+342 -56
View File
@@ -1,47 +1,306 @@
use std::sync::Arc;
use std::{
mem::MaybeUninit,
os::fd::{FromRawFd, IntoRawFd},
sync::Arc,
};
use wgui::gfx::{DmabufImportInfo, DmabufPlaneLayout, Format, Image, ImageView, WGfx};
use anyhow::Context;
use smallvec::{SmallVec, smallvec};
use vulkano::{
VulkanError, VulkanObject,
device::Device,
format::Format,
image::{
Image, ImageAspect, ImageCreateInfo, ImageMemory, ImageTiling, ImageType, ImageUsage,
SubresourceLayout, sys::RawImage, view::ImageView,
},
memory::{
DedicatedAllocation, DeviceMemory, ExternalMemoryHandleType, ExternalMemoryHandleTypes,
MemoryAllocateInfo, MemoryImportInfo, MemoryPropertyFlags, ResourceMemory,
allocator::{
AllocationCreateInfo, MemoryAllocatePreference, MemoryAllocator, MemoryTypeFilter,
},
},
sync::Sharing,
};
use wgui::gfx::WGfx;
use wlx_capture::{DrmFormat, DrmFourcc, DrmModifier, frame::DmabufFrame};
pub trait WGfxDmabuf {
fn dmabuf_texture_ex(
&self,
frame: DmabufFrame,
tiling: ImageTiling,
layouts: Vec<SubresourceLayout>,
modifiers: &[u64],
) -> anyhow::Result<Arc<Image>>;
fn dmabuf_texture(&self, frame: DmabufFrame) -> anyhow::Result<Arc<Image>>;
}
impl WGfxDmabuf for WGfx {
fn dmabuf_texture(&self, frame: DmabufFrame) -> anyhow::Result<Arc<Image>> {
fn dmabuf_texture_ex(
&self,
frame: DmabufFrame,
tiling: ImageTiling,
layouts: Vec<SubresourceLayout>,
modifiers: &[u64],
) -> anyhow::Result<Arc<Image>> {
let extent = [frame.format.width, frame.format.height, 1];
let format = fourcc_to_vk(frame.format.drm_format.code)?;
let modifier = (!matches!(frame.format.drm_format.modifier, DrmModifier::Invalid))
.then(|| u64::from(frame.format.drm_format.modifier));
let plane_layouts = if modifier.is_some() {
(0..frame.num_planes)
.map(|i| DmabufPlaneLayout {
offset: frame.planes[i].offset.into(),
row_pitch: frame.planes[i].stride as u64,
})
.collect()
} else {
Vec::new()
};
let Some(fd) = frame.planes[0].fd else {
anyhow::bail!("DMA-buf plane has no FD");
let image = unsafe {
create_dmabuf_image(
self.device.clone(),
ImageCreateInfo {
format,
extent,
usage: ImageUsage::SAMPLED,
external_memory_handle_types: ExternalMemoryHandleTypes::DMA_BUF,
tiling,
drm_format_modifiers: modifiers.to_owned(),
drm_format_modifier_plane_layouts: layouts,
..Default::default()
},
)?
};
log::trace!(
"DMA-buf import start: {}x{}, format={:?}, modifier={:?}, planes={}",
frame.format.width,
frame.format.height,
format,
modifier,
frame.num_planes,
let requirements = image.memory_requirements()[0];
let memory_type_index = self
.memory_allocator
.find_memory_type_index(
requirements.memory_type_bits,
MemoryTypeFilter {
required_flags: MemoryPropertyFlags::DEVICE_LOCAL,
..Default::default()
},
)
.context("failed to get memory type index")?;
debug_assert!(self.device.enabled_extensions().khr_external_memory_fd);
debug_assert!(self.device.enabled_extensions().khr_external_memory);
debug_assert!(self.device.enabled_extensions().ext_external_memory_dma_buf);
// only do the 1st
unsafe {
let Some(fd) = frame.planes[0].fd else {
anyhow::bail!("DMA-buf plane has no FD");
};
let file = std::fs::File::from_raw_fd(fd);
let new_file = file.try_clone()?;
let _ = file.into_raw_fd();
let memory = DeviceMemory::allocate_unchecked(
self.device.clone(),
MemoryAllocateInfo {
allocation_size: requirements.layout.size(),
memory_type_index,
dedicated_allocation: Some(DedicatedAllocation::Image(&image)),
..Default::default()
},
Some(MemoryImportInfo::Fd {
file: new_file,
handle_type: ExternalMemoryHandleType::DmaBuf,
}),
)?;
let mem_alloc = ResourceMemory::new_dedicated(memory);
match image.bind_memory_unchecked([mem_alloc]) {
Ok(image) => Ok(Arc::new(image)),
Err(e) => {
anyhow::bail!("Failed to bind memory to image: {}", e.0);
}
}
}
}
fn dmabuf_texture(&self, frame: DmabufFrame) -> anyhow::Result<Arc<Image>> {
let mut modifiers: SmallVec<[u64; 4]> = smallvec![];
let mut tiling: ImageTiling = ImageTiling::Optimal;
let mut layouts: Vec<SubresourceLayout> = vec![];
if !matches!(frame.format.drm_format.modifier, DrmModifier::Invalid) {
(0..frame.num_planes).for_each(|i| {
let plane = &frame.planes[i];
layouts.push(SubresourceLayout {
offset: plane.offset.into(),
size: 0,
row_pitch: plane.stride as _,
array_pitch: None,
depth_pitch: None,
});
modifiers.push(frame.format.drm_format.modifier.into());
});
tiling = ImageTiling::DrmFormatModifier;
}
self.dmabuf_texture_ex(frame, tiling, layouts, &modifiers)
}
}
#[allow(clippy::all, clippy::pedantic)]
pub(super) unsafe fn create_dmabuf_image(
device: Arc<Device>,
create_info: ImageCreateInfo,
) -> Result<RawImage, VulkanError> {
let &ImageCreateInfo {
flags,
image_type,
format,
ref view_formats,
extent,
array_layers,
mip_levels,
samples,
tiling,
usage,
stencil_usage,
ref sharing,
initial_layout,
ref drm_format_modifiers,
ref drm_format_modifier_plane_layouts,
external_memory_handle_types,
_ne: _,
} = &create_info;
let (sharing_mode, queue_family_index_count, p_queue_family_indices) = match sharing {
Sharing::Exclusive => (ash::vk::SharingMode::EXCLUSIVE, 0, &[] as _),
Sharing::Concurrent(queue_family_indices) => (
ash::vk::SharingMode::CONCURRENT,
queue_family_indices.len() as u32,
queue_family_indices.as_ptr(),
),
};
let mut create_info_vk = ash::vk::ImageCreateInfo {
flags: flags.into(),
image_type: image_type.into(),
format: format.into(),
extent: ash::vk::Extent3D {
width: extent[0],
height: extent[1],
depth: extent[2],
},
mip_levels,
array_layers,
samples: samples.into(),
tiling: tiling.into(),
usage: usage.into(),
sharing_mode,
queue_family_index_count,
p_queue_family_indices,
initial_layout: initial_layout.into(),
..Default::default()
};
let mut drm_format_modifier_explicit_info_vk = None;
let drm_format_modifier_plane_layouts_vk: SmallVec<[_; 4]>;
let mut drm_format_modifier_list_info_vk = None;
let mut external_memory_info_vk = None;
let mut format_list_info_vk = None;
let format_list_view_formats_vk: Vec<_>;
let mut stencil_usage_info_vk = None;
if drm_format_modifiers.len() == 1 {
drm_format_modifier_plane_layouts_vk = drm_format_modifier_plane_layouts
.iter()
.map(|subresource_layout| {
let &SubresourceLayout {
offset,
size,
row_pitch,
array_pitch,
depth_pitch,
} = subresource_layout;
ash::vk::SubresourceLayout {
offset,
size,
row_pitch,
array_pitch: array_pitch.unwrap_or(0),
depth_pitch: depth_pitch.unwrap_or(0),
}
})
.collect();
let next = drm_format_modifier_explicit_info_vk.insert(
ash::vk::ImageDrmFormatModifierExplicitCreateInfoEXT {
drm_format_modifier: drm_format_modifiers[0],
drm_format_modifier_plane_count: drm_format_modifier_plane_layouts_vk.len() as u32,
p_plane_layouts: drm_format_modifier_plane_layouts_vk.as_ptr(),
..Default::default()
},
);
self.import_dmabuf(&DmabufImportInfo {
extent: [frame.format.width, frame.format.height, 1],
format,
fd,
modifier,
plane_layouts,
})
next.p_next = create_info_vk.p_next;
create_info_vk.p_next = next as *const _ as *const _;
} else if drm_format_modifiers.len() > 1 {
let next = drm_format_modifier_list_info_vk.insert(
ash::vk::ImageDrmFormatModifierListCreateInfoEXT {
drm_format_modifier_count: drm_format_modifiers.len() as u32,
p_drm_format_modifiers: drm_format_modifiers.as_ptr(),
..Default::default()
},
);
next.p_next = create_info_vk.p_next;
create_info_vk.p_next = next as *const _ as *const _;
}
if !external_memory_handle_types.is_empty() {
let next = external_memory_info_vk.insert(ash::vk::ExternalMemoryImageCreateInfo {
handle_types: external_memory_handle_types.into(),
..Default::default()
});
next.p_next = create_info_vk.p_next;
create_info_vk.p_next = next as *const _ as *const _;
}
if !view_formats.is_empty() {
format_list_view_formats_vk = view_formats
.iter()
.copied()
.map(ash::vk::Format::from)
.collect();
let next = format_list_info_vk.insert(ash::vk::ImageFormatListCreateInfo {
view_format_count: format_list_view_formats_vk.len() as u32,
p_view_formats: format_list_view_formats_vk.as_ptr(),
..Default::default()
});
next.p_next = create_info_vk.p_next;
create_info_vk.p_next = next as *const _ as *const _;
}
if let Some(stencil_usage) = stencil_usage {
let next = stencil_usage_info_vk.insert(ash::vk::ImageStencilUsageCreateInfo {
stencil_usage: stencil_usage.into(),
..Default::default()
});
next.p_next = create_info_vk.p_next;
create_info_vk.p_next = next as *const _ as *const _;
}
unsafe {
let handle = {
let fns = device.fns();
let mut output = MaybeUninit::uninit();
(fns.v1_0.create_image)(
device.handle(),
&create_info_vk,
std::ptr::null(),
output.as_mut_ptr(),
)
.result()
.map_err(VulkanError::from)?;
output.assume_init()
};
RawImage::from_handle(device, handle, create_info)
}
}
@@ -54,23 +313,52 @@ pub struct ExportedDmabufImage {
}
pub fn export_dmabuf_image(
gfx: &WGfx,
allocator: Arc<dyn MemoryAllocator>,
extent: [u32; 3],
format: Format,
modifier: DrmModifier,
) -> anyhow::Result<ExportedDmabufImage> {
let exported = gfx.export_dmabuf_image(extent, format, modifier.into())?;
let image = Image::new(
allocator.clone(),
ImageCreateInfo {
image_type: ImageType::Dim2d,
format,
extent,
usage: ImageUsage::TRANSFER_DST | ImageUsage::TRANSFER_SRC | ImageUsage::SAMPLED,
tiling: ImageTiling::DrmFormatModifier,
drm_format_modifiers: vec![modifier.into()],
external_memory_handle_types: ExternalMemoryHandleTypes::DMA_BUF,
..Default::default()
},
AllocationCreateInfo {
allocate_preference: MemoryAllocatePreference::AlwaysAllocate,
..Default::default()
},
)
.context("Could not create image to export")?;
let fd = match image.memory() {
ImageMemory::Normal(planes) if planes.len() == 1 => {
let plane = planes.first().unwrap();
plane
.device_memory()
.export_fd(ExternalMemoryHandleType::DmaBuf)?
}
_ => anyhow::bail!("Could not export DMA-buf: invalid ImageMemory"),
};
let layout = unsafe { image.subresource_layout_unchecked(ImageAspect::MemoryPlane0, 0, 0) };
Ok(ExportedDmabufImage {
view: exported.view,
fd: exported.fd,
modifier: DrmModifier::from(exported.modifier),
offset: exported.offset as _,
stride: exported.stride as _,
view: ImageView::new_default(image)?,
fd,
modifier,
offset: layout.offset as _,
stride: layout.row_pitch as _,
})
}
pub(super) fn get_drm_formats(gfx: &WGfx) -> Vec<DrmFormat> {
pub(super) fn get_drm_formats(device: Arc<Device>) -> Vec<DrmFormat> {
let possible_formats = [
DrmFourcc::Abgr8888,
DrmFourcc::Xbgr8888,
@@ -83,28 +371,26 @@ pub(super) fn get_drm_formats(gfx: &WGfx) -> Vec<DrmFormat> {
let mut out_formats = vec![];
for &code in &possible_formats {
let Ok(format) = fourcc_to_vk(code) else {
let Ok(vk_fmt) = fourcc_to_vk(code) else {
continue;
};
let Ok(props) = device.physical_device().format_properties(vk_fmt) else {
continue;
};
match gfx.dmabuf_import_modifiers(format) {
Ok(modifiers) => {
for modifier in modifiers {
log::debug!(
"DMA-buf format {code} modifier {modifier:#018x}: sampled + importable"
);
out_formats.push(DrmFormat {
code,
modifier: DrmModifier::from(modifier),
});
}
}
Err(e) => {
log::warn!("Failed to query DMA-buf modifiers for {code}: {e:?}");
}
for m in props
.drm_format_modifier_properties
.iter()
.filter(|m| m.drm_format_modifier_plane_count == 1)
.map(|m| m.drm_format_modifier)
{
out_formats.push(DrmFormat {
code,
modifier: DrmModifier::from(m),
});
}
// always accept implicit modifier
// accept implicit modifiers
out_formats.push(DrmFormat {
code,
modifier: DrmModifier::Invalid,
+655 -120
View File
@@ -1,208 +1,743 @@
pub mod dds;
pub mod dmabuf;
use std::{collections::HashMap, sync::Arc};
use smallvec::SmallVec;
use wgui::gfx::{
BufferUsage, BuiltCommandBuffer, Format, ShaderModule, Vert2Buf, Vert2Uv, WGfx, WGfxCreateInfo,
use std::{
collections::HashMap,
sync::{Arc, OnceLock},
};
use glam::{Vec2, vec2};
use smallvec::SmallVec;
use vulkano::{
buffer::{BufferCreateInfo, BufferUsage},
command_buffer::{CommandBufferUsage, PrimaryAutoCommandBuffer, PrimaryCommandBufferAbstract},
image::view::ImageView,
memory::allocator::{AllocationCreateInfo, MemoryTypeFilter},
sync::GpuFuture,
};
use wgui::gfx::WGfx;
#[cfg(feature = "openvr")]
use vulkano::instance::InstanceCreateFlags;
use wlx_capture::DrmFormat;
use crate::shaders::{
frag_color, frag_grid, frag_screen, frag_simple, frag_sky, frag_srgb, frag_wayland, vert_quad,
frag_color, frag_grid, frag_screen, frag_simple, frag_sky, frag_srgb, vert_quad,
};
#[cfg(feature = "openxr")]
use {ash::vk, std::os::raw::c_void};
use vulkano::{
self, VulkanObject,
buffer::{Buffer, BufferContents, Subbuffer},
device::{
DeviceCreateInfo, DeviceExtensions, DeviceFeatures, Queue, QueueCreateInfo, QueueFlags,
physical::{PhysicalDevice, PhysicalDeviceType},
},
format::Format,
instance::{Instance, InstanceCreateInfo, InstanceExtensions},
pipeline::graphics::vertex_input::Vertex,
shader::ShaderModule,
};
use dmabuf::get_drm_formats;
pub type Vert2Buf = Subbuffer<[Vert2Uv]>;
#[repr(C)]
#[derive(BufferContents, Vertex, Copy, Clone, Debug)]
pub struct Vert2Uv {
#[format(R32G32_SFLOAT)]
pub in_pos: [f32; 2],
#[format(R32G32_SFLOAT)]
pub in_uv: [f32; 2],
}
pub struct WGfxExtras {
pub shaders: HashMap<&'static str, Arc<ShaderModule>>,
pub drm_formats: Arc<[DrmFormat]>,
pub queue_capture: Option<Arc<Queue>>,
pub quad_verts: Vert2Buf,
pub fallback_image: Arc<ImageView>,
pub drm_device: Option<(i64, i64)>,
}
impl WGfxExtras {
pub fn new(gfx: &Arc<WGfx>) -> anyhow::Result<Self> {
pub fn new(gfx: Arc<WGfx>, queue_capture: Option<Arc<Queue>>) -> anyhow::Result<Self> {
let mut shaders = HashMap::new();
shaders.insert("vert_quad", vert_quad::load(gfx)?);
shaders.insert("frag_color", frag_color::load(gfx)?);
shaders.insert("frag_srgb", frag_srgb::load(gfx)?);
shaders.insert("frag_sky", frag_sky::load(gfx)?);
shaders.insert("frag_grid", frag_grid::load(gfx)?);
shaders.insert("frag_screen", frag_screen::load(gfx)?);
shaders.insert("frag_simple", frag_simple::load(gfx)?);
shaders.insert("frag_wayland", frag_wayland::load(gfx)?);
let drm_formats = get_drm_formats(gfx).into();
let shader = vert_quad::load(gfx.device.clone())?;
shaders.insert("vert_quad", shader);
let shader = frag_color::load(gfx.device.clone())?;
shaders.insert("frag_color", shader);
let shader = frag_srgb::load(gfx.device.clone())?;
shaders.insert("frag_srgb", shader);
let shader = frag_sky::load(gfx.device.clone())?;
shaders.insert("frag_sky", shader);
let shader = frag_grid::load(gfx.device.clone())?;
shaders.insert("frag_grid", shader);
let shader = frag_screen::load(gfx.device.clone())?;
shaders.insert("frag_screen", shader);
let shader = frag_simple::load(gfx.device.clone())?;
shaders.insert("frag_simple", shader);
let drm_formats = get_drm_formats(gfx.device.clone()).into();
let vertices = [
Vert2Uv {
in_pos: [0.0, 0.0],
in_uv: [0.0, 0.0],
in_pos: [0., 0.],
in_uv: [0., 0.],
},
Vert2Uv {
in_pos: [1.0, 0.0],
in_uv: [1.0, 0.0],
in_pos: [1., 0.],
in_uv: [1., 0.],
},
Vert2Uv {
in_pos: [0.0, 1.0],
in_uv: [0.0, 1.0],
in_pos: [0., 1.],
in_uv: [0., 1.],
},
Vert2Uv {
in_pos: [1.0, 1.0],
in_uv: [1.0, 1.0],
in_pos: [1., 1.],
in_uv: [1., 1.],
},
];
let quad_verts = gfx.new_buffer(BufferUsage::VERTEX_BUFFER, &vertices)?;
let quad_verts = Buffer::from_iter(
gfx.memory_allocator.clone(),
BufferCreateInfo {
usage: BufferUsage::VERTEX_BUFFER,
..Default::default()
},
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
},
vertices,
)?;
let drm_device = gfx.device_info().drm_render_node.map(|(major, minor)| {
log::info!("DRM render device: {major} {minor}");
(i64::from(major), i64::from(minor))
});
if drm_device.is_none() {
let mut cmd_xfer = gfx.create_xfer_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
let fallback_image =
cmd_xfer.upload_image(1, 1, Format::R8G8B8A8_SRGB, &[255, 0, 255, 255])?;
cmd_xfer.build_and_execute_now()?;
let fallback_image = ImageView::new_default(fallback_image)?;
let p = gfx.device.physical_device().properties();
let drm_device = if let (Some(maj), Some(min)) = (p.render_major, p.render_minor) {
log::info!("DRM render device: {maj} {min}");
Some((maj, min))
} else {
log::warn!("No DRM device.");
}
None
};
Ok(Self {
shaders,
drm_formats,
queue_capture,
quad_verts,
fallback_image,
drm_device,
})
}
}
fn base_create_info() -> WGfxCreateInfo {
WGfxCreateInfo {
application_name: "wayvr".to_owned(),
engine_name: "wayvr".to_owned(),
surface_format: Format::R8G8B8A8_SRGB,
..WGfxCreateInfo::default()
const fn get_dmabuf_extensions() -> DeviceExtensions {
DeviceExtensions {
khr_external_memory: true,
khr_external_memory_fd: true,
ext_external_memory_dma_buf: true,
..DeviceExtensions::empty()
}
}
fn log_device(gfx: &WGfx) {
log::info!("Using vkPhysicalDevice: {}", gfx.device_info().name);
log::debug!(
" DMA-buf supported: {}",
gfx.capabilities().external_memory_dma_buf
);
log::debug!(
" DRM format modifiers supported: {}",
gfx.capabilities().image_drm_format_modifier
);
log::debug!(" Separate capture queue: {}", gfx.has_capture_queue());
static VULKAN_LIBRARY: OnceLock<Arc<vulkano::VulkanLibrary>> = OnceLock::new();
fn get_vulkan_library() -> &'static Arc<vulkano::VulkanLibrary> {
VULKAN_LIBRARY.get_or_init(|| vulkano::VulkanLibrary::new().unwrap()) // want panic
}
#[cfg(feature = "openxr")]
unsafe extern "system" fn get_instance_proc_addr(
instance: openxr::sys::platform::VkInstance,
name: *const std::ffi::c_char,
) -> Option<unsafe extern "system" fn()> {
use vulkano::Handle;
let instance = ash::vk::Instance::from_raw(instance as _);
let library = get_vulkan_library();
unsafe { library.get_instance_proc_addr(instance, name) }
}
#[cfg(feature = "openxr")]
#[allow(clippy::too_many_lines)]
pub fn init_openxr_graphics(
xr_instance: openxr::Instance,
system: openxr::SystemId,
) -> anyhow::Result<(Arc<WGfx>, WGfxExtras)> {
let mut create_info = base_create_info();
create_info
.required_instance_extensions
.push("VK_KHR_get_physical_device_properties2".to_owned());
use std::ffi::{self, CString};
let xr_for_instance = xr_instance.clone();
let create_instance = move |get_instance_proc_addr: usize,
create_info: *const std::ffi::c_void| {
let get_instance_proc_addr: openxr::sys::platform::VkGetInstanceProcAddr =
unsafe { std::mem::transmute(get_instance_proc_addr) };
let result = unsafe {
xr_for_instance.create_vulkan_instance(
use vulkano::{Handle, Version};
let instance_extensions = InstanceExtensions {
khr_get_physical_device_properties2: true,
..InstanceExtensions::empty()
};
let instance_extensions_raw = instance_extensions
.into_iter()
.filter_map(|(name, enabled)| {
if enabled {
Some(ffi::CString::new(name).unwrap().into_raw().cast_const())
// want panic
} else {
None
}
})
.collect::<Vec<_>>();
let vk_target_version = vk::make_api_version(0, 1, 3, 0);
let target_version = vulkano::Version::V1_3;
let library = get_vulkan_library();
let vk_app_info_raw = vk::ApplicationInfo::default()
.application_version(0)
.engine_version(0)
.api_version(vk_target_version);
let instance = unsafe {
let vk_instance = xr_instance
.create_vulkan_instance(
system,
get_instance_proc_addr,
create_info.cast(),
std::ptr::from_ref(
&vk::InstanceCreateInfo::default()
.application_info(&vk_app_info_raw)
.enabled_extension_names(&instance_extensions_raw),
)
.cast(),
)
}
.map_err(|e| anyhow::anyhow!("XR error creating Vulkan instance: {e:?}"))?;
let instance =
result.map_err(|e| anyhow::anyhow!("Vulkan error creating Vulkan instance: {e:?}"))?;
Ok(instance as u64)
.expect("XR error creating Vulkan instance")
.map_err(vk::Result::from_raw)
.expect("Vulkan error creating Vulkan instance");
Instance::from_handle(
library.clone(),
ash::vk::Instance::from_raw(vk_instance as _),
InstanceCreateInfo {
application_version: Version::major_minor(0, 0),
engine_version: Version::major_minor(0, 0),
max_api_version: Some(Version::V1_3),
enabled_extensions: instance_extensions,
..Default::default()
},
)
};
let xr_for_physical_device = xr_instance.clone();
let get_physical_device = move |instance: u64| {
unsafe { xr_for_physical_device.vulkan_graphics_device(system, instance as _) }
.map(|physical_device| physical_device as u64)
.map_err(|e| anyhow::anyhow!("XR error getting Vulkan graphics device: {e:?}"))
let physical_device = unsafe {
PhysicalDevice::from_handle(
instance.clone(),
vk::PhysicalDevice::from_raw(
xr_instance.vulkan_graphics_device(system, instance.handle().as_raw() as _)? as _,
),
)
}?;
let vk_device_properties = physical_device.properties();
assert!(
(vk_device_properties.api_version >= target_version),
"Vulkan physical device doesn't support Vulkan {target_version}"
);
log::info!(
"Using vkPhysicalDevice: {}",
physical_device.properties().device_name,
);
let queue_families = try_all_queue_families(physical_device.as_ref())
.expect("vkPhysicalDevice does not have a GRAPHICS / TRANSFER queue.");
let mut device_extensions = DeviceExtensions::empty();
let dmabuf_extensions = get_dmabuf_extensions();
if physical_device
.supported_extensions()
.contains(&dmabuf_extensions)
{
device_extensions = device_extensions.union(&dmabuf_extensions);
device_extensions.ext_image_drm_format_modifier = physical_device
.supported_extensions()
.ext_image_drm_format_modifier;
}
if physical_device
.supported_extensions()
.ext_physical_device_drm
{
device_extensions.ext_physical_device_drm = true;
}
let device_extensions_raw = device_extensions
.into_iter()
.filter_map(|(name, enabled)| {
if enabled {
Some(ffi::CString::new(name).unwrap().into_raw().cast_const())
// want panic
} else {
None
}
})
.collect::<Vec<_>>();
// If adding anything here, also add to the native device_create_info below
let features = DeviceFeatures {
dynamic_rendering: true,
descriptor_binding_sampled_image_update_after_bind: true,
..DeviceFeatures::empty()
};
let xr_for_device = xr_instance;
let create_device = move |get_instance_proc_addr: usize,
physical_device: u64,
create_info: *const std::ffi::c_void| {
let get_instance_proc_addr: openxr::sys::platform::VkGetInstanceProcAddr =
unsafe { std::mem::transmute(get_instance_proc_addr) };
let result = unsafe {
xr_for_device.create_vulkan_device(
let queue_create_infos = queue_families
.iter()
.map(|fam| {
vk::DeviceQueueCreateInfo::default()
.queue_family_index(fam.queue_family_index)
.queue_priorities(&fam.priorities)
})
.collect::<Vec<_>>();
let mut device_create_info = vk::DeviceCreateInfo::default()
.queue_create_infos(&queue_create_infos)
.enabled_extension_names(&device_extensions_raw);
let mut dynamic_rendering =
vk::PhysicalDeviceDynamicRenderingFeatures::default().dynamic_rendering(true);
let mut indexing_features = vk::PhysicalDeviceDescriptorIndexingFeatures::default()
.descriptor_binding_sampled_image_update_after_bind(true);
dynamic_rendering.p_next = device_create_info.p_next.cast_mut();
indexing_features.p_next = (&raw mut dynamic_rendering).cast::<c_void>();
device_create_info.p_next = &raw mut indexing_features as *const c_void;
let (device, queues) = unsafe {
let vk_device = xr_instance
.create_vulkan_device(
system,
get_instance_proc_addr,
physical_device as _,
create_info.cast(),
physical_device.handle().as_raw() as _,
(&raw const device_create_info).cast(),
)
}
.map_err(|e| anyhow::anyhow!("XR error creating Vulkan device: {e:?}"))?;
let device =
result.map_err(|e| anyhow::anyhow!("Vulkan error creating Vulkan device: {e:?}"))?;
Ok(device as u64)
.expect("XR error creating Vulkan device")
.map_err(vk::Result::from_raw)
.expect("Vulkan error creating Vulkan device");
vulkano::device::Device::from_handle(
physical_device,
vk::Device::from_raw(vk_device as _),
DeviceCreateInfo {
queue_create_infos: queue_families
.iter()
.map(|fam| QueueCreateInfo {
queue_family_index: fam.queue_family_index,
queues: fam.priorities.clone(),
..Default::default()
})
.collect::<Vec<_>>(),
enabled_extensions: device_extensions,
enabled_features: features,
..Default::default()
},
)
};
let gfx = unsafe {
WGfx::new_external_vulkan(
&create_info,
create_instance,
get_physical_device,
create_device,
)?
};
log_device(&gfx);
let extras = WGfxExtras::new(&gfx)?;
log::debug!(
" DMA-buf supported: {}",
device.enabled_extensions().ext_external_memory_dma_buf
);
log::debug!(
" DRM format modifiers supported: {}",
device.enabled_extensions().ext_image_drm_format_modifier
);
// Drop the CStrings
device_extensions_raw
.into_iter()
.for_each(|c_string| unsafe {
let _ = CString::from_raw(c_string.cast_mut());
});
let (queue_gfx, queue_xfer, queue_capture) = unwrap_queues(queues.collect());
let gfx = WGfx::new_from_raw(
instance,
device,
queue_gfx,
queue_xfer,
Format::R8G8B8A8_SRGB,
);
let extras = WGfxExtras::new(gfx.clone(), queue_capture)?;
Ok((gfx, extras))
}
#[allow(clippy::too_many_lines)]
#[cfg(feature = "openvr")]
pub fn init_openvr_graphics(
mut instance_extensions: Vec<String>,
required_device_extensions: impl FnMut(u64) -> Vec<String>,
mut vk_instance_extensions: InstanceExtensions,
mut vk_device_extensions_fn: impl FnMut(&PhysicalDevice) -> DeviceExtensions,
) -> anyhow::Result<(Arc<WGfx>, WGfxExtras)> {
const PHYSICAL_DEVICE_PROPERTIES_2: &str = "VK_KHR_get_physical_device_properties2";
if !instance_extensions
.iter()
.any(|extension| extension == PHYSICAL_DEVICE_PROPERTIES_2)
{
instance_extensions.push(PHYSICAL_DEVICE_PROPERTIES_2.to_owned());
use vulkano::device::Device;
//#[cfg(debug_assertions)]
//let layers = vec!["VK_LAYER_KHRONOS_validation".to_owned()];
//#[cfg(not(debug_assertions))]
let layers = vec![];
log::debug!("Instance exts for runtime: {vk_instance_extensions:?}");
vk_instance_extensions.khr_get_physical_device_properties2 = true;
let instance = Instance::new(
get_vulkan_library().clone(),
InstanceCreateInfo {
flags: InstanceCreateFlags::ENUMERATE_PORTABILITY,
enabled_extensions: vk_instance_extensions,
enabled_layers: layers,
..Default::default()
},
)?;
let dmabuf_extensions = get_dmabuf_extensions();
let (physical_device, my_extensions, queue_families) = instance
.enumerate_physical_devices()?
.filter_map(|p| {
let mut my_extensions = vk_device_extensions_fn(&p);
if !p.supported_extensions().contains(&my_extensions) {
log::debug!(
"Not using {} due to missing extensions:",
p.properties().device_name,
);
for (ext, missing) in p.supported_extensions().difference(&my_extensions) {
if missing {
log::debug!(" {ext}");
}
}
return None;
}
if p.supported_extensions().contains(&dmabuf_extensions) {
my_extensions = my_extensions.union(&dmabuf_extensions);
my_extensions.ext_image_drm_format_modifier =
p.supported_extensions().ext_image_drm_format_modifier;
}
if p.supported_extensions().ext_filter_cubic {
my_extensions.ext_filter_cubic = true;
}
if p.supported_extensions().ext_physical_device_drm {
// needed for wayland_server
my_extensions.ext_physical_device_drm = true;
}
log::debug!(
"Device exts for {}: {:?}",
p.properties().device_name,
my_extensions
);
Some((p, my_extensions))
})
.filter_map(|(p, my_extensions)| {
try_all_queue_families(p.as_ref()).map(|families| (p, my_extensions, families))
})
.min_by_key(|(p, _, families)| prio_from_device_type(p) * 10 + prio_from_families(families))
.expect("no suitable physical device found");
log::info!(
"Using vkPhysicalDevice: {}",
physical_device.properties().device_name,
);
let (device, queues) = Device::new(
physical_device,
DeviceCreateInfo {
enabled_extensions: my_extensions,
enabled_features: DeviceFeatures {
dynamic_rendering: true,
descriptor_binding_sampled_image_update_after_bind: true,
..DeviceFeatures::empty()
},
queue_create_infos: queue_families
.iter()
.map(|fam| QueueCreateInfo {
queue_family_index: fam.queue_family_index,
queues: fam.priorities.clone(),
..Default::default()
})
.collect::<Vec<_>>(),
..Default::default()
},
)?;
log::debug!(
" DMA-buf supported: {}",
device.enabled_extensions().ext_external_memory_dma_buf
);
log::debug!(
" DRM format modifiers supported: {}",
device.enabled_extensions().ext_image_drm_format_modifier
);
let (queue_gfx, queue_xfer, queue_capture) = unwrap_queues(queues.collect());
let gfx = WGfx::new_from_raw(
instance,
device,
queue_gfx,
queue_xfer,
Format::R8G8B8A8_SRGB,
);
let extras = WGfxExtras::new(gfx.clone(), queue_capture)?;
Ok((gfx, extras))
}
pub fn upload_quad_vertices(
buf: &mut Subbuffer<[Vert2Uv]>,
width: f32,
height: f32,
x: f32,
y: f32,
w: f32,
h: f32,
) -> anyhow::Result<()> {
let rw = width;
let rh = height;
let x0 = x / rw;
let y0 = y / rh;
let x1 = w / rw + x0;
let y1 = h / rh + y0;
let data = [
Vert2Uv {
in_pos: [x0, y0],
in_uv: [0.0, 0.0],
},
Vert2Uv {
in_pos: [x0, y1],
in_uv: [0.0, 1.0],
},
Vert2Uv {
in_pos: [x1, y0],
in_uv: [1.0, 0.0],
},
Vert2Uv {
in_pos: [x1, y1],
in_uv: [1.0, 1.0],
},
];
buf.write()?[0..4].copy_from_slice(&data);
Ok(())
}
#[derive(Debug)]
struct QueueFamilyLayout {
queue_family_index: u32,
priorities: Vec<f32>,
}
fn prio_from_device_type(physical_device: &PhysicalDevice) -> u32 {
match physical_device.properties().device_type {
PhysicalDeviceType::DiscreteGpu => 0,
PhysicalDeviceType::IntegratedGpu => 1,
PhysicalDeviceType::VirtualGpu => 2,
PhysicalDeviceType::Cpu => 3,
_ => 4,
}
}
const fn prio_from_families(families: &[QueueFamilyLayout]) -> u32 {
match families.len() {
2 | 3 => 0,
_ => 1,
}
}
fn unwrap_queues(queues: Vec<Arc<Queue>>) -> (Arc<Queue>, Arc<Queue>, Option<Arc<Queue>>) {
match queues[..] {
[ref g, ref t, ref c] => (g.clone(), t.clone(), Some(c.clone())),
[ref gt, ref c] => (gt.clone(), gt.clone(), Some(c.clone())),
[ref gt] => (gt.clone(), gt.clone(), None),
_ => unreachable!(),
}
}
fn try_all_queue_families(physical_device: &PhysicalDevice) -> Option<Vec<QueueFamilyLayout>> {
queue_families_priorities(
physical_device,
vec![
// main-thread graphics + uploads
QueueFlags::GRAPHICS | QueueFlags::TRANSFER,
// capture-thread uploads
QueueFlags::TRANSFER,
],
)
.or_else(|| {
queue_families_priorities(
physical_device,
vec![
// main thread graphics
QueueFlags::GRAPHICS,
// main thread uploads
QueueFlags::TRANSFER,
// capture thread uploads
QueueFlags::TRANSFER,
],
)
})
.or_else(|| {
queue_families_priorities(
physical_device,
// main thread-only. software capture not supported.
vec![QueueFlags::GRAPHICS | QueueFlags::TRANSFER],
)
})
}
fn queue_families_priorities(
physical_device: &PhysicalDevice,
mut requested_queues: Vec<QueueFlags>,
) -> Option<Vec<QueueFamilyLayout>> {
let mut result = Vec::with_capacity(3);
for (idx, props) in physical_device.queue_family_properties().iter().enumerate() {
let mut remaining = props.queue_count;
let mut want = 0usize;
requested_queues.retain(|requested| {
if props.queue_flags.intersects(*requested) && remaining > 0 {
remaining -= 1;
want += 1;
false
} else {
true
}
});
if want > 0 {
result.push(QueueFamilyLayout {
queue_family_index: idx as u32,
priorities: std::iter::repeat_n(1.0, want).collect(),
});
}
}
log::debug!("Instance exts for runtime: {instance_extensions:?}");
if requested_queues.is_empty() {
log::debug!("Selected GPU queue families: {result:?}");
Some(result)
} else {
None
}
}
let mut create_info = base_create_info();
create_info.required_instance_extensions = instance_extensions;
let gfx = WGfx::new_with_device_extensions(&create_info, required_device_extensions)?;
log_device(&gfx);
let extras = WGfxExtras::new(&gfx)?;
Ok((gfx, extras))
pub struct RenderResult {
pub queue: Arc<Queue>,
pub cmd_buf: Arc<PrimaryAutoCommandBuffer>,
}
#[derive(Default)]
pub struct GpuFutures {
command_buffers: Vec<Arc<BuiltCommandBuffer>>,
futures: Vec<Box<dyn GpuFuture>>,
}
impl GpuFutures {
pub fn execute(&mut self, command_buffer: Arc<BuiltCommandBuffer>) {
self.command_buffers.push(command_buffer);
pub fn execute(
&mut self,
queue: Arc<Queue>,
cmd_buf: Arc<PrimaryAutoCommandBuffer>,
) -> anyhow::Result<()> {
self.futures.push(cmd_buf.execute(queue)?.boxed());
Ok(())
}
pub fn execute_results(&mut self, results: SmallVec<[Arc<BuiltCommandBuffer>; 2]>) {
self.command_buffers.extend(results);
}
pub fn wait(self) -> anyhow::Result<()> {
for command_buffer in self.command_buffers {
command_buffer.submit_and_wait()?;
pub fn execute_results(&mut self, results: SmallVec<[RenderResult; 2]>) -> anyhow::Result<()> {
for (i, res) in results.into_iter().enumerate() {
if i == 0 {
let future = res.cmd_buf.execute(res.queue)?;
self.futures.push(Box::new(future));
} else {
let future = self.futures.pop().unwrap();
let future = future.then_execute(res.queue, res.cmd_buf)?;
self.futures.push(Box::new(future));
}
}
Ok(())
}
pub fn wait(self) -> anyhow::Result<()> {
let mut it = self.futures.into_iter();
let Some(mut all) = it.next() else {
return Ok(());
};
for f in it {
all = all.join(f).boxed();
}
let finished = all.then_signal_fence_and_flush()?;
finished.wait(None)?;
Ok(())
}
}
pub trait ExtentExt {
fn extent_f32(&self) -> [f32; 2];
#[allow(dead_code)]
fn extent_vec2(&self) -> Vec2;
fn extent_u32arr(&self) -> [u32; 2];
}
impl ExtentExt for Arc<ImageView> {
fn extent_f32(&self) -> [f32; 2] {
let [w, h, _] = self.image().extent();
[w as _, h as _]
}
fn extent_vec2(&self) -> Vec2 {
let [w, h, _] = self.image().extent();
vec2(w as _, h as _)
}
fn extent_u32arr(&self) -> [u32; 2] {
let [w, h, _] = self.image().extent();
[w, h]
}
}
impl ExtentExt for [u32; 3] {
fn extent_f32(&self) -> [f32; 2] {
let [w, h, _] = *self;
[w as _, h as _]
}
fn extent_vec2(&self) -> Vec2 {
let [w, h, _] = *self;
Vec2 {
x: w as _,
y: h as _,
}
}
fn extent_u32arr(&self) -> [u32; 2] {
let [w, h, _] = *self;
[w, h]
}
}
+3 -3
View File
@@ -244,7 +244,7 @@ pub(super) fn setup_custom_button<S: 'static>(
template_name: template_name.clone(),
template_params: template_params.clone(),
},
position: data.metadata.get_mouse_pos_absolute().unwrap().into(), // want panic
position: data.metadata.get_mouse_pos_absolute().unwrap(), //want panic
});
Ok(EventResult::Consumed)
}
@@ -625,12 +625,12 @@ pub(super) fn setup_custom_button<S: 'static>(
)
.with_timeout(1.0)
.with_lerp_amount(1.0)
.with_animate(false)
.with_sound(i == 0)
.submit_at(app, now + Duration::from_secs(i as _));
}
app.audio.play_sample("fix_floor");
app.audio_sample_player
.play_sample(&mut app.audio_system, "fix_floor");
let deadline = now + Duration::from_secs(duration_secs);
Loaded 100 of 195 files, more files were not shown because too many files have changed in this diff. Show more