Merge remote-tracking branch 'origin/main' into next-dash-interface

[skip ci]
This commit is contained in:
Aleksander
2025-12-23 17:01:16 +01:00
parent 848674c143
commit 9a606dbce5
63 changed files with 2457 additions and 309 deletions

146
Cargo.lock generated
View File

@@ -2890,8 +2890,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d941b405bd2322993887859a8ee6ac9134945a24ec5ec763a8a962fc64dfec2d"
dependencies = [
"doctest-file",
"futures-core",
"libc",
"recvmsg",
"tokio",
"widestring",
"windows-sys 0.52.0",
]
@@ -3343,6 +3345,17 @@ version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff"
[[package]]
name = "mio"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
dependencies = [
"libc",
"wasi",
"windows-sys 0.61.2",
]
[[package]]
name = "moveit"
version = "0.6.0"
@@ -5236,6 +5249,16 @@ dependencies = [
"serde",
]
[[package]]
name = "socket2"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
dependencies = [
"libc",
"windows-sys 0.60.2",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
@@ -5639,6 +5662,45 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
dependencies = [
"bytes",
"libc",
"mio",
"pin-project-lite",
"socket2",
"tokio-macros",
"windows-sys 0.61.2",
]
[[package]]
name = "tokio-macros"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.111",
]
[[package]]
name = "tokio-util"
version = "0.7.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
]
[[package]]
name = "toml"
version = "0.8.2"
@@ -6299,16 +6361,18 @@ dependencies = [
]
[[package]]
name = "wayvr_ipc"
name = "wayvr-ipc"
version = "0.1.0"
source = "git+https://github.com/olekolek1000/wayvr-ipc.git?rev=6d253ef9e36db0f181566030a4990454ecb60395#6d253ef9e36db0f181566030a4990454ecb60395"
dependencies = [
"anyhow",
"bytes",
"interprocess",
"log",
"serde",
"serde_json",
"smallvec",
"tokio",
"tokio-util",
]
[[package]]
@@ -6608,6 +6672,15 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets 0.53.5",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
@@ -6641,13 +6714,30 @@ dependencies = [
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm",
"windows_i686_gnullvm 0.52.6",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link 0.2.1",
"windows_aarch64_gnullvm 0.53.1",
"windows_aarch64_msvc 0.53.1",
"windows_i686_gnu 0.53.1",
"windows_i686_gnullvm 0.53.1",
"windows_i686_msvc 0.53.1",
"windows_x86_64_gnu 0.53.1",
"windows_x86_64_gnullvm 0.53.1",
"windows_x86_64_msvc 0.53.1",
]
[[package]]
name = "windows-threading"
version = "0.1.0"
@@ -6669,6 +6759,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
@@ -6681,6 +6777,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
@@ -6693,12 +6795,24 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
@@ -6711,6 +6825,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
@@ -6723,6 +6843,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
@@ -6735,6 +6861,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
@@ -6747,6 +6879,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]]
name = "winit"
version = "0.30.12"
@@ -6903,7 +7041,7 @@ dependencies = [
"vulkano-shaders",
"wayland-client",
"wayland-egl",
"wayvr_ipc",
"wayvr-ipc",
"wgui",
"winit",
"wlx-capture",

View File

@@ -17,6 +17,7 @@ members = [
"wlx-overlay-s",
"wlx-capture",
"dash-frontend",
"wayvr-ipc",
]
resolver = "3"

View File

@@ -16,14 +16,14 @@
"HEADSET_SETTINGS": "ヘッドセット設定",
"BRIGHTNESS": "明るさ",
"WLX": {
"NOTIFICATIONS_ENABLED": "通知が有効",
"NOTIFICATIONS_SOUND_ENABLED": "通知音を有効にする",
"KEYBOARD_SOUND_ENABLED": "キーボード音を有効にする",
"NOTIFICATIONS_ENABLED": "通知",
"NOTIFICATIONS_SOUND_ENABLED": "通知音",
"KEYBOARD_SOUND_ENABLED": "キーボード音",
"BLOCK_GAME_INPUT": "ゲーム入力をブロック",
"SPACE_DRAG_MULTIPLIER": "スペースドラッグ乗数",
"SPACE_DRAG_ROTATION_ENABLED": "スペースドラッグでの回転を有効にする",
"SHOW_SKYBOX": "スカイボックスを表示",
"ENABLE_PASSTHROUGH": "Passthroughを有効にする"
"SPACE_DRAG_ROTATION_ENABLED": "スペースドラッグでの回転",
"SHOW_SKYBOX": "スカイボックス",
"ENABLE_PASSTHROUGH": "パススルー"
},
"RESTART_SOFTWARE": "ソフトウェアを再起動"
},
@@ -64,4 +64,4 @@
"NO_PROCESSES_FOUND": "プロセスが見つかりませんでした",
"LOCATED_ON": "に"
}
}
}

View File

@@ -124,7 +124,7 @@ impl ToastManager {
// show-up animation
layout.animations.add(Animation::new(
rect.id,
160,
160, // does not use anim_mult
AnimationEasing::Linear,
Box::new(move |common, data| {
let pos_showup = AnimationEasing::OutQuint.interpolate((data.pos * 4.0).min(1.0));

View File

@@ -4,7 +4,7 @@ cd "$(dirname "$0")"
bun install
export MODEL="gemma3:12b"
export MODEL="gemma3:27b"
TEMPLATE="pl" bun main.ts
TEMPLATE="de" bun main.ts

390
wayvr-ipc/Cargo.lock generated Normal file
View File

@@ -0,0 +1,390 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "addr2line"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
dependencies = [
"gimli",
]
[[package]]
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "anyhow"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
[[package]]
name = "backtrace"
version = "0.3.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
dependencies = [
"addr2line",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
"windows-targets",
]
[[package]]
name = "bytes"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "doctest-file"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562"
[[package]]
name = "futures-core"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-sink"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
[[package]]
name = "gimli"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "interprocess"
version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "894148491d817cb36b6f778017b8ac46b17408d522dd90f539d677ea938362eb"
dependencies = [
"doctest-file",
"futures-core",
"libc",
"recvmsg",
"tokio",
"widestring",
"windows-sys",
]
[[package]]
name = "itoa"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]]
name = "libc"
version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "log"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "miniz_oxide"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394"
dependencies = [
"adler2",
]
[[package]]
name = "mio"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
dependencies = [
"libc",
"wasi",
"windows-sys",
]
[[package]]
name = "object"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
dependencies = [
"memchr",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "proc-macro2"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "recvmsg"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175"
[[package]]
name = "rustc-demangle"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "ryu"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "serde"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.135"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "socket2"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "syn"
version = "2.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tokio"
version = "1.43.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "492a604e2fd7f814268a378409e6c92b5525d747d10db9a229723f55a417958c"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
"pin-project-lite",
"socket2",
"tokio-macros",
"windows-sys",
]
[[package]]
name = "tokio-macros"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tokio-util"
version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
]
[[package]]
name = "unicode-ident"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wayvr-ipc"
version = "0.1.0"
dependencies = [
"anyhow",
"bytes",
"interprocess",
"log",
"serde",
"serde_json",
"smallvec",
"tokio",
"tokio-util",
]
[[package]]
name = "widestring"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"

21
wayvr-ipc/Cargo.toml Normal file
View File

@@ -0,0 +1,21 @@
[package]
name = "wayvr-ipc"
version = "0.1.0"
edition = "2024"
[dependencies]
bytes = "1.9.0"
smallvec = "1.13.2"
serde = { version = "1", features = ["derive"] }
anyhow = "1.0.93"
log = "0.4.22"
# client-only deps
interprocess = { version = "2.2.2", features = ["tokio"], optional = true }
tokio = { version = "1.43.1", features = ["macros"], optional = true }
tokio-util = { version = "0.7.13", optional = true }
serde_json = "1.0.135"
[features]
default = ["client"]
client = ["dep:tokio", "dep:tokio-util", "dep:interprocess"]

21
wayvr-ipc/LICENSE.txt Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Aleksander <aleksander@oo8.dev>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

9
wayvr-ipc/README.md Normal file
View File

@@ -0,0 +1,9 @@
# WayVR Protocol Specification & Client Implementation
This repository contains the IPC specification and client implementation for the WayVR Server protocol (which is used in wlx-overlay-s). The primary purpose is to enable communication between applications and the WayVR server, offering a range of API functions.
[WayVR Server README](https://github.com/galister/wlx-overlay-s/tree/main/contrib/wayvr)
## Usage
[Example WayVR IPC Client usage in WayVR Dashboard](https://github.com/olekolek1000/wayvr-dashboard/blob/master/src-tauri/src/frontend_ipc.rs)

2
wayvr-ipc/rustfmt.toml Normal file
View File

@@ -0,0 +1,2 @@
tab_spaces = 2
hard_tabs = true

496
wayvr-ipc/src/client.rs Normal file
View File

@@ -0,0 +1,496 @@
use bytes::BufMut;
use interprocess::local_socket::{
self,
tokio::{prelude::*, Stream},
GenericNamespaced,
};
use serde::Serialize;
use smallvec::SmallVec;
use std::sync::{Arc, Weak};
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
sync::Mutex,
};
use tokio_util::sync::CancellationToken;
use crate::{
gen_id,
ipc::{self, Serial},
packet_client::{self, PacketClient},
packet_server::{self, PacketServer},
util::notifier::Notifier,
};
pub struct QueuedPacket {
notifier: Notifier,
serial: Serial,
packet: Option<PacketServer>,
}
gen_id!(
QueuedPacketVec,
QueuedPacket,
QueuedPacketCell,
QueuedPacketHandle
);
#[derive(Debug, Serialize, Clone)]
pub struct AuthInfo {
pub runtime: String,
}
type SignalFunc = Box<dyn FnMut(&packet_server::PacketServer) -> bool + Send>;
pub struct WayVRClient {
receiver: ReceiverMutex,
sender: SenderMutex,
cancel_token: CancellationToken,
exiting: bool,
queued_packets: QueuedPacketVec,
pub auth: Option<AuthInfo>, // Some if authenticated
pub on_signal: Option<SignalFunc>,
}
pub async fn send_packet(sender: &SenderMutex, data: &[u8]) -> anyhow::Result<()> {
let mut bytes = bytes::BytesMut::new();
// packet size
bytes.put_u32(data.len() as u32);
// packet data
bytes.put_slice(data);
sender.lock().await.write_all(&bytes).await?;
Ok(())
}
pub type WayVRClientMutex = Arc<Mutex<WayVRClient>>;
pub type WayVRClientWeak = Weak<Mutex<WayVRClient>>;
type ReceiverMutex = Arc<Mutex<local_socket::tokio::RecvHalf>>;
type SenderMutex = Arc<Mutex<local_socket::tokio::SendHalf>>;
async fn client_runner(client: WayVRClientMutex) -> anyhow::Result<()> {
loop {
WayVRClient::tick(client.clone()).await?;
}
}
type Payload = SmallVec<[u8; 64]>;
async fn read_payload(
conn: &mut local_socket::tokio::RecvHalf,
size: u32,
) -> anyhow::Result<Payload> {
let mut payload = Payload::new();
payload.resize(size as usize, 0);
conn.read_exact(&mut payload).await?;
Ok(payload)
}
macro_rules! bail_unexpected_response {
() => {
anyhow::bail!("unexpected response");
};
}
// Send and wait for a response from the server
macro_rules! send_and_wait {
($client_mtx:expr, $serial:expr, $packet_to_send:expr, $expected_response_type:ident) => {{
let result =
WayVRClient::queue_wait_packet($client_mtx, $serial, &ipc::data_encode($packet_to_send))
.await?;
if let PacketServer::$expected_response_type(_, res) = result {
res
} else {
bail_unexpected_response!();
}
}};
}
// Send without expecting any response
macro_rules! send_only {
($client_mtx:expr, $packet_to_send:expr) => {{
WayVRClient::send_payload($client_mtx, &ipc::data_encode($packet_to_send)).await?;
}};
}
impl WayVRClient {
pub fn set_signal_handler(&mut self, on_signal: SignalFunc) {
self.on_signal = Some(on_signal);
}
pub async fn new(client_name: &str) -> anyhow::Result<WayVRClientMutex> {
let printname = "/tmp/wayvr_ipc.sock";
let name = printname.to_ns_name::<GenericNamespaced>()?;
let stream = match Stream::connect(name).await {
Ok(c) => c,
Err(e) => {
anyhow::bail!("Failed to connect to the WayVR IPC: {}", e)
}
};
let (receiver, sender) = stream.split();
let receiver = Arc::new(Mutex::new(receiver));
let sender = Arc::new(Mutex::new(sender));
let cancel_token = CancellationToken::new();
let client = Arc::new(Mutex::new(Self {
receiver,
sender: sender.clone(),
exiting: false,
cancel_token: cancel_token.clone(),
queued_packets: QueuedPacketVec::new(),
auth: None,
on_signal: None,
}));
WayVRClient::start_runner(client.clone(), cancel_token);
// Send handshake to the server
send_packet(
&sender,
&ipc::data_encode(&PacketClient::Handshake(packet_client::Handshake {
client_name: String::from(client_name),
magic: String::from(ipc::CONNECTION_MAGIC),
protocol_version: ipc::PROTOCOL_VERSION,
})),
)
.await?;
Ok(client)
}
fn start_runner(client: WayVRClientMutex, cancel_token: CancellationToken) {
tokio::spawn(async move {
tokio::select! {
_ = cancel_token.cancelled() => {
log::info!("Exiting IPC runner gracefully");
}
e = client_runner(client.clone()) => {
log::info!("IPC Runner failed: {:?}", e);
}
}
});
}
async fn tick(client_mtx: WayVRClientMutex) -> anyhow::Result<()> {
let receiver = {
let client = client_mtx.lock().await;
client.receiver.clone()
};
// read packet
let packet = {
let mut receiver = receiver.lock().await;
let packet_size = receiver.read_u32().await?;
log::trace!("packet size {}", packet_size);
if packet_size > 128 * 1024 {
anyhow::bail!("packet size too large (> 128 KiB)");
}
let payload = read_payload(&mut receiver, packet_size).await?;
let packet: PacketServer = ipc::data_decode(&payload)?;
packet
};
{
let mut client = client_mtx.lock().await;
if let PacketServer::HandshakeSuccess(success) = &packet {
if client.auth.is_some() {
anyhow::bail!("Got handshake response twice");
}
client.auth = Some(AuthInfo {
runtime: success.runtime.clone(),
});
log::info!(
"Authenticated. Server runtime name: \"{}\"",
success.runtime
);
}
if let PacketServer::Disconnect(disconnect) = &packet {
anyhow::bail!("Server disconnected us. Reason: {}", disconnect.reason);
}
if client.auth.is_none() {
anyhow::bail!(
"Server tried to send us a packet which is not a HandshakeSuccess or Disconnect"
);
}
if let PacketServer::WvrStateChanged(_) = &packet {
if let Some(on_signal) = &mut client.on_signal {
if (*on_signal)(&packet) {
// Signal consumed
return Ok(());
}
}
}
// queue packet to read if it contains a serial response
if let Some(serial) = packet.serial() {
for qpacket in &mut client.queued_packets.vec {
let Some(qpacket) = qpacket else {
continue;
};
let qpacket = &mut qpacket.obj;
if qpacket.serial != *serial {
continue; //skip
}
// found response serial, fill it and notify the receiver
qpacket.packet = Some(packet);
let notifier = qpacket.notifier.clone();
drop(client);
notifier.notify();
break;
}
}
}
Ok(())
}
// Send packet without feedback
async fn send_payload(client_mtx: WayVRClientMutex, payload: &[u8]) -> anyhow::Result<()> {
let client = client_mtx.lock().await;
let sender = client.sender.clone();
drop(client);
send_packet(&sender, payload).await?;
Ok(())
}
async fn queue_wait_packet(
client_mtx: WayVRClientMutex,
serial: Serial,
payload: &[u8],
) -> anyhow::Result<PacketServer> {
let notifier = Notifier::new();
// Send packet to the server
let queued_packet_handle = {
let mut client = client_mtx.lock().await;
let handle = client.queued_packets.add(QueuedPacket {
notifier: notifier.clone(),
packet: None, // will be filled after notify
serial,
});
let sender = client.sender.clone();
drop(client);
send_packet(&sender, payload).await?;
handle
};
// Wait for response message
notifier.wait().await;
// Fetch response packet
{
let mut client = client_mtx.lock().await;
let cell = client
.queued_packets
.get_mut(&queued_packet_handle)
.ok_or(anyhow::anyhow!(
"missing packet cell, this shouldn't happen"
))?;
let Some(packet) = cell.packet.take() else {
anyhow::bail!("packet is None, this shouldn't happen");
};
client.queued_packets.remove(&queued_packet_handle);
Ok(packet)
}
}
pub async fn fn_wvr_display_list(
client: WayVRClientMutex,
serial: Serial,
) -> anyhow::Result<Vec<packet_server::WvrDisplay>> {
Ok(
send_and_wait!(
client,
serial,
&PacketClient::WvrDisplayList(serial),
WvrDisplayListResponse
)
.list,
)
}
pub async fn fn_wvr_display_get(
client: WayVRClientMutex,
serial: Serial,
handle: packet_server::WvrDisplayHandle,
) -> anyhow::Result<Option<packet_server::WvrDisplay>> {
Ok(send_and_wait!(
client,
serial,
&PacketClient::WvrDisplayGet(serial, handle),
WvrDisplayGetResponse
))
}
pub async fn fn_wvr_display_remove(
client: WayVRClientMutex,
serial: Serial,
handle: packet_server::WvrDisplayHandle,
) -> anyhow::Result<()> {
send_and_wait!(
client,
serial,
&PacketClient::WvrDisplayRemove(serial, handle),
WvrDisplayRemoveResponse
)
.map_err(|e| anyhow::anyhow!("{}", e))
}
pub async fn fn_wvr_display_create(
client: WayVRClientMutex,
serial: Serial,
params: packet_client::WvrDisplayCreateParams,
) -> anyhow::Result<packet_server::WvrDisplayHandle> {
Ok(send_and_wait!(
client,
serial,
&PacketClient::WvrDisplayCreate(serial, params),
WvrDisplayCreateResponse
))
}
pub async fn fn_wvr_display_set_visible(
client: WayVRClientMutex,
handle: packet_server::WvrDisplayHandle,
visible: bool,
) -> anyhow::Result<()> {
send_only!(client, &PacketClient::WvrDisplaySetVisible(handle, visible));
Ok(())
}
pub async fn fn_wvr_display_set_layout(
client: WayVRClientMutex,
handle: packet_server::WvrDisplayHandle,
layout: packet_server::WvrDisplayWindowLayout,
) -> anyhow::Result<()> {
send_only!(
client,
&PacketClient::WvrDisplaySetWindowLayout(handle, layout)
);
Ok(())
}
pub async fn fn_wvr_display_window_list(
client: WayVRClientMutex,
serial: Serial,
handle: packet_server::WvrDisplayHandle,
) -> anyhow::Result<Option<Vec<packet_server::WvrWindow>>> {
Ok(
send_and_wait!(
client,
serial,
&PacketClient::WvrDisplayWindowList(serial, handle),
WvrDisplayWindowListResponse
)
.map(|res| res.list),
)
}
pub async fn fn_wvr_window_set_visible(
client: WayVRClientMutex,
handle: packet_server::WvrWindowHandle,
visible: bool,
) -> anyhow::Result<()> {
send_only!(client, &PacketClient::WvrWindowSetVisible(handle, visible));
Ok(())
}
pub async fn fn_wvr_process_list(
client: WayVRClientMutex,
serial: Serial,
) -> anyhow::Result<Vec<packet_server::WvrProcess>> {
Ok(
send_and_wait!(
client,
serial,
&PacketClient::WvrProcessList(serial),
WvrProcessListResponse
)
.list,
)
}
pub async fn fn_wvr_process_get(
client: WayVRClientMutex,
serial: Serial,
handle: packet_server::WvrProcessHandle,
) -> anyhow::Result<Option<packet_server::WvrProcess>> {
Ok(send_and_wait!(
client,
serial,
&PacketClient::WvrProcessGet(serial, handle),
WvrProcessGetResponse
))
}
pub async fn fn_wvr_process_terminate(
client: WayVRClientMutex,
handle: packet_server::WvrProcessHandle,
) -> anyhow::Result<()> {
send_only!(client, &PacketClient::WvrProcessTerminate(handle));
Ok(())
}
pub async fn fn_wvr_process_launch(
client: WayVRClientMutex,
serial: Serial,
params: packet_client::WvrProcessLaunchParams,
) -> anyhow::Result<packet_server::WvrProcessHandle> {
send_and_wait!(
client,
serial,
&PacketClient::WvrProcessLaunch(serial, params),
WvrProcessLaunchResponse
)
.map_err(|e| anyhow::anyhow!("{}", e))
}
pub async fn fn_wlx_input_state(
client: WayVRClientMutex,
serial: Serial,
) -> anyhow::Result<packet_server::WlxInputState> {
Ok(send_and_wait!(
client,
serial,
&PacketClient::WlxInputState(serial),
WlxInputStateResponse
))
}
pub async fn fn_wlx_haptics(
client: WayVRClientMutex,
params: packet_client::WlxHapticsParams,
) -> anyhow::Result<()> {
send_only!(client, &PacketClient::WlxHaptics(params));
Ok(())
}
}
impl Drop for WayVRClient {
fn drop(&mut self) {
self.exiting = true;
self.cancel_token.cancel();
}
}

42
wayvr-ipc/src/ipc.rs Normal file
View File

@@ -0,0 +1,42 @@
use std::sync::{Arc, Mutex as SyncMutex};
pub type Serial = u64;
#[derive(Clone, Default)]
pub struct SerialGenerator {
serial: Arc<SyncMutex<u64>>,
}
impl SerialGenerator {
pub fn new() -> SerialGenerator {
Self {
serial: Arc::new(SyncMutex::new(0)),
}
}
pub fn increment_get(&self) -> Serial {
let mut serial = self.serial.lock().unwrap();
let cur = *serial;
*serial += 1;
cur
}
}
pub const PROTOCOL_VERSION: u32 = 3;
pub const CONNECTION_MAGIC: &str = "wayvr_ipc";
pub fn data_encode<T>(data: &T) -> Vec<u8>
where
T: serde::Serialize,
{
let str = serde_json::to_string(&data).unwrap();
log::debug!("serialized data: {}", str);
str.into_bytes()
}
pub fn data_decode<T>(data: &[u8]) -> anyhow::Result<T>
where
T: for<'a> serde::Deserialize<'a>,
{
Ok(serde_json::from_str::<T>(std::str::from_utf8(data)?)?)
}

7
wayvr-ipc/src/lib.rs Normal file
View File

@@ -0,0 +1,7 @@
pub mod ipc;
pub mod packet_client;
pub mod packet_server;
mod util;
#[cfg(feature = "client")]
pub mod client;

View File

@@ -0,0 +1,71 @@
// Contents of this file should be the same as on wlx-overlay-s.
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use super::{ipc::Serial, packet_server};
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Handshake {
pub protocol_version: u32, // always set to PROTOCOL_VERSION
pub magic: String, // always set to CONNECTION_MAGIC
pub client_name: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub enum AttachTo {
None,
HandLeft,
HandRight,
Head,
Stage,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct WvrProcessLaunchParams {
pub name: String,
pub exec: String,
pub target_display: packet_server::WvrDisplayHandle,
pub env: Vec<String>,
pub args: String,
pub userdata: HashMap<String, String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct WvrDisplayCreateParams {
pub width: u16,
pub height: u16,
pub name: String,
pub scale: Option<f32>,
pub attach_to: AttachTo,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct WlxHapticsParams {
pub intensity: f32,
pub duration: f32,
pub frequency: f32,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum PacketClient {
Handshake(Handshake),
WvrDisplayCreate(Serial, WvrDisplayCreateParams),
WvrDisplayGet(Serial, packet_server::WvrDisplayHandle),
WvrDisplayList(Serial),
WvrDisplayRemove(Serial, packet_server::WvrDisplayHandle),
WvrDisplaySetVisible(packet_server::WvrDisplayHandle, bool),
WvrDisplayWindowList(Serial, packet_server::WvrDisplayHandle),
WvrDisplaySetWindowLayout(
packet_server::WvrDisplayHandle,
packet_server::WvrDisplayWindowLayout,
),
WvrWindowSetVisible(packet_server::WvrWindowHandle, bool),
WvrProcessGet(Serial, packet_server::WvrProcessHandle),
WvrProcessLaunch(Serial, WvrProcessLaunchParams),
WvrProcessList(Serial),
WvrProcessTerminate(packet_server::WvrProcessHandle),
WlxHaptics(WlxHapticsParams),
WlxInputState(Serial),
}

View File

@@ -0,0 +1,163 @@
// Contents of this file should be the same as on wlx-overlay-s.
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use super::ipc::Serial;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerInfo {}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HandshakeSuccess {
pub runtime: String, // Runtime name, for example "wlx-overlay-s"
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Disconnect {
pub reason: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct WvrDisplayHandle {
pub idx: u32,
pub generation: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct WvrProcessHandle {
pub idx: u32,
pub generation: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct WvrWindowHandle {
pub idx: u32,
pub generation: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WvrDisplay {
pub width: u16,
pub height: u16,
pub name: String,
pub visible: bool,
pub handle: WvrDisplayHandle,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WvrWindow {
pub pos_x: i32,
pub pos_y: i32,
pub size_x: u32,
pub size_y: u32,
pub visible: bool,
pub handle: WvrWindowHandle,
pub process_handle: WvrProcessHandle,
pub display_handle: WvrDisplayHandle,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WvrDisplayList {
pub list: Vec<WvrDisplay>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WvrWindowList {
pub list: Vec<WvrWindow>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WvrProcess {
pub name: String,
pub display_handle: WvrDisplayHandle,
pub handle: WvrProcessHandle,
pub userdata: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WvrProcessList {
pub list: Vec<WvrProcess>,
}
#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
pub struct Margins {
pub left: u16,
pub right: u16,
pub top: u16,
pub bottom: u16,
}
#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
pub struct StackingOptions {
pub margins_first: Margins,
pub margins_rest: Margins,
}
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
pub enum WvrDisplayWindowLayout {
Tiling,
Stacking(StackingOptions),
}
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
pub enum WvrStateChanged {
DisplayCreated,
DisplayRemoved,
ProcessCreated,
ProcessRemoved,
WindowCreated,
WindowRemoved,
DashboardShown,
DashboardHidden,
}
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
pub struct WlxInputStatePointer {
pub pos: [f32; 3],
}
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
pub struct WlxInputState {
pub hmd_pos: [f32; 3],
pub left: WlxInputStatePointer,
pub right: WlxInputStatePointer,
}
// "Wvr" prefixes are WayVR-specific
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PacketServer {
Disconnect(Disconnect),
HandshakeSuccess(HandshakeSuccess),
WlxInputStateResponse(Serial, WlxInputState),
WvrDisplayCreateResponse(Serial, WvrDisplayHandle),
WvrDisplayGetResponse(Serial, Option<WvrDisplay>),
WvrDisplayListResponse(Serial, WvrDisplayList),
WvrDisplayRemoveResponse(Serial, Result<(), String>),
WvrDisplayWindowListResponse(Serial, Option<WvrWindowList>),
WvrProcessGetResponse(Serial, Option<WvrProcess>),
WvrProcessLaunchResponse(Serial, Result<WvrProcessHandle, String>),
WvrProcessListResponse(Serial, WvrProcessList),
WvrStateChanged(WvrStateChanged),
}
impl PacketServer {
pub fn serial(&self) -> Option<&Serial> {
match self {
PacketServer::Disconnect(_) => None,
PacketServer::HandshakeSuccess(_) => None,
PacketServer::WlxInputStateResponse(serial, _) => Some(serial),
PacketServer::WvrDisplayCreateResponse(serial, _) => Some(serial),
PacketServer::WvrDisplayGetResponse(serial, _) => Some(serial),
PacketServer::WvrDisplayListResponse(serial, _) => Some(serial),
PacketServer::WvrDisplayRemoveResponse(serial, _) => Some(serial),
PacketServer::WvrDisplayWindowListResponse(serial, _) => Some(serial),
PacketServer::WvrProcessGetResponse(serial, _) => Some(serial),
PacketServer::WvrProcessLaunchResponse(serial, _) => Some(serial),
PacketServer::WvrProcessListResponse(serial, _) => Some(serial),
PacketServer::WvrStateChanged(_) => None,
}
}
}

View File

@@ -0,0 +1,173 @@
#[macro_export]
macro_rules! gen_id {
(
$container_name:ident,
$instance_name:ident,
$cell_name:ident,
$handle_name:ident) => {
//ThingCell
pub struct $cell_name {
pub obj: $instance_name,
pub generation: u64,
}
//ThingVec
pub struct $container_name {
// Vec<Option<ThingCell>>
pub vec: Vec<Option<$cell_name>>,
cur_generation: u64,
}
//ThingHandle
#[derive(Default, Clone, Copy, PartialEq, Hash, Eq)]
pub struct $handle_name {
idx: u32,
generation: u64,
}
#[allow(dead_code)]
impl $handle_name {
pub fn reset(&mut self) {
self.generation = 0;
}
pub fn is_set(&self) -> bool {
self.generation > 0
}
pub fn id(&self) -> u32 {
self.idx
}
pub fn new(idx: u32, generation: u64) -> Self {
Self { idx, generation }
}
}
//ThingVec
#[allow(dead_code)]
impl $container_name {
pub fn new() -> Self {
Self {
vec: Vec::new(),
cur_generation: 0,
}
}
pub fn iter(&self, callback: &dyn Fn($handle_name, &$instance_name)) {
for (idx, opt_cell) in self.vec.iter().enumerate() {
if let Some(cell) = opt_cell {
let handle = $container_name::get_handle(&cell, idx);
callback(handle, &cell.obj);
}
}
}
pub fn iter_mut(
&mut self,
callback: &mut dyn FnMut($handle_name, &mut $instance_name),
) {
for (idx, opt_cell) in self.vec.iter_mut().enumerate() {
if let Some(cell) = opt_cell {
let handle = $container_name::get_handle(&cell, idx);
callback(handle, &mut cell.obj);
}
}
}
pub fn get_handle(cell: &$cell_name, idx: usize) -> $handle_name {
$handle_name {
idx: idx as u32,
generation: cell.generation,
}
}
fn find_unused_idx(&mut self) -> Option<u32> {
for (num, obj) in self.vec.iter().enumerate() {
if obj.is_none() {
return Some(num as u32);
}
}
None
}
pub fn add(&mut self, obj: $instance_name) -> $handle_name {
self.cur_generation += 1;
let generation = self.cur_generation;
let unused_idx = self.find_unused_idx();
let idx = if let Some(idx) = unused_idx {
idx
} else {
self.vec.len() as u32
};
let handle = $handle_name { idx, generation };
let cell = $cell_name { obj, generation };
if let Some(idx) = unused_idx {
self.vec[idx as usize] = Some(cell);
} else {
self.vec.push(Some(cell))
}
handle
}
pub fn remove(&mut self, handle: &$handle_name) {
// Out of bounds, ignore
if handle.idx as usize >= self.vec.len() {
return;
}
// Remove only if the generation matches
if let Some(cell) = &self.vec[handle.idx as usize] {
if cell.generation == handle.generation {
self.vec[handle.idx as usize] = None;
}
}
}
pub fn get(&self, handle: &$handle_name) -> Option<&$instance_name> {
// Out of bounds, ignore
if handle.idx as usize >= self.vec.len() {
return None;
}
if let Some(cell) = &self.vec[handle.idx as usize] {
if cell.generation == handle.generation {
return Some(&cell.obj);
}
}
None
}
pub fn get_mut(&mut self, handle: &$handle_name) -> Option<&mut $instance_name> {
// Out of bounds, ignore
if handle.idx as usize >= self.vec.len() {
return None;
}
if let Some(cell) = &mut self.vec[handle.idx as usize] {
if cell.generation == handle.generation {
return Some(&mut cell.obj);
}
}
None
}
}
};
}
/* Example usage:
gen_id!(ThingVec, ThingInstance, ThingCell, ThingHandle);
struct ThingInstance {}
impl ThingInstance {}
*/

View File

@@ -0,0 +1,4 @@
pub mod handle;
#[cfg(feature = "client")]
pub mod notifier;

View File

@@ -0,0 +1,25 @@
use std::sync::Arc;
use tokio::sync::Notify;
// Copyable wrapped Notify struct for easier usage
#[derive(Default, Clone)]
pub struct Notifier {
notifier: Arc<Notify>,
}
impl Notifier {
pub fn new() -> Self {
Self {
notifier: Arc::new(Notify::new()),
}
}
pub fn notify(&self) {
self.notifier.notify_waiters();
}
pub async fn wait(&self) {
self.notifier.notified().await;
}
}

View File

@@ -22,7 +22,11 @@ use crate::{
},
};
use glam::{Mat4, Vec3};
use std::{cell::RefCell, rc::Rc};
use std::{
cell::RefCell,
rc::Rc,
time::{Duration, Instant},
};
use taffy::{AlignItems, JustifyContent, prelude::length};
pub struct Params<'a> {
@@ -41,6 +45,7 @@ pub struct Params<'a> {
/// until "un-clicked". this is visual only.
/// set the initial state using `set_sticky_state`
pub sticky: bool,
pub long_press_time: f32,
}
impl Default for Params<'_> {
@@ -58,6 +63,7 @@ impl Default for Params<'_> {
text_style: TextStyle::default(),
tooltip: None,
sticky: false,
long_press_time: 0.0,
}
}
}
@@ -79,6 +85,7 @@ struct State {
on_click: Option<ButtonClickCallback>,
active_tooltip: Option<Rc<ComponentTooltip>>,
colors: Colors,
last_pressed: Instant,
}
struct Data {
@@ -151,6 +158,10 @@ impl ComponentButton {
rect.params.color2 = get_color2(&color);
}
pub fn get_time_since_last_pressed(&self) -> Duration {
self.state.borrow().last_pressed.elapsed()
}
pub fn on_click(&self, func: ButtonClickCallback) {
self.state.borrow_mut().on_click = Some(func);
}
@@ -170,10 +181,13 @@ impl ComponentButton {
return;
}
let anim_mult = common.state.globals.defaults().animation_mult;
let anim_ticks = if sticky_down { 5. } else { 10. };
let state = self.state.clone();
let anim = Animation::new(
self.data.id_rect,
if sticky_down { 5 } else { 10 },
(anim_ticks * anim_mult) as _,
AnimationEasing::OutCubic,
Box::new(move |common, anim_data| {
let rect = anim_data.obj.get_as_mut::<WidgetRectangle>().unwrap();
@@ -227,10 +241,10 @@ fn anim_hover(
rect.params.border_color = init_border_color.lerp(&colors.hover_border_color, mult);
}
fn anim_hover_create(state: Rc<RefCell<State>>, widget_id: WidgetID, fade_in: bool) -> Animation {
fn anim_hover_create(state: Rc<RefCell<State>>, widget_id: WidgetID, fade_in: bool, anim_mult: f32) -> Animation {
Animation::new(
widget_id,
if fade_in { 5 } else { 10 },
((if fade_in { 5. } else { 10. }) * anim_mult) as _,
AnimationEasing::OutCubic,
Box::new(move |common, anim_data| {
let rect = anim_data.obj.get_as_mut::<WidgetRectangle>().unwrap();
@@ -254,6 +268,7 @@ fn register_event_mouse_enter(
state: Rc<RefCell<State>>,
listeners: &mut EventListenerCollection,
info: Option<components::tooltip::TooltipInfo>,
anim_mult: f32,
) -> EventListenerID {
listeners.register(
EventListenerKind::MouseEnter,
@@ -262,7 +277,7 @@ fn register_event_mouse_enter(
common.alterables.mark_redraw();
common
.alterables
.animate(anim_hover_create(state.clone(), event_data.widget_id, true));
.animate(anim_hover_create(state.clone(), event_data.widget_id, true, anim_mult));
if let Some(info) = info.clone() {
common.alterables.tasks.push(LayoutTask::ModifyLayoutState({
@@ -282,14 +297,18 @@ fn register_event_mouse_enter(
)
}
fn register_event_mouse_leave(state: Rc<RefCell<State>>, listeners: &mut EventListenerCollection) -> EventListenerID {
fn register_event_mouse_leave(
state: Rc<RefCell<State>>,
listeners: &mut EventListenerCollection,
anim_mult: f32,
) -> EventListenerID {
listeners.register(
EventListenerKind::MouseLeave,
Box::new(move |common, event_data, (), ()| {
common.alterables.trigger_haptics();
common
.alterables
.animate(anim_hover_create(state.clone(), event_data.widget_id, false));
.animate(anim_hover_create(state.clone(), event_data.widget_id, false, anim_mult));
let mut state = state.borrow_mut();
state.active_tooltip = None;
state.hovered = false;
@@ -320,7 +339,7 @@ fn register_event_mouse_press(state: Rc<RefCell<State>>, listeners: &mut EventLi
if state.hovered {
state.down = true;
state.active_tooltip = None;
state.last_pressed = Instant::now();
Ok(EventResult::Consumed)
} else {
Ok(EventResult::Pass)
@@ -349,7 +368,6 @@ fn register_event_mouse_release(
if state.down {
state.down = false;
if state.hovered
&& let Some(on_click) = &state.on_click
{
@@ -503,6 +521,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
on_click: None,
active_tooltip: None,
sticky_down: false,
last_pressed: Instant::now(),
colors: Colors {
color,
border_color,
@@ -515,9 +534,16 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
id: root.id,
lhandles: {
let mut widget = ess.layout.state.widgets.get(id_rect).unwrap().state();
let anim_mult = ess.layout.state.globals.defaults().animation_mult;
vec![
register_event_mouse_enter(data.clone(), state.clone(), &mut widget.event_listeners, params.tooltip),
register_event_mouse_leave(state.clone(), &mut widget.event_listeners),
register_event_mouse_enter(
data.clone(),
state.clone(),
&mut widget.event_listeners,
params.tooltip,
anim_mult,
),
register_event_mouse_leave(state.clone(), &mut widget.event_listeners, anim_mult),
register_event_mouse_press(state.clone(), &mut widget.event_listeners),
register_event_mouse_release(data.clone(), state.clone(), &mut widget.event_listeners),
]

View File

@@ -1,7 +1,7 @@
use std::{cell::RefCell, rc::Rc};
use taffy::{
AlignItems,
prelude::{length, percent},
AlignItems,
};
use crate::{
@@ -13,10 +13,10 @@ use crate::{
layout::{self, WidgetID, WidgetPair},
renderer_vk::text::{FontWeight, TextStyle},
widget::{
ConstructEssentials, EventResult,
label::{WidgetLabel, WidgetLabelParams},
rectangle::{WidgetRectangle, WidgetRectangleParams},
util::WLength,
ConstructEssentials, EventResult,
},
};
@@ -120,10 +120,10 @@ fn anim_hover(rect: &mut WidgetRectangle, pos: f32, pressed: bool) {
}
}
fn anim_hover_in(state: Rc<RefCell<State>>, widget_id: WidgetID) -> Animation {
fn anim_hover_in(state: Rc<RefCell<State>>, widget_id: WidgetID, anim_mult: f32) -> Animation {
Animation::new(
widget_id,
5,
(5. * anim_mult) as _,
AnimationEasing::OutQuad,
Box::new(move |common, anim_data| {
let rect = anim_data.obj.get_as_mut::<WidgetRectangle>().unwrap();
@@ -133,10 +133,10 @@ fn anim_hover_in(state: Rc<RefCell<State>>, widget_id: WidgetID) -> Animation {
)
}
fn anim_hover_out(state: Rc<RefCell<State>>, widget_id: WidgetID) -> Animation {
fn anim_hover_out(state: Rc<RefCell<State>>, widget_id: WidgetID, anim_mult: f32) -> Animation {
Animation::new(
widget_id,
8,
(8. * anim_mult) as _,
AnimationEasing::OutQuad,
Box::new(move |common, anim_data| {
let rect = anim_data.obj.get_as_mut::<WidgetRectangle>().unwrap();
@@ -146,28 +146,36 @@ fn anim_hover_out(state: Rc<RefCell<State>>, widget_id: WidgetID) -> Animation {
)
}
fn register_event_mouse_enter(state: Rc<RefCell<State>>, listeners: &mut EventListenerCollection) -> EventListenerID {
fn register_event_mouse_enter(
state: Rc<RefCell<State>>,
listeners: &mut EventListenerCollection,
anim_mult: f32,
) -> EventListenerID {
listeners.register(
EventListenerKind::MouseEnter,
Box::new(move |common, event_data, (), ()| {
common.alterables.trigger_haptics();
common
.alterables
.animate(anim_hover_in(state.clone(), event_data.widget_id));
.animate(anim_hover_in(state.clone(), event_data.widget_id, anim_mult));
state.borrow_mut().hovered = true;
Ok(EventResult::Pass)
}),
)
}
fn register_event_mouse_leave(state: Rc<RefCell<State>>, listeners: &mut EventListenerCollection) -> EventListenerID {
fn register_event_mouse_leave(
state: Rc<RefCell<State>>,
listeners: &mut EventListenerCollection,
anim_mult: f32,
) -> EventListenerID {
listeners.register(
EventListenerKind::MouseLeave,
Box::new(move |common, event_data, (), ()| {
common.alterables.trigger_haptics();
common
.alterables
.animate(anim_hover_out(state.clone(), event_data.widget_id));
.animate(anim_hover_out(state.clone(), event_data.widget_id, anim_mult));
state.borrow_mut().hovered = false;
Ok(EventResult::Pass)
}),
@@ -341,9 +349,10 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
id: root.id,
lhandles: {
let mut widget = ess.layout.state.widgets.get(id_container).unwrap().state();
let anim_mult = ess.layout.state.globals.defaults().animation_mult;
vec![
register_event_mouse_enter(state.clone(), &mut widget.event_listeners),
register_event_mouse_leave(state.clone(), &mut widget.event_listeners),
register_event_mouse_enter(state.clone(), &mut widget.event_listeners, anim_mult),
register_event_mouse_leave(state.clone(), &mut widget.event_listeners, anim_mult),
register_event_mouse_press(state.clone(), &mut widget.event_listeners),
register_event_mouse_release(data.clone(), state.clone(), &mut widget.event_listeners),
]

View File

@@ -18,11 +18,11 @@ use crate::{
util,
},
widget::{
ConstructEssentials, EventResult,
div::WidgetDiv,
label::{WidgetLabel, WidgetLabelParams},
rectangle::{WidgetRectangle, WidgetRectangleParams},
util::WLength,
ConstructEssentials, EventResult,
},
};
@@ -266,10 +266,10 @@ fn anim_rect(rect: &mut WidgetRectangle, pos: f32) {
rect.params.border_color = drawing::Color::lerp(&HANDLE_BORDER_COLOR, &HANDLE_BORDER_COLOR_HOVERED, pos);
}
fn on_enter_anim(common: &mut event::CallbackDataCommon, handle_id: WidgetID) {
fn on_enter_anim(common: &mut event::CallbackDataCommon, handle_id: WidgetID, anim_mult: f32) {
common.alterables.animate(Animation::new(
handle_id,
20,
(20. * anim_mult) as _,
AnimationEasing::OutBack,
Box::new(move |common, data| {
let rect = data.obj.get_as_mut::<WidgetRectangle>().unwrap();
@@ -280,10 +280,10 @@ fn on_enter_anim(common: &mut event::CallbackDataCommon, handle_id: WidgetID) {
));
}
fn on_leave_anim(common: &mut event::CallbackDataCommon, handle_id: WidgetID) {
fn on_leave_anim(common: &mut event::CallbackDataCommon, handle_id: WidgetID, anim_mult: f32) {
common.alterables.animate(Animation::new(
handle_id,
10,
(10. * anim_mult) as _,
AnimationEasing::OutQuad,
Box::new(move |common, data| {
let rect = data.obj.get_as_mut::<WidgetRectangle>().unwrap();
@@ -298,13 +298,14 @@ fn register_event_mouse_enter(
data: Rc<Data>,
state: Rc<RefCell<State>>,
listeners: &mut EventListenerCollection,
anim_mult: f32,
) -> event::EventListenerID {
listeners.register(
EventListenerKind::MouseEnter,
Box::new(move |common, _data, (), ()| {
common.alterables.trigger_haptics();
state.borrow_mut().hovered = true;
on_enter_anim(common, data.slider_handle_rect_id);
on_enter_anim(common, data.slider_handle_rect_id, anim_mult);
Ok(EventResult::Pass)
}),
)
@@ -314,13 +315,14 @@ fn register_event_mouse_leave(
data: Rc<Data>,
state: Rc<RefCell<State>>,
listeners: &mut EventListenerCollection,
anim_mult: f32,
) -> event::EventListenerID {
listeners.register(
EventListenerKind::MouseLeave,
Box::new(move |common, _data, (), ()| {
common.alterables.trigger_haptics();
state.borrow_mut().hovered = false;
on_leave_anim(common, data.slider_handle_rect_id);
on_leave_anim(common, data.slider_handle_rect_id, anim_mult);
Ok(EventResult::Pass)
}),
)
@@ -508,12 +510,12 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
id: root.id,
lhandles: {
let mut widget = ess.layout.state.widgets.get(body_id).unwrap().state();
let anim_mult = ess.layout.state.globals.defaults().animation_mult;
vec![
register_event_mouse_enter(data.clone(), state.clone(), &mut widget.event_listeners),
register_event_mouse_leave(data.clone(), state.clone(), &mut widget.event_listeners),
register_event_mouse_enter(data.clone(), state.clone(), &mut widget.event_listeners, anim_mult),
register_event_mouse_leave(data.clone(), state.clone(), &mut widget.event_listeners, anim_mult),
register_event_mouse_motion(data.clone(), state.clone(), &mut widget.event_listeners),
register_event_mouse_press(data.clone(), state.clone(), &mut widget.event_listeners),
register_event_mouse_leave(data.clone(), state.clone(), &mut widget.event_listeners),
register_event_mouse_release(state.clone(), &mut widget.event_listeners),
]
},

View File

@@ -22,7 +22,10 @@ pub struct Defaults {
pub accent_color: drawing::Color,
pub danger_color: drawing::Color,
pub faded_color: drawing::Color,
pub bg_color: drawing::Color,
pub translucent_alpha: f32,
pub animation_mult: f32,
pub rounding_mult: f32,
}
impl Default for Defaults {
@@ -34,7 +37,10 @@ impl Default for Defaults {
accent_color: drawing::Color::new(0.0, 0.54, 1.0, 1.0),
danger_color: drawing::Color::new(0.8, 0.0, 0.0, 1.0),
faded_color: drawing::Color::new(0.4, 0.5, 0.6, 1.0),
bg_color: drawing::Color::new(0.0039, 0.0078, 0.0235, 0.8352),
translucent_alpha: 0.25,
animation_mult: 1.0,
rounding_mult: 1.0,
}
}
}

View File

@@ -1,13 +1,13 @@
use crate::{
assets::AssetPath,
components::{Component, button, tooltip},
components::{button, tooltip, Component},
drawing::Color,
i18n::Translation,
layout::WidgetID,
parser::{
AttribPair, ParserContext, ParserFile, parse_check_f32, parse_check_i32, parse_children, print_invalid_attrib,
process_component,
parse_check_f32, parse_check_i32, parse_children, parse_f32, print_invalid_attrib, process_component,
style::{parse_color_opt, parse_round, parse_style, parse_text_style},
AttribPair, ParserContext, ParserFile,
},
widget::util::WLength,
};
@@ -28,6 +28,7 @@ pub fn parse_component_button<'a>(
let mut tooltip: Option<String> = None;
let mut tooltip_side: Option<tooltip::TooltipSide> = None;
let mut sticky: bool = false;
let mut long_press_time = 0.0;
let mut sprite_src: Option<AssetPath> = None;
let mut translation: Option<Translation> = None;
@@ -45,7 +46,7 @@ pub fn parse_component_button<'a>(
translation = Some(Translation::from_translation_key(value));
}
"round" => {
parse_round(value, &mut round);
parse_round(value, &mut round, ctx.doc_params.globals.get().defaults.rounding_mult);
}
"color" => {
parse_color_opt(value, &mut color);
@@ -92,6 +93,9 @@ pub fn parse_component_button<'a>(
let mut sticky_i32 = 0;
sticky = parse_check_i32(value, &mut sticky_i32) && sticky_i32 == 1;
}
"long_press_time" => {
long_press_time = parse_f32(value).unwrap_or(long_press_time);
}
_ => {}
}
}
@@ -113,6 +117,7 @@ pub fn parse_component_button<'a>(
text: Translation::from_translation_key(&t),
}),
sticky,
long_press_time,
sprite_src,
},
)?;

View File

@@ -8,7 +8,7 @@ mod widget_rectangle;
mod widget_sprite;
use crate::{
assets::{AssetPath, AssetPathOwned, normalize_path},
assets::{normalize_path, AssetPath, AssetPathOwned},
components::{Component, ComponentWeak},
drawing::{self},
globals::WguiGlobals,
@@ -400,6 +400,7 @@ impl ParserContext<'_> {
insert_color_vars!(self, "accent", def.accent_color, def.translucent_alpha);
insert_color_vars!(self, "danger", def.danger_color, def.translucent_alpha);
insert_color_vars!(self, "faded", def.faded_color, def.translucent_alpha);
insert_color_vars!(self, "bg", def.bg_color, def.translucent_alpha);
}
}

View File

@@ -6,14 +6,14 @@ use taffy::{
use crate::{
drawing,
parser::{
AttribPair, is_percent, parse_color_hex, parse_f32, parse_percent, parse_size_unit, parse_val,
print_invalid_attrib, print_invalid_value,
is_percent, parse_color_hex, parse_f32, parse_percent, parse_size_unit, parse_val, print_invalid_attrib,
print_invalid_value, AttribPair,
},
renderer_vk::text::{FontWeight, HorizontalAlign, TextStyle},
widget::util::WLength,
};
pub fn parse_round(value: &str, round: &mut WLength) {
pub fn parse_round(value: &str, round: &mut WLength, multiplier: f32) {
if is_percent(value) {
if let Some(val) = parse_percent(value) {
*round = WLength::Percent(val);
@@ -21,7 +21,7 @@ pub fn parse_round(value: &str, round: &mut WLength) {
print_invalid_value(value);
}
} else if let Some(val) = parse_f32(value) {
*round = WLength::Units(val);
*round = WLength::Units((val * multiplier).max(0.));
} else {
print_invalid_value(value);
}

View File

@@ -2,8 +2,9 @@ use crate::{
drawing::GradientMode,
layout::WidgetID,
parser::{
AttribPair, ParserContext, ParserFile, parse_children, parse_widget_universal, print_invalid_attrib,
parse_children, parse_widget_universal, print_invalid_attrib,
style::{parse_color, parse_round, parse_style},
AttribPair, ParserContext, ParserFile,
},
widget::rectangle::{WidgetRectangle, WidgetRectangleParams},
};
@@ -40,7 +41,11 @@ pub fn parse_widget_rectangle<'a>(
}
}
"round" => {
parse_round(value, &mut params.round);
parse_round(
value,
&mut params.round,
ctx.doc_params.globals.get().defaults.rounding_mult,
);
}
"border" => {
params.border = value.parse().unwrap_or_else(|_| {

View File

@@ -205,7 +205,11 @@ impl EventResult {
#[must_use]
pub fn merge(self, other: Self) -> Self {
if self > other { self } else { other }
if self > other {
self
} else {
other
}
}
}

View File

@@ -47,12 +47,14 @@ use crate::frame::Transform;
use crate::frame::WlxFrame;
use crate::frame::{DmabufFrame, FramePlane, MemFdFrame, MemPtrFrame};
#[derive(Debug, Clone)]
pub struct PipewireStream {
pub node_id: u32,
pub position: Option<(i32, i32)>,
pub size: Option<(i32, i32)>,
}
#[derive(Debug, Clone)]
pub struct PipewireSelectScreenResult {
pub streams: Vec<PipewireStream>,
pub restore_token: Option<String>,
@@ -279,6 +281,7 @@ where
U: Any,
R: Any,
{
log::debug!("{}: pipewire main_loop start", &name);
let main_loop = MainLoop::new(None)?;
let context = Context::new(&main_loop)?;
let core = context.connect(None)?;

View File

@@ -92,9 +92,18 @@ pub struct GeneralConfig {
#[serde(default = "def_theme_path")]
pub theme_path: Arc<str>,
pub color_text: Option<String>,
pub color_accent: Option<String>,
pub color_danger: Option<String>,
pub color_faded: Option<String>,
pub color_background: Option<String>,
#[serde(default = "def_one")]
pub animation_speed: f32,
#[serde(default = "def_one")]
pub round_multiplier: f32,
pub default_keymap: Option<String>,
#[serde(default)]
@@ -112,6 +121,9 @@ pub struct GeneralConfig {
#[serde(default = "def_one")]
pub scroll_speed: f32,
#[serde(default = "def_one")]
pub long_press_duration: f32,
#[serde(default = "def_mouse_move_interval_ms")]
pub mouse_move_interval_ms: u32,
@@ -205,6 +217,9 @@ pub struct GeneralConfig {
#[serde(default = "def_point3")]
pub pointer_lerp_factor: f32,
#[serde(default = "def_true")]
pub space_drag_unlocked: bool,
#[serde(default = "def_false")]
pub space_rotate_unlocked: bool,

View File

@@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, IntegerId, Serialize, Deserialize)]
pub enum ToastTopic {
System,
Error,
DesktopNotification,
XSNotification,
IpdChange,

View File

@@ -5551,19 +5551,6 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "wayvr_ipc"
version = "0.1.0"
source = "git+https://github.com/olekolek1000/wayvr-ipc.git?rev=a72587d23f3bb8624d9aeb1f13c0a21e65350f51#a72587d23f3bb8624d9aeb1f13c0a21e65350f51"
dependencies = [
"anyhow",
"bytes",
"log",
"serde",
"serde_json",
"smallvec",
]
[[package]]
name = "web-sys"
version = "0.3.77"
@@ -6206,7 +6193,7 @@ dependencies = [
"uuid",
"wayland-client",
"wayland-egl",
"wayvr_ipc",
"wayvr-ipc",
"wgui",
"winit",
"wlx-capture",

View File

@@ -97,6 +97,7 @@ uuid = { version = "1.19.0", features = ["v4", "fast-rng"], optional = true }
wayland-client = { workspace = true, optional = true }
wayland-egl = { version = "0.32.8", optional = true }
bytes = { version = "1.11.0", optional = true }
wayvr-ipc = { path = "../wayvr-ipc", default-features = false, optional = true }
rust-embed = { workspace = true }
signal-hook = "0.3.18"
################################
@@ -121,5 +122,6 @@ wayvr = [
"dep:wayland-client",
"dep:wayland-egl",
"dep:bytes",
"dep:wayvr-ipc",
]
as-raw-xcb-connection = []

View File

@@ -15,7 +15,7 @@
</template>
<template name="TopButtonDanger">
<Button macro="button_style" tooltip="${tooltip}" _press="${press}" _release="${release}" border_color="~color_danger_translucent" color="~color_danger_5" color2="~color_danger_1">
<Button macro="button_style" tooltip="${tooltip}" _long_release="${long_release}" border_color="~color_danger_translucent" color="~color_danger_5" color2="~color_danger_1">
<sprite width="48" height="48" src="${src}" />
</Button>
</template>
@@ -39,7 +39,7 @@
<rectangle padding="16" gap="8" round="32" color="~color_bg" border="2" border_color="~color_accent" justify_content="center">
<div flex_direction="column" gap="8">
<div flex_direction="row" gap="4">
<TopButton sticky="1" id="top_lock" src="edit/lock_open.svg" tooltip="EDIT_MODE.LOCK_INTERACTION" press="::EditModeToggleLock" />
<TopButton sticky="0" id="top_lock" src="edit/lock_open.svg" tooltip="EDIT_MODE.LOCK_INTERACTION" press="::EditModeToggleLock" />
<TopButton sticky="1" id="top_grab" src="edit/disable-grab.svg" tooltip="EDIT_MODE.DISABLE_GRAB" press="::EditModeToggleGrab" />
<TopButton sticky="0" id="top_pos" src="edit/anchor.svg" tooltip="EDIT_MODE.POSITIONING" press="::EditModeTab pos" />
<TopButton sticky="0" id="top_alpha" src="edit/fade.svg" tooltip="EDIT_MODE.OPACITY" press="::EditModeTab alpha" />
@@ -48,7 +48,7 @@
<TopButton sticky="0" id="top_mouse" src="edit/normal.svg" tooltip="EDIT_MODE.MOUSE.TITLE" press="::EditModeTab mouse" />
<!-- TopButton sticky="0" id="top_move" src="edit/move-all.svg" tooltip="EDIT_MODE.MOVE_PRESS_AND_DRAG" / -->
<!-- TopButton sticky="0" id="top_resize" src="edit/resize.svg" tooltip="EDIT_MODE.RESIZE_PRESS_AND_DRAG" / -->
<TopButtonDanger src="edit/delete.svg" tooltip="EDIT_MODE.DELETE" press="::EditModeDeletePress" release="::EditModeDeleteRelease" />
<TopButtonDanger src="edit/delete.svg" tooltip="EDIT_MODE.DELETE" long_release="::EditModeDelete" />
<div width="8" height="100%" />
<TopButtonFaded src="watch/edit.svg" tooltip="EDIT_MODE.LEAVE" press="::EditToggle" />
</div>

View File

@@ -15,7 +15,7 @@
<template name="KeySpecial">
<div macro="keycap_div">
<rectangle id="${id}" macro="keycap_rect">
<sprite width="32" height="32" src="keyboard/${text}.svg" />
<sprite color="~color_text" width="32" height="32" src="keyboard/${text}.svg" />
</rectangle>
</div>
</template>

View File

@@ -34,8 +34,8 @@ All overlays are listed on bottom row.
</template>
<template name="Overlay">
<Button macro="button_style" id="overlay_${idx}" sticky="1"
tooltip="WATCH.TOGGLE_FOR_CURRENT_SET" _press="::SingleSetOverlayToggle ${idx}"
<Button macro="button_style" id="overlay_${idx}"
tooltip="WATCH.TOGGLE_FOR_CURRENT_SET" _press="::SingleSetOverlayToggle ${idx}" _long_release="::SingleSetOverlayReset ${idx}"
align_items="center"
height="40">
<sprite id="overlay_${idx}_sprite" src_builtin="${src}" width="32" height="32" />
@@ -111,13 +111,13 @@ All overlays are listed on bottom row.
<!-- Bottom buttons -->
<div id="toolbox-condensed" gap="6" width="100%" max_width="400" flex_direction="row" flex_wrap="wrap">
<Button id="btn_dashboard" height="40" macro="button_style" _press="::DashToggle" tooltip="WATCH.DASHBOARD" tooltip_side="top" sticky="1" >
<Button id="btn_dashboard" height="40" macro="button_style" _press="::DashToggle" tooltip="WATCH.DASHBOARD" tooltip_side="top" >
<sprite color="~set_color" width="32" height="32" src="watch/wayvr_dashboard_mono.svg" />
</Button>
<Button id="btn_edit_mode" height="40" macro="button_style" _press="::EditToggle" tooltip="WATCH.EDIT_MODE" tooltip_side="top">
<sprite color="~set_color" width="32" height="32" src="watch/edit.svg" />
</Button>
<Button id="btn_keyboard" height="40" macro="button_style" tooltip="WATCH.TOGGLE_FOR_CURRENT_SET" _press="::OverlayToggle kbd" sticky="1" >
<Button id="btn_keyboard" height="40" macro="button_style" tooltip="WATCH.TOGGLE_FOR_CURRENT_SET" _press="::OverlayToggle kbd" >
<sprite src_builtin="watch/keyboard.svg" width="32" height="32" />
</Button>
<!-- Src here may be changed, but maintain `OverlayCategory` order: Panel, Screen, Mirror, WayVR -->

View File

@@ -30,7 +30,7 @@
</template>
<template name="Overlay">
<Button macro="button_style" id="overlay_${idx}" sticky="1"
<Button macro="button_style" id="overlay_${idx}"
tooltip="WATCH.TOGGLE_FOR_CURRENT_SET" _press="::EditModeOverlayToggle ${idx}"
align_items="center"
height="40">
@@ -120,7 +120,7 @@
</div>
<div flex_direction="column" align_items="center" justify_content="center">
<div id="toolbox" gap="8" width="100%" max_width="400" flex_direction="row" flex_wrap="wrap">
<Button id="btn_keyboard" height="40" macro="button_style" tooltip="WATCH.TOGGLE_FOR_CURRENT_SET" _press="::OverlayToggle kbd" sticky="1" >
<Button id="btn_keyboard" height="40" macro="button_style" tooltip="WATCH.TOGGLE_FOR_CURRENT_SET" _press="::OverlayToggle kbd" >
<sprite src_builtin="watch/keyboard.svg" width="32" height="32" />
<label translation="EDIT_MODE.KEYBOARD" size="18" />
</Button>
@@ -136,11 +136,11 @@
<!-- Bottom buttons -->
<div flex_direction="row" gap="4">
<Button id="btn_dashboard" macro="button_style" _press="::DashToggle" tooltip="WATCH.DASHBOARD" tooltip_side="top" sticky="1" >
<Button id="btn_dashboard" macro="button_style" _press="::DashToggle" tooltip="WATCH.DASHBOARD" tooltip_side="top" >
<sprite color="~set_color" width="40" height="40" src="watch/wayvr_dashboard_mono.svg" />
</Button>
<div id="edit_delete" display="none">
<Button macro="button_style" _press="::EditModeDeleteDown" _release="::EditModeDeleteUp" tooltip="WATCH.LONG_PRESS_TO_DELETE_SET" tooltip_side="top" border_color="~color_danger_translucent" color="~color_danger_5" color2="~color_danger_1">
<Button macro="button_style" _long_release="::EditModeDeleteSet" tooltip="WATCH.LONG_PRESS_TO_DELETE_SET" tooltip_side="top" border_color="~color_danger_translucent" color="~color_danger_5" color2="~color_danger_1">
<sprite color="~set_color" width="40" height="40" src="edit/delete.svg" />
</Button>
</div>

View File

@@ -45,7 +45,43 @@
"SPLIT_TOP_BOTTOM": "OBEN→UNTEN",
"SPLIT_BOTTOM_TOP": "UNTEN→OBEN"
},
"STEREO_3D_MODE_": {}
"STEREO_3D_MODE_": {},
"ALIGN_TO_HMD": "An HMD ausrichten",
"MOUSE": {
"TITLE": "Mauskorrekturen",
"WRONG_SCREEN_SELECTION_HELP": "Wenn der Cursor auf einem völlig anderen Bildschirm verschwindet,\nwurden die Bildschirme wahrscheinlich falsch ausgewählt. Siehe Lesezeichen.",
"NORMAL": "Normal",
"FLIPPED": "Umgedreht",
"FLIP90": "Um 90° gedreht",
"FLIP180": "Um 180° gedreht",
"FLIP270": "Um 270° gedreht",
"ROTATE90": "Um 90° gedreht",
"ROTATE180": "Um 180° gedreht",
"ROTATE270": "Um 270° gedreht"
}
},
"DISABLED": "Deaktiviert"
}
"DISABLED": "Deaktiviert",
"DEFAULT": "Standard",
"GRAB": {
"ADJUST_DISTANCE": "Entfernung anpassen",
"ADJUST_SIZE": "Größe anpassen",
"UNRESTRICTED_MOVEMENT": "Unbeschränkte Bewegung",
"GRABBING_WATCH": "Um die Hände zu vertauschen, bewegen Sie die Uhr vor sich und greifen Sie sie mit der anderen Hand.",
"GRABBING_STATIC": "Dieses Overlay ist statisch und bleibt an Ort und Stelle, ignoriert die Neu-Zentrierung.",
"GRABBING_ANCHORED": "Festgelegte Overlays bewegen sich alle zusammen. Trennen Sie ein einzelnes Fenster, indem Sie es mit der anderen Hand greifen, während Sie den Anker weiterhin greifen.",
"GRABBING_ANCHORED_EDIT": "Diese Overlay-Darstellung bleibt an der Mittelmarkierung verankert.",
"GRABBING_FLOATING": "Diese Overlay-Darstellung ist frei schwebend und bleibt an Ort und Stelle, es sei denn, sie wird neu zentriert.",
"GRABBING_FOLLOW": "Dieses Overlay wird dem Gerät folgen, an das es angehängt ist."
},
"TOAST": {
"DEFAULT_TITLE": "Benachrichtigung",
"ERROR": "Fehler",
"CANNOT_REMOVE_SET": "Satz kann nicht entfernt werden!",
"NO_SET_SELECTED": "Kein Set ausgewählt.",
"LAST_EXISTING_SET": "Dies ist das letzte vorhandene Set.",
"EMPTY_SET": "Leeres Set!",
"LETS_ADD_OVERLAYS": "Lass uns ein paar Overlays von der Uhr hinzufügen!",
"FIXING_FLOOR": "Boden wird in 5 Sekunden fixiert...",
"ONE_CONTROLLER_ON_FLOOR": "Lege einen Controller auf den Boden!"
}
}

View File

@@ -39,12 +39,12 @@
"WRONG_SCREEN_SELECTION_HELP": "If the cursor moves on a completely different screen,\nthe screens were likely selected wrong. See readme.",
"NORMAL": "Normal",
"ROTATE90": "Rotated 90°",
"ROTATE180": "Rotated 170°",
"ROTATE180": "Rotated 180°",
"ROTATE270": "Rotated 270°",
"FLIPPED": "Flipped",
"ROTATE90": "Flipped 90°",
"ROTATE180": "Flipped 170°",
"ROTATE270": "Flipped 270°"
"FLIP90": "Flipped 90°",
"FLIP180": "Flipped 180°",
"FLIP270": "Flipped 270°"
}
},
"GRAB": {
@@ -58,6 +58,17 @@
"GRABBING_FLOATING": "This overlay is Floating and will stay in place, unless recentered.",
"GRABBING_FOLLOW": "This overlay will follow the device it is attached to."
},
"TOAST": {
"DEFAULT_TITLE": "Notification",
"ERROR": "Error",
"CANNOT_REMOVE_SET": "Cannot remove set!",
"NO_SET_SELECTED": "No set is selected.",
"LAST_EXISTING_SET": "This is the last existing set.",
"EMPTY_SET": "Empty set!",
"LETS_ADD_OVERLAYS": "Let's add some overlays from the watch!",
"FIXING_FLOOR": "Fixing floor in 5 seconds...",
"ONE_CONTROLLER_ON_FLOOR": "Place one controller on the floor!"
},
"WATCH": {
"ADD_NEW_SET": "Add a new set",
"CLEANUP_MIRRORS": "Remove mirrors that are\nnot currently visible",

View File

@@ -45,7 +45,43 @@
"SPLIT_TOP_BOTTOM": "ARRIBA→ABAJO",
"SPLIT_BOTTOM_TOP": "ABAJO→ARRIBA"
},
"STEREO_3D_MODE_": {}
"STEREO_3D_MODE_": {},
"ALIGN_TO_HMD": "Alinear al HMD",
"MOUSE": {
"TITLE": "Correcciones del ratón",
"WRONG_SCREEN_SELECTION_HELP": "Si el cursor se mueve en una pantalla completamente diferente,\nes probable que las pantallas se hayan seleccionado incorrectamente. Consulta el archivo readme.",
"NORMAL": "Normal",
"FLIPPED": "Invertido",
"FLIP90": "Girado 90°",
"FLIP180": "Invertido 180°",
"FLIP270": "Invertido 270°",
"ROTATE90": "Rotado 90°",
"ROTATE180": "Rotado 180°",
"ROTATE270": "Rotado 270°"
}
},
"DISABLED": "Deshabilitado"
"DISABLED": "Deshabilitado",
"DEFAULT": "Por defecto",
"GRAB": {
"ADJUST_DISTANCE": "Ajustar distancia",
"ADJUST_SIZE": "Ajustar tamaño",
"UNRESTRICTED_MOVEMENT": "Movimiento sin restricciones",
"GRABBING_WATCH": "Para intercambiar las manos, mueve el reloj al frente y agrárralo con la otra mano.",
"GRABBING_STATIC": "Esta superposición es estática y permanecerá en su lugar, ignorando el recentrado.",
"GRABBING_ANCHORED": "Las superposiciones ancladas se mueven todas juntas. Separa una ventana individual agarrándola con la otra mano mientras aún sujetas el ancla.",
"GRABBING_ANCHORED_EDIT": "Esta superposición permanecerá anclada al marcador central.",
"GRABBING_FLOATING": "Esta superposición es flotante y permanecerá en su lugar a menos que se recentre.",
"GRABBING_FOLLOW": "Esta superposición seguirá el dispositivo al que está adjunta."
},
"TOAST": {
"DEFAULT_TITLE": "Notificación",
"ERROR": "Error",
"CANNOT_REMOVE_SET": "¡No se puede eliminar el conjunto!",
"NO_SET_SELECTED": "No se ha seleccionado ningún conjunto.",
"LAST_EXISTING_SET": "Este es el último conjunto existente.",
"EMPTY_SET": "¡Conjunto vacío!",
"LETS_ADD_OVERLAYS": "¡Añadamos algunos overlays desde el reloj!",
"FIXING_FLOOR": "Fijando el suelo en 5 segundos...",
"ONE_CONTROLLER_ON_FLOOR": "¡Coloca un mando en el suelo!"
}
}

View File

@@ -16,28 +16,27 @@
"LONG_PRESS_TO_DELETE_SET": "長押しでセットを削除"
},
"EDIT_MODE": {
"ADJUST_CURVATURE": "曲率調整する",
"ALPHA_BLEND_MODE": "アルファブレンドモード",
"BLENDING_ADDITIVE": "加算",
"ADJUST_CURVATURE": "曲率調整",
"ALPHA_BLEND_MODE": "アルファブレンド",
"BLENDING_ADDITIVE": "加算モード",
"CURVATURE": "曲率",
"DELETE": "削除",
"HINT_POINT_WINDOW": "ウィンドウをタップしてそのパラメータを変更ます。\n完了したら、右側のボタンで編集モードを終了してください。",
"LEAVE": "編集モードを終了",
"LOCK_INTERACTION": "インタラクションをロック",
"HINT_POINT_WINDOW": "ウィンドウをタップしてそのパラメータを変更できます。\n右側のボタンで編集モードを終了できます。",
"LEAVE": "編集モードを終了する",
"LOCK_INTERACTION": "インタラクションを無効化",
"DISABLE_GRAB": "グラブを無効化",
"MOVE_PRESS_AND_DRAG": "移動(押してドラッグ)",
"OPACITY": "不透明度",
"POSITIONING": "位置調整",
"POSITIONING": "位置付け",
"RESIZE_PRESS_AND_DRAG": "サイズ変更(押してドラッグ)",
"POS_STATIC": "固定:その場に留まり、再センタリングされません。",
"POS_ANCHORED": "アンカー:他のセット要素と一緒に移動します。デフォルト。",
"POS_FLOATING": "フローティング:その場に留ま、表示されるときに再センタリングされます。",
"POS_STATIC": "固定:置かれた場所に留まり、再センタリングされません。",
"POS_ANCHORED": "アンカー:セット内の他のウィンドウと共に移動します。デフォルト。",
"POS_FLOATING": "フローティング:置かれた場所に留まるが、表示されるに再センタリングされます。",
"POS_HMD": "HMD に追従する。",
"POS_HAND_L": "左手に追従する。",
"POS_HAND_R": "右手に追従する。",
"INTERPOLATION": "補間",
"INTERPOLATION": "スムージング",
"KEYBOARD": "キーボード",
"STEREO_3D_MODE_A": {},
"STEREO_3D_MODE": {
"TITLE": "ステレオ3Dモード",
"SPLIT_LEFT_RIGHT": "左→右",
@@ -45,7 +44,42 @@
"SPLIT_TOP_BOTTOM": "上→下",
"SPLIT_BOTTOM_TOP": "下→上"
},
"STEREO_3D_MODE_": {}
"ALIGN_TO_HMD": "常にHMDの方を向く",
"MOUSE": {
"TITLE": "マウスの修正",
"WRONG_SCREEN_SELECTION_HELP": "カーソルが全く別のスクリーンで動く場合、\nスクリーンが正しく選択されていない可能性があります。\nマニュアルをご確認ください。",
"NORMAL": "通常",
"FLIPPED": "ミラー",
"FLIP90": "ミラー 90°",
"FLIP180": "ミラー 180°",
"FLIP270": "ミラー 270°",
"ROTATE90": "回転 90°",
"ROTATE180": "回転 180°",
"ROTATE270": "回転 270°"
}
},
"DISABLED": "無効"
"DISABLED": "無効",
"DEFAULT": "デフォルト",
"GRAB": {
"ADJUST_DISTANCE": "距離を調整",
"ADJUST_SIZE": "サイズ調整",
"UNRESTRICTED_MOVEMENT": "配置制限なし",
"GRABBING_WATCH": "手を入れ替えるにはウォッチを前に移動させて、もう一方の手で掴んでください。",
"GRABBING_STATIC": "このオーバーレイは固定であり、リセンターを無視してその場に留まります。",
"GRABBING_ANCHORED": "アンカーされたオーバーレイはすべて一緒に移動します。もう一方の手で掴んでアンカーを掴んだまま、単一のウィンドウを分離できます。",
"GRABBING_ANCHORED_EDIT": "このオーバーレイは、センター・マーカーに固定されます。",
"GRABBING_FLOATING": "このオーバーレイはフローティングのため、リセンターまではその場所に留まります。",
"GRABBING_FOLLOW": "このオーバーレイは、接続されているデバイスに追従します。"
},
"TOAST": {
"DEFAULT_TITLE": "通知",
"ERROR": "エラー",
"CANNOT_REMOVE_SET": "セットを削除できません!",
"NO_SET_SELECTED": "セットが選択されていません。",
"LAST_EXISTING_SET": "これが最後の設定です。",
"EMPTY_SET": "空のセットです!",
"LETS_ADD_OVERLAYS": "ウォッチからオーバーレイを追加しましょう!",
"FIXING_FLOOR": "5秒後にフロアを固定します...",
"ONE_CONTROLLER_ON_FLOOR": "コントローラーを床に置いてください!"
}
}

View File

@@ -43,7 +43,43 @@
"SPLIT_RIGHT_LEFT": "PRAWO→LEWO",
"SPLIT_TOP_BOTTOM": "GÓRA→DÓŁ",
"TITLE": "Tryb stereoskopowy 3D"
},
"ALIGN_TO_HMD": "Dopasuj do HMD",
"MOUSE": {
"TITLE": "Naprawa myszy",
"WRONG_SCREEN_SELECTION_HELP": "Jeśli kursor porusza się na zupełnie innym ekranie,\nekrany zostały prawdopodobnie nieprawidłowo wybrane. Zobacz dokument README.",
"NORMAL": "Normalny",
"ROTATE90": "Obrócony o 90°",
"ROTATE180": "Obrócony o 180°",
"ROTATE270": "Obrócony o 270°",
"FLIPPED": "Odwrócony",
"FLIP90": "Odwrócony o 90°",
"FLIP180": "Odwrócony o 180°",
"FLIP270": "Odwrócony o 270°"
}
},
"DISABLED": "Wyłączone"
"DISABLED": "Wyłączone",
"DEFAULT": "Domyślny",
"GRAB": {
"ADJUST_DISTANCE": "Dostosuj odległość",
"ADJUST_SIZE": "Dostosuj rozmiar",
"UNRESTRICTED_MOVEMENT": "Nieograniczony ruch",
"GRABBING_WATCH": "Aby zamienić ręce, przenieś zegarek na przód i chwyć go drugą ręką.",
"GRABBING_STATIC": "Ta nakładka jest statyczna i pozostanie na swoim miejscu, ignorując centrowanie.",
"GRABBING_ANCHORED": "Przymocowane nakładki poruszają się razem. Aby oddzielić pojedyncze okno, chwyć je drugą ręką, podczas gdy nadal trzymasz kotwicę.",
"GRABBING_ANCHORED_EDIT": "Ta nakładka pozostanie zakotwiczona do znacznika centralnego.",
"GRABBING_FLOATING": "Ta nakładka jest zawieszony i pozostanie na miejscu, chyba że zostanie wycentrowany.",
"GRABBING_FOLLOW": "Ta nakładka będzie śledzić urządzenie, do którego jest dołączona."
},
"TOAST": {
"DEFAULT_TITLE": "Powiadomienie",
"ERROR": "Błąd",
"CANNOT_REMOVE_SET": "Nie można usunąć zestawu!",
"NO_SET_SELECTED": "Nie wybrano żadnego zestawu.",
"LAST_EXISTING_SET": "To jest ostatni istniejący zestaw.",
"EMPTY_SET": "Pusty zestaw!",
"LETS_ADD_OVERLAYS": "Dodajmy kilka nakładek z zegarka!",
"FIXING_FLOOR": "Naprawianie podłogi za 5 sekund...",
"ONE_CONTROLLER_ON_FLOOR": "Umieść jeden kontroler na podłodze!"
}
}

View File

@@ -1,4 +1,4 @@
use glam::{Affine3A, Quat, Vec3, Vec3A};
use glam::{Affine3A, Quat, Vec3, Vec3A, vec3a};
use ovr_overlay::{
chaperone_setup::ChaperoneSetupManager,
compositor::CompositorManager,
@@ -58,12 +58,12 @@ impl PlayspaceMover {
&mut self,
chaperone_mgr: &mut ChaperoneSetupManager,
overlays: &mut OverlayWindowManager<OpenVrOverlayData>,
state: &AppState,
app: &AppState,
) {
let universe = self.universe.clone();
if let Some(data) = self.rotate.as_mut() {
let pointer = &state.input_state.pointers[data.hand];
let pointer = &app.input_state.pointers[data.hand];
if !pointer.now.space_rotate {
self.rotate = None;
log::info!("End space rotate");
@@ -71,7 +71,7 @@ impl PlayspaceMover {
}
let new_hand =
Quat::from_affine3(&(data.pose * state.input_state.pointers[data.hand].raw_pose));
Quat::from_affine3(&(data.pose * app.input_state.pointers[data.hand].raw_pose));
let dq = new_hand * data.hand_pose.conjugate();
let rel_y = f32::atan2(
@@ -80,8 +80,8 @@ impl PlayspaceMover {
);
let mut space_transform = Affine3A::from_rotation_y(rel_y);
let offset = (space_transform.transform_vector3a(state.input_state.hmd.translation)
- state.input_state.hmd.translation)
let offset = (space_transform.transform_vector3a(app.input_state.hmd.translation)
- app.input_state.hmd.translation)
* -1.0;
let mut overlay_transform = Affine3A::from_rotation_y(-rel_y);
@@ -97,7 +97,7 @@ impl PlayspaceMover {
set_working_copy(&universe, chaperone_mgr, &data.pose);
chaperone_mgr.commit_working_copy(EChaperoneConfigFile::EChaperoneConfigFile_Live);
} else {
for (i, pointer) in state.input_state.pointers.iter().enumerate() {
for (i, pointer) in app.input_state.pointers.iter().enumerate() {
if pointer.now.space_rotate {
let Some(mat) = get_working_copy(&universe, chaperone_mgr) else {
log::warn!("Can't space rotate - failed to get zero pose");
@@ -117,7 +117,7 @@ impl PlayspaceMover {
}
if let Some(data) = self.drag.as_mut() {
let pointer = &state.input_state.pointers[data.hand];
let pointer = &app.input_state.pointers[data.hand];
if !pointer.now.space_drag {
self.drag = None;
log::info!("End space drag");
@@ -126,9 +126,13 @@ impl PlayspaceMover {
let new_hand = data
.pose
.transform_point3a(state.input_state.pointers[data.hand].raw_pose.translation);
let relative_pos =
(new_hand - data.hand_pose) * state.session.config.space_drag_multiplier;
.transform_point3a(app.input_state.pointers[data.hand].raw_pose.translation);
let relative_pos = if app.session.config.space_drag_unlocked {
new_hand - data.hand_pose
} else {
vec3a(0., new_hand.y - data.hand_pose.y, 0.)
} * app.session.config.space_drag_multiplier;
if relative_pos.length_squared() > 1000.0 {
log::warn!("Space drag too fast, ignoring");
@@ -156,7 +160,7 @@ impl PlayspaceMover {
set_working_copy(&universe, chaperone_mgr, &data.pose);
chaperone_mgr.commit_working_copy(EChaperoneConfigFile::EChaperoneConfigFile_Live);
} else {
for (i, pointer) in state.input_state.pointers.iter().enumerate() {
for (i, pointer) in app.input_state.pointers.iter().enumerate() {
if pointer.now.space_drag {
let Some(mat) = get_working_copy(&universe, chaperone_mgr) else {
log::warn!("Can't space drag - failed to get zero pose");

View File

@@ -11,7 +11,6 @@ use std::{
};
use wgui::gfx::{
WGfx,
cmd::WGfxClearMode,
pass::WGfxPass,
pipeline::{WGfxPipeline, WPipelineCreateInfo},
@@ -49,8 +48,6 @@ static COLORS: [[f32; 6]; 5] = {
pub(super) struct LinePool {
lines: IdMap<usize, LineContainer>,
pipeline: Arc<WGfxPipeline<Vert2Uv>>,
pass: WGfxPass<Vert2Uv>,
buf_color: Subbuffer<[f32]>,
}
impl LinePool {
@@ -61,13 +58,22 @@ impl LinePool {
WPipelineCreateInfo::new(app.gfx.surface_format),
)?;
Ok(Self {
lines: IdMap::new(),
pipeline,
})
}
pub(super) fn allocate(&mut self, xr: &XrState, app: &AppState) -> anyhow::Result<usize> {
let id = LINE_AUTO_INCREMENT.fetch_add(1, Ordering::Relaxed);
let buf_color = app
.gfx
.empty_buffer(BufferUsage::TRANSFER_DST | BufferUsage::UNIFORM_BUFFER, 6)?;
let set0 = pipeline.buffer(0, buf_color.clone())?;
let set0 = self.pipeline.buffer(0, buf_color.clone())?;
let pass = pipeline.create_pass(
let pass = self.pipeline.create_pass(
[1.0, 1.0],
app.gfx_extras.quad_verts.clone(),
0..4,
@@ -76,22 +82,13 @@ impl LinePool {
&Default::default(),
)?;
Ok(Self {
lines: IdMap::new(),
pipeline,
pass,
buf_color,
})
}
pub(super) fn allocate(&mut self, xr: &XrState, gfx: Arc<WGfx>) -> anyhow::Result<usize> {
let id = LINE_AUTO_INCREMENT.fetch_add(1, Ordering::Relaxed);
let srd = create_swapchain(xr, gfx, [1, 1, 1], SwapchainOpts::new())?;
let srd = create_swapchain(xr, app.gfx.clone(), [1, 1, 1], SwapchainOpts::new())?;
self.lines.insert(
id,
LineContainer {
swapchain: srd,
buf_color,
pass,
maybe_line: None,
},
);
@@ -165,13 +162,13 @@ impl LinePool {
.next()
.unwrap();
self.buf_color.write()?[0..6].copy_from_slice(&COLORS[inner.color]);
line.buf_color.write()?[0..6].copy_from_slice(&COLORS[inner.color]);
let mut cmd_buffer = app
.gfx
.create_gfx_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
cmd_buffer.begin_rendering(tgt, WGfxClearMode::DontCare)?;
cmd_buffer.run_ref(&self.pass)?;
cmd_buffer.run_ref(&line.pass)?;
cmd_buffer.end_rendering()?;
futures.execute(cmd_buffer.queue.clone(), cmd_buffer.build()?)?;
@@ -217,5 +214,7 @@ pub(super) struct Line {
struct LineContainer {
swapchain: WlxSwapchain,
buf_color: Subbuffer<[f32]>,
pass: WGfxPass<Vert2Uv>,
maybe_line: Option<Line>,
}

View File

@@ -149,8 +149,8 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
};
let pointer_lines = [
lines.allocate(&xr_state, app.gfx.clone())?,
lines.allocate(&xr_state, app.gfx.clone())?,
lines.allocate(&xr_state, &mut app)?,
lines.allocate(&xr_state, &mut app)?,
];
let watch_id = overlays.lookup(WATCH_NAME).unwrap(); // want panic
@@ -238,6 +238,7 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
if !session_running {
std::thread::sleep(Duration::from_millis(100));
log::trace!("session not running: continue");
continue 'main_loop;
}
@@ -387,10 +388,12 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
for o in overlays.values_mut() {
o.data.cur_visible = false;
let Some(alpha) = o.config.active_state.as_ref().map(|x| x.alpha) else {
log::trace!("{}: hidden, skip render", o.config.name);
continue;
};
if !o.data.init {
log::trace!("{}: init", o.config.name);
o.init(&mut app)?;
o.data.init = true;
}
@@ -400,8 +403,10 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
ShouldRender::Can => (o.data.last_alpha - alpha).abs() > f32::EPSILON,
ShouldRender::Unable => false, //try show old image if exists
};
log::trace!("{}: should_render returned: {should_render}", o.config.name);
if should_render {
log::trace!("{}: render new frame", o.config.name);
let meta = o.config.backend.frame_meta().unwrap(); // want panic
let wsi = o.ensure_swapchain_acquire(&app, &xr_state, meta.extent)?;
let tgt = RenderTarget { views: wsi.views };
@@ -410,7 +415,10 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
o.data.last_alpha = alpha;
futures.execute_results(rdr.end()?)?;
} else if o.data.swapchain.is_none() {
log::trace!("{}: not showing due to missing swapchain", o.config.name);
continue;
} else {
log::trace!("{}: showing stale frame", o.config.name);
}
o.data.cur_visible = true;
}

View File

@@ -1,4 +1,4 @@
use glam::{Affine3A, Quat, Vec3A};
use glam::{Affine3A, Quat, Vec3A, vec3a};
use libmonado::{Monado, Pose, ReferenceSpaceType};
use crate::{
@@ -60,10 +60,10 @@ impl PlayspaceMover {
pub fn update(
&mut self,
overlays: &mut OverlayWindowManager<OpenXrOverlayData>,
state: &AppState,
app: &AppState,
monado: &mut Monado,
) {
for pointer in &state.input_state.pointers {
for pointer in &app.input_state.pointers {
if pointer.now.space_reset {
if !pointer.before.space_reset {
log::info!("Space reset");
@@ -74,7 +74,7 @@ impl PlayspaceMover {
}
if let Some(mut data) = self.rotate.take() {
let pointer = &state.input_state.pointers[data.hand];
let pointer = &app.input_state.pointers[data.hand];
if !pointer.now.space_rotate {
self.last_transform = data.pose;
log::info!("End space rotate");
@@ -82,10 +82,10 @@ impl PlayspaceMover {
}
let new_hand =
Quat::from_affine3(&(data.pose * state.input_state.pointers[data.hand].raw_pose));
Quat::from_affine3(&(data.pose * app.input_state.pointers[data.hand].raw_pose));
let dq = new_hand * data.hand_pose.conjugate();
let mut space_transform = if state.session.config.space_rotate_unlocked {
let mut space_transform = if app.session.config.space_rotate_unlocked {
Affine3A::from_quat(dq)
} else {
let rel_y = f32::atan2(
@@ -95,8 +95,8 @@ impl PlayspaceMover {
Affine3A::from_rotation_y(rel_y)
};
let offset = (space_transform.transform_vector3a(state.input_state.hmd.translation)
- state.input_state.hmd.translation)
let offset = (space_transform.transform_vector3a(app.input_state.hmd.translation)
- app.input_state.hmd.translation)
* -1.0;
space_transform.translation = offset;
@@ -107,7 +107,7 @@ impl PlayspaceMover {
apply_offset(data.pose, monado);
self.rotate = Some(data);
} else {
for (i, pointer) in state.input_state.pointers.iter().enumerate() {
for (i, pointer) in app.input_state.pointers.iter().enumerate() {
if pointer.now.space_rotate {
let hand_pose = Quat::from_affine3(&(self.last_transform * pointer.raw_pose));
self.rotate = Some(MoverData {
@@ -123,7 +123,7 @@ impl PlayspaceMover {
}
if let Some(mut data) = self.drag.take() {
let pointer = &state.input_state.pointers[data.hand];
let pointer = &app.input_state.pointers[data.hand];
if !pointer.now.space_drag {
self.last_transform = data.pose;
log::info!("End space drag");
@@ -132,9 +132,13 @@ impl PlayspaceMover {
let new_hand = data
.pose
.transform_point3a(state.input_state.pointers[data.hand].raw_pose.translation);
let relative_pos =
(new_hand - data.hand_pose) * state.session.config.space_drag_multiplier;
.transform_point3a(app.input_state.pointers[data.hand].raw_pose.translation);
let relative_pos = if app.session.config.space_drag_unlocked {
new_hand - data.hand_pose
} else {
vec3a(0., new_hand.y - data.hand_pose.y, 0.)
} * app.session.config.space_drag_multiplier;
if relative_pos.length_squared() > 1000.0 {
log::warn!("Space drag too fast, ignoring");
@@ -159,7 +163,7 @@ impl PlayspaceMover {
apply_offset(data.pose, monado);
self.drag = Some(data);
} else {
for (i, pointer) in state.input_state.pointers.iter().enumerate() {
for (i, pointer) in app.input_state.pointers.iter().enumerate() {
if pointer.now.space_drag {
let hand_pos = self
.last_transform
@@ -234,7 +238,7 @@ impl PlayspaceMover {
let y1 = input.pointers[0].raw_pose.translation.y;
let y2 = input.pointers[1].raw_pose.translation.y;
let delta = y1.min(y2) - 0.03;
let delta = y1.min(y2) - 0.05;
pose.position.y += delta;

View File

@@ -75,6 +75,7 @@ pub fn dds_to_vk(dds_fmt: ImageFormat) -> anyhow::Result<Format> {
ImageFormat::Rgba8UnormSrgb => Ok(Format::R8G8B8A8_SRGB),
ImageFormat::Rgba16Float => Ok(Format::R16G16B16A16_SFLOAT),
ImageFormat::Rgba32Float => Ok(Format::R32G32B32A32_SFLOAT),
ImageFormat::Bgr8Unorm => Ok(Format::B8G8R8_UNORM),
ImageFormat::Bgra8Unorm => Ok(Format::B8G8R8A8_UNORM),
ImageFormat::Bgra8UnormSrgb => Ok(Format::B8G8R8A8_SRGB),
// DXT1

View File

@@ -81,6 +81,17 @@ Supported events:
<button _press="..." _release="..." />
```
Laser-color-specific variants are also available
- `_press_left` & `_release_left` for blue laser
- `_press_right` & `_release_right` for orange laser
- `_press_middle` & `_release_middle` for purple laser
Release after short/long press (length controlled by config `long_press_duration`)
- `_short_release` & `_long_release` for any laser
- `_short_release_left` & `_long_release_left` for blue laser
- `_short_release_right` & `_long_release_right` for orange laser
- `_short_release_middle` & `_long_release_middle` for purple laser
#### Supported button actions
##### `::ShellExec <command> [args ..]`

View File

@@ -10,7 +10,9 @@ use std::{
use anyhow::Context;
use wgui::{
components::button::ComponentButton,
event::{self, EventCallback, EventListenerKind},
event::{
self, CallbackData, CallbackMetadata, EventCallback, EventListenerKind, MouseButtonIndex,
},
i18n::Translation,
layout::Layout,
parser::CustomAttribsInfoOwned,
@@ -31,18 +33,159 @@ use crate::{
#[cfg(feature = "wayvr")]
use crate::backend::wayvr::WayVRAction;
pub const BUTTON_EVENTS: [(&str, EventListenerKind); 2] = [
("_press", EventListenerKind::MousePress),
("_release", EventListenerKind::MouseRelease),
pub const BUTTON_EVENTS: [(
&str,
EventListenerKind,
fn(&mut CallbackData) -> bool,
fn(&ComponentButton, &AppState) -> bool,
); 16] = [
(
"_press",
EventListenerKind::MousePress,
button_any,
short_duration,
),
(
"_release",
EventListenerKind::MouseRelease,
button_any,
ignore_duration,
),
(
"_press_left",
EventListenerKind::MousePress,
button_left,
ignore_duration,
),
(
"_release_left",
EventListenerKind::MouseRelease,
button_left,
ignore_duration,
),
(
"_press_right",
EventListenerKind::MousePress,
button_right,
ignore_duration,
),
(
"_release_right",
EventListenerKind::MouseRelease,
button_right,
ignore_duration,
),
(
"_press_middle",
EventListenerKind::MousePress,
button_middle,
ignore_duration,
),
(
"_release_middle",
EventListenerKind::MouseRelease,
button_middle,
ignore_duration,
),
(
"_short_release",
EventListenerKind::MouseRelease,
button_any,
short_duration,
),
(
"_short_release_left",
EventListenerKind::MouseRelease,
button_left,
short_duration,
),
(
"_short_release_right",
EventListenerKind::MouseRelease,
button_right,
short_duration,
),
(
"_short_release_middle",
EventListenerKind::MouseRelease,
button_middle,
short_duration,
),
(
"_long_release",
EventListenerKind::MouseRelease,
button_any,
long_duration,
),
(
"_long_release_left",
EventListenerKind::MouseRelease,
button_left,
long_duration,
),
(
"_long_release_right",
EventListenerKind::MouseRelease,
button_right,
long_duration,
),
(
"_long_release_middle",
EventListenerKind::MouseRelease,
button_middle,
long_duration,
),
];
fn button_any(_: &mut CallbackData) -> bool {
true
}
fn button_left(data: &mut CallbackData) -> bool {
if let CallbackMetadata::MouseButton(b) = data.metadata
&& let MouseButtonIndex::Left = b.index
{
true
} else {
false
}
}
fn button_right(data: &mut CallbackData) -> bool {
if let CallbackMetadata::MouseButton(b) = data.metadata
&& let MouseButtonIndex::Right = b.index
{
true
} else {
false
}
}
fn button_middle(data: &mut CallbackData) -> bool {
if let CallbackMetadata::MouseButton(b) = data.metadata
&& let MouseButtonIndex::Middle = b.index
{
true
} else {
false
}
}
fn ignore_duration(_btn: &ComponentButton, _app: &AppState) -> bool {
true
}
fn long_duration(btn: &ComponentButton, app: &AppState) -> bool {
btn.get_time_since_last_pressed().as_secs_f32() > app.session.config.long_press_duration
}
fn short_duration(btn: &ComponentButton, app: &AppState) -> bool {
btn.get_time_since_last_pressed().as_secs_f32() < app.session.config.long_press_duration
}
pub(super) fn setup_custom_button<S: 'static>(
layout: &mut Layout,
attribs: &CustomAttribsInfoOwned,
_app: &AppState,
button: Rc<ComponentButton>,
) {
for (name, kind) in &BUTTON_EVENTS {
for (name, kind, test_button, test_duration) in &BUTTON_EVENTS {
let Some(action) = attribs.get_value(name) else {
continue;
};
@@ -52,9 +195,15 @@ pub(super) fn setup_custom_button<S: 'static>(
continue;
};
let button = button.clone();
let callback: EventCallback<AppState, S> = match command {
#[cfg(feature = "wayvr")]
"::DashToggle" => Box::new(move |_common, _data, app, _| {
"::DashToggle" => Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
app.tasks
.enqueue(TaskType::WayVR(WayVRAction::ToggleDashboard));
Ok(EventResult::Consumed)
@@ -65,7 +214,11 @@ pub(super) fn setup_custom_button<S: 'static>(
log::error!("{command} has invalid argument: \"{arg}\"");
return;
};
Box::new(move |_common, _data, app, _| {
Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
app.tasks
.enqueue(TaskType::Overlay(OverlayTask::ToggleSet(set_idx)));
Ok(EventResult::Consumed)
@@ -77,7 +230,11 @@ pub(super) fn setup_custom_button<S: 'static>(
return;
};
Box::new(move |_common, _data, app, _| {
Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
OverlaySelector::Name(arg.clone()),
Box::new(move |app, owc| {
@@ -91,54 +248,84 @@ pub(super) fn setup_custom_button<S: 'static>(
Ok(EventResult::Consumed)
})
}
"::EditToggle" => Box::new(move |_common, _data, app, _| {
"::EditToggle" => Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
app.tasks
.enqueue(TaskType::Overlay(OverlayTask::ToggleEditMode));
Ok(EventResult::Consumed)
}),
#[cfg(feature = "wayland")]
"::NewMirror" => Box::new(move |_common, _data, app, _| {
let name = crate::overlays::mirror::new_mirror_name();
"::NewMirror" => Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
let name = crate::overlays::screen::mirror::new_mirror_name();
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Create(
OverlaySelector::Name(name.clone()),
Box::new(move |app| {
Some(crate::overlays::mirror::new_mirror(name, &app.session))
Some(crate::overlays::screen::mirror::new_mirror(
name,
&app.session,
))
}),
)));
Ok(EventResult::Consumed)
}),
"::CleanupMirrors" => Box::new(move |_common, _data, app, _| {
"::CleanupMirrors" => Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
app.tasks
.enqueue(TaskType::Overlay(OverlayTask::CleanupMirrors));
Ok(EventResult::Consumed)
}),
"::PlayspaceReset" => Box::new(move |_common, _data, app, _| {
"::PlayspaceReset" => Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
app.tasks.enqueue(TaskType::Playspace(PlayspaceTask::Reset));
Ok(EventResult::Consumed)
}),
"::PlayspaceRecenter" => Box::new(move |_common, _data, app, _| {
"::PlayspaceRecenter" => Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
app.tasks
.enqueue(TaskType::Playspace(PlayspaceTask::Recenter));
Ok(EventResult::Consumed)
}),
"::PlayspaceFixFloor" => Box::new(move |_common, _data, app, _| {
for i in 0..5 {
Toast::new(
ToastTopic::System,
format!("Fixing floor in {}", 5 - i),
"Touch your controller to the floor!".into(),
)
.with_timeout(1.)
.with_sound(true)
.submit_at(app, Instant::now() + Duration::from_secs(i));
"::PlayspaceFixFloor" => Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
Toast::new(
ToastTopic::System,
"TOAST.FIXING_FLOOR".into(),
"TOAST.ONE_CONTROLLER_ON_FLOOR".into(),
)
.with_timeout(5.)
.with_sound(true)
.submit(app);
app.tasks.enqueue_at(
TaskType::Playspace(PlayspaceTask::FixFloor),
Instant::now() + Duration::from_secs(5),
);
Ok(EventResult::Consumed)
}),
"::Shutdown" => Box::new(move |_common, _data, _app, _| {
"::Shutdown" => Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
RUNNING.store(false, Ordering::Relaxed);
Ok(EventResult::Consumed)
}),
@@ -155,7 +342,11 @@ pub(super) fn setup_custom_button<S: 'static>(
log::error!("{command} has bad/missing arguments");
return;
};
Box::new(move |_common, _data, app, _| {
Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
app.hid_provider.send_key_routed(key, down);
Ok(EventResult::Consumed)
})
@@ -182,7 +373,11 @@ pub(super) fn setup_custom_button<S: 'static>(
}),
);
Box::new(move |_common, _data, _app, _| {
Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
let _ = shell_on_action(&state).inspect_err(|e| log::error!("{e:?}"));
Ok(EventResult::Consumed)
})
@@ -206,7 +401,11 @@ pub(super) fn setup_custom_button<S: 'static>(
osc_args.push(osc_arg);
}
Box::new(move |_common, _data, app, _| {
Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
let Some(sender) = app.osc_sender.as_mut() else {
log::error!("OscSend: sender is not available.");
return Ok(EventResult::Consumed);

View File

@@ -70,7 +70,8 @@ pub type OnCustomIdFunc<S> = Box<
) -> anyhow::Result<()>,
>;
pub type OnCustomAttribFunc = Box<dyn Fn(&mut Layout, &CustomAttribsInfoOwned, &AppState)>;
pub type OnCustomAttribFunc =
Box<dyn Fn(&mut Layout, &ParserState, &CustomAttribsInfoOwned, &AppState)>;
pub struct NewGuiPanelParams<S> {
pub on_custom_id: Option<OnCustomIdFunc<S>>, // used only in `new_from_template`
@@ -155,7 +156,7 @@ impl<S: 'static> GuiPanel<S> {
}
if let Some(on_custom_attrib) = &params.on_custom_attrib {
on_custom_attrib(&mut layout, elem, app);
on_custom_attrib(&mut layout, &parser_state, elem, app);
}
}

View File

@@ -1,7 +1,10 @@
use std::rc::Rc;
use anyhow::Context;
use glam::FloatExt;
use wgui::{
animation::{Animation, AnimationEasing},
components::button::ComponentButton,
event::CallbackDataCommon,
layout::WidgetID,
parser::Fetchable,
@@ -15,6 +18,7 @@ pub(super) struct InteractLockHandler {
id: WidgetID,
color: wgui::drawing::Color,
interactable: bool,
button: Option<Rc<ComponentButton>>,
}
impl InteractLockHandler {
@@ -27,10 +31,13 @@ impl InteractLockHandler {
.get_as::<WidgetRectangle>(id)
.context("Element with id=\"shadow\" must be a <rectangle>")?;
let button = panel.parser_state.fetch_component_as("top_lock")?;
Ok(Self {
id,
color: shadow_rect.params.color,
interactable: true,
button: Some(button),
})
}
@@ -42,6 +49,10 @@ impl InteractLockHandler {
.get_as::<WidgetRectangle>(self.id)
.unwrap(); // can only fail if set_up_rect has issues
if let Some(button) = self.button.as_ref() {
button.set_sticky_state(common, !interactable);
}
let globals = common.state.globals.get();
if interactable {
set_anim_color(&mut rect, 0.0, self.color, globals.defaults.danger_color);
@@ -54,16 +65,20 @@ impl InteractLockHandler {
&mut self,
common: &mut CallbackDataCommon,
app: &mut AppState,
anim_mult: f32,
) -> Box<ModifyOverlayTask> {
let defaults = app.wgui_globals.get().defaults.clone();
let rect_color = self.color;
self.interactable = !self.interactable;
if let Some(button) = self.button.as_ref() {
button.set_sticky_state(common, !self.interactable);
}
let anim = if self.interactable {
Animation::new(
self.id,
10,
(10. * anim_mult) as _,
AnimationEasing::OutQuad,
Box::new(move |common, data| {
let rect = data.obj.get_as_mut::<WidgetRectangle>().unwrap();
@@ -79,7 +94,7 @@ impl InteractLockHandler {
} else {
Animation::new(
self.id,
10,
(10. * anim_mult) as _,
AnimationEasing::OutBack,
Box::new(move |common, data| {
let rect = data.obj.get_as_mut::<WidgetRectangle>().unwrap();

View File

@@ -47,21 +47,9 @@ mod sprite_tab;
mod stereo;
pub mod tab;
pub(super) struct LongPressButtonState {
pub(super) pressed: Instant,
}
impl Default for LongPressButtonState {
fn default() -> Self {
Self {
pressed: Instant::now(),
}
}
}
struct EditModeState {
tasks: Rc<RefCell<TaskContainer>>,
id: Rc<RefCell<OverlayID>>,
delete: LongPressButtonState,
tabs: ButtonPaneTabSwitcher,
lock: InteractLockHandler,
pos: SpriteTabHandler<PosTabState>,
@@ -272,9 +260,6 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
let state = EditModeState {
id: Rc::new(RefCell::new(OverlayID::null())),
tasks: Rc::new(RefCell::new(TaskContainer::new())),
delete: LongPressButtonState {
pressed: Instant::now(),
},
tabs: ButtonPaneTabSwitcher::default(),
lock: InteractLockHandler::default(),
pos: SpriteTabHandler::default(),
@@ -282,8 +267,16 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
mouse: SpriteTabHandler::default(),
};
let on_custom_attrib: OnCustomAttribFunc = Box::new(move |layout, attribs, _app| {
for (name, kind) in &BUTTON_EVENTS {
let anim_mult = app.wgui_globals.defaults().animation_mult;
let on_custom_attrib: OnCustomAttribFunc = Box::new(move |layout, parser, attribs, _app| {
let Ok(button) =
parser.fetch_component_from_widget_id_as::<ComponentButton>(attribs.widget_id)
else {
return;
};
for (name, kind, test_button, test_duration) in &BUTTON_EVENTS {
let Some(action) = attribs.get_value(name) else {
continue;
};
@@ -293,15 +286,25 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
continue;
};
let button = button.clone();
let callback: EventCallback<AppState, EditModeState> = match command {
"::EditModeToggleLock" => Box::new(move |common, _data, app, state| {
"::EditModeToggleLock" => Box::new(move |common, data, app, state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
let sel = OverlaySelector::Id(*state.id.borrow());
let task = state.lock.toggle(common, app);
let task = state.lock.toggle(common, app, anim_mult);
app.tasks
.enqueue(TaskType::Overlay(OverlayTask::Modify(sel, task)));
Ok(EventResult::Consumed)
}),
"::EditModeToggleGrab" => Box::new(move |_common, _data, app, state| {
"::EditModeToggleGrab" => Box::new(move |_common, data, app, state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
let sel = OverlaySelector::Id(*state.id.borrow());
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
sel,
@@ -314,14 +317,22 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
}),
"::EditModeTab" => {
let tab_name = args.next().unwrap().to_owned();
Box::new(move |common, _data, _app, state| {
Box::new(move |common, data, app, state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
state.tabs.tab_button_clicked(common, &tab_name);
Ok(EventResult::Consumed)
})
}
"::EditModeSetPos" => {
let key = args.next().unwrap().to_owned();
Box::new(move |common, _data, app, state| {
Box::new(move |common, data, app, state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
let sel = OverlaySelector::Id(*state.id.borrow());
let task = state.pos.button_clicked(common, &key);
app.tasks
@@ -331,7 +342,11 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
}
"::EditModeSetStereo" => {
let key = args.next().unwrap().to_owned();
Box::new(move |common, _data, app, state| {
Box::new(move |common, data, app, state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
let sel = OverlaySelector::Id(*state.id.borrow());
let task = state.stereo.button_clicked(common, &key);
app.tasks
@@ -341,7 +356,11 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
}
"::EditModeSetMouse" => {
let key = args.next().unwrap().to_owned();
Box::new(move |common, _data, app, state| {
Box::new(move |common, data, app, state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
let sel = OverlaySelector::Id(*state.id.borrow());
let task = state.mouse.button_clicked(common, &key);
app.tasks
@@ -349,15 +368,11 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
Ok(EventResult::Consumed)
})
}
"::EditModeDeletePress" => Box::new(move |_common, _data, _app, state| {
state.delete.pressed = Instant::now();
// TODO: animate to light up button after 2s
Ok(EventResult::Consumed)
}),
"::EditModeDeleteRelease" => Box::new(move |_common, _data, app, state| {
if state.delete.pressed.elapsed() < Duration::from_secs(1) {
"::EditModeDelete" => Box::new(move |_common, data, app, state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
OverlaySelector::Id(*state.id.borrow()),
Box::new(move |_app, owc| {

View File

@@ -1,7 +1,7 @@
use std::{collections::HashMap, rc::Rc};
use crate::{gui::panel::GuiPanel, state::AppState, subsystem::hid::XkbKeymap};
use glam::{FloatExt, Mat4, Vec2, Vec3, vec2};
use glam::{FloatExt, Mat4, Vec2, vec2, vec3};
use wgui::{
animation::{Animation, AnimationEasing},
assets::AssetPath,
@@ -39,11 +39,13 @@ pub(super) fn create_keyboard_panel(
let globals = app.wgui_globals.clone();
let accent_color = globals.get().defaults.accent_color;
let anim_mult = globals.defaults().animation_mult;
let (background, _) = panel.layout.add_child(
panel.layout.content_root_widget,
WidgetRectangle::create(WidgetRectangleParams {
color: wgui::drawing::Color::new(0., 0., 0., 0.75),
round: WLength::Units(16.0),
color: globals.defaults().bg_color,
round: WLength::Units((16.0 * globals.defaults().rounding_mult).max(0.)),
border: 2.0,
border_color: accent_color,
..Default::default()
@@ -169,6 +171,8 @@ pub(super) fn create_keyboard_panel(
})
};
let width_mul = 1. / my_size_f32;
panel.add_event_listener(
widget_id,
EventListenerKind::MouseEnter,
@@ -176,7 +180,14 @@ pub(super) fn create_keyboard_panel(
let k = key_state.clone();
move |common, data, _app, _state| {
common.alterables.trigger_haptics();
on_enter_anim(k.clone(), common, data, accent_color);
on_enter_anim(
k.clone(),
common,
data,
accent_color,
anim_mult,
width_mul,
);
Ok(EventResult::Pass)
}
}),
@@ -188,7 +199,14 @@ pub(super) fn create_keyboard_panel(
let k = key_state.clone();
move |common, data, _app, _state| {
common.alterables.trigger_haptics();
on_leave_anim(k.clone(), common, data, accent_color);
on_leave_anim(
k.clone(),
common,
data,
accent_color,
anim_mult,
width_mul,
);
Ok(EventResult::Pass)
}
}),
@@ -254,11 +272,14 @@ pub(super) fn create_keyboard_panel(
const BUTTON_HOVER_SCALE: f32 = 0.1;
fn get_anim_transform(pos: f32, widget_size: Vec2) -> Mat4 {
util::centered_matrix(
widget_size,
&Mat4::from_scale(Vec3::splat(BUTTON_HOVER_SCALE.mul_add(pos, 1.0))),
)
fn get_anim_transform(pos: f32, widget_size: Vec2, width_mult: f32) -> Mat4 {
let scale = vec3(
(BUTTON_HOVER_SCALE * width_mult).mul_add(pos, 1.0),
BUTTON_HOVER_SCALE.mul_add(pos, 1.0),
1.0,
);
util::centered_matrix(widget_size, &Mat4::from_scale(scale))
}
fn set_anim_color(
@@ -291,15 +312,18 @@ fn on_enter_anim(
common: &mut event::CallbackDataCommon,
data: &event::CallbackData,
accent_color: drawing::Color,
anim_mult: f32,
width_mult: f32,
) {
common.alterables.animate(Animation::new(
data.widget_id,
10,
(10. * anim_mult) as _,
AnimationEasing::OutBack,
Box::new(move |common, data| {
let rect = data.obj.get_as_mut::<WidgetRectangle>().unwrap();
set_anim_color(&key_state, rect, data.pos, accent_color);
data.data.transform = get_anim_transform(data.pos, data.widget_boundary.size);
data.data.transform =
get_anim_transform(data.pos, data.widget_boundary.size, width_mult);
common.alterables.mark_redraw();
}),
));
@@ -310,15 +334,18 @@ fn on_leave_anim(
common: &mut event::CallbackDataCommon,
data: &event::CallbackData,
accent_color: drawing::Color,
anim_mult: f32,
width_mult: f32,
) {
common.alterables.animate(Animation::new(
data.widget_id,
15,
(15. * anim_mult) as _,
AnimationEasing::OutQuad,
Box::new(move |common, data| {
let rect = data.obj.get_as_mut::<WidgetRectangle>().unwrap();
set_anim_color(&key_state, rect, 1.0 - data.pos, accent_color);
data.data.transform = get_anim_transform(1.0 - data.pos, data.widget_boundary.size);
data.data.transform =
get_anim_transform(1.0 - data.pos, data.widget_boundary.size, width_mult);
common.alterables.mark_redraw();
}),
));

View File

@@ -2,8 +2,6 @@ pub mod anchor;
pub mod custom;
pub mod edit;
pub mod keyboard;
#[cfg(feature = "wayland")]
pub mod mirror;
pub mod screen;
pub mod toast;
pub mod watch;

View File

@@ -215,6 +215,7 @@ impl OverlayBackend for ScreenBackend {
Ok(ShouldRender::Can)
}
} else {
log::trace!("{}: backend ready, but no image received.", self.name);
Ok(ShouldRender::Unable)
}
}

View File

@@ -8,7 +8,10 @@ use std::{
use futures::{Future, FutureExt};
use glam::{Affine2, Affine3A, Quat, Vec3, vec3};
use wlx_capture::pipewire::{PipewireCapture, PipewireSelectScreenResult, pipewire_select_screen};
use wlx_capture::{
WlxCapture,
pipewire::{PipewireCapture, PipewireSelectScreenResult, pipewire_select_screen},
};
use wlx_common::{
overlays::{BackendAttrib, BackendAttribValue},
windowing::OverlayWindowState,
@@ -19,6 +22,7 @@ use crate::{
input::{HoverResult, PointerHit},
task::{OverlayTask, TaskType},
},
overlays::screen::capture::{MainThreadWlxCapture, new_wlx_capture},
state::{AppSession, AppState},
subsystem::hid::WheelDelta,
windowing::{
@@ -31,7 +35,7 @@ use crate::{
},
};
use super::screen::backend::ScreenBackend;
use super::backend::ScreenBackend;
type PinnedSelectorFuture = core::pin::Pin<
Box<dyn Future<Output = Result<PipewireSelectScreenResult, wlx_capture::pipewire::AshpdError>>>,
>;
@@ -76,13 +80,21 @@ impl OverlayBackend for MirrorBackend {
match maybe_pw_result {
Ok(pw_result) => {
log::debug!(
"{}: PipeWire result streams: {:?}",
self.name,
&pw_result.streams
);
let node_id = pw_result.streams.first().unwrap().node_id; // streams guaranteed to have at least one element
log::info!("{}: PipeWire node selected: {}", self.name.clone(), node_id);
let capture = PipewireCapture::new(self.name.clone(), node_id);
log::info!("{}: PipeWire node selected: {}", self.name, node_id);
let capture = new_wlx_capture!(
app.gfx_extras.queue_capture,
PipewireCapture::new(self.name.clone(), node_id)
);
self.renderer = Some(ScreenBackend::new_raw(
self.name.clone(),
app.xr_backend,
Box::new(capture),
capture,
));
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
OverlaySelector::Name(self.name.clone()),

View File

@@ -15,6 +15,8 @@ use crate::{
pub mod backend;
mod capture;
#[cfg(feature = "wayland")]
pub mod mirror;
#[cfg(feature = "pipewire")]
pub mod pw;
#[cfg(feature = "wayland")]

View File

@@ -42,6 +42,12 @@ impl ScreenBackend {
false,
)?;
log::debug!(
"{}: PipeWire result streams: {:?}",
output.name,
&select_screen_result.streams
);
let node_id = select_screen_result.streams.first().unwrap().node_id; // streams guaranteed to have at least one element
let capture = new_wlx_capture!(

View File

@@ -16,7 +16,6 @@ use wlx_common::{
use crate::{
backend::task::{OverlayTask, TaskType},
gui::panel::{GuiPanel, NewGuiPanelParams, OnCustomIdFunc},
overlays::watch::{WATCH_POS, WATCH_ROT},
state::AppState,
windowing::{OverlaySelector, Z_ORDER_TOAST, window::OverlayWindowConfig},
};
@@ -121,35 +120,29 @@ fn new_toast(toast: Toast, app: &mut AppState) -> Option<OverlayWindowConfig> {
Positioning::FollowHead { lerp: 0.1 },
),
ToastDisplayMethod::Watch => {
//FIXME: properly follow watch
let watch_pos = WATCH_POS + vec3(-0.005, -0.05, 0.02);
let watch_rot = WATCH_ROT;
let relative_to = /*match app.session.config.watch_hand {
LeftRight::Left =>*/ Positioning::FollowHand {
hand: LeftRight::Left,
lerp: 1.0,
align_to_hmd: true,
/*
},
LeftRight::Right => {
watch_pos.x = -watch_pos.x;
watch_rot = watch_rot * Quat::from_rotation_x(PI) * Quat::from_rotation_z(PI);
Positioning::FollowHand {
hand: LeftRight::Right,
lerp: 1.0,
}
}*/
let relative_to = Positioning::FollowHand {
hand: LeftRight::Left,
lerp: 0.1,
align_to_hmd: true,
};
(watch_pos, watch_rot, relative_to)
(vec3(0., 0., 0.), Quat::IDENTITY, relative_to)
}
};
let title = if toast.title.is_empty() {
Translation::from_translation_key("TOAST.DEFAULT_TITLE")
} else if matches!(toast.topic, ToastTopic::System | ToastTopic::Error) {
Translation::from_translation_key(&toast.title)
} else {
Translation::from_raw_text(&toast.title)
};
let body = if matches!(toast.topic, ToastTopic::System) {
Translation::from_translation_key(&toast.body)
} else {
Translation::from_raw_text(&toast.body)
};
let on_custom_id: OnCustomIdFunc<()> =
Box::new(move |id, widget, _doc_params, layout, _parser_state, ()| {
if &*id == "toast_title" {
@@ -168,7 +161,7 @@ fn new_toast(toast: Toast, app: &mut AppState) -> Option<OverlayWindowConfig> {
.get_as::<WidgetLabel>(widget)
.context("toast.xml: missing element with id: toast_body")?;
let mut globals = layout.state.globals.get();
label.set_text_simple(&mut globals, Translation::from_raw_text(&toast.body));
label.set_text_simple(&mut globals, body.clone());
}
Ok(())
});
@@ -206,7 +199,7 @@ fn new_toast(toast: Toast, app: &mut AppState) -> Option<OverlayWindowConfig> {
}
fn msg_err(app: &mut AppState, message: &str) {
Toast::new(ToastTopic::System, "Error".into(), message.into())
Toast::new(ToastTopic::Error, "TOAST.ERROR".into(), message.into())
.with_timeout(3.)
.submit(app);
}

View File

@@ -1,11 +1,7 @@
use std::{
collections::HashMap,
rc::Rc,
time::{Duration, Instant},
};
use std::{collections::HashMap, rc::Rc, time::Duration};
use glam::{Affine3A, Quat, Vec3, Vec3A, vec3};
use idmap::{DirectIdMap, ordered::Keys};
use idmap::DirectIdMap;
use slotmap::SecondaryMap;
use wgui::{
components::button::ComponentButton,
@@ -31,7 +27,6 @@ use crate::{
panel::{GuiPanel, NewGuiPanelParams, OnCustomAttribFunc, button::BUTTON_EVENTS},
timer::GuiTimer,
},
overlays::edit::LongPressButtonState,
state::AppState,
windowing::{
OverlayID, OverlaySelector, Z_ORDER_WATCH,
@@ -70,7 +65,6 @@ struct WatchState {
keyboard_oid: OverlayID,
dashboard_oid: OverlayID,
num_sets: usize,
delete: LongPressButtonState,
}
#[allow(clippy::significant_drop_tightening)]
@@ -78,8 +72,14 @@ struct WatchState {
pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
let state = WatchState::default();
let on_custom_attrib: OnCustomAttribFunc = Box::new(move |layout, attribs, _app| {
for (name, kind) in &BUTTON_EVENTS {
let on_custom_attrib: OnCustomAttribFunc = Box::new(move |layout, parser, attribs, _app| {
let Ok(button) =
parser.fetch_component_from_widget_id_as::<ComponentButton>(attribs.widget_id)
else {
return;
};
for (name, kind, test_button, test_duration) in &BUTTON_EVENTS {
let Some(action) = attribs.get_value(name) else {
continue;
};
@@ -89,20 +89,23 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
continue;
};
let button = button.clone();
let callback: EventCallback<AppState, WatchState> = match command {
"::EditModeDeleteDown" => Box::new(move |_common, _data, _app, state| {
state.delete.pressed = Instant::now();
Ok(EventResult::Consumed)
}),
"::EditModeDeleteUp" => Box::new(move |_common, _data, app, state| {
if state.delete.pressed.elapsed() < Duration::from_secs(1) {
return Ok(EventResult::Consumed);
"::EditModeDeleteSet" => Box::new(move |_common, data, app, _state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
app.tasks
.enqueue(TaskType::Overlay(OverlayTask::DeleteActiveSet));
Ok(EventResult::Consumed)
}),
"::EditModeAddSet" => Box::new(move |_common, _data, app, _state| {
"::EditModeAddSet" => Box::new(move |_common, data, app, _state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
app.tasks.enqueue(TaskType::Overlay(OverlayTask::AddSet));
Ok(EventResult::Consumed)
}),
@@ -112,7 +115,11 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
log::error!("{command} has invalid argument: \"{arg}\"");
return;
};
Box::new(move |_common, _data, app, state| {
Box::new(move |_common, data, app, state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
let Some(overlay) = state.overlay_metas.get(idx) else {
log::error!("No overlay at index {idx}.");
return Ok(EventResult::Consumed);
@@ -137,7 +144,11 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
log::error!("{command} has invalid argument: \"{arg}\"");
return;
};
Box::new(move |_common, _data, app, state| {
Box::new(move |_common, data, app, state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
let Some(overlay) = state.overlay_metas.get(idx) else {
log::error!("No overlay at index {idx}.");
return Ok(EventResult::Consumed);
@@ -150,6 +161,29 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
Ok(EventResult::Consumed)
})
}
"::SingleSetOverlayReset" => {
let arg = args.next().unwrap_or_default();
let Ok(idx) = arg.parse::<usize>() else {
log::error!("{command} has invalid argument: \"{arg}\"");
return;
};
Box::new(move |_common, data, app, state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
let Some(overlay) = state.overlay_metas.get(idx) else {
log::error!("No overlay at index {idx}.");
return Ok(EventResult::Consumed);
};
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
OverlaySelector::Id(overlay.id),
Box::new(|app, owc| owc.activate(app)),
)));
Ok(EventResult::Consumed)
})
}
_ => return,
};

View File

@@ -28,8 +28,12 @@
## How fast to drag when the space drag feature is activated
#space_drag_multiplier: 1.0
## When enabled, the space_rotate binding can be used
## to rotate in any axis. Imagine horizon mode².
## Setting this to false will make space_drag
## only affect the vertical axis (Y).
#space_drag_unlocked: true
## Monado/WiVRn only. When enabled, the space_rotate binding
## can rotate in any axis. Imagine horizon mode².
#space_rotate_unlocked: false
## Monado/WiVRn only. Use passthrough camera if the headset supports it.
@@ -87,9 +91,17 @@
#theme_path: "theme"
## These can be used to control the color theme of WlxOverlay-S.
#color_text: "#ffffff"
#color_accent: "#008cff"
#color_danger: "#ff3300"
#color_faded: "#668299"
#color_background: "#010206"
## Multiplier for animation speed. 2.0 → double speed, 0.5 → half speed
#animation_speed: 1.0
## Adjust this between 0..1 for a more rectangular feel.
#round_multiplier: 1.0
## Path to custom skybox texture, relative to `~/.config/wlxoverlay`
#skybox_texture: ""
@@ -163,6 +175,10 @@
#xr_click_sensitivity: 0.7
#xr_click_sensitivity_release: 0.5
## How many seconds to buttons need to be held
## before it's considered a long press
#long_press_duration: 1.0
## Change speed of scrolling
# 0.5 → half the speed
# 2.0 → twice the speed

View File

@@ -3,7 +3,7 @@ use idmap::IdMap;
use smallvec::{SmallVec, smallvec};
use std::sync::Arc;
use wgui::{
font_config::WguiFontConfig, gfx::WGfx, globals::WguiGlobals, parser::parse_color_hex,
drawing, font_config::WguiFontConfig, gfx::WGfx, globals::WguiGlobals, parser::parse_color_hex,
renderer_vk::context::SharedContext as WSharedContext,
};
use wlx_common::{
@@ -96,26 +96,21 @@ impl AppState {
let theme = session.config.theme_path.clone();
let mut defaults = wgui::globals::Defaults::default();
defaults.accent_color = session
.config
.color_accent
.as_ref()
.and_then(|c| parse_color_hex(&c))
.unwrap_or(defaults.accent_color);
defaults.danger_color = session
.config
.color_danger
.as_ref()
.and_then(|c| parse_color_hex(&c))
.unwrap_or(defaults.danger_color);
fn apply_color(default: &mut drawing::Color, value: &Option<String>) {
if let Some(parsed) = value.as_ref().and_then(|c| parse_color_hex(c)) {
*default = parsed;
}
}
defaults.faded_color = session
.config
.color_faded
.as_ref()
.and_then(|c| parse_color_hex(&c))
.unwrap_or(defaults.faded_color);
apply_color(&mut defaults.text_color, &session.config.color_text);
apply_color(&mut defaults.accent_color, &session.config.color_accent);
apply_color(&mut defaults.danger_color, &session.config.color_danger);
apply_color(&mut defaults.faded_color, &session.config.color_faded);
apply_color(&mut defaults.bg_color, &session.config.color_background);
defaults.animation_mult = 1. / session.config.animation_speed;
defaults.rounding_mult = session.config.round_multiplier;
let dbus = DbusConnector::default();
@@ -205,6 +200,7 @@ impl AppSession {
let mut toast_topics = IdMap::new();
toast_topics.insert(ToastTopic::System, ToastDisplayMethod::Center);
toast_topics.insert(ToastTopic::Error, ToastDisplayMethod::Center);
toast_topics.insert(ToastTopic::DesktopNotification, ToastDisplayMethod::Center);
toast_topics.insert(ToastTopic::XSNotification, ToastDisplayMethod::Center);

View File

@@ -27,6 +27,7 @@ pub struct FrameMeta {
pub clear: WGfxClearMode,
}
#[derive(Debug, Clone, Copy)]
pub enum ShouldRender {
/// The overlay is dirty and needs to be rendered.
Should,

View File

@@ -34,7 +34,7 @@ use crate::{
},
};
pub const MAX_OVERLAY_SETS: usize = 7;
pub const MAX_OVERLAY_SETS: usize = 6;
pub struct OverlayWindowManager<T> {
wrappers: EditWrapperManager,
@@ -49,6 +49,7 @@ pub struct OverlayWindowManager<T> {
watch_id: OverlayID,
edit_mode: bool,
dropped_overlays: VecDeque<OverlayWindowData<T>>,
initialized: bool,
}
impl<T> OverlayWindowManager<T>
@@ -66,6 +67,7 @@ where
watch_id: OverlayID::null(), // set down below
edit_mode: false,
dropped_overlays: VecDeque::with_capacity(8),
initialized: false,
};
let mut wayland = false;
@@ -148,6 +150,8 @@ where
.notify(app, ev)?;
}
me.initialized = true;
Ok(me)
}
@@ -199,8 +203,8 @@ where
let Some(set) = self.current_set else {
Toast::new(
ToastTopic::System,
"Can't remove set".into(),
"No set is selected!".into(),
"TOAST.CANNOT_REMOVE_SET".into(),
"TOAST.NO_SET_SELECTED".into(),
)
.with_timeout(5.)
.with_sound(true)
@@ -211,8 +215,8 @@ where
if self.sets.len() <= 1 {
Toast::new(
ToastTopic::System,
"Can't remove set".into(),
"This is the last existing set!".into(),
"TOAST.CANNOT_REMOVE_SET".into(),
"TOAST.LAST_EXISTING_SET".into(),
)
.with_timeout(5.)
.with_sound(true)
@@ -658,6 +662,7 @@ impl<T> OverlayWindowManager<T> {
return;
}
let mut num_overlays = 0;
let ws = &mut self.sets[new_set];
for (id, data) in self.overlays.iter_mut().filter(|(_, d)| !d.config.global) {
if let Some(state) = ws.overlays.remove(id) {
@@ -666,10 +671,28 @@ impl<T> OverlayWindowManager<T> {
if !keep_transforms {
data.config.reset(app, false);
}
if !matches!(
data.config.category,
OverlayCategory::Internal
| OverlayCategory::Keyboard
| OverlayCategory::Dashboard
) {
num_overlays += 1;
}
}
}
ws.overlays.clear();
self.restore_set = new_set;
if !self.edit_mode && self.initialized && num_overlays < 1 {
Toast::new(
ToastTopic::System,
"TOAST.EMPTY_SET".into(),
"TOAST.LETS_ADD_OVERLAYS".into(),
)
.with_timeout(3.)
.submit(app);
}
}
self.current_set = new_set;