Compare commits

..
57 Commits
Author SHA1 Message Date
galister bcc9f5d277 Release 25.4.2 2025-04-12 18:04:00 +09:00
galister 5723d668e3 add issue template 2025-04-11 21:33:51 +09:00
Orion 1ae50a35ec add basic input blocking through libmonado (#190) 2025-04-11 11:14:09 +00:00
AdiMCS 5b23de0599 Add default space_reset binding for all controllers (#191)
* Converted tabs to spaces for consistency

* Added default space_reset binding as space_drag's double click variant for all controllers

* Fixed duplicate space_resets
2025-04-10 22:49:58 +00:00
galister cdbe3c4ad3 openvr lines are dirty by default 2025-04-11 05:12:02 +09:00
galister 412f199f91 openvr: native is srgb, set image_view for lines 2025-04-11 04:41:04 +09:00
Aleksander 4e30bb8cbe WayVR: doc: Rename executable name in the wayvr.yaml (#192)
[skip ci]
2025-04-10 19:30:44 +00:00
galister 45e53c15a3 steamvr staging texture to use native_format 2025-04-11 04:14:27 +09:00
galister 21a4797d2a update wlx-capture 2025-04-11 02:23:01 +09:00
galister a8ecc973a4 use srgb for openxr, linear for openvr 2025-04-11 02:13:02 +09:00
galister f12d151abf workaround attempt for cosmic desktop 2025-04-11 02:05:11 +09:00
galister 5ca634b552 Release 25.4.1 2025-04-10 05:11:24 +09:00
galister 409b89585e release native binary as well as appimage 2025-04-10 05:07:42 +09:00
galister 92c59850b6 only run clippy on default features 2025-04-08 19:59:23 +09:00
galister bffb4c12cf fix build with non-default features 2025-04-08 19:52:34 +09:00
galister feac111949 CI to cover clippy + uidev 2025-04-08 19:25:11 +09:00
galister f13a473048 use multi-threaded capture if vk queues ≥ 2 2025-04-08 00:49:34 +09:00
galister b2a7d3c4cb use-bigger-hammer style fix for wayvr dmabufs 2025-04-07 07:56:01 +09:00
galister 4cb12a2a01 Revert "WayVR: Vulkano dmabuf attempt"
This reverts commit 45ab38c310.
2025-04-07 07:34:56 +09:00
galister b830c826a0 separate transfer and capture queues 2025-04-07 05:41:02 +09:00
Aleksander 45ab38c310 WayVR: Vulkano dmabuf attempt 2025-04-06 22:03:40 +02:00
galister bc6c510af1 get rid of cstr dependency 2025-04-07 04:46:47 +09:00
galister 3dd31ec468 fix mouse renderer 2025-04-07 03:38:16 +09:00
galister 60850d8d70 get rid of once_cell 2025-04-07 03:31:58 +09:00
galister 7ac3072506 move drm modifiers to WlxGraphics 2025-04-07 03:06:22 +09:00
galister 2952a3cea1 update vulkano 2025-04-07 02:33:40 +09:00
Aleksander bb2f8380fd WayVR: Fix frame_meta returning None 2025-04-06 19:03:29 +02:00
galister 1deae19f92 refactors & clippy recommendations 2025-04-07 00:58:03 +09:00
galister 3365aefc13 update deps 2025-04-06 22:15:22 +09:00
galister cf29682e66 rework rendering backend 2025-04-06 22:12:56 +09:00
galister 55867e803f Release 25.4 2025-04-04 04:06:11 +09:00
xytovl 60bc8b986e CI step to generate a tarball of crates (#185) 2025-03-29 01:26:32 +00:00
Jay 5f93bc9cac Config notification/toast sound (#179)
* try adding config to load different sound

looks like include_bytes!() doesn't take a variable parameter, so will need a different solution lol.

* no errors in loading, but toast.rs now has a lifetime issue

* hey, config toast sound works now!

the sound is loaded in AppState now, since Toast are created in several different places at different times, and i don't want to load the sound every time a toast is made.

also put back the original sound since i accidentally commited my custom one.

* clean up

* change custom toast sound leak to happen on load rather than every play

* move toast sound loading into a function

so it can be reused in the future, for example if we want to load arbitrary keypress sounds or other [u8] data.

* remove label from try_load_bytes, return early when failing
2025-03-24 11:32:29 +00:00
Aleksander 659f1492fb Merge pull request #180 from olekolek1000/devel
WayVR: Convert `target_display` to lowercase at runtime (Closes #158)
2025-03-12 18:01:21 +01:00
Aleksander a55d0d74bb WayVR: Convert target_display to lowercase at runtime (Closes #158)
[skip ci]
2025-03-12 17:55:05 +01:00
Aleksander 6d39380ebc WayVR: Implement software texture blitting as an alternative to dmabuf (Closes #174) (#178) 2025-03-11 22:42:37 +00:00
galister a1cc41f541 cargo fmt 2025-03-12 03:07:35 +09:00
galister bca6376b50 only log keys if debug build and log level trace 2025-03-12 03:06:52 +09:00
Aleksander 049f91eed3 Merge pull request #177 from olekolek1000/devel
WayVR: Clean-up destroyed toplevel windows
2025-03-09 22:13:58 +01:00
Aleksander 05b3baa5d9 WayVR: Clean-up destroyed toplevel windows 2025-03-09 22:04:46 +01:00
Aleksander ed804820e9 Merge pull request #176 from olekolek1000/devel
WayVR: Read the `WAYVR_DISPLAY_AUTH` environment variable from external processes, matching it to our virtual display
2025-03-09 21:26:08 +01:00
Aleksander 91af7c99b1 WayVR: Read the WAYVR_DISPLAY_AUTH environment variable from external processes, matching it to our virtual display.
This enables applications to be run via Flatpak or any other wrapper application.
2025-03-09 21:14:50 +01:00
galister 0b7cd61d05 wayvr modifiers 2025-03-10 03:45:00 +09:00
galister 59b489680a fix wayvr keyboard focus 2025-03-10 02:21:19 +09:00
AdiMCS b0883e81bf Switch to WheelHiRes. Add horizontal scrolling (#171)
* Switch to HiResScroll. Add horizontal scrolling

* Fix OpenVR compilation for horizontal scrolling

* fix OpenXR scroll using x axis for both scroll axes
2025-03-09 17:20:10 +00:00
Aleksander 36074307b7 Use tracing-subscriber for logging (#175) 2025-03-09 17:05:18 +00:00
galister fa3af63e89 pin smithay version 2025-03-09 05:02:04 +09:00
Aleksander 5cf6eeedb0 Merge pull request #170 from Beyley/main
Fix OpenXR IPD calculation
2025-03-04 00:38:28 +01:00
Beyley Thomas 89680bf8cc Fix OpenXR IPD calculation 2025-03-03 14:22:11 -08:00
galister 009862e2a2 Release 25.3 2025-03-02 21:52:50 +09:00
Aleksander 075e55a8e8 WayVR: Add DashboardShown and DashboardHidden events, add WlxInputState support (#167)
Wlx: Fix various Clippy warnings
2025-02-28 21:05:27 +00:00
AdiMCS e66a8d27c9 Add "Toggle Dashboard" button to the WayVR example watch config (#166) 2025-02-28 01:07:55 +00:00
galister 507ff7353e Release 25.2 2025-02-20 06:26:35 +09:00
galister c5a82c2879 update libmonado 2025-02-20 06:26:35 +09:00
Diamond White 965174c781 seperate combined UInputHandle into two handles for mouse and keyboard each (#155) 2025-02-12 19:24:42 +00:00
Aleksander bf3f3dafbe Merge pull request #154 from olekolek1000/wayvr_dashboard_ipc
WayVR: Do not hard-error in case if the user bought NVIDIA
2025-02-11 22:43:38 +01:00
Aleksander 4701d2b734 WayVR: Do not hard-error in case if the user bought NVIDIA 2025-02-11 22:30:38 +01:00
72 changed files with 4687 additions and 3630 deletions

No files matched your search

+42
View File
@@ -0,0 +1,42 @@
---
name: Bug Report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
## Description
<!--
If this is a regression, please mention which version was working previously.
-->
## System Info
**Linux Distribution**:
**Desktop Environment**:
<!-- Paste output of `uname -r` -->
**Kernel version**:
**VR Runtime**:
- [ ] Monado/WiVRn
- [ ] SteamVR/ALVR
<!-- Run `vulkaninfo --summary` and paste the devices section from the bottom. -->
**GPU models and driver versions**:
## Overlay Logs
<!-- Start the overlay once more with the following environment variables:
RUST_BACKTRACE=full
RUST_LOG=debug
If your issue is graphical or crash or freeze, also add:
VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation
Be sure to go and reproduce the issue once more, after these have been set.
Upload the log file from: /tmp/wlx.log
-->
@@ -1,4 +1,4 @@
name: Check Full
name: Check All Features
on:
pull_request:
@@ -23,6 +23,6 @@ jobs:
sudo apt-get update -y
sudo apt-get install -y fuse cmake pkg-config fontconfig libasound2-dev libxkbcommon-dev libxkbcommon-x11-0 libxkbcommon-x11-dev libopenxr-dev libfontconfig-dev libdbus-1-dev libpipewire-0.3-0 libpipewire-0.3-dev libspa-0.2-dev libx11-6 libxext6 libxrandr2 libx11-dev libxext-dev libxrandr-dev libopenvr-dev libopenvr-api1 libwayland-dev libegl-dev
- name: Build
run: cargo build --verbose
run: cargo build --verbose --all-features
- name: Run tests
run: cargo test --verbose
run: cargo test --verbose --all-features
+32
View File
@@ -0,0 +1,32 @@
name: Check Default
on:
pull_request:
branches: [ "!main" ]
env:
CARGO_TERM_COLOR: always
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache"
jobs:
build:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.3
- name: Prepare Environment
run: |
sudo add-apt-repository -syn universe
sudo add-apt-repository -syn ppa:pipewire-debian/pipewire-upstream || sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 25088A0359807596
sudo apt-get update -y
sudo apt-get install -y fuse cmake pkg-config fontconfig libasound2-dev libxkbcommon-dev libxkbcommon-x11-0 libxkbcommon-x11-dev libopenxr-dev libfontconfig-dev libdbus-1-dev libpipewire-0.3-0 libpipewire-0.3-dev libspa-0.2-dev libx11-6 libxext6 libxrandr2 libx11-dev libxext-dev libxrandr-dev libopenvr-dev libopenvr-api1 libwayland-dev libegl-dev
- name: Run cargo fmt
run: cargo fmt --check
- name: Build
run: cargo build --verbose
- name: Run clippy
run: cargo clippy --no-deps
- name: Run tests
run: cargo test --verbose
+29 -2
View File
@@ -17,7 +17,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.3
uses: mozilla-actions/sccache-action@v0.0.9
- name: Prepare Environment
run: |
sudo add-apt-repository -syn universe
@@ -35,7 +35,7 @@ jobs:
- name: Cargo Build
run: |
cargo build --release
mv target/release/wlx-overlay-s ${APPDIR}/usr/bin
cp target/release/wlx-overlay-s ${APPDIR}/usr/bin
chmod +x ${APPDIR}/usr/bin/wlx-overlay-s
- name: Package AppImage
@@ -44,6 +44,13 @@ jobs:
./linuxdeploy-x86_64.AppImage -dwlx-overlay-s.desktop -iwlx-overlay-s.png --appdir=${APPDIR} --output appimage --exclude-library '*libpipewire*'
mv WlxOverlay-S-$VERSION-x86_64.AppImage WlxOverlay-S-x86_64.AppImage
- name: Make tarball
run: |
pip install portage pycargoebuild
wget https://github.com/gentoo/gentoo/raw/refs/heads/master/metadata/license-mapping.conf
mkdir dist
pycargoebuild --distdir dist --license-mapping license-mapping.conf --crate-tarball --crate-tarball-path wlx-overlay-s-crates.tar.xz
- name: Create Release
id: create_release
uses: actions/create-release@v1
@@ -55,6 +62,16 @@ jobs:
draft: true
prerelease: false
- name: Upload ELF
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_KEY }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: target/release/wlx-overlay-s
asset_name: wlx-overlay-s
asset_content_type: application/octet-stream
- name: Upload AppImage
uses: actions/upload-release-asset@v1
env:
@@ -64,3 +81,13 @@ jobs:
asset_path: ./WlxOverlay-S-x86_64.AppImage
asset_name: WlxOverlay-S-${{ github.ref_name }}-x86_64.AppImage
asset_content_type: application/octet-stream
- name: Upload crates tarball
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_KEY }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./wlx-overlay-s-crates.tar.xz
asset_name: WlxOverlay-S-${{ github.ref_name }}-crates.tar.xz
asset_content_type: application/x-gtar
Generated
+729 -565
View File
File diff suppressed because it is too large. Load diff
+24 -19
View File
@@ -4,26 +4,30 @@ debug = true
[package]
name = "wlx-overlay-s"
version = "25.2.0"
version = "25.4.2"
edition = "2021"
license = "GPL-3.0-only"
authors = ["galister"]
description = "Access your Wayland/X11 desktop from Monado/WiVRn/SteamVR. Now with Vulkan!"
repository = "https://github.com/galister/wlx-overlay-s"
keywords = ["linux", "openvr", "openxr", "x11", "wayland", "openvr-overlay", "openxr-overlay"]
categories = ["games"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.89"
ash = "^0.37.2"
ash = "^0.38.0" # must match vulkano
chrono = "0.4.38"
chrono-tz = "0.10.0"
clap = { version = "4.5.6", features = ["derive"] }
config = "0.14.0"
cstr = "0.2.12"
config = "0.15.11"
ctrlc = { version = "3.4.4", features = ["termination"] }
dbus = { version = "0.9.7" }
env_logger = "0.11.5"
fontconfig-rs = "0.1.1"
freetype-rs = "0.36.0" # latest version supported on ubuntu 22.04
futures = "0.3.30"
glam = { version = "0.29.2", features = ["approx", "mint", "serde"] }
glam = { version = "0.30.1", features = ["approx", "mint", "serde"] }
idmap = { version = "0.2.21", features = ["serde"] }
idmap-derive = "0.1.2"
input-linux = "0.7.0"
@@ -31,7 +35,6 @@ json = { version = "0.12.4", optional = true }
json5 = "0.4.1"
libc = "0.2.155"
log = "0.4.21"
once_cell = "1.19.0"
openxr = { git = "https://github.com/galister/openxrs", rev = "af4a55d", features = [
"linked",
"mint",
@@ -40,41 +43,43 @@ ovr_overlay = { features = [
"ovr_input",
"ovr_system",
], git = "https://github.com/galister/ovr_overlay_oyasumi", optional = true }
regex = "1.10.4"
regex = "1.11.1"
rodio = { version = "0.20.1", default-features = false, features = [
"wav",
"hound",
] }
rosc = { version = "0.10.1", optional = true }
rosc = { version = "0.11.4", optional = true }
serde = { version = "1.0.203", features = ["derive", "rc"] }
serde_json = "1.0.117"
serde_yaml = "0.9.34"
smallvec = "1.13.2"
strum = { version = "0.26.2", features = ["derive"] }
sysinfo = { version = "0.32.0" }
strum = { version = "0.27.1", features = ["derive"] }
sysinfo = { version = "0.34.2" }
thiserror = "2.0.3"
vulkano = { git = "https://github.com/vulkano-rs/vulkano", rev = "94f50f1" }
vulkano-shaders = { git = "https://github.com/vulkano-rs/vulkano", rev = "94f50f1" }
wlx-capture = { git = "https://github.com/galister/wlx-capture", tag = "v0.4.2", default-features = false }
vulkano = { version = "0.35.1" }
vulkano-shaders = { version = "0.35.0" }
wlx-capture = { git = "https://github.com/galister/wlx-capture", tag = "v0.5.2", default-features = false }
libmonado = { git = "https://github.com/technobaboo/libmonado-rs", rev = "256895b18c8f9368174fad8a6232ff07764eeacb", optional = true }
winit = { version = "0.30.0", optional = true }
xdg = "2.5.2"
log-panics = { version = "2.1.0", features = ["with-backtrace"] }
serde_json5 = "0.1.0"
serde_json5 = "0.2.1"
xkbcommon = { version = "0.8.0" }
xcb = { version = "1.4.0", optional = true, features = [
"as-raw-xcb-connection",
] }
image_dds = { version = "0.6.0", default-features = false, features = [
image_dds = { version = "0.7.2", default-features = false, features = [
"ddsfile",
] }
mint = "0.5.9"
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
tracing = "0.1.41"
################################
#WayVR-only deps
################################
khronos-egl = { version = "6.0.0", features = ["static"], optional = true }
smithay = { git = "https://github.com/Smithay/smithay.git", default-features = false, features = [
smithay = { version = "0.5.1", default-features = false, features = [
"renderer_gl",
"backend_egl",
"backend_drm",
@@ -86,11 +91,11 @@ wayland-client = { version = "0.31.6", optional = true }
wayland-egl = { version = "0.32.4", optional = true }
interprocess = { version = "2.2.2", optional = true }
bytes = { version = "1.9.0", optional = true }
wayvr_ipc = { git = "https://github.com/olekolek1000/wayvr-ipc.git", rev = "3c411d09ba1bba2609288e29739c0f1ec736b012", default-features = false, optional = true }
wayvr_ipc = { git = "https://github.com/olekolek1000/wayvr-ipc.git", rev = "a72587d23f3bb8624d9aeb1f13c0a21e65350f51", default-features = false, optional = true }
################################
[build-dependencies]
regex = { version = "*" }
regex = { version = "1.11.1" }
[features]
default = ["openvr", "openxr", "osc", "x11", "wayland", "wayvr"]
+14 -2
View File
@@ -28,9 +28,21 @@ elements:
target: settings
action: Destroy # only triggers if exists since before current frame
# Dashboard toggle button
- type: Button
rect: [32, 162, 48, 36]
corner_radius: 4
font_size: 15
bg_color: "#2288FF"
fg_color: "#24273a"
text: "Dash"
click_up:
- type: WayVR
action: ToggleDashboard
# Keyboard button
- type: Button
rect: [32, 162, 60, 36]
rect: [84, 162, 48, 36]
corner_radius: 4
font_size: 15
fg_color: "#24273a"
@@ -65,7 +77,7 @@ elements:
# bottom row, of keyboard + overlays
- type: OverlayList
rect: [94, 160, 306, 40]
rect: [134, 160, 266, 40]
corner_radius: 4
font_size: 15
fg_color: "#cad3f5"
+12 -11
View File
@@ -1,6 +1,5 @@
use std::sync::Arc;
use std::sync::{Arc, LazyLock};
use once_cell::sync::Lazy;
#[cfg(feature = "openxr")]
use openxr as xr;
@@ -69,17 +68,17 @@ where
app.screens.clear();
let data = if let Some(wl) = wl.as_mut() {
keymap = get_keymap_wl()
.map_err(|f| log::warn!("Could not load keyboard layout: {}", f))
.map_err(|f| log::warn!("Could not load keyboard layout: {f}"))
.ok();
crate::overlays::screen::create_screens_wayland(wl, app)?
crate::overlays::screen::create_screens_wayland(wl, app)
} else {
keymap = get_keymap_x11()
.map_err(|f| log::warn!("Could not load keyboard layout: {}", f))
.map_err(|f| log::warn!("Could not load keyboard layout: {f}"))
.ok();
match crate::overlays::screen::create_screens_x11pw(app) {
Ok(data) => data,
Err(e) => {
log::info!("Will not use PipeWire capture: {:?}", e);
log::info!("Will not use PipeWire capture: {e:?}");
crate::overlays::screen::create_screens_xshm(app)?
}
}
@@ -127,6 +126,8 @@ where
Ok(vec![])
}
#[cfg(feature = "wayland")]
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
#[allow(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
pub fn update(&mut self, app: &mut AppState) -> anyhow::Result<Vec<OverlayData<T>>> {
use crate::overlays::{
screen::{create_screen_interaction, create_screen_renderer_wl, load_pw_token_config},
@@ -154,7 +155,7 @@ where
if create_ran {
continue;
}
let data = crate::overlays::screen::create_screens_wayland(wl, app)?;
let data = crate::overlays::screen::create_screens_wayland(wl, app);
create_ran = true;
for (meta, state, backend) in data.screens {
self.overlays.insert(
@@ -228,7 +229,7 @@ where
has_wlr_dmabuf,
has_wlr_screencopy,
pw_token_store,
&app.session,
&app,
) {
overlay.backend.set_renderer(Box::new(renderer));
}
@@ -317,8 +318,8 @@ where
.any(|o| o.state.show_hide && o.state.want_visible);
if !any_shown {
static ANCHOR_LOCAL: Lazy<Affine3A> =
Lazy::new(|| Affine3A::from_translation(Vec3::NEG_Z));
static ANCHOR_LOCAL: LazyLock<Affine3A> =
LazyLock::new(|| Affine3A::from_translation(Vec3::NEG_Z));
let hmd = snap_upright(app.input_state.hmd, Vec3A::Y);
app.anchor = hmd * *ANCHOR_LOCAL;
}
@@ -337,7 +338,7 @@ where
if !any_shown && *o.state.name == *WATCH_NAME {
o.state.reset(app, true);
}
})
});
}
}
+100 -85
View File
@@ -50,7 +50,7 @@ impl InputState {
}
}
pub fn pre_update(&mut self) {
pub const fn pre_update(&mut self) {
self.pointers[0].before = self.pointers[0].now;
self.pointers[1].before = self.pointers[1].now;
}
@@ -58,50 +58,7 @@ impl InputState {
pub fn post_update(&mut self, session: &AppSession) {
for hand in &mut self.pointers {
#[cfg(debug_assertions)]
{
if hand.now.click != hand.before.click {
log::debug!("Hand {}: click {}", hand.idx, hand.now.click);
}
if hand.now.grab != hand.before.grab {
log::debug!("Hand {}: grab {}", hand.idx, hand.now.grab);
}
if hand.now.alt_click != hand.before.alt_click {
log::debug!("Hand {}: alt_click {}", hand.idx, hand.now.alt_click);
}
if hand.now.show_hide != hand.before.show_hide {
log::debug!("Hand {}: show_hide {}", hand.idx, hand.now.show_hide);
}
if hand.now.toggle_dashboard != hand.before.toggle_dashboard {
log::debug!(
"Hand {}: toggle_dashboard {}",
hand.idx,
hand.now.toggle_dashboard
);
}
if hand.now.space_drag != hand.before.space_drag {
log::debug!("Hand {}: space_drag {}", hand.idx, hand.now.space_drag);
}
if hand.now.space_rotate != hand.before.space_rotate {
log::debug!("Hand {}: space_rotate {}", hand.idx, hand.now.space_rotate);
}
if hand.now.space_reset != hand.before.space_reset {
log::debug!("Hand {}: space_reset {}", hand.idx, hand.now.space_reset);
}
if hand.now.click_modifier_right != hand.before.click_modifier_right {
log::debug!(
"Hand {}: click_modifier_right {}",
hand.idx,
hand.now.click_modifier_right
);
}
if hand.now.click_modifier_middle != hand.before.click_modifier_middle {
log::debug!(
"Hand {}: click_modifier_middle {}",
hand.idx,
hand.now.click_modifier_middle
);
}
}
debug_print_hand(hand);
if hand.now.click {
hand.last_click = Instant::now();
@@ -118,8 +75,8 @@ impl InputState {
}
let hmd_up = self.hmd.transform_vector3a(Vec3A::Y);
let dot =
hmd_up.dot(hand.pose.transform_vector3a(Vec3A::X)) * (1.0 - 2.0 * hand.idx as f32);
let dot = hmd_up.dot(hand.pose.transform_vector3a(Vec3A::X))
* 2.0f32.mul_add(-(hand.idx as f32), 1.0);
hand.interaction.mode = if dot < -0.85 {
PointerMode::Right
@@ -143,7 +100,7 @@ impl InputState {
}
}
_ => {}
};
}
if hand.now.alt_click != hand.before.alt_click {
// Reap previous processes
@@ -166,6 +123,54 @@ impl InputState {
}
}
#[cfg(debug_assertions)]
fn debug_print_hand(hand: &Pointer) {
{
if hand.now.click != hand.before.click {
log::debug!("Hand {}: click {}", hand.idx, hand.now.click);
}
if hand.now.grab != hand.before.grab {
log::debug!("Hand {}: grab {}", hand.idx, hand.now.grab);
}
if hand.now.alt_click != hand.before.alt_click {
log::debug!("Hand {}: alt_click {}", hand.idx, hand.now.alt_click);
}
if hand.now.show_hide != hand.before.show_hide {
log::debug!("Hand {}: show_hide {}", hand.idx, hand.now.show_hide);
}
if hand.now.toggle_dashboard != hand.before.toggle_dashboard {
log::debug!(
"Hand {}: toggle_dashboard {}",
hand.idx,
hand.now.toggle_dashboard
);
}
if hand.now.space_drag != hand.before.space_drag {
log::debug!("Hand {}: space_drag {}", hand.idx, hand.now.space_drag);
}
if hand.now.space_rotate != hand.before.space_rotate {
log::debug!("Hand {}: space_rotate {}", hand.idx, hand.now.space_rotate);
}
if hand.now.space_reset != hand.before.space_reset {
log::debug!("Hand {}: space_reset {}", hand.idx, hand.now.space_reset);
}
if hand.now.click_modifier_right != hand.before.click_modifier_right {
log::debug!(
"Hand {}: click_modifier_right {}",
hand.idx,
hand.now.click_modifier_right
);
}
if hand.now.click_modifier_middle != hand.before.click_modifier_middle {
log::debug!(
"Hand {}: click_modifier_middle {}",
hand.idx,
hand.now.click_modifier_middle
);
}
}
}
pub struct InteractionState {
pub mode: PointerMode,
pub grabbed: Option<GrabData>,
@@ -207,17 +212,18 @@ impl Pointer {
idx,
pose: Affine3A::IDENTITY,
raw_pose: Affine3A::IDENTITY,
now: Default::default(),
before: Default::default(),
now: PointerState::default(),
before: PointerState::default(),
last_click: Instant::now(),
interaction: Default::default(),
interaction: InteractionState::default(),
}
}
}
#[derive(Clone, Copy, Default)]
pub struct PointerState {
pub scroll: f32,
pub scroll_x: f32,
pub scroll_y: f32,
pub click: bool,
pub grab: bool,
pub alt_click: bool,
@@ -252,7 +258,7 @@ pub trait InteractionHandler {
fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option<Haptics>;
fn on_left(&mut self, app: &mut AppState, pointer: usize);
fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool);
fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta: f32);
fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta_y: f32, delta_x: f32);
}
pub struct DummyInteractionHandler;
@@ -263,7 +269,7 @@ impl InteractionHandler for DummyInteractionHandler {
None
}
fn on_pointer(&mut self, _app: &mut AppState, _hit: &PointerHit, _pressed: bool) {}
fn on_scroll(&mut self, _app: &mut AppState, _hit: &PointerHit, _delta: f32) {}
fn on_scroll(&mut self, _app: &mut AppState, _hit: &PointerHit, _delta_y: f32, _delta_x: f32) {}
}
#[derive(Debug, Clone, Copy, Default)]
@@ -319,6 +325,7 @@ where
}
}
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
fn interact_hand<O>(
idx: usize,
overlays: &mut OverlayContainer<O>,
@@ -419,20 +426,21 @@ where
pointer = &mut app.input_state.pointers[idx];
if pointer.now.scroll.abs() > 0.1 {
let scroll = pointer.now.scroll;
if pointer.now.scroll_x.abs() > 0.1 || pointer.now.scroll_y.abs() > 0.1 {
let scroll_x = pointer.now.scroll_x;
let scroll_y = pointer.now.scroll_y;
if app.input_state.pointers[1 - idx]
.interaction
.grabbed
.is_some_and(|x| x.grabbed_id == hit.overlay)
{
let can_curve = hovered
.frame_transform()
.frame_meta()
.is_some_and(|e| e.extent[0] >= e.extent[1]);
if can_curve {
let cur = hovered.state.curvature.unwrap_or(0.0);
let new = (cur - scroll * 0.01).min(0.5);
let new = scroll_y.mul_add(-0.01, cur).min(0.5);
if new <= f32::EPSILON {
hovered.state.curvature = None;
} else {
@@ -442,7 +450,7 @@ where
hovered.state.curvature = None;
}
} else {
hovered.backend.on_scroll(app, &hit, scroll);
hovered.backend.on_scroll(app, &hit, scroll_y, scroll_x);
}
pointer = &mut app.input_state.pointers[idx];
}
@@ -478,7 +486,7 @@ impl Pointer {
if let Some(hit) = self.ray_test(
overlay.state.id,
&overlay.state.transform,
&overlay.state.curvature,
overlay.state.curvature.as_ref(),
) {
if hit.dist.is_infinite() || hit.dist.is_nan() {
continue;
@@ -489,7 +497,7 @@ impl Pointer {
hits.sort_by(|a, b| a.dist.total_cmp(&b.dist));
for hit in hits.iter() {
for hit in &hits {
let overlay = overlays.get_by_id(hit.overlay).unwrap(); // safe because we just got the id from the overlay
let uv = overlay
@@ -560,10 +568,10 @@ impl Pointer {
if self.now.click {
self.interaction.mode = PointerMode::Special;
let cur_scale = overlay.state.transform.x_axis.length();
if cur_scale < 0.1 && self.now.scroll > 0.0 {
if cur_scale < 0.1 && self.now.scroll_y > 0.0 {
return;
}
if cur_scale > 20. && self.now.scroll < 0.0 {
if cur_scale > 20. && self.now.scroll_y < 0.0 {
return;
}
@@ -571,9 +579,9 @@ impl Pointer {
.state
.transform
.matrix3
.mul_scalar(1.0 - 0.025 * self.now.scroll);
} else if config.allow_sliding && self.now.scroll.is_finite() {
grab_data.offset.z -= self.now.scroll * 0.05;
.mul_scalar(0.025f32.mul_add(-self.now.scroll_y, 1.0));
} else if config.allow_sliding && self.now.scroll_y.is_finite() {
grab_data.offset.z -= self.now.scroll_y * 0.05;
}
overlay.state.transform.translation = self.pose.transform_point3a(grab_data.offset);
overlay.state.realign(hmd);
@@ -618,12 +626,19 @@ impl Pointer {
&self,
overlay: OverlayID,
transform: &Affine3A,
curvature: &Option<f32>,
curvature: Option<&f32>,
) -> Option<RayHit> {
let (dist, local_pos) = match curvature {
Some(curvature) => raycast_cylinder(&self.pose, Vec3A::NEG_Z, transform, *curvature),
_ => raycast_plane(&self.pose, Vec3A::NEG_Z, transform, Vec3A::NEG_Z),
}?;
let (dist, local_pos) = curvature.map_or_else(
|| {
Some(raycast_plane(
&self.pose,
Vec3A::NEG_Z,
transform,
Vec3A::NEG_Z,
))
},
|curvature| raycast_cylinder(&self.pose, Vec3A::NEG_Z, transform, *curvature),
)?;
if dist < 0.0 {
// hit is behind us
@@ -644,7 +659,7 @@ fn raycast_plane(
source_fwd: Vec3A,
plane: &Affine3A,
plane_norm: Vec3A,
) -> Option<(f32, Vec2)> {
) -> (f32, Vec2) {
let plane_normal = plane.transform_vector3a(plane_norm);
let ray_dir = source.transform_vector3a(source_fwd);
@@ -656,7 +671,7 @@ fn raycast_plane(
.transform_point3a(source.translation + ray_dir * dist)
.xy();
Some((dist, hit_local))
(dist, hit_local)
}
fn raycast_cylinder(
@@ -673,27 +688,27 @@ fn raycast_cylinder(
}
.inverse();
let r = size / (2.0 * PI * curvature);
let radius = size / (2.0 * PI * curvature);
let ray_dir = to_local.transform_vector3a(source.transform_vector3a(source_fwd));
let ray_origin = to_local.transform_point3a(source.translation) + Vec3A::NEG_Z * r;
let ray_origin = to_local.transform_point3a(source.translation) + Vec3A::NEG_Z * radius;
let d = ray_dir.xz();
let s = ray_origin.xz();
let v_dir = ray_dir.xz();
let v_pos = ray_origin.xz();
let a = d.dot(d);
let b = d.dot(s);
let c = s.dot(s) - r * r;
let l_dir = v_dir.dot(v_dir);
let l_pos = v_dir.dot(v_pos);
let c = radius.mul_add(-radius, v_pos.dot(v_pos));
let d = (b * b) - (a * c);
let d = l_pos.mul_add(l_pos, -(l_dir * c));
if d < f32::EPSILON {
return None;
}
let sqrt_d = d.sqrt();
let t1 = (-b - sqrt_d) / a;
let t2 = (-b + sqrt_d) / a;
let t1 = (-l_pos - sqrt_d) / l_dir;
let t2 = (-l_pos + sqrt_d) / l_dir;
let t = t1.max(t2);
@@ -707,8 +722,8 @@ fn raycast_cylinder(
return None;
}
let max_angle = 2.0 * (size / (2.0 * r));
let x_angle = (hit_local.x / r).asin();
let max_angle = 2.0 * (size / (2.0 * radius));
let x_angle = (hit_local.x / radius).asin();
hit_local.x = x_angle / max_angle;
hit_local.y /= size;
+44 -49
View File
@@ -57,8 +57,7 @@ impl NotificationManager {
Ok(c) => c,
Err(e) => {
log::error!(
"Failed to connect to dbus. Desktop notifications will not work. Cause: {:?}",
e
"Failed to connect to dbus. Desktop notifications will not work. Cause: {e:?}"
);
return;
}
@@ -81,55 +80,50 @@ impl NotificationManager {
(vec![rule.match_str()], 0u32),
);
match result {
Ok(_) => {
let sender = self.tx_toast.clone();
c.start_receive(
rule,
Box::new(move |msg, _| {
if let Ok(toast) = parse_dbus(&msg) {
match sender.try_send(toast) {
Ok(_) => {}
Err(e) => {
log::error!("Failed to send notification: {:?}", e);
}
}
}
true
}),
);
log::info!("Listening to DBus notifications via BecomeMonitor.");
}
Err(_) => {
let rule_with_eavesdrop = {
let mut rule = rule.clone();
rule.eavesdrop = true;
rule
};
let sender2 = self.tx_toast.clone();
let result = c.add_match(rule_with_eavesdrop, move |_: (), _, msg| {
if let Ok(toast) = parse_dbus(msg) {
match sender2.try_send(toast) {
Ok(_) => {}
if matches!(result, Ok(())) {
let sender = self.tx_toast.clone();
c.start_receive(
rule,
Box::new(move |msg, _| {
if let Ok(toast) = parse_dbus(&msg) {
match sender.try_send(toast) {
Ok(()) => {}
Err(e) => {
log::error!("Failed to send notification: {:?}", e);
log::error!("Failed to send notification: {e:?}");
}
}
}
true
});
}),
);
log::info!("Listening to DBus notifications via BecomeMonitor.");
} else {
let rule_with_eavesdrop = {
let mut rule = rule.clone();
rule.eavesdrop = true;
rule
};
match result {
Ok(_) => {
log::info!("Listening to DBus notifications via eavesdrop.");
}
Err(_) => {
log::error!(
"Failed to add DBus match. Desktop notifications will not work.",
);
let sender2 = self.tx_toast.clone();
let result = c.add_match(rule_with_eavesdrop, move |(): (), _, msg| {
if let Ok(toast) = parse_dbus(msg) {
match sender2.try_send(toast) {
Ok(()) => {}
Err(e) => {
log::error!("Failed to send notification: {e:?}");
}
}
}
true
});
match result {
Ok(_) => {
log::info!("Listening to DBus notifications via eavesdrop.");
}
Err(_) => {
log::error!("Failed to add DBus match. Desktop notifications will not work.",);
}
}
}
@@ -144,12 +138,12 @@ impl NotificationManager {
let socket = match std::net::UdpSocket::bind(addr) {
Ok(s) => s,
Err(e) => {
log::error!("Failed to bind notification socket @ {}: {:?}", addr, e);
log::error!("Failed to bind notification socket @ {addr}: {e:?}");
return;
}
};
if let Err(err) = socket.set_read_timeout(Some(Duration::from_millis(200))) {
log::error!("Failed to set read timeout: {:?}", err);
log::error!("Failed to set read timeout: {err:?}");
}
let mut buf = [0u8; 1024 * 16]; // vrcx embeds icons as b64
@@ -159,14 +153,14 @@ impl NotificationManager {
let json_str = match std::str::from_utf8(&buf[..num_bytes]) {
Ok(s) => s,
Err(e) => {
log::error!("Failed to receive notification message: {:?}", e);
log::error!("Failed to receive notification message: {e:?}");
continue;
}
};
let msg = match serde_json::from_str::<XsoMessage>(json_str) {
Ok(m) => m,
Err(e) => {
log::error!("Failed to parse notification message: {:?}", e);
log::error!("Failed to parse notification message: {e:?}");
continue;
}
};
@@ -184,9 +178,9 @@ impl NotificationManager {
.with_sound(msg.volume.unwrap_or(-1.) >= 0.); // XSOverlay still plays at 0,
match sender.try_send(toast) {
Ok(_) => {}
Ok(()) => {}
Err(e) => {
log::error!("Failed to send notification: {:?}", e);
log::error!("Failed to send notification: {e:?}");
}
}
}
@@ -278,6 +272,7 @@ fn parse_dbus(msg: &dbus::Message) -> anyhow::Result<Toast> {
// leave the audio part to the desktop env
}
#[allow(dead_code)]
#[allow(non_snake_case)]
#[derive(Debug, Deserialize)]
struct XsoMessage {
+127 -41
View File
@@ -1,13 +1,22 @@
// This code was autogenerated with `dbus-codegen-rust -g -m None -d org.freedesktop.Notifications -p /org/freedesktop/Notifications`, see https://github.com/diwic/dbus-rs
use dbus as dbus;
use dbus;
#[allow(unused_imports)]
use dbus::arg;
use dbus::blocking;
pub trait OrgFreedesktopDBusProperties {
fn get<R0: for<'b> arg::Get<'b> + 'static>(&self, interface_name: &str, property_name: &str) -> Result<R0, dbus::Error>;
fn get<R0: for<'b> arg::Get<'b> + 'static>(
&self,
interface_name: &str,
property_name: &str,
) -> Result<R0, dbus::Error>;
fn get_all(&self, interface_name: &str) -> Result<arg::PropMap, dbus::Error>;
fn set<I2: arg::Arg + arg::Append>(&self, interface_name: &str, property_name: &str, value: I2) -> Result<(), dbus::Error>;
fn set<I2: arg::Arg + arg::Append>(
&self,
interface_name: &str,
property_name: &str,
value: I2,
) -> Result<(), dbus::Error>;
}
#[derive(Debug)]
@@ -27,7 +36,7 @@ impl arg::AppendAll for OrgFreedesktopDBusPropertiesPropertiesChanged {
impl arg::ReadAll for OrgFreedesktopDBusPropertiesPropertiesChanged {
fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
Ok(OrgFreedesktopDBusPropertiesPropertiesChanged {
Ok(Self {
interface_name: i.read()?,
changed_properties: i.read()?,
invalidated_properties: i.read()?,
@@ -40,20 +49,42 @@ impl dbus::message::SignalArgs for OrgFreedesktopDBusPropertiesPropertiesChanged
const INTERFACE: &'static str = "org.freedesktop.DBus.Properties";
}
impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref<Target=T>> OrgFreedesktopDBusProperties for blocking::Proxy<'a, C> {
fn get<R0: for<'b> arg::Get<'b> + 'static>(&self, interface_name: &str, property_name: &str) -> Result<R0, dbus::Error> {
self.method_call("org.freedesktop.DBus.Properties", "Get", (interface_name, property_name, ))
.and_then(|r: (arg::Variant<R0>, )| Ok((r.0).0, ))
impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref<Target = T>> OrgFreedesktopDBusProperties
for blocking::Proxy<'a, C>
{
fn get<R0: for<'b> arg::Get<'b> + 'static>(
&self,
interface_name: &str,
property_name: &str,
) -> Result<R0, dbus::Error> {
self.method_call(
"org.freedesktop.DBus.Properties",
"Get",
(interface_name, property_name),
)
.and_then(|r: (arg::Variant<R0>,)| Ok((r.0).0))
}
fn get_all(&self, interface_name: &str) -> Result<arg::PropMap, dbus::Error> {
self.method_call("org.freedesktop.DBus.Properties", "GetAll", (interface_name, ))
.and_then(|r: (arg::PropMap, )| Ok(r.0, ))
self.method_call(
"org.freedesktop.DBus.Properties",
"GetAll",
(interface_name,),
)
.and_then(|r: (arg::PropMap,)| Ok(r.0))
}
fn set<I2: arg::Arg + arg::Append>(&self, interface_name: &str, property_name: &str, value: I2) -> Result<(), dbus::Error> {
self.method_call("org.freedesktop.DBus.Properties", "Set", (interface_name, property_name, arg::Variant(value), ))
fn set<I2: arg::Arg + arg::Append>(
&self,
interface_name: &str,
property_name: &str,
value: I2,
) -> Result<(), dbus::Error> {
self.method_call(
"org.freedesktop.DBus.Properties",
"Set",
(interface_name, property_name, arg::Variant(value)),
)
}
}
@@ -61,11 +92,12 @@ pub trait OrgFreedesktopDBusIntrospectable {
fn introspect(&self) -> Result<String, dbus::Error>;
}
impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref<Target=T>> OrgFreedesktopDBusIntrospectable for blocking::Proxy<'a, C> {
impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref<Target = T>>
OrgFreedesktopDBusIntrospectable for blocking::Proxy<'a, C>
{
fn introspect(&self) -> Result<String, dbus::Error> {
self.method_call("org.freedesktop.DBus.Introspectable", "Introspect", ())
.and_then(|r: (String, )| Ok(r.0, ))
.and_then(|r: (String,)| Ok(r.0))
}
}
@@ -74,15 +106,16 @@ pub trait OrgFreedesktopDBusPeer {
fn get_machine_id(&self) -> Result<String, dbus::Error>;
}
impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref<Target=T>> OrgFreedesktopDBusPeer for blocking::Proxy<'a, C> {
impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref<Target = T>> OrgFreedesktopDBusPeer
for blocking::Proxy<'a, C>
{
fn ping(&self) -> Result<(), dbus::Error> {
self.method_call("org.freedesktop.DBus.Peer", "Ping", ())
}
fn get_machine_id(&self) -> Result<String, dbus::Error> {
self.method_call("org.freedesktop.DBus.Peer", "GetMachineId", ())
.and_then(|r: (String, )| Ok(r.0, ))
.and_then(|r: (String,)| Ok(r.0))
}
}
@@ -95,7 +128,17 @@ pub trait OrgFreedesktopNotifications {
fn close_all_notifications(&self) -> Result<(), dbus::Error>;
fn hide_latest_notification(&self, close: bool) -> Result<(), dbus::Error>;
fn get_capabilities(&self) -> Result<Vec<String>, dbus::Error>;
fn notify(&self, app_name: &str, replaces_id: u32, app_icon: &str, summary: &str, body: &str, actions: Vec<&str>, hints: arg::PropMap, expire_timeout: i32) -> Result<u32, dbus::Error>;
fn notify(
&self,
app_name: &str,
replaces_id: u32,
app_icon: &str,
summary: &str,
body: &str,
actions: Vec<&str>,
hints: arg::PropMap,
expire_timeout: i32,
) -> Result<u32, dbus::Error>;
fn close_notification(&self, id: u32) -> Result<(), dbus::Error>;
fn get_server_information(&self) -> Result<(String, String, String, String), dbus::Error>;
fn dnd(&self) -> Result<bool, dbus::Error>;
@@ -115,9 +158,7 @@ impl arg::AppendAll for OrgFreedesktopNotificationsOnDndToggle {
impl arg::ReadAll for OrgFreedesktopNotificationsOnDndToggle {
fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
Ok(OrgFreedesktopNotificationsOnDndToggle {
dnd: i.read()?,
})
Ok(Self { dnd: i.read()? })
}
}
@@ -141,7 +182,7 @@ impl arg::AppendAll for OrgFreedesktopNotificationsNotificationClosed {
impl arg::ReadAll for OrgFreedesktopNotificationsNotificationClosed {
fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
Ok(OrgFreedesktopNotificationsNotificationClosed {
Ok(Self {
id: i.read()?,
reason: i.read()?,
})
@@ -168,7 +209,7 @@ impl arg::AppendAll for OrgFreedesktopNotificationsActionInvoked {
impl arg::ReadAll for OrgFreedesktopNotificationsActionInvoked {
fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
Ok(OrgFreedesktopNotificationsActionInvoked {
Ok(Self {
id: i.read()?,
action_key: i.read()?,
})
@@ -195,7 +236,7 @@ impl arg::AppendAll for OrgFreedesktopNotificationsNotificationReplied {
impl arg::ReadAll for OrgFreedesktopNotificationsNotificationReplied {
fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
Ok(OrgFreedesktopNotificationsNotificationReplied {
Ok(Self {
id: i.read()?,
text: i.read()?,
})
@@ -207,28 +248,37 @@ impl dbus::message::SignalArgs for OrgFreedesktopNotificationsNotificationReplie
const INTERFACE: &'static str = "org.freedesktop.Notifications";
}
impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref<Target=T>> OrgFreedesktopNotifications for blocking::Proxy<'a, C> {
impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref<Target = T>> OrgFreedesktopNotifications
for blocking::Proxy<'a, C>
{
fn set_noti_window_visibility(&self, value: bool) -> Result<(), dbus::Error> {
self.method_call("org.freedesktop.Notifications", "SetNotiWindowVisibility", (value, ))
self.method_call(
"org.freedesktop.Notifications",
"SetNotiWindowVisibility",
(value,),
)
}
fn toggle_dnd(&self) -> Result<bool, dbus::Error> {
self.method_call("org.freedesktop.Notifications", "ToggleDnd", ())
.and_then(|r: (bool, )| Ok(r.0, ))
.and_then(|r: (bool,)| Ok(r.0))
}
fn set_dnd(&self, state: bool) -> Result<(), dbus::Error> {
self.method_call("org.freedesktop.Notifications", "SetDnd", (state, ))
self.method_call("org.freedesktop.Notifications", "SetDnd", (state,))
}
fn get_dnd(&self) -> Result<bool, dbus::Error> {
self.method_call("org.freedesktop.Notifications", "GetDnd", ())
.and_then(|r: (bool, )| Ok(r.0, ))
.and_then(|r: (bool,)| Ok(r.0))
}
fn manually_close_notification(&self, id: u32, timeout: bool) -> Result<(), dbus::Error> {
self.method_call("org.freedesktop.Notifications", "ManuallyCloseNotification", (id, timeout, ))
self.method_call(
"org.freedesktop.Notifications",
"ManuallyCloseNotification",
(id, timeout),
)
}
fn close_all_notifications(&self) -> Result<(), dbus::Error> {
@@ -236,21 +286,48 @@ impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref<Target=T>> OrgFreedes
}
fn hide_latest_notification(&self, close: bool) -> Result<(), dbus::Error> {
self.method_call("org.freedesktop.Notifications", "HideLatestNotification", (close, ))
self.method_call(
"org.freedesktop.Notifications",
"HideLatestNotification",
(close,),
)
}
fn get_capabilities(&self) -> Result<Vec<String>, dbus::Error> {
self.method_call("org.freedesktop.Notifications", "GetCapabilities", ())
.and_then(|r: (Vec<String>, )| Ok(r.0, ))
.and_then(|r: (Vec<String>,)| Ok(r.0))
}
fn notify(&self, app_name: &str, replaces_id: u32, app_icon: &str, summary: &str, body: &str, actions: Vec<&str>, hints: arg::PropMap, expire_timeout: i32) -> Result<u32, dbus::Error> {
self.method_call("org.freedesktop.Notifications", "Notify", (app_name, replaces_id, app_icon, summary, body, actions, hints, expire_timeout, ))
.and_then(|r: (u32, )| Ok(r.0, ))
fn notify(
&self,
app_name: &str,
replaces_id: u32,
app_icon: &str,
summary: &str,
body: &str,
actions: Vec<&str>,
hints: arg::PropMap,
expire_timeout: i32,
) -> Result<u32, dbus::Error> {
self.method_call(
"org.freedesktop.Notifications",
"Notify",
(
app_name,
replaces_id,
app_icon,
summary,
body,
actions,
hints,
expire_timeout,
),
)
.and_then(|r: (u32,)| Ok(r.0))
}
fn close_notification(&self, id: u32) -> Result<(), dbus::Error> {
self.method_call("org.freedesktop.Notifications", "CloseNotification", (id, ))
self.method_call("org.freedesktop.Notifications", "CloseNotification", (id,))
}
fn get_server_information(&self) -> Result<(String, String, String, String), dbus::Error> {
@@ -258,10 +335,19 @@ impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref<Target=T>> OrgFreedes
}
fn dnd(&self) -> Result<bool, dbus::Error> {
<Self as blocking::stdintf::org_freedesktop_dbus::Properties>::get(self, "org.freedesktop.Notifications", "Dnd")
<Self as blocking::stdintf::org_freedesktop_dbus::Properties>::get(
self,
"org.freedesktop.Notifications",
"Dnd",
)
}
fn set_dnd_(&self, value: bool) -> Result<(), dbus::Error> {
<Self as blocking::stdintf::org_freedesktop_dbus::Properties>::set(self, "org.freedesktop.Notifications", "Dnd", value)
<Self as blocking::stdintf::org_freedesktop_dbus::Properties>::set(
self,
"org.freedesktop.Notifications",
"Dnd",
value,
)
}
}
+8 -9
View File
@@ -13,7 +13,7 @@ pub trait Affine3AConvert {
impl Affine3AConvert for Matrix3x4 {
fn from_affine(affine: &Affine3A) -> Self {
Matrix3x4([
Self([
[
affine.matrix3.x_axis.x,
affine.matrix3.y_axis.x,
@@ -47,7 +47,7 @@ impl Affine3AConvert for Matrix3x4 {
impl Affine3AConvert for HmdMatrix34_t {
fn from_affine(affine: &Affine3A) -> Self {
HmdMatrix34_t {
Self {
m: [
[
affine.matrix3.x_axis.x,
@@ -89,22 +89,21 @@ pub(super) enum OVRError {
impl From<ovr_overlay::errors::EVRInputError> for OVRError {
fn from(e: ovr_overlay::errors::EVRInputError) -> Self {
OVRError::InputError(e.description())
Self::InputError(e.description())
}
}
impl From<OVRError> for BackendError {
fn from(e: OVRError) -> Self {
BackendError::Fatal(anyhow::Error::new(e))
Self::Fatal(anyhow::Error::new(e))
}
}
use cstr::cstr;
const STEAMVR_SECTION: &CStr = cstr!("steamvr");
const STEAMVR_SECTION: &CStr = c"steamvr";
const COLOR_GAIN_CSTR: [&CStr; 3] = [
cstr!("hmdDisplayColorGainR"),
cstr!("hmdDisplayColorGainG"),
cstr!("hmdDisplayColorGainB"),
c"hmdDisplayColorGainR",
c"hmdDisplayColorGainG",
c"hmdDisplayColorGainB",
];
pub(super) fn adjust_gain(
+12 -10
View File
@@ -107,7 +107,8 @@ impl OpenVrInputSource {
haptics_hnd: haptics_hnd[i],
});
Ok(OpenVrInputSource {
Ok(Self {
hands,
set_hnd,
click_hnd,
grab_hnd,
@@ -120,14 +121,13 @@ impl OpenVrInputSource {
click_modifier_right_hnd,
click_modifier_middle_hnd,
move_mouse_hnd,
hands,
})
}
pub fn haptics(&mut self, input: &mut InputManager, hand: usize, haptics: &Haptics) {
let hnd = self.hands[hand].haptics_hnd;
let action_handle = self.hands[hand].haptics_hnd;
let _ = input.trigger_haptic_vibration_action(
hnd,
action_handle,
0.0,
Duration::from_secs_f32(haptics.duration),
haptics.frequency,
@@ -230,10 +230,12 @@ impl OpenVrInputSource {
.map(|x| x.0.bState)
.unwrap_or(false);
app_hand.now.scroll = input
let scroll = input
.get_analog_action_data(self.scroll_hnd, hand.input_hnd)
.map(|x| x.0.y)
.unwrap_or(0.0);
.map(|x| (x.0.x, x.0.y))
.unwrap_or((0.0, 0.0));
app_hand.now.scroll_x = scroll.0;
app_hand.now.scroll_y = scroll.1;
}
}
@@ -275,8 +277,8 @@ impl OpenVrInputSource {
}
app.input_state.devices.sort_by(|a, b| {
(a.soc.is_none() as u8)
.cmp(&(b.soc.is_none() as u8))
u8::from(a.soc.is_none())
.cmp(&u8::from(b.soc.is_none()))
.then((a.role as u8).cmp(&(b.role as u8)))
.then(a.soc.unwrap_or(999.).total_cmp(&b.soc.unwrap_or(999.)))
});
@@ -330,7 +332,7 @@ pub fn set_action_manifest(input: &mut InputManager) -> anyhow::Result<()> {
if let Err(e) = File::create(&action_path)
.and_then(|mut f| f.write_all(include_bytes!("../../res/actions.json")))
{
log::warn!("Could not write action manifest: {}", e);
log::warn!("Could not write action manifest: {e}");
}
let binding_path = config_io::get_config_root().join("actions_binding_knuckles.json");
+24 -21
View File
@@ -12,9 +12,10 @@ use vulkano::image::view::ImageView;
use vulkano::image::ImageLayout;
use crate::backend::overlay::{
FrameTransform, OverlayData, OverlayRenderer, OverlayState, SplitOverlayBackend, Z_ORDER_LINES,
FrameMeta, OverlayData, OverlayRenderer, OverlayState, ShouldRender, SplitOverlayBackend,
Z_ORDER_LINES,
};
use crate::graphics::WlxGraphics;
use crate::graphics::{CommandBuffers, WlxGraphics};
use crate::state::AppState;
use super::overlay::OpenVrOverlayData;
@@ -29,8 +30,10 @@ pub(super) struct LinePool {
impl LinePool {
pub fn new(graphics: Arc<WlxGraphics>) -> anyhow::Result<Self> {
let mut command_buffer =
graphics.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
let mut command_buffer = graphics.create_uploads_command_buffer(
graphics.transfer_queue.clone(),
CommandBufferUsage::OneTimeSubmit,
)?;
let buf = vec![255; 16];
@@ -47,7 +50,7 @@ impl LinePool {
let view = ImageView::new_default(texture)?;
Ok(LinePool {
Ok(Self {
lines: IdMap::new(),
view,
colors: [
@@ -65,7 +68,7 @@ impl LinePool {
let mut data = OverlayData::<OpenVrOverlayData> {
state: OverlayState {
name: Arc::from(format!("wlx-line{}", id)),
name: Arc::from(format!("wlx-line{id}")),
show_hide: true,
..Default::default()
},
@@ -78,6 +81,8 @@ impl LinePool {
data: OpenVrOverlayData {
width: 0.002,
override_width: true,
image_view: Some(self.view.clone()),
image_dirty: true,
..Default::default()
},
..Default::default()
@@ -131,15 +136,7 @@ impl LinePool {
data.state.transform = transform;
data.data.color = color;
} else {
log::warn!("Line {} does not exist", id);
}
}
pub fn hide(&mut self, id: usize) {
if let Some(data) = self.lines.get_mut(id) {
data.state.want_visible = false;
} else {
log::warn!("Line {} does not exist", id);
log::warn!("Line {id} does not exist");
}
}
@@ -185,14 +182,20 @@ impl OverlayRenderer for StaticRenderer {
fn resume(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
Ok(())
}
fn render(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
Ok(())
fn should_render(&mut self, _app: &mut AppState) -> anyhow::Result<ShouldRender> {
Ok(ShouldRender::Unable)
}
fn view(&mut self) -> Option<Arc<ImageView>> {
Some(self.view.clone())
fn render(
&mut self,
_app: &mut AppState,
_tgt: Arc<ImageView>,
_buf: &mut CommandBuffers,
_alpha: f32,
) -> anyhow::Result<bool> {
Ok(false)
}
fn frame_transform(&mut self) -> Option<FrameTransform> {
Some(FrameTransform {
fn frame_meta(&mut self) -> Option<FrameMeta> {
Some(FrameMeta {
extent: self.view.image().extent(),
..Default::default()
})
+6 -6
View File
@@ -21,7 +21,7 @@ pub(super) fn install_manifest(app_mgr: &mut ApplicationsManager) -> anyhow::Res
.ok_or_else(|| anyhow::anyhow!("Invalid executable path"))?,
};
if let Ok(true) = app_mgr.is_application_installed(APP_KEY) {
if app_mgr.is_application_installed(APP_KEY) == Ok(true) {
if let Ok(mut file) = File::open(&manifest_path) {
let mut buf = String::new();
if file.read_to_string(&mut buf).is_ok() {
@@ -62,15 +62,15 @@ pub(super) fn install_manifest(app_mgr: &mut ApplicationsManager) -> anyhow::Res
manifest_path,
e
);
};
}
if let Err(e) = app_mgr.add_application_manifest(&manifest_path, false) {
bail!("Failed to add manifest to OpenVR: {}", e.description());
};
}
if let Err(e) = app_mgr.set_application_auto_launch(APP_KEY, true) {
bail!("Failed to set auto launch: {}", e.description());
};
}
Ok(())
}
@@ -78,10 +78,10 @@ pub(super) fn install_manifest(app_mgr: &mut ApplicationsManager) -> anyhow::Res
pub(super) fn uninstall_manifest(app_mgr: &mut ApplicationsManager) -> anyhow::Result<()> {
let manifest_path = config_io::get_config_root().join("wlx-overlay-s.vrmanifest");
if let Ok(true) = app_mgr.is_application_installed(APP_KEY) {
if app_mgr.is_application_installed(APP_KEY) == Ok(true) {
if let Err(e) = app_mgr.remove_application_manifest(&manifest_path) {
bail!("Failed to remove manifest from OpenVR: {}", e.description());
};
}
log::info!("Uninstalled manifest");
}
Ok(())
+36 -22
View File
@@ -13,11 +13,7 @@ use ovr_overlay::{
sys::{ETrackedDeviceProperty, EVRApplicationType, EVREventType},
TrackedDeviceIndex,
};
use vulkano::{
device::{physical::PhysicalDevice, DeviceExtensions},
instance::InstanceExtensions,
Handle, VulkanObject,
};
use vulkano::{device::physical::PhysicalDevice, Handle, VulkanObject};
use crate::{
backend::{
@@ -31,10 +27,10 @@ use crate::{
manifest::{install_manifest, uninstall_manifest},
overlay::OpenVrOverlayData,
},
overlay::OverlayData,
overlay::{OverlayData, ShouldRender},
task::{SystemTask, TaskType},
},
graphics::WlxGraphics,
graphics::{CommandBuffers, WlxGraphics},
overlays::{
toast::{Toast, ToastTopic},
watch::{watch_fade, WATCH_NAME},
@@ -65,6 +61,7 @@ pub fn openvr_uninstall() {
let _ = uninstall_manifest(&mut app_mgr);
}
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
pub fn openvr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(), BackendError> {
let app_type = EVRApplicationType::VRApplication_Overlay;
let Ok(context) = ovr_overlay::Context::init(app_type) else {
@@ -84,14 +81,13 @@ pub fn openvr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
let device_extensions_fn = |device: &PhysicalDevice| {
let names = compositor_mgr.get_vulkan_device_extensions_required(device.handle().as_raw());
let ext = DeviceExtensions::from_iter(names.iter().map(|s| s.as_str()));
ext
names.iter().map(std::string::String::as_str).collect()
};
let mut compositor_mngr = context.compositor_mngr();
let mut compositor_mgr = context.compositor_mngr();
let instance_extensions = {
let names = compositor_mngr.get_vulkan_instance_extensions_required();
InstanceExtensions::from_iter(names.iter().map(|s| s.as_str()))
let names = compositor_mgr.get_vulkan_instance_extensions_required();
names.iter().map(std::string::String::as_str).collect()
};
let mut state = {
@@ -103,7 +99,7 @@ pub fn openvr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
state.tasks.enqueue_at(
TaskType::System(SystemTask::ShowHide),
Instant::now().add(Duration::from_secs(1)),
)
);
}
if let Ok(ipd) = system_mgr.get_tracked_device_property::<f32>(
@@ -137,7 +133,7 @@ pub fn openvr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
)));
};
log::info!("HMD running @ {} Hz", refresh_rate);
log::info!("HMD running @ {refresh_rate} Hz");
let watch_id = overlays.get_by_name(WATCH_NAME).unwrap().state.id; // want panic
@@ -188,7 +184,7 @@ pub fn openvr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
Toast::new(
ToastTopic::IpdChange,
"IPD".into(),
format!("{:.1} mm", ipd).into(),
format!("{ipd:.1} mm").into(),
)
.submit(&mut state);
}
@@ -209,18 +205,17 @@ pub fn openvr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
state.tasks.retrieve_due(&mut due_tasks);
let mut removed_overlays = overlays.update(&mut state)?;
for o in removed_overlays.iter_mut() {
for o in &mut removed_overlays {
o.destroy(&mut overlay_mgr);
}
while let Some(task) = due_tasks.pop_front() {
match task {
TaskType::Global(f) => f(&mut state),
TaskType::Overlay(sel, f) => {
if let Some(o) = overlays.mut_by_selector(&sel) {
f(&mut state, &mut o.state);
} else {
log::warn!("Overlay not found for task: {:?}", sel);
log::warn!("Overlay not found for task: {sel:?}");
}
}
TaskType::CreateOverlay(sel, f) => {
@@ -316,11 +311,12 @@ pub fn openvr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
&state.input_state.hmd,
);
if let Some(haptics) = haptics {
input_source.haptics(&mut input_mgr, idx, haptics)
input_source.haptics(&mut input_mgr, idx, haptics);
}
}
state.hid_provider.commit();
let mut buffers = CommandBuffers::default();
lines.update(universe.clone(), &mut overlay_mgr, &mut state)?;
@@ -331,25 +327,43 @@ pub fn openvr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
#[cfg(feature = "osc")]
if let Some(ref mut sender) = state.osc_sender {
let _ = sender.send_params(&overlays, &state.input_state.devices);
};
}
#[cfg(feature = "wayvr")]
if let Err(e) =
crate::overlays::wayvr::tick_events::<OpenVrOverlayData>(&mut state, &mut overlays)
{
log::error!("WayVR tick_events failed: {:?}", e);
log::error!("WayVR tick_events failed: {e:?}");
}
log::trace!("Rendering frame");
for o in overlays.iter_mut() {
if o.state.want_visible {
o.render(&mut state)?;
let ShouldRender::Should = o.should_render(&mut state)? else {
continue;
};
if !o.ensure_image_allocated(&mut state)? {
continue;
}
o.data.image_dirty = o.render(
&mut state,
o.data.image_view.as_ref().unwrap().clone(),
&mut buffers,
1.0, // alpha is instead set using OVR API
)?;
}
}
log::trace!("Rendering overlays");
if let Some(mut future) = buffers.execute_now(state.graphics.graphics_queue.clone())? {
if let Err(e) = future.flush() {
return Err(BackendError::Fatal(e.into()));
}
future.cleanup_finished();
}
overlays
.iter_mut()
.for_each(|o| o.after_render(universe.clone(), &mut overlay_mgr, &state.graphics));
+31 -21
View File
@@ -1,4 +1,5 @@
use core::f32;
use std::sync::Arc;
use glam::Vec4;
use ovr_overlay::{
@@ -6,25 +7,21 @@ use ovr_overlay::{
pose::Matrix3x4,
sys::{ETrackingUniverseOrigin, VRVulkanTextureData_t},
};
use vulkano::{Handle, VulkanObject};
use vulkano::{image::view::ImageView, Handle, VulkanObject};
use crate::{
backend::overlay::{OverlayData, RelativeTo},
graphics::WlxGraphics,
state::AppState,
};
use crate::{backend::overlay::OverlayData, graphics::WlxGraphics, state::AppState};
use super::helpers::Affine3AConvert;
#[derive(Default)]
pub(super) struct OpenVrOverlayData {
pub(super) handle: Option<OverlayHandle>,
pub(super) last_image: Option<u64>,
pub(super) visible: bool,
pub(super) color: Vec4,
pub(crate) width: f32,
pub(super) override_width: bool,
pub(super) relative_to: RelativeTo,
pub(super) image_view: Option<Arc<ImageView>>,
pub(super) image_dirty: bool,
}
impl OverlayData<OpenVrOverlayData> {
@@ -38,7 +35,7 @@ impl OverlayData<OpenVrOverlayData> {
let handle = match overlay.create_overlay(&key, &key) {
Ok(handle) => handle,
Err(e) => {
panic!("Failed to create overlay: {}", e);
panic!("Failed to create overlay: {e}");
}
};
log::debug!("{}: initialize", self.state.name);
@@ -61,6 +58,22 @@ impl OverlayData<OpenVrOverlayData> {
Ok(handle)
}
pub(super) fn ensure_image_allocated(&mut self, app: &mut AppState) -> anyhow::Result<bool> {
if self.data.image_view.is_some() {
return Ok(true);
}
let Some(meta) = self.backend.frame_meta() else {
return Ok(false);
};
let image = app.graphics.render_texture(
meta.extent[0],
meta.extent[1],
app.graphics.native_format,
)?;
self.data.image_view = Some(ImageView::new_default(image)?);
Ok(true)
}
pub(super) fn after_input(
&mut self,
overlay: &mut OverlayManager,
@@ -200,7 +213,7 @@ impl OverlayData<OpenVrOverlayData> {
let effective = self.state.transform
* self
.backend
.frame_transform()
.frame_meta()
.map(|f| f.transform)
.unwrap_or_default();
@@ -221,21 +234,17 @@ impl OverlayData<OpenVrOverlayData> {
return;
};
let Some(view) = self.backend.view() else {
let Some(view) = self.data.image_view.as_ref() else {
log::debug!("{}: Not rendered", self.state.name);
return;
};
let image = view.image().clone();
let raw_image = image.handle().as_raw();
if let Some(last_image) = self.data.last_image {
if last_image == raw_image {
return;
}
if !self.data.image_dirty {
return;
}
self.data.image_dirty = false;
let image = view.image().clone();
let dimensions = image.extent();
if !self.data.override_width {
let new_width = ((dimensions[0] as f32) / (dimensions[1] as f32)).min(1.0);
@@ -246,6 +255,7 @@ impl OverlayData<OpenVrOverlayData> {
}
}
let raw_image = image.handle().as_raw();
let format = image.format();
let mut texture = VRVulkanTextureData_t {
@@ -257,8 +267,8 @@ impl OverlayData<OpenVrOverlayData> {
m_pDevice: graphics.device.handle().as_raw() as *mut _,
m_pPhysicalDevice: graphics.device.physical_device().handle().as_raw() as *mut _,
m_pInstance: graphics.instance.handle().as_raw() as *mut _,
m_pQueue: graphics.queue.handle().as_raw() as *mut _,
m_nQueueFamilyIndex: graphics.queue.queue_family_index(),
m_pQueue: graphics.graphics_queue.handle().as_raw() as *mut _,
m_nQueueFamilyIndex: graphics.graphics_queue.queue_family_index(),
};
log::trace!(
"{}: UploadTex {:?}, {}x{}, {:?}",
+11 -10
View File
@@ -25,7 +25,7 @@ pub(super) struct PlayspaceMover {
}
impl PlayspaceMover {
pub fn new() -> Self {
pub const fn new() -> Self {
Self {
universe: ETrackingUniverseOrigin::TrackingUniverseRawAndUncalibrated,
drag: None,
@@ -33,6 +33,7 @@ impl PlayspaceMover {
}
}
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
pub fn update(
&mut self,
chaperone_mgr: &mut ChaperoneSetupManager,
@@ -54,8 +55,8 @@ impl PlayspaceMover {
let dq = new_hand * data.hand_pose.conjugate();
let rel_y = f32::atan2(
2.0 * (dq.y * dq.w + dq.x * dq.z),
(2.0 * (dq.w * dq.w + dq.x * dq.x)) - 1.0,
2.0 * dq.y.mul_add(dq.w, dq.x * dq.z),
2.0f32.mul_add(dq.w.mul_add(dq.w, dq.x * dq.x), -1.0),
);
let mut space_transform = Affine3A::from_rotation_y(rel_y);
@@ -241,7 +242,7 @@ impl PlayspaceMover {
}
}
fn universe_str(universe: &ETrackingUniverseOrigin) -> &'static str {
const fn universe_str(universe: &ETrackingUniverseOrigin) -> &'static str {
match universe {
ETrackingUniverseOrigin::TrackingUniverseSeated => "Seated",
ETrackingUniverseOrigin::TrackingUniverseStanding => "Standing",
@@ -271,31 +272,31 @@ fn set_working_copy(
let mat = HmdMatrix34_t::from_affine(mat);
match universe {
ETrackingUniverseOrigin::TrackingUniverseStanding => {
chaperone_mgr.set_working_standing_zero_pose_to_raw_tracking_pose(&mat)
chaperone_mgr.set_working_standing_zero_pose_to_raw_tracking_pose(&mat);
}
_ => chaperone_mgr.set_working_seated_zero_pose_to_raw_tracking_pose(&mat),
};
}
}
fn apply_chaperone_offset(offset: Vec3A, chaperone_mgr: &mut ChaperoneSetupManager) {
let mut quads = chaperone_mgr.get_live_collision_bounds_info();
quads.iter_mut().for_each(|quad| {
for quad in &mut quads {
quad.vCorners.iter_mut().for_each(|corner| {
corner.v[0] += offset.x;
corner.v[2] += offset.z;
});
});
}
chaperone_mgr.set_working_collision_bounds_info(quads.as_mut_slice());
}
fn apply_chaperone_transform(transform: Affine3A, chaperone_mgr: &mut ChaperoneSetupManager) {
let mut quads = chaperone_mgr.get_live_collision_bounds_info();
quads.iter_mut().for_each(|quad| {
for quad in &mut quads {
quad.vCorners.iter_mut().for_each(|corner| {
let coord = transform.transform_point3a(Vec3A::from_slice(&corner.v));
corner.v[0] = coord.x;
corner.v[2] = coord.z;
});
});
}
chaperone_mgr.set_working_collision_bounds_info(quads.as_mut_slice());
}
+73
View File
@@ -0,0 +1,73 @@
use libmonado::{ClientState, Monado};
use log::{info, warn};
use crate::{backend::overlay::OverlayID, state::AppState};
pub(super) struct InputBlocker {
hovered_last_frame: bool,
}
impl InputBlocker {
pub const fn new() -> Self {
Self {
hovered_last_frame: false,
}
}
pub fn update(&mut self, state: &AppState, watch_id: OverlayID, monado: &mut Monado) {
if !state.session.config.block_game_input {
return;
}
let any_hovered = state.input_state.pointers.iter().any(|p| {
p.interaction.hovered_id.is_some_and(|id| {
id != watch_id || !state.session.config.block_game_input_ignore_watch
})
});
match (any_hovered, self.hovered_last_frame) {
(true, false) => {
info!("Blocking input");
set_clients_io_active(monado, false);
}
(false, true) => {
info!("Unblocking input");
set_clients_io_active(monado, true);
}
_ => {}
}
self.hovered_last_frame = any_hovered;
}
}
fn set_clients_io_active(monado: &mut Monado, active: bool) {
match monado.clients() {
Ok(clients) => {
for mut client in clients {
let name = match client.name() {
Ok(n) => n,
Err(e) => {
warn!("Failed to get client name: {e}");
continue;
}
};
let state = match client.state() {
Ok(s) => s,
Err(e) => {
warn!("Failed to get client state: {e}");
continue;
}
};
if name != "wlx-overlay-s" && state.contains(ClientState::ClientSessionVisible) {
if let Err(e) = client.set_io_active(active) {
warn!("Failed to set io active for client: {e}");
}
}
}
}
Err(e) => warn!("Failed to get clients from Monado: {e}"),
}
}
+5 -5
View File
@@ -1,6 +1,6 @@
use anyhow::{bail, ensure};
use glam::{Affine3A, Quat, Vec3, Vec3A};
use openxr as xr;
use openxr::{self as xr, SessionCreateFlags};
use xr::OverlaySessionCreateFlagsEXTX;
pub(super) fn init_xr() -> Result<(xr::Instance, xr::SystemId), anyhow::Error> {
@@ -110,7 +110,7 @@ pub(super) unsafe fn create_overlay_session(
};
let binding = xr::sys::GraphicsBindingVulkanKHR {
ty: xr::sys::GraphicsBindingVulkanKHR::TYPE,
next: &overlay as *const _ as *const _,
next: (&raw const overlay).cast(),
instance: info.instance,
physical_device: info.physical_device,
device: info.device,
@@ -119,8 +119,8 @@ pub(super) unsafe fn create_overlay_session(
};
let info = xr::sys::SessionCreateInfo {
ty: xr::sys::SessionCreateInfo::TYPE,
next: &binding as *const _ as *const _,
create_flags: Default::default(),
next: (&raw const binding).cast(),
create_flags: SessionCreateFlags::default(),
system_id: system,
};
let mut out = xr::sys::Session::NULL;
@@ -139,7 +139,7 @@ pub(super) fn ipd_from_views(views: &[xr::View]) -> f32 {
let p0: Vec3 = Vec3M::from(views[0].pose.position).into();
let p1: Vec3 = Vec3M::from(views[1].pose.position).into();
(p0.distance(p1) * 1000.0).round() * 0.1
(p0.distance(p1) * 10000.0).round() * 0.1
}
pub(super) fn transform_to_norm_quat(transform: &Affine3A) -> Quat {
+133 -155
View File
@@ -6,7 +6,7 @@ use std::{
use glam::{bool, Affine3A, Quat, Vec3};
use libmonado as mnd;
use openxr::{self as xr, Quaternionf, Vector3f};
use openxr::{self as xr, Quaternionf, Vector2f, Vector3f};
use serde::{Deserialize, Serialize};
use crate::{
@@ -17,16 +17,12 @@ use crate::{
use super::{helpers::posef_to_transform, XrState};
type XrSession = xr::Session<xr::Vulkan>;
static CLICK_TIMES: [Duration; 3] = [
Duration::ZERO,
Duration::from_millis(500),
Duration::from_millis(750),
];
pub(super) struct OpenXrAction {}
pub(super) struct OpenXrInputSource {
action_set: xr::ActionSet,
hands: [OpenXrHand; 2],
@@ -48,7 +44,7 @@ pub struct MultiClickHandler<const COUNT: usize> {
impl<const COUNT: usize> MultiClickHandler<COUNT> {
fn new(action_set: &xr::ActionSet, action_name: &str, side: &str) -> anyhow::Result<Self> {
let name = format!("{}_{}-{}", side, COUNT, action_name);
let name = format!("{side}_{COUNT}-{action_name}");
let name_f32 = format!("{}_value", &name);
let action_bool = action_set.create_action::<bool>(&name, &name, &[])?;
@@ -97,10 +93,10 @@ impl<const COUNT: usize> MultiClickHandler<COUNT> {
self.held_inactive = false;
// reset to no prior clicks
let long_ago = Instant::now() - Duration::from_secs(10);
let long_ago = Instant::now().checked_sub(Duration::from_secs(10)).unwrap();
self.previous
.iter_mut()
.for_each(|instant| *instant = long_ago)
.for_each(|instant| *instant = long_ago);
} else if COUNT > 0 {
log::trace!("{}: rotate", self.name);
self.previous.rotate_right(1);
@@ -149,20 +145,20 @@ impl CustomClickAction {
}
pub(super) struct OpenXrHandSource {
action_pose: xr::Action<xr::Posef>,
action_click: CustomClickAction,
action_grab: CustomClickAction,
action_alt_click: CustomClickAction,
action_show_hide: CustomClickAction,
action_toggle_dashboard: CustomClickAction,
action_space_drag: CustomClickAction,
action_space_rotate: CustomClickAction,
action_space_reset: CustomClickAction,
action_modifier_right: CustomClickAction,
action_modifier_middle: CustomClickAction,
action_move_mouse: CustomClickAction,
action_scroll: xr::Action<f32>,
action_haptics: xr::Action<xr::Haptic>,
pose: xr::Action<xr::Posef>,
click: CustomClickAction,
grab: CustomClickAction,
alt_click: CustomClickAction,
show_hide: CustomClickAction,
toggle_dashboard: CustomClickAction,
space_drag: CustomClickAction,
space_rotate: CustomClickAction,
space_reset: CustomClickAction,
modifier_right: CustomClickAction,
modifier_middle: CustomClickAction,
move_mouse: CustomClickAction,
scroll: xr::Action<Vector2f>,
haptics: xr::Action<xr::Haptic>,
}
impl OpenXrInputSource {
@@ -175,7 +171,7 @@ impl OpenXrInputSource {
let left_source = OpenXrHandSource::new(&mut action_set, "left")?;
let right_source = OpenXrHandSource::new(&mut action_set, "right")?;
suggest_bindings(&xr.instance, &[&left_source, &right_source])?;
suggest_bindings(&xr.instance, &[&left_source, &right_source]);
xr.session.attach_action_sets(&[&action_set])?;
@@ -189,9 +185,9 @@ impl OpenXrInputSource {
}
pub fn haptics(&self, xr: &XrState, hand: usize, haptics: &Haptics) {
let action = &self.hands[hand].source.action_haptics;
let action = &self.hands[hand].source.haptics;
let duration_nanos = (haptics.duration as f64) * 1_000_000_000.0;
let duration_nanos = f64::from(haptics.duration) * 1_000_000_000.0;
let _ = action.apply_feedback(
&xr.session,
@@ -251,7 +247,7 @@ impl OpenXrInputSource {
}
}
pub fn update_devices(&mut self, app: &mut AppState, monado: &mut mnd::Monado) {
pub fn update_devices(app: &mut AppState, monado: &mut mnd::Monado) {
app.input_state.devices.clear();
let roles = [
@@ -293,8 +289,8 @@ impl OpenXrInputSource {
}
app.input_state.devices.sort_by(|a, b| {
(a.soc.is_none() as u8)
.cmp(&(b.soc.is_none() as u8))
u8::from(a.soc.is_none())
.cmp(&u8::from(b.soc.is_none()))
.then((a.role as u8).cmp(&(b.role as u8)))
.then(a.soc.unwrap_or(999.).total_cmp(&b.soc.unwrap_or(999.)))
});
@@ -303,11 +299,10 @@ impl OpenXrInputSource {
impl OpenXrHand {
pub(super) fn new(xr: &XrState, source: OpenXrHandSource) -> Result<Self, xr::sys::Result> {
let space = source.action_pose.create_space(
xr.session.clone(),
xr::Path::NULL,
xr::Posef::IDENTITY,
)?;
let space =
source
.pose
.create_space(xr.session.clone(), xr::Path::NULL, xr::Posef::IDENTITY)?;
Ok(Self { source, space })
}
@@ -340,68 +335,62 @@ impl OpenXrHand {
);
}
pointer.now.click = self
.source
.action_click
.state(pointer.before.click, xr, session)?;
pointer.now.click = self.source.click.state(pointer.before.click, xr, session)?;
pointer.now.grab = self
.source
.action_grab
.state(pointer.before.grab, xr, session)?;
pointer.now.grab = self.source.grab.state(pointer.before.grab, xr, session)?;
pointer.now.scroll = self
let scroll = self
.source
.action_scroll
.scroll
.state(&xr.session, xr::Path::NULL)?
.current_state;
pointer.now.scroll_x = scroll.x;
pointer.now.scroll_y = scroll.y;
pointer.now.alt_click =
self.source
.action_alt_click
.alt_click
.state(pointer.before.alt_click, xr, session)?;
pointer.now.show_hide =
self.source
.action_show_hide
.show_hide
.state(pointer.before.show_hide, xr, session)?;
pointer.now.click_modifier_right = self.source.action_modifier_right.state(
pointer.before.click_modifier_right,
xr,
session,
)?;
pointer.now.click_modifier_right =
self.source
.modifier_right
.state(pointer.before.click_modifier_right, xr, session)?;
pointer.now.toggle_dashboard = self.source.action_toggle_dashboard.state(
pointer.before.toggle_dashboard,
xr,
session,
)?;
pointer.now.toggle_dashboard =
self.source
.toggle_dashboard
.state(pointer.before.toggle_dashboard, xr, session)?;
pointer.now.click_modifier_middle = self.source.action_modifier_middle.state(
pointer.before.click_modifier_middle,
xr,
session,
)?;
pointer.now.click_modifier_middle =
self.source
.modifier_middle
.state(pointer.before.click_modifier_middle, xr, session)?;
pointer.now.move_mouse =
self.source
.action_move_mouse
.move_mouse
.state(pointer.before.move_mouse, xr, session)?;
pointer.now.space_drag =
self.source
.action_space_drag
.space_drag
.state(pointer.before.space_drag, xr, session)?;
pointer.now.space_rotate =
self.source
.action_space_rotate
.space_rotate
.state(pointer.before.space_rotate, xr, session)?;
pointer.now.space_reset =
self.source
.action_space_reset
.space_reset
.state(pointer.before.space_reset, xr, session)?;
Ok(())
@@ -412,76 +401,66 @@ impl OpenXrHand {
impl OpenXrHandSource {
pub(super) fn new(action_set: &mut xr::ActionSet, side: &str) -> anyhow::Result<Self> {
let action_pose = action_set.create_action::<xr::Posef>(
&format!("{}_hand", side),
&format!("{} hand pose", side),
&format!("{side}_hand"),
&format!("{side} hand pose"),
&[],
)?;
let action_scroll = action_set.create_action::<f32>(
&format!("{}_scroll", side),
&format!("{} hand scroll", side),
let action_scroll = action_set.create_action::<Vector2f>(
&format!("{side}_scroll"),
&format!("{side} hand scroll"),
&[],
)?;
let action_haptics = action_set.create_action::<xr::Haptic>(
&format!("{}_haptics", side),
&format!("{} hand haptics", side),
&format!("{side}_haptics"),
&format!("{side} hand haptics"),
&[],
)?;
Ok(Self {
action_pose,
action_click: CustomClickAction::new(action_set, "click", side)?,
action_grab: CustomClickAction::new(action_set, "grab", side)?,
action_scroll,
action_alt_click: CustomClickAction::new(action_set, "alt_click", side)?,
action_show_hide: CustomClickAction::new(action_set, "show_hide", side)?,
action_toggle_dashboard: CustomClickAction::new(action_set, "toggle_dashboard", side)?,
action_space_drag: CustomClickAction::new(action_set, "space_drag", side)?,
action_space_rotate: CustomClickAction::new(action_set, "space_rotate", side)?,
action_space_reset: CustomClickAction::new(action_set, "space_reset", side)?,
action_modifier_right: CustomClickAction::new(
action_set,
"click_modifier_right",
side,
)?,
action_modifier_middle: CustomClickAction::new(
action_set,
"click_modifier_middle",
side,
)?,
action_move_mouse: CustomClickAction::new(action_set, "move_mouse", side)?,
action_haptics,
pose: action_pose,
click: CustomClickAction::new(action_set, "click", side)?,
grab: CustomClickAction::new(action_set, "grab", side)?,
scroll: action_scroll,
alt_click: CustomClickAction::new(action_set, "alt_click", side)?,
show_hide: CustomClickAction::new(action_set, "show_hide", side)?,
toggle_dashboard: CustomClickAction::new(action_set, "toggle_dashboard", side)?,
space_drag: CustomClickAction::new(action_set, "space_drag", side)?,
space_rotate: CustomClickAction::new(action_set, "space_rotate", side)?,
space_reset: CustomClickAction::new(action_set, "space_reset", side)?,
modifier_right: CustomClickAction::new(action_set, "click_modifier_right", side)?,
modifier_middle: CustomClickAction::new(action_set, "click_modifier_middle", side)?,
move_mouse: CustomClickAction::new(action_set, "move_mouse", side)?,
haptics: action_haptics,
})
}
}
fn to_path(maybe_path_str: &Option<String>, instance: &xr::Instance) -> Option<xr::Path> {
maybe_path_str
.as_ref()
.and_then(|s| match instance.string_to_path(s) {
Ok(path) => Some(path),
Err(_) => {
log::warn!("Invalid binding path: {}", s);
None
}
})
fn to_path(maybe_path_str: Option<&String>, instance: &xr::Instance) -> Option<xr::Path> {
maybe_path_str.as_ref().and_then(|s| {
instance
.string_to_path(s)
.inspect_err(|_| {
log::warn!("Invalid binding path: {s}");
})
.ok()
})
}
fn is_bool(maybe_type_str: &Option<String>) -> bool {
fn is_bool(maybe_type_str: Option<&String>) -> bool {
maybe_type_str
.as_ref()
.unwrap() // want panic
.split('/')
.last()
.map(|last| matches!(last, "click" | "touch"))
.unwrap_or(false)
.next_back()
.is_some_and(|last| matches!(last, "click" | "touch"))
}
macro_rules! add_custom {
($action:expr, $left:expr, $right:expr, $bindings:expr, $instance:expr) => {
if let Some(action) = $action.as_ref() {
if let Some(p) = to_path(&action.left, $instance) {
if is_bool(&action.left) {
if let Some(p) = to_path(action.left.as_ref(), $instance) {
if is_bool(action.left.as_ref()) {
if action.triple_click.unwrap_or(false) {
$bindings.push(xr::Binding::new(&$left.triple.action_bool, p));
} else if action.double_click.unwrap_or(false) {
@@ -499,8 +478,8 @@ macro_rules! add_custom {
}
}
}
if let Some(p) = to_path(&action.right, $instance) {
if is_bool(&action.right) {
if let Some(p) = to_path(action.right.as_ref(), $instance) {
if is_bool(action.right.as_ref()) {
if action.triple_click.unwrap_or(false) {
$bindings.push(xr::Binding::new(&$right.triple.action_bool, p));
} else if action.double_click.unwrap_or(false) {
@@ -522,8 +501,9 @@ macro_rules! add_custom {
};
}
fn suggest_bindings(instance: &xr::Instance, hands: &[&OpenXrHandSource; 2]) -> anyhow::Result<()> {
let profiles = load_action_profiles()?;
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
fn suggest_bindings(instance: &xr::Instance, hands: &[&OpenXrHandSource; 2]) {
let profiles = load_action_profiles();
for profile in profiles {
let Ok(profile_path) = instance.string_to_path(&profile.profile) else {
@@ -534,116 +514,116 @@ fn suggest_bindings(instance: &xr::Instance, hands: &[&OpenXrHandSource; 2]) ->
let mut bindings: Vec<xr::Binding> = vec![];
if let Some(action) = profile.pose {
if let Some(p) = to_path(&action.left, instance) {
bindings.push(xr::Binding::new(&hands[0].action_pose, p));
if let Some(p) = to_path(action.left.as_ref(), instance) {
bindings.push(xr::Binding::new(&hands[0].pose, p));
}
if let Some(p) = to_path(&action.right, instance) {
bindings.push(xr::Binding::new(&hands[1].action_pose, p));
if let Some(p) = to_path(action.right.as_ref(), instance) {
bindings.push(xr::Binding::new(&hands[1].pose, p));
}
}
if let Some(action) = profile.haptic {
if let Some(p) = to_path(&action.left, instance) {
bindings.push(xr::Binding::new(&hands[0].action_haptics, p));
if let Some(p) = to_path(action.left.as_ref(), instance) {
bindings.push(xr::Binding::new(&hands[0].haptics, p));
}
if let Some(p) = to_path(&action.right, instance) {
bindings.push(xr::Binding::new(&hands[1].action_haptics, p));
if let Some(p) = to_path(action.right.as_ref(), instance) {
bindings.push(xr::Binding::new(&hands[1].haptics, p));
}
}
if let Some(action) = profile.scroll {
if let Some(p) = to_path(&action.left, instance) {
bindings.push(xr::Binding::new(&hands[0].action_scroll, p));
if let Some(p) = to_path(action.left.as_ref(), instance) {
bindings.push(xr::Binding::new(&hands[0].scroll, p));
}
if let Some(p) = to_path(&action.right, instance) {
bindings.push(xr::Binding::new(&hands[1].action_scroll, p));
if let Some(p) = to_path(action.right.as_ref(), instance) {
bindings.push(xr::Binding::new(&hands[1].scroll, p));
}
}
add_custom!(
profile.click,
hands[0].action_click,
hands[1].action_click,
hands[0].click,
hands[1].click,
bindings,
instance
);
add_custom!(
profile.alt_click,
&hands[0].action_alt_click,
&hands[1].action_alt_click,
&hands[0].alt_click,
&hands[1].alt_click,
bindings,
instance
);
add_custom!(
profile.grab,
&hands[0].action_grab,
&hands[1].action_grab,
&hands[0].grab,
&hands[1].grab,
bindings,
instance
);
add_custom!(
profile.show_hide,
&hands[0].action_show_hide,
&hands[1].action_show_hide,
&hands[0].show_hide,
&hands[1].show_hide,
bindings,
instance
);
add_custom!(
profile.toggle_dashboard,
&hands[0].action_toggle_dashboard,
&hands[1].action_toggle_dashboard,
&hands[0].toggle_dashboard,
&hands[1].toggle_dashboard,
bindings,
instance
);
add_custom!(
profile.space_drag,
&hands[0].action_space_drag,
&hands[1].action_space_drag,
&hands[0].space_drag,
&hands[1].space_drag,
bindings,
instance
);
add_custom!(
profile.space_rotate,
&hands[0].action_space_rotate,
&hands[1].action_space_rotate,
&hands[0].space_rotate,
&hands[1].space_rotate,
bindings,
instance
);
add_custom!(
profile.space_reset,
&hands[0].action_space_reset,
&hands[1].action_space_reset,
&hands[0].space_reset,
&hands[1].space_reset,
bindings,
instance
);
add_custom!(
profile.click_modifier_right,
&hands[0].action_modifier_right,
&hands[1].action_modifier_right,
&hands[0].modifier_right,
&hands[1].modifier_right,
bindings,
instance
);
add_custom!(
profile.click_modifier_middle,
&hands[0].action_modifier_middle,
&hands[1].action_modifier_middle,
&hands[0].modifier_middle,
&hands[1].modifier_middle,
bindings,
instance
);
add_custom!(
profile.move_mouse,
&hands[0].action_move_mouse,
&hands[1].action_move_mouse,
&hands[0].move_mouse,
&hands[1].move_mouse,
bindings,
instance
);
@@ -656,8 +636,6 @@ fn suggest_bindings(instance: &xr::Instance, hands: &[&OpenXrHandSource; 2]) ->
log::error!("Verify config: ~/.config/wlxoverlay/openxr_actions.json5");
}
}
Ok(())
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -690,26 +668,26 @@ struct OpenXrActionConfProfile {
const DEFAULT_PROFILES: &str = include_str!("openxr_actions.json5");
fn load_action_profiles() -> anyhow::Result<Vec<OpenXrActionConfProfile>> {
fn load_action_profiles() -> Vec<OpenXrActionConfProfile> {
let mut profiles: Vec<OpenXrActionConfProfile> =
serde_json5::from_str(DEFAULT_PROFILES).unwrap(); // want panic
let Some(conf) = config_io::load("openxr_actions.json5") else {
return Ok(profiles);
return profiles;
};
match serde_json5::from_str::<Vec<OpenXrActionConfProfile>>(&conf) {
Ok(override_profiles) => {
override_profiles.into_iter().for_each(|new| {
for new in override_profiles {
if let Some(i) = profiles.iter().position(|old| old.profile == new.profile) {
profiles[i] = new;
}
});
}
}
Err(e) => {
log::error!("Failed to load openxr_actions.json5: {}", e);
log::error!("Failed to load openxr_actions.json5: {e}");
}
}
Ok(profiles)
profiles
}
+66 -50
View File
@@ -9,55 +9,53 @@ use std::{
},
};
use vulkano::{command_buffer::CommandBufferUsage, format::Format, image::view::ImageView};
use vulkano::command_buffer::CommandBufferUsage;
use crate::{
backend::openxr::helpers,
graphics::{WlxCommandBuffer, WlxGraphics},
graphics::{CommandBuffers, WlxGraphics, WlxPipeline},
};
use super::{
swapchain::{create_swapchain_render_data, SwapchainOpts, SwapchainRenderData},
swapchain::{create_swapchain, SwapchainOpts, WlxSwapchain},
CompositionLayer, XrState,
};
static LINE_AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(1);
pub(super) const LINE_WIDTH: f32 = 0.002;
// TODO customizable colors
static COLORS: [[f32; 6]; 5] = {
[
[1., 1., 1., 1., 0., 0.],
[0., 0.375, 0.5, 1., 0., 0.],
[0.69, 0.188, 0., 1., 0., 0.],
[0.375, 0., 0.5, 1., 0., 0.],
[1., 0., 0., 1., 0., 0.],
]
};
pub(super) struct LinePool {
lines: IdMap<usize, LineContainer>,
colors: Vec<Arc<ImageView>>,
pipeline: Arc<WlxPipeline>,
}
impl LinePool {
pub(super) fn new(graphics: Arc<WlxGraphics>) -> anyhow::Result<Self> {
let mut command_buffer =
graphics.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
let Ok(shaders) = graphics.shared_shaders.read() else {
anyhow::bail!("Failed to lock shared shaders for reading");
};
// TODO customizable colors
let colors = [
[0xff, 0xff, 0xff, 0xff],
[0x00, 0x60, 0x80, 0xff],
[0xB0, 0x30, 0x00, 0xff],
[0x60, 0x00, 0x80, 0xff],
[0xff, 0x00, 0x00, 0xff],
];
let pipeline = graphics.create_pipeline(
shaders.get("vert_common").unwrap().clone(), // want panic
shaders.get("frag_color").unwrap().clone(), // want panic
graphics.native_format,
None,
)?;
let views: anyhow::Result<Vec<Arc<ImageView>>> = colors
.into_iter()
.map(
|color| match command_buffer.texture2d_raw(1, 1, Format::R8G8B8A8_UNORM, &color) {
Ok(tex) => ImageView::new_default(tex).map_err(|e| anyhow::anyhow!(e)),
Err(e) => Err(e),
},
)
.collect();
command_buffer.build_and_execute_now()?;
Ok(LinePool {
Ok(Self {
lines: IdMap::new(),
colors: views?,
pipeline,
})
}
@@ -68,8 +66,7 @@ impl LinePool {
) -> anyhow::Result<usize> {
let id = LINE_AUTO_INCREMENT.fetch_add(1, Ordering::Relaxed);
let srd =
create_swapchain_render_data(xr, graphics, [1, 1, 1], SwapchainOpts::new().srgb())?;
let srd = create_swapchain(xr, graphics, [1, 1, 1], SwapchainOpts::new())?;
self.lines.insert(
id,
LineContainer {
@@ -92,10 +89,10 @@ impl LinePool {
return;
}
debug_assert!(color < self.colors.len());
debug_assert!(color < COLORS.len());
let Some(line) = self.lines.get_mut(id) else {
log::warn!("Line {} not found", id);
log::warn!("Line {id} not found");
return;
};
@@ -125,28 +122,55 @@ impl LinePool {
let posef = helpers::transform_to_posef(&transform);
line.maybe_line = Some(Line {
view: self.colors[color].clone(),
color,
pose: posef,
length: len,
});
}
pub(super) fn present_xr<'a>(
pub(super) fn render(
&mut self,
graphics: Arc<WlxGraphics>,
buf: &mut CommandBuffers,
) -> anyhow::Result<()> {
for line in self.lines.values_mut() {
if let Some(inner) = line.maybe_line.as_mut() {
let tgt = line.swapchain.acquire_wait_image()?;
let set0 = self
.pipeline
.uniform_buffer(0, COLORS[inner.color].to_vec())?;
let pass = self
.pipeline
.create_pass_for_target(tgt.clone(), vec![set0])?;
let mut cmd_buffer =
graphics.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
cmd_buffer.begin_rendering(tgt)?;
cmd_buffer.run_ref(&pass)?;
cmd_buffer.end_rendering()?;
buf.push(cmd_buffer.build()?);
}
}
Ok(())
}
pub(super) fn present<'a>(
&'a mut self,
xr: &'a XrState,
command_buffer: &mut WlxCommandBuffer,
) -> anyhow::Result<Vec<CompositionLayer<'a>>> {
let mut quads = Vec::new();
for line in self.lines.values_mut() {
line.swapchain.ensure_image_released()?;
if let Some(inner) = line.maybe_line.take() {
let quad = xr::CompositionLayerQuad::new()
.pose(inner.pose)
.sub_image(line.swapchain.acquire_present_release(
command_buffer,
inner.view,
1.0,
)?)
.sub_image(line.swapchain.get_subimage())
.eye_visibility(xr::EyeVisibility::BOTH)
.space(&xr.stage)
.size(xr::Extent2Df {
@@ -160,23 +184,15 @@ impl LinePool {
Ok(quads)
}
/// the number of lines that are waiting to be drawn
pub(super) fn num_pending(&self) -> usize {
self.lines
.values()
.filter(|l| l.maybe_line.is_some())
.count()
}
}
pub(super) struct Line {
pub(super) view: Arc<ImageView>,
pub(super) color: usize,
pub(super) pose: xr::Posef,
pub(super) length: f32,
}
struct LineContainer {
swapchain: SwapchainRenderData,
swapchain: WlxSwapchain,
maybe_line: Option<Line>,
}
+149 -111
View File
@@ -9,10 +9,11 @@ use std::{
};
use glam::{Affine3A, Vec3};
use input::OpenXrInputSource;
use libmonado::Monado;
use openxr as xr;
use skybox::create_skybox;
use vulkano::{command_buffer::CommandBufferUsage, Handle, VulkanObject};
use vulkano::{Handle, VulkanObject};
use crate::{
backend::{
@@ -20,10 +21,10 @@ use crate::{
input::interact,
notifications::NotificationManager,
openxr::{lines::LinePool, overlay::OpenXrOverlayData},
overlay::OverlayData,
overlay::{OverlayData, ShouldRender},
task::{SystemTask, TaskType},
},
graphics::WlxGraphics,
graphics::{CommandBuffers, WlxGraphics},
overlays::{
toast::{Toast, ToastTopic},
watch::{watch_fade, WATCH_NAME},
@@ -34,6 +35,7 @@ use crate::{
#[cfg(feature = "wayvr")]
use crate::{gui::modular::button::WayVRAction, overlays::wayvr::wayvr_action};
mod blocker;
mod helpers;
mod input;
mod lines;
@@ -43,30 +45,28 @@ mod skybox;
mod swapchain;
const VIEW_TYPE: xr::ViewConfigurationType = xr::ViewConfigurationType::PRIMARY_STEREO;
const VIEW_COUNT: u32 = 2;
static FRAME_COUNTER: AtomicUsize = AtomicUsize::new(0);
struct XrState {
instance: xr::Instance,
system: xr::SystemId,
session: xr::Session<xr::Vulkan>,
predicted_display_time: xr::Time,
fps: f32,
stage: Arc<xr::Space>,
view: Arc<xr::Space>,
stage_offset: Affine3A,
}
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(), BackendError> {
let (xr_instance, system) = match helpers::init_xr() {
Ok((xr_instance, system)) => (xr_instance, system),
Err(e) => {
log::warn!("Will not use OpenXR: {}", e);
log::warn!("Will not use OpenXR: {e}");
return Err(BackendError::NotSupported);
}
};
let mut app_state = {
let mut app = {
let graphics = WlxGraphics::new_openxr(xr_instance.clone(), system)?;
AppState::from_graphics(graphics)?
};
@@ -74,24 +74,24 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
let environment_blend_mode = {
let modes = xr_instance.enumerate_environment_blend_modes(system, VIEW_TYPE)?;
if modes.contains(&xr::EnvironmentBlendMode::ALPHA_BLEND)
&& app_state.session.config.use_passthrough
&& app.session.config.use_passthrough
{
xr::EnvironmentBlendMode::ALPHA_BLEND
} else {
modes[0]
}
};
log::info!("Using environment blend mode: {:?}", environment_blend_mode);
log::info!("Using environment blend mode: {environment_blend_mode:?}");
if show_by_default {
app_state.tasks.enqueue_at(
app.tasks.enqueue_at(
TaskType::System(SystemTask::ShowHide),
Instant::now().add(Duration::from_secs(1)),
)
);
}
let mut overlays = OverlayContainer::<OpenXrOverlayData>::new(&mut app_state)?;
let mut lines = LinePool::new(app_state.graphics.clone())?;
let mut overlays = OverlayContainer::<OpenXrOverlayData>::new(&mut app)?;
let mut lines = LinePool::new(app.graphics.clone())?;
let mut notifications = NotificationManager::new();
notifications.run_dbus();
@@ -100,29 +100,26 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
let mut delete_queue = vec![];
let mut monado = Monado::auto_connect()
.map_err(|e| log::warn!("Will not use libmonado: {}", e))
.map_err(|e| log::warn!("Will not use libmonado: {e}"))
.ok();
let mut playspace = monado.as_mut().and_then(|m| {
playspace::PlayspaceMover::new(m)
.map_err(|e| log::warn!("Will not use Monado playspace mover: {}", e))
.map_err(|e| log::warn!("Will not use Monado playspace mover: {e}"))
.ok()
});
let mut blocker = monado.is_some().then(blocker::InputBlocker::new);
let (session, mut frame_wait, mut frame_stream) = unsafe {
let raw_session = helpers::create_overlay_session(
&xr_instance,
system,
&xr::vulkan::SessionCreateInfo {
instance: app_state.graphics.instance.handle().as_raw() as _,
physical_device: app_state
.graphics
.device
.physical_device()
.handle()
.as_raw() as _,
device: app_state.graphics.device.handle().as_raw() as _,
queue_family_index: app_state.graphics.queue.queue_family_index(),
instance: app.graphics.instance.handle().as_raw() as _,
physical_device: app.graphics.device.physical_device().handle().as_raw() as _,
device: app.graphics.device.handle().as_raw() as _,
queue_family_index: app.graphics.graphics_queue.queue_family_index(),
queue_index: 0,
},
)?;
@@ -136,24 +133,22 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
let mut xr_state = XrState {
instance: xr_instance,
system,
session,
predicted_display_time: xr::Time::from_nanos(0),
fps: 30.0,
stage: Arc::new(stage),
view: Arc::new(view),
stage_offset: Affine3A::IDENTITY,
};
let mut skybox = if environment_blend_mode == xr::EnvironmentBlendMode::OPAQUE {
create_skybox(&xr_state, &app_state)
create_skybox(&xr_state, &app)
} else {
None
};
let pointer_lines = [
lines.allocate(&xr_state, app_state.graphics.clone())?,
lines.allocate(&xr_state, app_state.graphics.clone())?,
lines.allocate(&xr_state, app.graphics.clone())?,
lines.allocate(&xr_state, app.graphics.clone())?,
];
let watch_id = overlays.get_by_name(WATCH_NAME).unwrap().state.id; // want panic
@@ -176,19 +171,18 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
if !running.load(Ordering::Relaxed) {
log::warn!("Received shutdown signal.");
match xr_state.session.request_exit() {
Ok(_) => log::info!("OpenXR session exit requested."),
Ok(()) => log::info!("OpenXR session exit requested."),
Err(xr::sys::Result::ERROR_SESSION_NOT_RUNNING) => break 'main_loop,
Err(e) => {
log::error!("Failed to request OpenXR session exit: {}", e);
log::error!("Failed to request OpenXR session exit: {e}");
break 'main_loop;
}
}
}
while let Some(event) = xr_state.instance.poll_event(&mut event_storage)? {
use xr::Event::*;
match event {
SessionStateChanged(e) => {
xr::Event::SessionStateChanged(e) => {
// Session state change is where we can begin and end sessions, as well as
// find quit messages!
log::info!("entered state {:?}", e.state());
@@ -207,22 +201,22 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
_ => {}
}
}
InstanceLossPending(_) => {
xr::Event::InstanceLossPending(_) => {
break 'main_loop;
}
EventsLost(e) => {
xr::Event::EventsLost(e) => {
log::warn!("lost {} events", e.lost_event_count());
}
MainSessionVisibilityChangedEXTX(e) => {
xr::Event::MainSessionVisibilityChangedEXTX(e) => {
if main_session_visible != e.visible() {
main_session_visible = e.visible();
log::info!("Main session visible: {}", main_session_visible);
log::info!("Main session visible: {main_session_visible}");
if main_session_visible {
log::debug!("Destroying skybox.");
skybox = None;
} else if environment_blend_mode == xr::EnvironmentBlendMode::OPAQUE {
log::debug!("Allocating skybox.");
skybox = create_skybox(&xr_state, &app_state);
skybox = create_skybox(&xr_state, &app);
}
}
}
@@ -232,7 +226,7 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
if next_device_update <= Instant::now() {
if let Some(monado) = &mut monado {
input_source.update_devices(&mut app_state, monado);
OpenXrInputSource::update_devices(&mut app, monado);
next_device_update = Instant::now() + Duration::from_secs(30);
}
}
@@ -259,8 +253,7 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
let total_elapsed = fps_counter
.front()
.map(|time| time.elapsed().as_secs_f32())
.unwrap_or(0f32);
.map_or(0f32, |time| time.elapsed().as_secs_f32());
fps_counter.len() as f32 / total_elapsed
};
@@ -274,46 +267,54 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
continue 'main_loop;
}
app_state.input_state.pre_update();
input_source.update(&xr_state, &mut app_state)?;
app_state.input_state.post_update(&app_state.session);
app.input_state.pre_update();
input_source.update(&xr_state, &mut app)?;
app.input_state.post_update(&app.session);
if app_state
if let Some(ref mut blocker) = blocker {
blocker.update(
&app,
watch_id,
monado.as_mut().unwrap(), // safe
);
}
if app
.input_state
.pointers
.iter()
.any(|p| p.now.show_hide && !p.before.show_hide)
{
overlays.show_hide(&mut app_state);
overlays.show_hide(&mut app);
}
#[cfg(feature = "wayvr")]
if app_state
if app
.input_state
.pointers
.iter()
.any(|p| p.now.toggle_dashboard && !p.before.toggle_dashboard)
{
wayvr_action(&mut app_state, &mut overlays, &WayVRAction::ToggleDashboard);
wayvr_action(&mut app, &mut overlays, &WayVRAction::ToggleDashboard);
}
watch_fade(&mut app_state, overlays.mut_by_id(watch_id).unwrap()); // want panic
watch_fade(&mut app, overlays.mut_by_id(watch_id).unwrap()); // want panic
if let Some(ref mut space_mover) = playspace {
space_mover.update(
&mut overlays,
&app_state,
&app,
monado.as_mut().unwrap(), // safe
);
}
for o in overlays.iter_mut() {
o.after_input(&mut app_state)?;
o.after_input(&mut app)?;
}
#[cfg(feature = "osc")]
if let Some(ref mut sender) = app_state.osc_sender {
let _ = sender.send_params(&overlays, &app_state.input_state.devices);
};
if let Some(ref mut sender) = app.osc_sender {
let _ = sender.send_params(&overlays, &app.input_state.devices);
}
let (_, views) = xr_state.session.locate_views(
VIEW_TYPE,
@@ -322,36 +323,36 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
)?;
let ipd = helpers::ipd_from_views(&views);
if (app_state.input_state.ipd - ipd).abs() > 0.01 {
log::info!("IPD changed: {} -> {}", app_state.input_state.ipd, ipd);
app_state.input_state.ipd = ipd;
if (app.input_state.ipd - ipd).abs() > 0.01 {
log::info!("IPD changed: {} -> {}", app.input_state.ipd, ipd);
app.input_state.ipd = ipd;
Toast::new(
ToastTopic::IpdChange,
"IPD".into(),
format!("{:.1} mm", ipd).into(),
format!("{ipd:.1} mm").into(),
)
.submit(&mut app_state);
.submit(&mut app);
}
overlays
.iter_mut()
.for_each(|o| o.state.auto_movement(&mut app_state));
.for_each(|o| o.state.auto_movement(&mut app));
let lengths_haptics = interact(&mut overlays, &mut app_state);
let lengths_haptics = interact(&mut overlays, &mut app);
for (idx, (len, haptics)) in lengths_haptics.iter().enumerate() {
lines.draw_from(
pointer_lines[idx],
app_state.input_state.pointers[idx].pose,
app.input_state.pointers[idx].pose,
*len,
app_state.input_state.pointers[idx].interaction.mode as usize + 1,
&app_state.input_state.hmd,
app.input_state.pointers[idx].interaction.mode as usize + 1,
&app.input_state.hmd,
);
if let Some(haptics) = haptics {
input_source.haptics(&xr_state, idx, haptics);
}
}
app_state.hid_provider.commit();
app.hid_provider.commit();
let watch = overlays.mut_by_id(watch_id).unwrap(); // want panic
let watch_transform = watch.state.transform;
@@ -364,71 +365,108 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
});
}
let mut layers = vec![];
let mut command_buffer = app_state
.graphics
.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
#[cfg(feature = "wayvr")]
if let Err(e) =
crate::overlays::wayvr::tick_events::<OpenXrOverlayData>(&mut app, &mut overlays)
{
log::error!("WayVR tick_events failed: {e:?}");
}
// Begin rendering
let mut buffers = CommandBuffers::default();
if !main_session_visible {
if let Some(skybox) = skybox.as_mut() {
for (idx, layer) in skybox
.present_xr(&xr_state, app_state.input_state.hmd, &mut command_buffer)?
.into_iter()
.enumerate()
{
layers.push((200.0 - 50.0 * (idx as f32), layer));
}
skybox.render(&xr_state, &app, &mut buffers)?;
}
}
#[cfg(feature = "wayvr")]
if let Err(e) =
crate::overlays::wayvr::tick_events::<OpenXrOverlayData>(&mut app_state, &mut overlays)
{
log::error!("WayVR tick_events failed: {:?}", e);
}
for o in overlays.iter_mut() {
o.data.cur_visible = false;
if !o.state.want_visible {
continue;
}
if !o.data.init {
o.init(&mut app_state)?;
o.init(&mut app)?;
o.data.init = true;
}
o.render(&mut app_state)?;
let should_render = match o.should_render(&mut app)? {
ShouldRender::Should => true,
ShouldRender::Can => (o.data.last_alpha - o.state.alpha).abs() > f32::EPSILON,
ShouldRender::Unable => false, //try show old image if exists
};
let dist_sq = (app_state.input_state.hmd.translation - o.state.transform.translation)
.length_squared()
+ (100f32 - o.state.z_order as f32);
if !dist_sq.is_normal() {
if should_render {
if !o.ensure_swapchain(&app, &xr_state)? {
continue;
}
let tgt = o.data.swapchain.as_mut().unwrap().acquire_wait_image()?; // want
if !o.render(&mut app, tgt, &mut buffers, o.state.alpha)? {
o.data.swapchain.as_mut().unwrap().ensure_image_released()?; // want
continue;
}
o.data.last_alpha = o.state.alpha;
} else if o.data.swapchain.is_none() {
continue;
}
o.data.cur_visible = true;
}
let maybe_layer = o.present_xr(&xr_state, &mut command_buffer)?;
if let CompositionLayer::None = maybe_layer {
lines.render(app.graphics.clone(), &mut buffers)?;
let future = buffers.execute_now(app.graphics.graphics_queue.clone())?;
if let Some(mut future) = future {
if let Err(e) = future.flush() {
return Err(BackendError::Fatal(e.into()));
}
future.cleanup_finished();
}
// End rendering
// Layer composition
let mut layers = vec![];
if !main_session_visible {
if let Some(skybox) = skybox.as_mut() {
for (idx, layer) in skybox.present(&xr_state, &app)?.into_iter().enumerate() {
layers.push(((idx as f32).mul_add(-50.0, 200.0), layer));
}
}
}
for o in overlays.iter_mut() {
if !o.data.cur_visible {
continue;
}
let dist_sq = (app.input_state.hmd.translation - o.state.transform.translation)
.length_squared()
+ (100f32 - o.state.z_order as f32);
if !dist_sq.is_normal() {
o.data.swapchain.as_mut().unwrap().ensure_image_released()?;
continue;
}
let maybe_layer = o.present(&xr_state)?;
if matches!(maybe_layer, CompositionLayer::None) {
continue;
}
layers.push((dist_sq, maybe_layer));
}
for maybe_layer in lines.present_xr(&xr_state, &mut command_buffer)? {
if let CompositionLayer::None = maybe_layer {
for maybe_layer in lines.present(&xr_state)? {
if matches!(maybe_layer, CompositionLayer::None) {
continue;
}
layers.push((0.0, maybe_layer));
}
// End layer composition
#[cfg(feature = "wayvr")]
if let Some(wayvr) = &app_state.wayvr {
if let Some(wayvr) = &app.wayvr {
wayvr.borrow_mut().data.tick_finish()?;
}
command_buffer.build_and_execute_now()?;
// Begin layer submit
layers.sort_by(|a, b| b.0.total_cmp(&a.0));
let frame_ref = layers
@@ -446,23 +484,23 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
environment_blend_mode,
&frame_ref,
)?;
// End layer submit
let removed_overlays = overlays.update(&mut app_state)?;
let removed_overlays = overlays.update(&mut app)?;
for o in removed_overlays {
delete_queue.push((o, cur_frame + 5));
}
notifications.submit_pending(&mut app_state);
notifications.submit_pending(&mut app);
app_state.tasks.retrieve_due(&mut due_tasks);
app.tasks.retrieve_due(&mut due_tasks);
while let Some(task) = due_tasks.pop_front() {
match task {
TaskType::Global(f) => f(&mut app_state),
TaskType::Overlay(sel, f) => {
if let Some(o) = overlays.mut_by_selector(&sel) {
f(&mut app_state, &mut o.state);
f(&mut app, &mut o.state);
} else {
log::warn!("Overlay not found for task: {:?}", sel);
log::warn!("Overlay not found for task: {sel:?}");
}
}
TaskType::CreateOverlay(sel, f) => {
@@ -470,14 +508,14 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
continue;
};
let Some((mut state, backend)) = f(&mut app_state) else {
let Some((mut overlay_state, overlay_backend)) = f(&mut app) else {
continue;
};
state.birthframe = cur_frame;
overlay_state.birthframe = cur_frame;
overlays.add(OverlayData {
state,
backend,
state: overlay_state,
backend: overlay_backend,
..Default::default()
});
}
@@ -496,7 +534,7 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
SystemTask::FixFloor => {
if let Some(ref mut playspace) = playspace {
playspace.fix_floor(
&app_state.input_state,
&app.input_state,
monado.as_mut().unwrap(), // safe
);
}
@@ -507,13 +545,13 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
}
}
SystemTask::ShowHide => {
overlays.show_hide(&mut app_state);
overlays.show_hide(&mut app);
}
_ => {}
},
#[cfg(feature = "wayvr")]
TaskType::WayVR(action) => {
wayvr_action(&mut app_state, &mut overlays, &action);
wayvr_action(&mut app, &mut overlays, &action);
}
}
}
+41 -5
View File
@@ -81,6 +81,10 @@
left: "/user/hand/left/input/thumbstick/y",
right: "/user/hand/right/input/thumbstick/y"
},
scroll_horizontal: {
left: "/user/hand/left/input/thumbstick/x",
right: "/user/hand/right/input/thumbstick/x"
},
show_hide: {
double_click: true,
left: "/user/hand/left/input/y/click",
@@ -88,6 +92,10 @@
space_drag: {
left: "/user/hand/left/input/menu/click",
},
space_reset: {
double_click: true,
left: "/user/hand/left/input/menu/click",
},
click_modifier_right: {
left: "/user/hand/left/input/y/touch",
right: "/user/hand/right/input/b/touch"
@@ -130,10 +138,14 @@
left: "/user/hand/left/input/thumbstick/y",
right: "/user/hand/right/input/thumbstick/y"
},
toggle_dashboard: {
double_click: false,
right: "/user/hand/right/input/system/click",
},
scroll_horizontal: {
left: "/user/hand/left/input/thumbstick/x",
right: "/user/hand/right/input/thumbstick/x"
},
toggle_dashboard: {
double_click: false,
right: "/user/hand/right/input/system/click",
},
show_hide: {
double_click: true,
left: "/user/hand/left/input/b/click",
@@ -143,8 +155,8 @@
// right trackpad is alt_click
},
space_reset: {
double_click: true,
left: "/user/hand/left/input/trackpad/force",
double_click: true,
},
click_modifier_right: {
left: "/user/hand/left/input/b/touch",
@@ -180,12 +192,20 @@
left: "/user/hand/left/input/trackpad/y",
right: "/user/hand/right/input/trackpad/y"
},
scroll_horizontal: {
left: "/user/hand/left/input/trackpad/x",
right: "/user/hand/right/input/trackpad/x"
},
show_hide: {
left: "/user/hand/left/input/menu/click",
},
space_drag: {
right: "/user/hand/right/input/menu/click",
},
space_reset: {
double_click: true,
right: "/user/hand/right/input/menu/click",
},
haptic: {
left: "/user/hand/left/output/haptic",
right: "/user/hand/right/output/haptic"
@@ -215,12 +235,20 @@
left: "/user/hand/left/input/thumbstick/y",
right: "/user/hand/right/input/thumbstick/y"
},
scroll_horizontal: {
left: "/user/hand/left/input/thumbstick/x",
right: "/user/hand/right/input/thumbstick/x"
},
show_hide: {
left: "/user/hand/left/input/menu/click",
},
space_drag: {
right: "/user/hand/right/input/menu/click",
},
space_reset: {
double_click: true,
right: "/user/hand/right/input/menu/click",
},
},
// HP Reverb G2 controller
@@ -246,12 +274,20 @@
left: "/user/hand/left/input/thumbstick/y",
right: "/user/hand/right/input/thumbstick/y"
},
scroll_horizontal: {
left: "/user/hand/left/input/thumbstick/x",
right: "/user/hand/right/input/thumbstick/x"
},
show_hide: {
left: "/user/hand/left/input/menu/click",
},
space_drag: {
right: "/user/hand/right/input/menu/click",
},
space_reset: {
double_click: true,
right: "/user/hand/right/input/menu/click",
},
},
]
+43 -45
View File
@@ -1,74 +1,72 @@
use glam::Vec3A;
use openxr::{self as xr, CompositionLayerFlags};
use std::{f32::consts::PI, sync::Arc};
use std::f32::consts::PI;
use xr::EyeVisibility;
use super::{helpers, swapchain::SwapchainRenderData, CompositionLayer, XrState};
use super::{helpers, swapchain::WlxSwapchain, CompositionLayer, XrState};
use crate::{
backend::{
openxr::swapchain::{create_swapchain_render_data, SwapchainOpts},
openxr::swapchain::{create_swapchain, SwapchainOpts},
overlay::OverlayData,
},
graphics::WlxCommandBuffer,
state::AppState,
};
use vulkano::image::view::ImageView;
#[derive(Default)]
pub struct OpenXrOverlayData {
last_view: Option<Arc<ImageView>>,
last_visible: bool,
pub(super) swapchain: Option<SwapchainRenderData>,
pub(super) swapchain: Option<WlxSwapchain>,
pub(super) init: bool,
pub(super) cur_visible: bool,
pub(super) last_alpha: f32,
}
impl OverlayData<OpenXrOverlayData> {
pub(super) fn present_xr<'a>(
pub(super) fn ensure_swapchain<'a>(
&'a mut self,
app: &AppState,
xr: &'a XrState,
command_buffer: &mut WlxCommandBuffer,
) -> anyhow::Result<CompositionLayer<'a>> {
if let Some(new_view) = self.view() {
self.data.last_view = Some(new_view);
) -> anyhow::Result<bool> {
if self.data.swapchain.is_some() {
return Ok(true);
}
let my_view = if let Some(view) = self.data.last_view.as_ref() {
view.clone()
} else {
log::warn!("{}: Will not show - image not ready", self.state.name);
let Some(meta) = self.frame_meta() else {
log::warn!(
"{}: swapchain cannot be created due to missing metadata",
self.state.name
);
return Ok(false);
};
let extent = meta.extent;
self.data.swapchain = Some(create_swapchain(
xr,
app.graphics.clone(),
extent,
SwapchainOpts::new(),
)?);
Ok(true)
}
pub(super) fn present<'a>(
&'a mut self,
xr: &'a XrState,
) -> anyhow::Result<CompositionLayer<'a>> {
let Some(swapchain) = self.data.swapchain.as_mut() else {
log::warn!("{}: swapchain not ready", self.state.name);
return Ok(CompositionLayer::None);
};
if !swapchain.ever_acquired {
log::warn!("{}: swapchain not rendered", self.state.name);
return Ok(CompositionLayer::None);
}
swapchain.ensure_image_released()?;
let frame_transform = self.frame_transform().unwrap(); // want panic
let extent = frame_transform.extent;
let sub_image = swapchain.get_subimage();
let transform = self.state.transform * self.backend.frame_meta().unwrap().transform; // contract
let data = match self.data.swapchain {
Some(ref mut data) => data,
None => {
let srd = create_swapchain_render_data(
xr,
command_buffer.graphics.clone(),
extent,
SwapchainOpts::new(),
)?;
log::debug!(
"{}: Created swapchain {}x{}, {} images, {} MB",
self.state.name,
extent[0],
extent[1],
srd.images.len(),
extent[0] * extent[1] * 4 * srd.images.len() as u32 / 1024 / 1024
);
self.data.swapchain = Some(srd);
self.data.swapchain.as_mut().unwrap() //safe
}
};
let sub_image = data.acquire_present_release(command_buffer, my_view, self.state.alpha)?;
let transform = self.state.transform * frame_transform.transform;
let aspect_ratio = extent[1] as f32 / extent[0] as f32;
let aspect_ratio = swapchain.extent[1] as f32 / swapchain.extent[0] as f32;
let (scale_x, scale_y) = if aspect_ratio < 1.0 {
let major = transform.matrix3.col(0).length();
(major, major * aspect_ratio)
+15 -22
View File
@@ -8,13 +8,6 @@ use crate::{
use super::overlay::OpenXrOverlayData;
#[repr(C)]
#[derive(Default, Debug)]
struct XrtPose {
orientation: [f32; 4],
position: [f32; 3],
}
struct MoverData<T> {
pose: Affine3A,
hand: usize,
@@ -55,7 +48,7 @@ impl PlayspaceMover {
state: &AppState,
monado: &mut Monado,
) {
for pointer in state.input_state.pointers.iter() {
for pointer in &state.input_state.pointers {
if pointer.now.space_reset {
if !pointer.before.space_reset {
log::info!("Space reset");
@@ -81,8 +74,8 @@ impl PlayspaceMover {
Affine3A::from_quat(dq)
} else {
let rel_y = f32::atan2(
2.0 * (dq.y * dq.w + dq.x * dq.z),
(2.0 * (dq.w * dq.w + dq.x * dq.x)) - 1.0,
2.0 * dq.y.mul_add(dq.w, dq.x * dq.z),
2.0f32.mul_add(dq.w.mul_add(dq.w, dq.x * dq.x), -1.0),
);
Affine3A::from_rotation_y(rel_y)
@@ -96,7 +89,7 @@ impl PlayspaceMover {
data.pose *= space_transform;
data.hand_pose = new_hand;
self.apply_offset(data.pose, monado);
apply_offset(data.pose, monado);
self.rotate = Some(data);
} else {
for (i, pointer) in state.input_state.pointers.iter().enumerate() {
@@ -145,7 +138,7 @@ impl PlayspaceMover {
data.pose.translation += relative_pos;
data.hand_pose = new_hand;
self.apply_offset(data.pose, monado);
apply_offset(data.pose, monado);
self.drag = Some(data);
} else {
for (i, pointer) in state.input_state.pointers.iter().enumerate() {
@@ -176,7 +169,7 @@ impl PlayspaceMover {
}
self.last_transform = Affine3A::IDENTITY;
self.apply_offset(self.last_transform, monado);
apply_offset(self.last_transform, monado);
}
pub fn fix_floor(&mut self, input: &InputState, monado: &mut Monado) {
@@ -193,14 +186,14 @@ impl PlayspaceMover {
let y2 = input.pointers[1].raw_pose.translation.y;
let delta = y1.min(y2) - 0.03;
self.last_transform.translation.y += delta;
self.apply_offset(self.last_transform, monado);
}
fn apply_offset(&self, transform: Affine3A, monado: &mut Monado) {
let pose = Pose {
position: transform.translation.into(),
orientation: Quat::from_affine3(&transform).into(),
};
let _ = monado.set_reference_space_offset(ReferenceSpaceType::Stage, pose);
apply_offset(self.last_transform, monado);
}
}
fn apply_offset(transform: Affine3A, monado: &mut Monado) {
let pose = Pose {
position: transform.translation.into(),
orientation: Quat::from_affine3(&transform).into(),
};
let _ = monado.set_reference_space_offset(ReferenceSpaceType::Stage, pose);
}
+146 -68
View File
@@ -1,32 +1,40 @@
use std::{f32::consts::PI, fs::File, sync::Arc};
use std::{
f32::consts::PI,
fs::File,
sync::{Arc, LazyLock},
};
use glam::{Affine3A, Quat, Vec3A};
use once_cell::sync::Lazy;
use glam::{Quat, Vec3A};
use openxr::{self as xr, CompositionLayerFlags};
use vulkano::{command_buffer::CommandBufferUsage, image::view::ImageView};
use vulkano::{
command_buffer::CommandBufferUsage, image::view::ImageView,
pipeline::graphics::color_blend::AttachmentBlend,
};
use crate::{
backend::openxr::{helpers::translation_rotation_to_posef, swapchain::SwapchainOpts},
config_io,
graphics::{dds::WlxCommandBufferDds, format_is_srgb, WlxCommandBuffer},
graphics::{dds::WlxCommandBufferDds, CommandBuffers},
state::AppState,
};
use super::{
swapchain::{create_swapchain_render_data, SwapchainRenderData},
swapchain::{create_swapchain, WlxSwapchain},
CompositionLayer, XrState,
};
pub(super) struct Skybox {
view: Arc<ImageView>,
srd: Option<(SwapchainRenderData, SwapchainRenderData)>,
sky: Option<WlxSwapchain>,
grid: Option<WlxSwapchain>,
}
impl Skybox {
pub fn new(app: &AppState) -> anyhow::Result<Self> {
let mut command_buffer = app
.graphics
.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
let mut command_buffer = app.graphics.create_uploads_command_buffer(
app.graphics.transfer_queue.clone(),
CommandBufferUsage::OneTimeSubmit,
)?;
let mut maybe_image = None;
@@ -52,7 +60,7 @@ impl Skybox {
"Could not use custom skybox texture at: {}",
app.session.config.skybox_texture
);
log::warn!("{:?}", e);
log::warn!("{e:?}");
}
}
}
@@ -66,80 +74,149 @@ impl Skybox {
let view = ImageView::new_default(maybe_image.unwrap())?; // safe unwrap
Ok(Self { view, srd: None })
Ok(Self {
view,
sky: None,
grid: None,
})
}
pub(super) fn present_xr<'a>(
fn prepare_sky<'a>(
&'a mut self,
xr: &'a XrState,
hmd: Affine3A,
command_buffer: &mut WlxCommandBuffer,
app: &AppState,
buf: &mut CommandBuffers,
) -> anyhow::Result<()> {
if self.sky.is_some() {
return Ok(());
}
let opts = SwapchainOpts::new().immutable();
let Ok(shaders) = app.graphics.shared_shaders.read() else {
anyhow::bail!("Failed to lock shared shaders for reading");
};
let extent = self.view.image().extent();
let mut swapchain = create_swapchain(xr, app.graphics.clone(), extent, opts)?;
let tgt = swapchain.acquire_wait_image()?;
let pipeline = app.graphics.create_pipeline(
shaders.get("vert_common").unwrap().clone(), // want panic
shaders.get("frag_srgb").unwrap().clone(), // want panic
app.graphics.native_format,
None,
)?;
let set0 =
pipeline.uniform_sampler(0, self.view.clone(), app.graphics.texture_filtering)?;
let set1 = pipeline.uniform_buffer(1, vec![1f32])?;
let pass = pipeline.create_pass_for_target(tgt.clone(), vec![set0, set1])?;
let mut cmd_buffer = app
.graphics
.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
cmd_buffer.begin_rendering(tgt)?;
cmd_buffer.run_ref(&pass)?;
cmd_buffer.end_rendering()?;
buf.push(cmd_buffer.build()?);
self.sky = Some(swapchain);
Ok(())
}
fn prepare_grid<'a>(
&'a mut self,
xr: &'a XrState,
app: &AppState,
buf: &mut CommandBuffers,
) -> anyhow::Result<()> {
if self.grid.is_some() {
return Ok(());
}
let Ok(shaders) = app.graphics.shared_shaders.read() else {
anyhow::bail!("Failed to lock shared shaders for reading");
};
let extent = [1024, 1024, 1];
let mut swapchain = create_swapchain(
xr,
app.graphics.clone(),
extent,
SwapchainOpts::new().immutable(),
)?;
let pipeline = app.graphics.create_pipeline(
shaders.get("vert_common").unwrap().clone(), // want panic
shaders.get("frag_grid").unwrap().clone(), // want panic
app.graphics.native_format,
Some(AttachmentBlend::alpha()),
)?;
let tgt = swapchain.acquire_wait_image()?;
let pass = pipeline.create_pass_for_target(tgt.clone(), vec![])?;
let mut cmd_buffer = app
.graphics
.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
cmd_buffer.begin_rendering(tgt)?;
cmd_buffer.run_ref(&pass)?;
cmd_buffer.end_rendering()?;
buf.push(cmd_buffer.build()?);
self.grid = Some(swapchain);
Ok(())
}
pub(super) fn render(
&mut self,
xr: &XrState,
app: &AppState,
buf: &mut CommandBuffers,
) -> anyhow::Result<()> {
self.prepare_sky(xr, app, buf)?;
self.prepare_grid(xr, app, buf)?;
Ok(())
}
pub(super) fn present<'a>(
&'a mut self,
xr: &'a XrState,
app: &AppState,
) -> anyhow::Result<Vec<CompositionLayer<'a>>> {
let (sky_image, grid_image) = if let Some((ref mut srd_sky, ref mut srd_grid)) = self.srd {
(srd_sky.present_last()?, srd_grid.present_last()?)
} else {
log::debug!("Render skybox.");
let mut opts = SwapchainOpts::new().immutable();
opts.srgb = format_is_srgb(self.view.image().format());
let srd_sky = create_swapchain_render_data(
xr,
command_buffer.graphics.clone(),
self.view.image().extent(),
opts,
)?;
let srd_grid = create_swapchain_render_data(
xr,
command_buffer.graphics.clone(),
[1024, 1024, 1],
SwapchainOpts::new().immutable().grid(),
)?;
self.srd = Some((srd_sky, srd_grid));
let (srd_sky, srd_grid) = self.srd.as_mut().unwrap(); // safe unwrap
(
srd_sky.acquire_present_release(command_buffer, self.view.clone(), 1.0)?,
srd_grid.acquire_compute_release(command_buffer)?,
)
};
let pose = xr::Posef {
orientation: xr::Quaternionf::IDENTITY,
position: xr::Vector3f {
x: hmd.translation.x,
y: hmd.translation.y,
z: hmd.translation.z,
},
};
// cover the entire sphere
const HORIZ_ANGLE: f32 = 2.0 * PI;
const HI_VERT_ANGLE: f32 = 0.5 * PI;
const LO_VERT_ANGLE: f32 = -0.5 * PI;
let mut layers = vec![];
static GRID_POSE: LazyLock<xr::Posef> = LazyLock::new(|| {
translation_rotation_to_posef(Vec3A::ZERO, Quat::from_rotation_x(PI * -0.5))
});
let pose = xr::Posef {
orientation: xr::Quaternionf::IDENTITY,
position: xr::Vector3f {
x: app.input_state.hmd.translation.x,
y: app.input_state.hmd.translation.y,
z: app.input_state.hmd.translation.z,
},
};
self.sky.as_mut().unwrap().ensure_image_released()?;
let sky = xr::CompositionLayerEquirect2KHR::new()
.layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA)
.pose(pose)
.radius(10.0)
.sub_image(sky_image)
.sub_image(self.sky.as_ref().unwrap().get_subimage())
.eye_visibility(xr::EyeVisibility::BOTH)
.space(&xr.stage)
.central_horizontal_angle(HORIZ_ANGLE)
.upper_vertical_angle(HI_VERT_ANGLE)
.lower_vertical_angle(LO_VERT_ANGLE);
layers.push(CompositionLayer::Equirect2(sky));
static GRID_POSE: Lazy<xr::Posef> = Lazy::new(|| {
translation_rotation_to_posef(Vec3A::ZERO, Quat::from_rotation_x(PI * -0.5))
});
self.grid.as_mut().unwrap().ensure_image_released()?;
let grid = xr::CompositionLayerQuad::new()
.layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA)
.pose(*GRID_POSE)
@@ -147,13 +224,14 @@ impl Skybox {
width: 10.0,
height: 10.0,
})
.sub_image(grid_image)
.sub_image(self.grid.as_ref().unwrap().get_subimage())
.eye_visibility(xr::EyeVisibility::BOTH)
.space(&xr.stage);
layers.push(CompositionLayer::Quad(grid));
Ok(layers)
Ok(vec![
CompositionLayer::Equirect2(sky),
CompositionLayer::Quad(grid),
])
}
}
+30 -132
View File
@@ -1,52 +1,39 @@
use std::sync::Arc;
use anyhow::bail;
use ash::vk;
use openxr as xr;
use smallvec::SmallVec;
use vulkano::{
format::Format,
image::{sys::RawImage, view::ImageView, ImageCreateInfo, ImageUsage},
pipeline::graphics::color_blend::AttachmentBlend,
Handle,
};
use crate::graphics::{WlxCommandBuffer, WlxGraphics, WlxPipeline, WlxPipelineDynamic};
use crate::graphics::WlxGraphics;
use super::XrState;
#[derive(Default)]
pub(super) struct SwapchainOpts {
pub immutable: bool,
pub srgb: bool,
pub grid: bool,
}
impl SwapchainOpts {
pub fn new() -> Self {
Default::default()
Self::default()
}
pub fn immutable(mut self) -> Self {
pub const fn immutable(mut self) -> Self {
self.immutable = true;
self
}
pub fn srgb(mut self) -> Self {
self.srgb = true;
self
}
pub fn grid(mut self) -> Self {
self.grid = true;
self
}
}
pub(super) fn create_swapchain_render_data(
pub(super) fn create_swapchain(
xr: &XrState,
graphics: Arc<WlxGraphics>,
extent: [u32; 3],
opts: SwapchainOpts,
) -> anyhow::Result<SwapchainRenderData> {
) -> anyhow::Result<WlxSwapchain> {
let create_flags = if opts.immutable {
xr::SwapchainCreateFlags::STATIC_IMAGE
} else {
@@ -56,7 +43,7 @@ pub(super) fn create_swapchain_render_data(
let swapchain = xr.session.create_swapchain(&xr::SwapchainCreateInfo {
create_flags,
usage_flags: xr::SwapchainUsageFlags::COLOR_ATTACHMENT | xr::SwapchainUsageFlags::SAMPLED,
format: Format::R8G8B8A8_SRGB as _,
format: graphics.native_format as _,
sample_count: 1,
width: extent[0],
height: extent[1],
@@ -65,28 +52,6 @@ pub(super) fn create_swapchain_render_data(
mip_count: 1,
})?;
let Ok(shaders) = graphics.shared_shaders.read() else {
bail!("Failed to lock shared shaders for reading");
};
let image_fmt = if opts.srgb {
Format::R8G8B8A8_SRGB
} else {
Format::R8G8B8A8_UNORM
};
let frag_shader = if opts.grid {
"frag_grid"
} else {
"frag_swapchain"
};
let pipeline = graphics.create_pipeline_dynamic(
shaders.get("vert_common").unwrap().clone(), // want panic
shaders.get(frag_shader).unwrap().clone(), // want panic
image_fmt,
Some(AttachmentBlend::alpha()),
)?;
let images = swapchain
.enumerate_images()?
.into_iter()
@@ -98,7 +63,7 @@ pub(super) fn create_swapchain_render_data(
graphics.device.clone(),
vk_image,
ImageCreateInfo {
format: image_fmt, // actually SRGB but we lie
format: graphics.native_format as _,
extent,
usage: ImageUsage::COLOR_ATTACHMENT,
..Default::default()
@@ -111,118 +76,51 @@ pub(super) fn create_swapchain_render_data(
})
.collect::<anyhow::Result<SmallVec<[Arc<ImageView>; 4]>>>()?;
Ok(SwapchainRenderData {
Ok(WlxSwapchain {
acquired: false,
ever_acquired: false,
swapchain,
pipeline,
images,
extent,
target_extent: [0, 0, 0],
})
}
pub(super) struct SwapchainRenderData {
pub(super) struct WlxSwapchain {
acquired: bool,
pub(super) ever_acquired: bool,
pub(super) swapchain: xr::Swapchain<xr::Vulkan>,
pub(super) pipeline: Arc<WlxPipeline<WlxPipelineDynamic>>,
pub(super) extent: [u32; 3],
pub(super) target_extent: [u32; 3],
pub(super) images: SmallVec<[Arc<ImageView>; 4]>,
}
impl SwapchainRenderData {
pub(super) fn acquire_present_release(
&mut self,
command_buffer: &mut WlxCommandBuffer,
view: Arc<ImageView>,
alpha: f32,
) -> anyhow::Result<xr::SwapchainSubImage<xr::Vulkan>> {
impl WlxSwapchain {
pub(super) fn acquire_wait_image(&mut self) -> anyhow::Result<Arc<ImageView>> {
let idx = self.swapchain.acquire_image()? as usize;
self.swapchain.wait_image(xr::Duration::INFINITE)?;
let render_target = &mut self.images[idx];
command_buffer.begin_rendering(render_target.clone())?;
self.target_extent = render_target.image().extent();
let set0 = self.pipeline.uniform_sampler(
0,
view.clone(),
command_buffer.graphics.texture_filtering,
)?;
let set1 = self.pipeline.uniform_buffer(1, vec![alpha])?;
let pass = self.pipeline.create_pass(
[self.target_extent[0] as _, self.target_extent[1] as _],
command_buffer.graphics.quad_verts.clone(),
command_buffer.graphics.quad_indices.clone(),
vec![set0, set1],
)?;
command_buffer.run_ref(&pass)?;
command_buffer.end_rendering()?;
self.swapchain.release_image()?;
Ok(xr::SwapchainSubImage::new()
.swapchain(&self.swapchain)
.image_rect(xr::Rect2Di {
offset: xr::Offset2Di { x: 0, y: 0 },
extent: xr::Extent2Di {
width: self.target_extent[0] as _,
height: self.target_extent[1] as _,
},
})
.image_array_index(0))
self.ever_acquired = true;
self.acquired = true;
Ok(self.images[idx].clone())
}
pub(super) fn acquire_compute_release(
&mut self,
command_buffer: &mut WlxCommandBuffer,
) -> anyhow::Result<xr::SwapchainSubImage<xr::Vulkan>> {
let idx = self.swapchain.acquire_image()? as usize;
self.swapchain.wait_image(xr::Duration::INFINITE)?;
let render_target = &mut self.images[idx];
command_buffer.begin_rendering(render_target.clone())?;
self.target_extent = render_target.image().extent();
let pass = self.pipeline.create_pass(
[self.target_extent[0] as _, self.target_extent[1] as _],
command_buffer.graphics.quad_verts.clone(),
command_buffer.graphics.quad_indices.clone(),
vec![],
)?;
command_buffer.run_ref(&pass)?;
command_buffer.end_rendering()?;
self.swapchain.release_image()?;
Ok(xr::SwapchainSubImage::new()
.swapchain(&self.swapchain)
.image_rect(xr::Rect2Di {
offset: xr::Offset2Di { x: 0, y: 0 },
extent: xr::Extent2Di {
width: self.target_extent[0] as _,
height: self.target_extent[1] as _,
},
})
.image_array_index(0))
pub(super) fn ensure_image_released(&mut self) -> anyhow::Result<()> {
if self.acquired {
self.swapchain.release_image()?;
self.acquired = false;
}
Ok(())
}
pub(super) fn present_last(&self) -> anyhow::Result<xr::SwapchainSubImage<xr::Vulkan>> {
debug_assert!(
self.target_extent[0] * self.target_extent[1] != 0,
"present_last: target_extent zero"
);
Ok(xr::SwapchainSubImage::new()
pub(super) fn get_subimage(&self) -> xr::SwapchainSubImage<xr::Vulkan> {
debug_assert!(self.ever_acquired, "swapchain was never acquired!");
xr::SwapchainSubImage::new()
.swapchain(&self.swapchain)
.image_rect(xr::Rect2Di {
offset: xr::Offset2Di { x: 0, y: 0 },
extent: xr::Extent2Di {
width: self.target_extent[0] as _,
height: self.target_extent[1] as _,
width: self.extent[0] as _,
height: self.extent[1] as _,
},
})
.image_array_index(0))
.image_array_index(0)
}
}
+8 -8
View File
@@ -26,7 +26,7 @@ impl OscSender {
bail!("Failed to bind UDP socket - OSC will not function.");
};
let Ok(_) = upstream.connect(SocketAddr::new(ip, send_port)) else {
let Ok(()) = upstream.connect(SocketAddr::new(ip, send_port)) else {
bail!("Failed to connect UDP socket - OSC will not function.");
};
@@ -75,7 +75,7 @@ impl OscSender {
KEYBOARD_NAME => has_keyboard = true,
_ => {
if o.state.interactable {
num_overlays += 1
num_overlays += 1;
}
}
}
@@ -146,25 +146,25 @@ impl OscSender {
// send device battery parameters
self.send_message(
format!("/avatar/parameters/{parameter}Battery").into(),
format!("/avatar/parameters/{parameter}Battery"),
vec![OscType::Float(level)],
)?;
self.send_message(
format!("/avatar/parameters/{parameter}Charging").into(),
format!("/avatar/parameters/{parameter}Charging"),
vec![OscType::Bool(device.charging)],
)?;
}
// send average controller and tracker battery parameters
self.send_message(
format!("/avatar/parameters/averageControllerBattery").into(),
String::from("/avatar/parameters/averageControllerBattery"),
vec![OscType::Float(
controller_total_bat / controller_count as f32,
controller_total_bat / f32::from(controller_count),
)],
)?;
self.send_message(
format!("/avatar/parameters/averageTrackerBattery").into(),
vec![OscType::Float(tracker_total_bat / tracker_count as f32)],
String::from("/avatar/parameters/averageTrackerBattery"),
vec![OscType::Float(tracker_total_bat / f32::from(tracker_count))],
)?;
}
+79 -55
View File
@@ -9,10 +9,11 @@ use std::{
use anyhow::Ok;
use glam::{Affine2, Affine3A, Mat3A, Quat, Vec2, Vec3, Vec3A};
use serde::Deserialize;
use vulkano::image::view::ImageView;
use vulkano::{format::Format, image::view::ImageView};
use crate::{
config::AStrMapExt,
graphics::CommandBuffers,
state::{AppState, KeyboardFocus},
};
@@ -55,14 +56,13 @@ pub struct OverlayState {
pub saved_transform: Option<Affine3A>,
pub relative_to: RelativeTo,
pub curvature: Option<f32>,
pub primary_pointer: Option<usize>,
pub interaction_transform: Affine2,
pub birthframe: usize,
}
impl Default for OverlayState {
fn default() -> Self {
OverlayState {
Self {
id: OverlayID(OVERLAY_AUTO_INCREMENT.fetch_add(1, Ordering::Relaxed)),
name: Arc::from(""),
want_visible: false,
@@ -82,7 +82,6 @@ impl Default for OverlayState {
spawn_rotation: Quat::IDENTITY,
saved_transform: None,
transform: Affine3A::IDENTITY,
primary_pointer: None,
interaction_transform: Affine2::IDENTITY,
birthframe: 0,
}
@@ -104,8 +103,8 @@ where
T: Default,
{
fn default() -> Self {
OverlayData {
state: Default::default(),
Self {
state: OverlayState::default(),
backend: Box::<SplitOverlayBackend>::default(),
primary_pointer: None,
data: Default::default(),
@@ -114,7 +113,7 @@ where
}
impl OverlayState {
pub fn parent_transform(&self, app: &AppState) -> Option<Affine3A> {
pub const fn parent_transform(&self, app: &AppState) -> Option<Affine3A> {
match self.relative_to {
RelativeTo::Head => Some(app.input_state.hmd),
RelativeTo::Hand(idx) => Some(app.input_state.pointers[idx].pose),
@@ -122,7 +121,7 @@ impl OverlayState {
}
}
fn get_anchor(&self, app: &AppState) -> Affine3A {
const fn get_anchor(&self, app: &AppState) -> Affine3A {
if self.anchored {
app.anchor
} else {
@@ -210,12 +209,14 @@ where
T: Default,
{
pub fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.state.curvature = app
.session
.config
.curve_values
.arc_get(self.state.name.as_ref())
.copied();
if self.state.curvature.is_none() {
self.state.curvature = app
.session
.config
.curve_values
.arc_get(self.state.name.as_ref())
.copied();
}
if matches!(self.state.relative_to, RelativeTo::None) {
let hard_reset;
@@ -234,33 +235,34 @@ where
}
self.backend.init(app)
}
pub fn render(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.backend.render(app)
pub fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender> {
self.backend.should_render(app)
}
pub fn view(&mut self) -> Option<Arc<ImageView>> {
self.backend.view()
pub fn render(
&mut self,
app: &mut AppState,
tgt: Arc<ImageView>,
buf: &mut CommandBuffers,
alpha: f32,
) -> anyhow::Result<bool> {
self.backend.render(app, tgt, buf, alpha)
}
pub fn frame_transform(&mut self) -> Option<FrameTransform> {
self.backend.frame_transform()
}
pub fn set_visible(&mut self, app: &mut AppState, visible: bool) -> anyhow::Result<()> {
let old_visible = self.state.want_visible;
self.state.want_visible = visible;
if visible != old_visible {
if visible {
self.backend.resume(app)?;
} else {
self.backend.pause(app)?;
}
}
Ok(())
pub fn frame_meta(&mut self) -> Option<FrameMeta> {
self.backend.frame_meta()
}
}
#[derive(Default)]
pub struct FrameTransform {
#[derive(Default, Clone, Copy)]
pub struct FrameMeta {
pub extent: [u32; 3],
pub transform: Affine3A,
pub format: Format,
}
pub enum ShouldRender {
Should,
Can,
Unable,
}
pub trait OverlayRenderer {
@@ -268,15 +270,24 @@ pub trait OverlayRenderer {
fn init(&mut self, app: &mut AppState) -> anyhow::Result<()>;
fn pause(&mut self, app: &mut AppState) -> anyhow::Result<()>;
fn resume(&mut self, app: &mut AppState) -> anyhow::Result<()>;
/// Called when the presentation layer is ready to present a new frame
fn render(&mut self, app: &mut AppState) -> anyhow::Result<()>;
/// Called to retrieve the current image to be displayed
fn view(&mut self) -> Option<Arc<ImageView>>;
fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender>;
/// Called when the contents need to be rendered to the swapchain
fn render(
&mut self,
app: &mut AppState,
tgt: Arc<ImageView>,
buf: &mut CommandBuffers,
alpha: f32,
) -> anyhow::Result<bool>;
/// Called to retrieve the effective extent of the image
/// Used for creating swapchains.
///
/// Muse not be None if view() is also not None
fn frame_transform(&mut self) -> Option<FrameTransform>;
/// Must be true if should_render was also true on the same frame.
fn frame_meta(&mut self) -> Option<FrameMeta>;
}
pub struct FallbackRenderer;
@@ -291,13 +302,19 @@ impl OverlayRenderer for FallbackRenderer {
fn resume(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
Ok(())
}
fn render(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
Ok(())
fn should_render(&mut self, _app: &mut AppState) -> anyhow::Result<ShouldRender> {
Ok(ShouldRender::Unable)
}
fn view(&mut self) -> Option<Arc<ImageView>> {
None
fn render(
&mut self,
_app: &mut AppState,
_tgt: Arc<ImageView>,
_buf: &mut CommandBuffers,
_alpha: f32,
) -> anyhow::Result<bool> {
Ok(false)
}
fn frame_transform(&mut self) -> Option<FrameTransform> {
fn frame_meta(&mut self) -> Option<FrameMeta> {
None
}
}
@@ -322,8 +339,8 @@ pub struct SplitOverlayBackend {
}
impl Default for SplitOverlayBackend {
fn default() -> SplitOverlayBackend {
SplitOverlayBackend {
fn default() -> Self {
Self {
renderer: Box::new(FallbackRenderer),
interaction: Box::new(DummyInteractionHandler),
}
@@ -348,16 +365,23 @@ impl OverlayRenderer for SplitOverlayBackend {
fn resume(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.renderer.resume(app)
}
fn render(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.renderer.render(app)
fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender> {
self.renderer.should_render(app)
}
fn view(&mut self) -> Option<Arc<ImageView>> {
self.renderer.view()
fn render(
&mut self,
app: &mut AppState,
tgt: Arc<ImageView>,
buf: &mut CommandBuffers,
alpha: f32,
) -> anyhow::Result<bool> {
self.renderer.render(app, tgt, buf, alpha)
}
fn frame_transform(&mut self) -> Option<FrameTransform> {
self.renderer.frame_transform()
fn frame_meta(&mut self) -> Option<FrameMeta> {
self.renderer.frame_meta()
}
}
impl InteractionHandler for SplitOverlayBackend {
fn on_left(&mut self, app: &mut AppState, pointer: usize) {
self.interaction.on_left(app, pointer);
@@ -365,15 +389,15 @@ impl InteractionHandler for SplitOverlayBackend {
fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option<Haptics> {
self.interaction.on_hover(app, hit)
}
fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta: f32) {
self.interaction.on_scroll(app, hit, delta);
fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta_y: f32, delta_x: f32) {
self.interaction.on_scroll(app, hit, delta_y, delta_x);
}
fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool) {
self.interaction.on_pointer(app, hit, pressed);
}
}
pub fn ui_transform(extent: &[u32; 2]) -> Affine2 {
pub fn ui_transform(extent: [u32; 2]) -> Affine2 {
let aspect = extent[0] as f32 / extent[1] as f32;
let scale = if aspect < 1.0 {
Vec2 {
+3 -4
View File
@@ -25,12 +25,12 @@ struct AppTask {
pub task: TaskType,
}
impl PartialEq<AppTask> for AppTask {
impl PartialEq<Self> for AppTask {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == cmp::Ordering::Equal
}
}
impl PartialOrd<AppTask> for AppTask {
impl PartialOrd<Self> for AppTask {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
@@ -57,7 +57,6 @@ pub type CreateOverlayTask =
dyn FnOnce(&mut AppState) -> Option<(OverlayState, Box<dyn OverlayBackend>)> + Send;
pub enum TaskType {
Global(Box<dyn FnOnce(&mut AppState) + Send>),
Overlay(OverlaySelector, Box<OverlayTask>),
CreateOverlay(OverlaySelector, Box<CreateOverlayTask>),
DropOverlay(OverlaySelector),
@@ -79,7 +78,7 @@ pub struct TaskContainer {
}
impl TaskContainer {
pub fn new() -> Self {
pub const fn new() -> Self {
Self {
tasks: BinaryHeap::new(),
}
+26 -54
View File
@@ -1,10 +1,10 @@
use std::sync::Arc;
use vulkano::{
command_buffer::CommandBufferUsage,
image::{view::ImageView, ImageUsage},
swapchain::{
acquire_next_image, Surface, Swapchain, SwapchainCreateInfo, SwapchainPresentInfo,
acquire_next_image, Surface, SurfaceInfo, Swapchain, SwapchainCreateInfo,
SwapchainPresentInfo,
},
sync::GpuFuture,
Validated, VulkanError,
@@ -19,7 +19,7 @@ use winit::{
use crate::{
config::load_custom_ui,
config_io,
graphics::{DynamicPass, DynamicPipeline, WlxGraphics, BLEND_ALPHA},
graphics::{CommandBuffers, WlxGraphics},
gui::{
canvas::Canvas,
modular::{modular_canvas, ModularData},
@@ -37,8 +37,6 @@ static LAST_SIZE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::n
struct PreviewState {
canvas: Canvas<(), ModularData>,
pipeline: Arc<DynamicPipeline>,
pass: DynamicPass,
swapchain: Arc<Swapchain>,
images: Vec<Arc<ImageView>>,
}
@@ -63,52 +61,27 @@ impl PreviewState {
window.set_min_inner_size(Some(logical_size));
window.set_max_inner_size(Some(logical_size));
LAST_SIZE.store(
(config.size[1] as u64) << 32 | config.size[0] as u64,
((config.size[1] as u64) << 32) | config.size[0] as u64,
std::sync::atomic::Ordering::Relaxed,
);
}
let inner_size = window.inner_size();
let swapchain_size = [inner_size.width, inner_size.height];
let (swapchain, images) =
create_swapchain(&state.graphics, surface.clone(), swapchain_size)?;
let (swapchain, images) = create_swapchain(&state.graphics, surface, swapchain_size)?;
let mut canvas = modular_canvas(&config.size, &config.elements, state)?;
let mut canvas = modular_canvas(config.size, &config.elements, state)?;
canvas.init(state)?;
let view = canvas.view().unwrap();
let pipeline = {
let shaders = state.graphics.shared_shaders.read().unwrap();
state.graphics.create_pipeline_dynamic(
shaders.get("vert_common").unwrap().clone(), // want panic
shaders.get("frag_sprite").unwrap().clone(), // want panic
state.graphics.native_format,
Some(BLEND_ALPHA),
)
}?;
let set0 = pipeline
.uniform_sampler(0, view.clone(), pipeline.graphics.texture_filtering)
.unwrap();
let pass = pipeline
.create_pass(
[swapchain_size[0] as f32, swapchain_size[1] as f32],
state.graphics.quad_verts.clone(),
state.graphics.quad_indices.clone(),
vec![set0],
)
.unwrap();
Ok(PreviewState {
Ok(Self {
canvas,
pipeline,
pass,
swapchain,
images,
})
}
}
#[allow(clippy::too_many_lines)]
pub fn uidev_run(panel_name: &str) -> anyhow::Result<()> {
let (graphics, event_loop, window, surface) = WlxGraphics::new_window()?;
window.set_resizable(false);
@@ -127,11 +100,12 @@ pub fn uidev_run(panel_name: &str) -> anyhow::Result<()> {
panel_name,
)?);
let watch_path = config_io::get_config_root().join(format!("{}.yaml", panel_name));
let watch_path = config_io::get_config_root().join(format!("{panel_name}.yaml"));
let mut path_last_modified = watch_path.metadata()?.modified()?;
let mut recreate = false;
let mut last_draw = std::time::Instant::now();
#[allow(deprecated)]
event_loop.run(move |event, elwt| {
elwt.set_control_flow(ControlFlow::Poll);
@@ -182,29 +156,27 @@ pub fn uidev_run(panel_name: &str) -> anyhow::Result<()> {
Err(e) => panic!("failed to acquire next image: {e}"),
};
if let Err(e) = preview.canvas.render(&mut state) {
let mut canvas_cmd_buf = CommandBuffers::default();
let tgt = preview.images[image_index as usize].clone();
if let Err(e) = preview
.canvas
.render(&mut state, tgt, &mut canvas_cmd_buf, 1.0)
{
log::error!("failed to render canvas: {e}");
window.request_redraw();
};
}
let target = preview.images[image_index as usize].clone();
let mut cmd_buf = state
.graphics
.create_command_buffer(CommandBufferUsage::OneTimeSubmit)
.unwrap();
cmd_buf.begin_rendering(target).unwrap();
cmd_buf.run_ref(&preview.pass).unwrap();
cmd_buf.end_rendering().unwrap();
last_draw = std::time::Instant::now();
let command_buffer = cmd_buf.build().unwrap();
vulkano::sync::now(graphics.device.clone())
.join(acquire_future)
.then_execute(graphics.queue.clone(), command_buffer)
canvas_cmd_buf
.execute_after(
state.graphics.graphics_queue.clone(),
Box::new(acquire_future),
)
.unwrap()
.then_swapchain_present(
graphics.queue.clone(),
graphics.graphics_queue.clone(),
SwapchainPresentInfo::swapchain_image_index(
preview.swapchain.clone(),
image_index,
@@ -236,12 +208,12 @@ fn create_swapchain(
let surface_capabilities = graphics
.device
.physical_device()
.surface_capabilities(&surface, Default::default())
.surface_capabilities(&surface, SurfaceInfo::default())
.unwrap(); // want panic
let (swapchain, images) = Swapchain::new(
graphics.device.clone(),
surface.clone(),
surface,
SwapchainCreateInfo {
min_image_count: surface_capabilities.min_image_count.max(2),
image_format: graphics.native_format,
+9 -13
View File
@@ -36,7 +36,7 @@ pub struct WayVRCompositor {
}
fn get_wayvr_env_from_pid(pid: i32) -> anyhow::Result<ProcessWayVREnv> {
let path = format!("/proc/{}/environ", pid);
let path = format!("/proc/{pid}/environ");
let mut env_data = String::new();
std::fs::File::open(path)?.read_to_string(&mut env_data)?;
@@ -162,7 +162,7 @@ impl WayVRCompositor {
) -> anyhow::Result<()> {
if let Some(stream) = self.listener.accept()? {
if let Err(e) = self.accept_connection(stream, displays, processes) {
log::error!("Failed to accept connection: {}", e);
log::error!("Failed to accept connection: {e}");
}
}
@@ -175,7 +175,7 @@ impl WayVRCompositor {
processes: &mut process::ProcessVec,
) -> anyhow::Result<()> {
if let Err(e) = self.accept_connections(displays, processes) {
log::error!("accept_connections failed: {}", e);
log::error!("accept_connections failed: {e}");
}
self.display.dispatch_clients(&mut self.state)?;
@@ -184,7 +184,7 @@ impl WayVRCompositor {
let surf_count = self.state.xdg_shell.toplevel_surfaces().len() as u32;
if surf_count != self.toplevel_surf_count {
self.toplevel_surf_count = surf_count;
log::info!("Toplevel surface count changed: {}", surf_count);
log::info!("Toplevel surface count changed: {surf_count}");
}
Ok(())
@@ -211,11 +211,9 @@ impl WayVRCompositor {
const STARTING_WAYLAND_ADDR_IDX: u32 = 20;
fn export_display_number(display_num: u32) -> anyhow::Result<()> {
let mut path = std::env::var("XDG_RUNTIME_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from("/tmp"));
let mut path = std::env::var("XDG_RUNTIME_DIR").map_or_else(|_| PathBuf::from("/tmp"), PathBuf::from);
path.push("wayvr.disp");
std::fs::write(path, format!("{}\n", display_num))?;
std::fs::write(path, format!("{display_num}\n"))?;
Ok(())
}
@@ -227,17 +225,15 @@ fn create_wayland_listener() -> anyhow::Result<(super::WaylandEnv, wayland_serve
let listener = loop {
let display_str = env.display_num_string();
log::debug!("Trying to open socket \"{}\"", display_str);
log::debug!("Trying to open socket \"{display_str}\"");
match wayland_server::ListeningSocket::bind(display_str.as_str()) {
Ok(listener) => {
log::debug!("Listening to {}", display_str);
log::debug!("Listening to {display_str}");
break listener;
}
Err(e) => {
log::debug!(
"Failed to open socket \"{}\" (reason: {}), trying next...",
display_str,
e
"Failed to open socket \"{display_str}\" (reason: {e}), trying next..."
);
env.display_num += 1;
+10 -5
View File
@@ -122,15 +122,13 @@ pub struct ClientState {
impl ClientData for ClientState {
fn initialized(&self, client_id: ClientId) {
log::debug!("Client ID {:?} connected", client_id);
log::debug!("Client ID {client_id:?} connected");
}
fn disconnected(&self, client_id: ClientId, reason: DisconnectReason) {
*self.disconnected.lock().unwrap() = true;
log::debug!(
"Client ID {:?} disconnected. Reason: {:?}",
client_id,
reason
"Client ID {client_id:?} disconnected. Reason: {reason:?}"
);
}
}
@@ -157,6 +155,13 @@ impl XdgShellHandler for Application {
surface.send_configure();
}
fn toplevel_destroyed(&mut self, surface: ToplevelSurface) {
if let Some(client) = surface.wl_surface().client() {
self.wayvr_tasks
.send(WayVRTask::DropToplevel(client.id(), surface.clone()));
}
}
fn new_popup(&mut self, _surface: PopupSurface, _positioner: PositionerState) {
// Handle popup creation here
}
@@ -195,7 +200,7 @@ impl DmabufHandler for Application {
notifier: ImportNotifier,
) {
if self.gles_renderer.import_dmabuf(&dmabuf, None).is_ok() {
let _ = notifier.successful::<Application>();
let _ = notifier.successful::<Self>();
} else {
notifier.failed();
}
+80 -41
View File
@@ -1,4 +1,4 @@
use std::{cell::RefCell, rc::Rc};
use std::{cell::RefCell, rc::Rc, sync::Arc};
use smithay::{
backend::renderer::{
@@ -23,7 +23,7 @@ use crate::{
use super::{
client::WayVRCompositor, comp::send_frames_surface_tree, egl_data, event_queue::SyncEventQueue,
process, smithay_wrapper, time, window, WayVRSignal,
process, smithay_wrapper, time, window, BlitMethod, WayVRSignal,
};
fn generate_auth_key() -> String {
@@ -71,7 +71,8 @@ pub struct Display {
gles_texture: GlesTexture, // TODO: drop texture
egl_image: khronos_egl::Image,
egl_data: Rc<egl_data::EGLData>,
pub dmabuf_data: egl_data::DMAbufData,
pub render_data: egl_data::RenderData,
pub tasks: SyncEventQueue<DisplayTask>,
}
@@ -87,6 +88,7 @@ impl Drop for Display {
pub struct DisplayInitParams<'a> {
pub wm: Rc<RefCell<window::WindowManager>>,
pub config: &'a super::Config,
pub renderer: &'a mut GlesRenderer,
pub egl_data: Rc<egl_data::EGLData>,
pub wayland_env: super::WaylandEnv,
@@ -120,18 +122,24 @@ impl Display {
let tex_id = params.renderer.with_context(|gl| {
smithay_wrapper::create_framebuffer_texture(
gl,
params.width as u32,
params.height as u32,
u32::from(params.width),
u32::from(params.height),
tex_format,
internal_format,
)
})?;
let egl_image = params.egl_data.create_egl_image(tex_id)?;
let dmabuf_data = params.egl_data.create_dmabuf_data(&egl_image)?;
let render_data = match params.config.blit_method {
BlitMethod::Dmabuf => {
egl_data::RenderData::Dmabuf(params.egl_data.create_dmabuf_data(&egl_image)?)
}
BlitMethod::Software => egl_data::RenderData::Software(None),
};
let opaque = false;
let size = (params.width as i32, params.height as i32).into();
let size = (i32::from(params.width), i32::from(params.height)).into();
let gles_texture = unsafe {
GlesTexture::from_raw(params.renderer, Some(tex_format), opaque, tex_id, size)
};
@@ -145,7 +153,7 @@ impl Display {
wayland_env: params.wayland_env,
wm: params.wm,
displayed_windows: Vec::new(),
dmabuf_data,
render_data,
egl_image,
gles_texture,
last_pressed_time_ms: 0,
@@ -183,13 +191,18 @@ impl Display {
self.reposition_windows();
}
pub fn remove_window(&mut self, window_handle: window::WindowHandle) {
self.displayed_windows
.retain(|disp| disp.window_handle != window_handle);
}
pub fn reposition_windows(&mut self) {
let window_count = self.displayed_windows.len();
match &self.layout {
packet_server::WvrDisplayWindowLayout::Tiling => {
let mut i = 0;
for win in self.displayed_windows.iter_mut() {
for win in &mut self.displayed_windows {
if let Some(window) = self.wm.borrow_mut().windows.get_mut(&win.window_handle) {
if !window.visible {
continue;
@@ -197,21 +210,21 @@ impl Display {
let d_cur = i as f32 / window_count as f32;
let d_next = (i + 1) as f32 / window_count as f32;
let left = (d_cur * self.width as f32) as i32;
let right = (d_next * self.width as f32) as i32;
let left = (d_cur * f32::from(self.width)) as i32;
let right = (d_next * f32::from(self.width)) as i32;
window.set_pos(left, 0);
window.set_size((right - left) as u32, self.height as u32);
window.set_size((right - left) as u32, u32::from(self.height));
i += 1;
}
}
}
packet_server::WvrDisplayWindowLayout::Stacking(opts) => {
let do_margins = |margins: &packet_server::Margins, window: &mut window::Window| {
let top = margins.top as i32;
let bottom = self.height as i32 - margins.bottom as i32;
let left = margins.left as i32;
let right = self.width as i32 - margins.right as i32;
let top = i32::from(margins.top);
let bottom = i32::from(self.height) - i32::from(margins.bottom);
let left = i32::from(margins.left);
let right = i32::from(self.width) - i32::from(margins.right);
let width = right - left;
let height = bottom - top;
if width < 0 || height < 0 {
@@ -223,7 +236,7 @@ impl Display {
};
let mut i = 0;
for win in self.displayed_windows.iter_mut() {
for win in &mut self.displayed_windows {
if let Some(window) = self.wm.borrow_mut().windows.get_mut(&win.window_handle) {
if !window.visible {
continue;
@@ -254,7 +267,7 @@ impl Display {
self.no_windows_since = None;
} else if let Some(auto_hide_delay) = config.auto_hide_delay {
if let Some(s) = self.no_windows_since {
if s + (auto_hide_delay as u64) < get_millis() {
if s + u64::from(auto_hide_delay) < get_millis() {
// Auto-hide after specific time
signals.send(WayVRSignal::DisplayVisibility(*handle, false));
}
@@ -287,12 +300,11 @@ impl Display {
}
}
pub fn tick_render(&self, renderer: &mut GlesRenderer, time_ms: u64) -> anyhow::Result<()> {
pub fn tick_render(&mut self, renderer: &mut GlesRenderer, time_ms: u64) -> anyhow::Result<()> {
renderer.bind(self.gles_texture.clone())?;
let size = Size::from((self.width as i32, self.height as i32));
let damage: Rectangle<i32, smithay::utils::Physical> =
Rectangle::from_loc_and_size((0, 0), size);
let size = Size::from((i32::from(self.width), i32::from(self.height)));
let damage: Rectangle<i32, smithay::utils::Physical> = Rectangle::from_size(size);
let elements: Vec<WaylandSurfaceRenderElement<GlesRenderer>> = self
.displayed_windows
@@ -336,6 +348,35 @@ impl Display {
send_frames_surface_tree(window.toplevel.wl_surface(), time_ms as u32);
}
if let egl_data::RenderData::Software(_) = &self.render_data {
// Read OpenGL texture into memory. Slow!
let pixel_data = renderer.with_context(|gl| unsafe {
gl.BindTexture(ffi::TEXTURE_2D, self.gles_texture.tex_id());
let len = self.width as usize * self.height as usize * 4;
let mut data: Box<[u8]> = Box::new_uninit_slice(len).assume_init();
gl.ReadPixels(
0,
0,
i32::from(self.width),
i32::from(self.height),
ffi::RGBA,
ffi::UNSIGNED_BYTE,
data.as_mut_ptr().cast(),
);
let data: Arc<[u8]> = Arc::from(data);
data
})?;
self.render_data =
egl_data::RenderData::Software(Some(egl_data::RenderSoftwarePixelsData {
data: pixel_data,
width: self.width,
height: self.height,
}));
}
Ok(())
}
@@ -360,7 +401,7 @@ impl Display {
None
}
pub fn trigger_rerender(&mut self) {
pub const fn trigger_rerender(&mut self) {
self.wants_redraw = true;
}
@@ -394,7 +435,7 @@ impl Display {
y: u32,
) {
let current_ms = time::get_millis();
if self.last_pressed_time_ms + config.click_freeze_time_ms as u64 > current_ms {
if self.last_pressed_time_ms + u64::from(config.click_freeze_time_ms) > current_ms {
return;
}
@@ -403,8 +444,8 @@ impl Display {
if let Some(window) = wm.windows.get(&window_handle) {
let surf = window.toplevel.wl_surface().clone();
let point = Point::<f64, Logical>::from((
(x as i32 - window.pos_x) as f64,
(y as i32 - window.pos_y) as f64,
f64::from(x as i32 - window.pos_x),
f64::from(y as i32 - window.pos_y),
));
manager.seat_pointer.motion(
@@ -422,7 +463,7 @@ impl Display {
}
}
fn get_mouse_index_number(index: super::MouseIndex) -> u32 {
const fn get_mouse_index_number(index: super::MouseIndex) -> u32 {
match index {
super::MouseIndex::Left => 0x110, /* BTN_LEFT */
super::MouseIndex::Center => 0x112, /* BTN_MIDDLE */
@@ -443,13 +484,11 @@ impl Display {
if let Some(window) = wm.windows.get(&window_handle) {
let surf = window.toplevel.wl_surface().clone();
if manager.seat_keyboard.current_focus().is_none() {
manager.seat_keyboard.set_focus(
&mut manager.state,
Some(surf),
manager.serial_counter.next_serial(),
);
}
manager.seat_keyboard.set_focus(
&mut manager.state,
Some(surf),
manager.serial_counter.next_serial(),
);
}
}
@@ -466,7 +505,7 @@ impl Display {
manager.seat_pointer.frame(&mut manager.state);
}
pub fn send_mouse_up(&self, manager: &mut WayVRCompositor, index: super::MouseIndex) {
pub fn send_mouse_up(manager: &mut WayVRCompositor, index: super::MouseIndex) {
manager.seat_pointer.button(
&mut manager.state,
&input::pointer::ButtonEvent {
@@ -480,7 +519,7 @@ impl Display {
manager.seat_pointer.frame(&mut manager.state);
}
pub fn send_mouse_scroll(&self, manager: &mut WayVRCompositor, delta: f32) {
pub fn send_mouse_scroll(manager: &mut WayVRCompositor, delta_y: f32, delta_x: f32) {
manager.seat_pointer.axis(
&mut manager.state,
input::pointer::AxisFrame {
@@ -490,8 +529,8 @@ impl Display {
smithay::backend::input::AxisRelativeDirection::Identical,
),
time: 0,
axis: (0.0, -delta as f64),
v120: Some((0, (delta * -120.0) as i32)),
axis: (f64::from(delta_x), f64::from(-delta_y)),
v120: Some((0, (delta_y * -120.0) as i32)),
stop: (false, false),
},
);
@@ -510,7 +549,7 @@ impl Display {
args: &[&str],
env: &[(&str, &str)],
) -> anyhow::Result<SpawnProcessResult> {
log::info!("Spawning subprocess with exec path \"{}\"", exec_path);
log::info!("Spawning subprocess with exec path \"{exec_path}\"");
let auth_key = generate_auth_key();
@@ -538,14 +577,14 @@ impl Display {
gen_id!(DisplayVec, Display, DisplayCell, DisplayHandle);
impl DisplayHandle {
pub fn from_packet(handle: packet_server::WvrDisplayHandle) -> Self {
pub const fn from_packet(handle: packet_server::WvrDisplayHandle) -> Self {
Self {
generation: handle.generation,
idx: handle.idx,
}
}
pub fn as_packet(&self) -> packet_server::WvrDisplayHandle {
pub const fn as_packet(&self) -> packet_server::WvrDisplayHandle {
packet_server::WvrDisplayHandle {
idx: self.idx,
generation: self.generation,
+36 -18
View File
@@ -1,3 +1,5 @@
use std::sync::Arc;
use super::egl_ex;
use anyhow::anyhow;
@@ -23,34 +25,47 @@ pub struct DMAbufModifierInfo {
}
#[derive(Debug, Clone)]
pub struct DMAbufData {
pub struct RenderDMAbufData {
pub fd: i32,
pub stride: i32,
pub offset: i32,
pub mod_info: DMAbufModifierInfo,
}
#[derive(Debug, Clone)]
pub struct RenderSoftwarePixelsData {
pub data: Arc<[u8]>,
pub width: u16,
pub height: u16,
}
#[derive(Debug, Clone)]
pub enum RenderData {
Dmabuf(RenderDMAbufData),
Software(Option<RenderSoftwarePixelsData>), // will be set if the next image data is available
}
impl EGLData {
pub fn load_func(&self, func_name: &str) -> anyhow::Result<extern "system" fn()> {
let raw_fn = self.egl.get_proc_address(func_name).ok_or(anyhow::anyhow!(
"Required EGL function {} not found",
func_name
))?;
let raw_fn = self
.egl
.get_proc_address(func_name)
.ok_or_else(|| anyhow::anyhow!("Required EGL function {} not found", func_name))?;
Ok(raw_fn)
}
pub fn new() -> anyhow::Result<EGLData> {
pub fn new() -> anyhow::Result<Self> {
unsafe {
let egl = khronos_egl::Instance::new(khronos_egl::Static);
let display = egl
.get_display(khronos_egl::DEFAULT_DISPLAY)
.ok_or(anyhow!(
.ok_or_else(|| anyhow!(
"eglGetDisplay failed. This shouldn't happen unless you don't have any display manager running. Cannot continue, check your EGL installation."
))?;
let (major, minor) = egl.initialize(display)?;
log::debug!("EGL version: {}.{}", major, minor);
log::debug!("EGL version: {major}.{minor}");
let attrib_list = [
khronos_egl::RED_SIZE,
@@ -68,7 +83,7 @@ impl EGLData {
let config = egl
.choose_first_config(display, &attrib_list)?
.ok_or(anyhow!("Failed to get EGL config"))?;
.ok_or_else(|| anyhow!("Failed to get EGL config"))?;
egl.bind_api(khronos_egl::OPENGL_ES_API)?;
@@ -89,7 +104,7 @@ impl EGLData {
egl.make_current(display, None, None, Some(context))?;
Ok(EGLData {
Ok(Self {
egl,
display,
config,
@@ -99,7 +114,7 @@ impl EGLData {
}
fn query_dmabuf_mod_info(&self) -> anyhow::Result<DMAbufModifierInfo> {
let target_fourcc = 0x34324258; //XB24
let target_fourcc = 0x3432_4258; //XB24
unsafe {
use egl_ex::PFNEGLQUERYDMABUFFORMATSEXTPROC;
@@ -172,13 +187,13 @@ impl EGLData {
&mut num_mods,
);
if mods[0] == 0xFFFFFFFFFFFFFFFF {
if mods[0] == 0xFFFF_FFFF_FFFF_FFFF {
anyhow::bail!("modifier is -1")
}
log::trace!("Modifier list:");
for modifier in &mods {
log::trace!("{:#x}", modifier);
log::trace!("{modifier:#x}");
}
// We should not change these modifier values. Passing all of them to the Vulkan dmabuf
@@ -188,13 +203,13 @@ impl EGLData {
// If not, the full list of modifiers will be passed. Further testing is required.
// For now, it looks like only NAVI32-based gpus have this problem.
let mod_whitelist: [u64; 2] = [
0x20000002086bf04, /* AMD RX 7800 XT, Navi32 */
0x20000001866bf04, /* AMD RX 7600 XT, Navi33 */
0x200_0000_2086_bf04, /* AMD RX 7800 XT, Navi32 */
0x200_0000_1866_bf04, /* AMD RX 7600 XT, Navi33 */
];
for modifier in &mod_whitelist {
if mods.contains(modifier) {
log::warn!("Using whitelisted dmabuf tiling modifier: {:#x}", modifier);
log::warn!("Using whitelisted dmabuf tiling modifier: {modifier:#x}");
mods = vec![*modifier, 0x0 /* also important (???) */];
break;
}
@@ -207,7 +222,10 @@ impl EGLData {
}
}
pub fn create_dmabuf_data(&self, egl_image: &khronos_egl::Image) -> anyhow::Result<DMAbufData> {
pub fn create_dmabuf_data(
&self,
egl_image: &khronos_egl::Image,
) -> anyhow::Result<RenderDMAbufData> {
use egl_ex::PFNEGLEXPORTDMABUFIMAGEMESAPROC as FUNC;
unsafe {
let egl_export_dmabuf_image_mesa =
@@ -235,7 +253,7 @@ impl EGLData {
let mod_info = self.query_dmabuf_mod_info()?;
Ok(DMAbufData {
Ok(RenderDMAbufData {
fd: fds[0],
stride: strides[0],
offset: offsets[0],
+1 -1
View File
@@ -16,7 +16,7 @@ impl<DataType> SyncEventQueue<DataType> {
pub fn new() -> Self {
Self {
data: Rc::new(RefCell::new(Data {
queue: Default::default(),
queue: VecDeque::default(),
})),
}
}
+7 -7
View File
@@ -30,27 +30,27 @@ macro_rules! gen_id {
#[allow(dead_code)]
impl $handle_name {
pub fn reset(&mut self) {
pub const fn reset(&mut self) {
self.generation = 0;
}
pub fn is_set(&self) -> bool {
pub const fn is_set(&self) -> bool {
self.generation > 0
}
pub fn id(&self) -> u32 {
pub const fn id(&self) -> u32 {
self.idx
}
pub fn new(idx: u32, generation: u64) -> Self {
pub const fn new(idx: u32, generation: u64) -> Self {
Self { idx, generation }
}
}
//ThingVec
#[allow(dead_code)]
#[allow(dead_code, clippy::iter_not_returning_iterator)]
impl $container_name {
pub fn new() -> Self {
pub const fn new() -> Self {
Self {
vec: Vec::new(),
cur_generation: 0,
@@ -78,7 +78,7 @@ macro_rules! gen_id {
}
}
pub fn get_handle(cell: &$cell_name, idx: usize) -> $handle_name {
pub const fn get_handle(cell: &$cell_name, idx: usize) -> $handle_name {
$handle_name {
idx: idx as u32,
generation: cell.generation,
+132 -67
View File
@@ -11,7 +11,7 @@ mod smithay_wrapper;
mod time;
mod window;
use comp::Application;
use display::{DisplayInitParams, DisplayVec};
use display::{Display, DisplayInitParams, DisplayVec};
use event_queue::SyncEventQueue;
use process::ProcessVec;
use server_ipc::WayVRServer;
@@ -21,7 +21,7 @@ use smithay::{
egl,
renderer::{gles::GlesRenderer, ImportDma},
},
input::SeatState,
input::{keyboard::XkbConfig, SeatState},
output::{Mode, Output},
reexports::wayland_server::{self, backend::ClientId},
wayland::{
@@ -40,8 +40,9 @@ use std::{
use time::get_millis;
use wayvr_ipc::{packet_client, packet_server};
use crate::{hid::MODS_TO_KEYS, state::AppState};
const STR_INVALID_HANDLE_DISP: &str = "Invalid display handle";
const STR_INVALID_HANDLE_PROCESS: &str = "Invalid process handle";
#[derive(Debug, Clone)]
pub struct WaylandEnv {
@@ -71,6 +72,7 @@ pub struct ExternalProcessRequest {
#[derive(Clone)]
pub enum WayVRTask {
NewToplevel(ClientId, ToplevelSurface),
DropToplevel(ClientId, ToplevelSurface),
NewExternalProcess(ExternalProcessRequest),
DropOverlay(super::overlay::OverlayID),
ProcessTerminationRequest(process::ProcessHandle),
@@ -87,11 +89,27 @@ pub enum WayVRSignal {
BroadcastStateChanged(packet_server::WvrStateChanged),
}
pub enum BlitMethod {
Dmabuf,
Software,
}
impl BlitMethod {
pub fn from_string(str: &str) -> Option<Self> {
match str {
"dmabuf" => Some(Self::Dmabuf),
"software" => Some(Self::Software),
_ => None,
}
}
}
pub struct Config {
pub click_freeze_time_ms: u32,
pub keyboard_repeat_delay_ms: u32,
pub keyboard_repeat_rate: u32,
pub auto_hide_delay: Option<u32>, // if None, auto-hide is disabled
pub blit_method: BlitMethod,
}
pub struct WayVRState {
@@ -101,12 +119,13 @@ pub struct WayVRState {
wm: Rc<RefCell<window::WindowManager>>,
egl_data: Rc<egl_data::EGLData>,
pub processes: process::ProcessVec,
config: Config,
pub config: Config,
dashboard_display: Option<display::DisplayHandle>,
pub tasks: SyncEventQueue<WayVRTask>,
pub signals: SyncEventQueue<WayVRSignal>,
ticks: u64,
pub pending_haptic: Option<super::input::Haptics>,
cur_modifiers: u8,
}
pub struct WayVR {
@@ -130,6 +149,7 @@ pub enum TickTask {
}
impl WayVR {
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
pub fn new(config: Config) -> anyhow::Result<Self> {
log::info!("Initializing WayVR");
let display: wayland_server::Display<Application> = wayland_server::Display::new()?;
@@ -185,33 +205,35 @@ impl WayVR {
}
Ok(None) => {
log::warn!("dmabuf: Failed to query render node");
debug_assert!(false);
None
}
Err(err) => {
log::warn!("dmabuf: Failed to get egl device for display: {}", err);
debug_assert!(false);
log::warn!("dmabuf: Failed to get egl device for display: {err}");
None
}
};
let dmabuf_state = if let Some(default_feedback) = dmabuf_default_feedback {
let mut dmabuf_state = DmabufState::new();
let dmabuf_global = dmabuf_state.create_global_with_default_feedback::<Application>(
&display.handle(),
&default_feedback,
);
(dmabuf_state, dmabuf_global, Some(default_feedback))
} else {
let dmabuf_formats = gles_renderer.dmabuf_formats();
let mut dmabuf_state = DmabufState::new();
let dmabuf_global =
dmabuf_state.create_global::<Application>(&display.handle(), dmabuf_formats);
(dmabuf_state, dmabuf_global, None)
};
let dmabuf_state = dmabuf_default_feedback.map_or_else(
|| {
let dmabuf_formats = gles_renderer.dmabuf_formats();
let mut dmabuf_state = DmabufState::new();
let dmabuf_global =
dmabuf_state.create_global::<Application>(&display.handle(), dmabuf_formats);
(dmabuf_state, dmabuf_global, None)
},
|default_feedback| {
let mut dmabuf_state = DmabufState::new();
let dmabuf_global = dmabuf_state
.create_global_with_default_feedback::<Application>(
&display.handle(),
&default_feedback,
);
(dmabuf_state, dmabuf_global, Some(default_feedback))
},
);
let seat_keyboard = seat.add_keyboard(
Default::default(),
XkbConfig::default(),
config.keyboard_repeat_delay_ms as i32,
config.keyboard_repeat_rate as i32,
)?;
@@ -248,27 +270,28 @@ impl WayVR {
tasks,
pending_haptic: None,
signals: SyncEventQueue::new(),
cur_modifiers: 0,
};
Ok(Self { state, ipc_server })
}
pub fn tick_display(&mut self, display: display::DisplayHandle) -> anyhow::Result<()> {
pub fn tick_display(&mut self, display: display::DisplayHandle) -> anyhow::Result<bool> {
// millis since the start of wayvr
let display = self
.state
.displays
.get_mut(&display)
.ok_or(anyhow::anyhow!(STR_INVALID_HANDLE_DISP))?;
.ok_or_else(|| anyhow::anyhow!(STR_INVALID_HANDLE_DISP))?;
if !display.wants_redraw {
// Nothing changed, do not render
return Ok(());
return Ok(false);
}
if !display.visible {
// Display is invisible, do not render
return Ok(());
return Ok(false);
}
let time_ms = get_millis() - self.state.time_start;
@@ -276,16 +299,18 @@ impl WayVR {
display.tick_render(&mut self.state.manager.state.gles_renderer, time_ms)?;
display.wants_redraw = false;
Ok(())
Ok(true)
}
pub fn tick_events(&mut self) -> anyhow::Result<Vec<TickTask>> {
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
pub fn tick_events(&mut self, app: &AppState) -> anyhow::Result<Vec<TickTask>> {
let mut tasks: Vec<TickTask> = Vec::new();
self.ipc_server.tick(&mut server_ipc::TickParams {
state: &mut self.state,
tasks: &mut tasks,
})?;
app,
});
// Check for redraw events
self.state.displays.iter_mut(&mut |_, disp| {
@@ -343,36 +368,63 @@ impl WayVR {
WayVRTask::NewToplevel(client_id, toplevel) => {
// Attach newly created toplevel surfaces to displays
for client in &self.state.manager.clients {
if client.client.id() == client_id {
if let Some(process_handle) =
process::find_by_pid(&self.state.processes, client.pid)
{
let window_handle = self
.state
.wm
.borrow_mut()
.create_window(client.display_handle, &toplevel);
if client.client.id() != client_id {
continue;
}
if let Some(display) =
self.state.displays.get_mut(&client.display_handle)
{
display.add_window(window_handle, process_handle, &toplevel);
self.state.signals.send(WayVRSignal::BroadcastStateChanged(
packet_server::WvrStateChanged::WindowCreated,
));
} else {
// This shouldn't happen, scream if it does
log::error!("Could not attach window handle into display");
}
} else {
log::error!(
let Some(process_handle) =
process::find_by_pid(&self.state.processes, client.pid)
else {
log::error!(
"WayVR window creation failed: Unexpected process ID {}. It wasn't registered before.",
client.pid
);
}
continue;
};
break;
let window_handle = self
.state
.wm
.borrow_mut()
.create_window(client.display_handle, &toplevel);
let Some(display) = self.state.displays.get_mut(&client.display_handle)
else {
// This shouldn't happen, scream if it does
log::error!("Could not attach window handle into display");
continue;
};
display.add_window(window_handle, process_handle, &toplevel);
self.state.signals.send(WayVRSignal::BroadcastStateChanged(
packet_server::WvrStateChanged::WindowCreated,
));
}
}
WayVRTask::DropToplevel(client_id, toplevel) => {
for client in &self.state.manager.clients {
if client.client.id() != client_id {
continue;
}
let mut wm = self.state.wm.borrow_mut();
let Some(window_handle) = wm.find_window_handle(&toplevel) else {
log::warn!("DropToplevel: Couldn't find matching window handle");
continue;
};
let Some(display) = self.state.displays.get_mut(&client.display_handle)
else {
log::warn!("DropToplevel: Couldn't find matching display");
continue;
};
display.remove_window(window_handle);
wm.remove_window(window_handle);
drop(wm);
display.reposition_windows();
}
}
WayVRTask::ProcessTerminationRequest(process_handle) => {
@@ -411,6 +463,7 @@ impl WayVR {
Ok(())
}
#[allow(dead_code)]
pub fn get_primary_display(displays: &DisplayVec) -> Option<display::DisplayHandle> {
for (idx, cell) in displays.vec.iter().enumerate() {
if let Some(cell) = cell {
@@ -456,22 +509,31 @@ impl WayVRState {
}
}
pub fn send_mouse_up(&mut self, display: display::DisplayHandle, index: MouseIndex) {
if let Some(display) = self.displays.get(&display) {
display.send_mouse_up(&mut self.manager, index);
}
pub fn send_mouse_up(&mut self, index: MouseIndex) {
Display::send_mouse_up(&mut self.manager, index);
}
pub fn send_mouse_scroll(&mut self, display: display::DisplayHandle, delta: f32) {
if let Some(display) = self.displays.get(&display) {
display.send_mouse_scroll(&mut self.manager, delta);
}
pub fn send_mouse_scroll(&mut self, delta_y: f32, delta_x: f32) {
Display::send_mouse_scroll(&mut self.manager, delta_y, delta_x);
}
pub fn send_key(&mut self, virtual_key: u32, down: bool) {
self.manager.send_key(virtual_key, down);
}
pub fn set_modifiers(&mut self, modifiers: u8) {
let changed = self.cur_modifiers ^ modifiers;
for i in 0..8 {
let m = 1 << i;
if changed & m != 0 {
if let Some(vk) = MODS_TO_KEYS.get(m).into_iter().flatten().next() {
self.send_key(*vk as u32, modifiers & m != 0);
}
}
}
self.cur_modifiers = modifiers;
}
pub fn set_display_visible(&mut self, display: display::DisplayHandle, visible: bool) {
if let Some(display) = self.displays.get_mut(&display) {
display.set_visible(visible);
@@ -488,10 +550,13 @@ impl WayVRState {
}
}
pub fn get_dmabuf_data(&self, display: display::DisplayHandle) -> Option<egl_data::DMAbufData> {
pub fn get_render_data(
&self,
display: display::DisplayHandle,
) -> Option<&egl_data::RenderData> {
self.displays
.get(&display)
.map(|display| display.dmabuf_data.clone())
.map(|display| &display.render_data)
}
pub fn create_display(
@@ -506,12 +571,12 @@ impl WayVRState {
egl_data: self.egl_data.clone(),
renderer: &mut self.manager.state.gles_renderer,
wayland_env: self.manager.wayland_env.clone(),
config: &self.config,
width,
height,
name,
primary,
})?;
let handle = self.displays.add(display);
self.signals.send(WayVRSignal::BroadcastStateChanged(
@@ -549,7 +614,7 @@ impl WayVRState {
self.manager.cleanup_clients();
for client in self.manager.clients.iter() {
for client in &self.manager.clients {
if client.display_handle == handle {
// This shouldn't happen, but make sure we are all set to destroy this display
anyhow::bail!("Wayland client still exists");
@@ -616,8 +681,8 @@ impl WayVRState {
) -> process::ProcessHandle {
self.processes
.add(process::Process::External(process::ExternalProcess {
display_handle,
pid,
display_handle,
}))
}
@@ -632,7 +697,7 @@ impl WayVRState {
let display = self
.displays
.get_mut(&display_handle)
.ok_or(anyhow::anyhow!(STR_INVALID_HANDLE_DISP))?;
.ok_or_else(|| anyhow::anyhow!(STR_INVALID_HANDLE_DISP))?;
let res = display.spawn_process(exec_path, args, env)?;
+67 -29
View File
@@ -1,4 +1,4 @@
use std::collections::HashMap;
use std::{collections::HashMap, io::Read};
use wayvr_ipc::packet_server;
@@ -7,6 +7,7 @@ use crate::gen_id;
use super::display;
#[derive(Debug)]
#[allow(dead_code)]
pub struct WayVRProcess {
pub auth_key: String,
pub child: std::process::Child,
@@ -32,45 +33,45 @@ pub enum Process {
}
impl Process {
pub fn display_handle(&self) -> display::DisplayHandle {
pub const fn display_handle(&self) -> display::DisplayHandle {
match self {
Process::Managed(p) => p.display_handle,
Process::External(p) => p.display_handle,
Self::Managed(p) => p.display_handle,
Self::External(p) => p.display_handle,
}
}
pub fn is_running(&mut self) -> bool {
match self {
Process::Managed(p) => p.is_running(),
Process::External(p) => p.is_running(),
Self::Managed(p) => p.is_running(),
Self::External(p) => p.is_running(),
}
}
pub fn terminate(&mut self) {
match self {
Process::Managed(p) => p.terminate(),
Process::External(p) => p.terminate(),
Self::Managed(p) => p.terminate(),
Self::External(p) => p.terminate(),
}
}
pub fn get_name(&self) -> String {
match self {
Process::Managed(p) => p.get_name().unwrap_or(String::from("unknown")),
Process::External(p) => p.get_name().unwrap_or(String::from("unknown")),
Self::Managed(p) => p.get_name().unwrap_or_else(|| String::from("unknown")),
Self::External(p) => p.get_name().unwrap_or_else(|| String::from("unknown")),
}
}
pub fn to_packet(&self, handle: ProcessHandle) -> packet_server::WvrProcess {
match self {
Process::Managed(p) => packet_server::WvrProcess {
name: p.get_name().unwrap_or(String::from("unknown")),
Self::Managed(p) => packet_server::WvrProcess {
name: p.get_name().unwrap_or_else(|| String::from("unknown")),
userdata: p.userdata.clone(),
display_handle: p.display_handle.as_packet(),
handle: handle.as_packet(),
},
Process::External(p) => packet_server::WvrProcess {
name: p.get_name().unwrap_or(String::from("unknown")),
userdata: Default::default(),
Self::External(p) => packet_server::WvrProcess {
name: p.get_name().unwrap_or_else(|| String::from("unknown")),
userdata: HashMap::default(),
display_handle: p.display_handle.as_packet(),
handle: handle.as_packet(),
},
@@ -88,6 +89,23 @@ impl Drop for WayVRProcess {
}
}
fn get_process_env_value(pid: i32, key: &str) -> anyhow::Result<Option<String>> {
let path = format!("/proc/{pid}/environ");
let mut env_data = String::new();
std::fs::File::open(path)?.read_to_string(&mut env_data)?;
let lines: Vec<&str> = env_data.split('\0').filter(|s| !s.is_empty()).collect();
for line in lines {
if let Some(cell) = line.split_once('=') {
if cell.0 == key {
return Ok(Some(String::from(cell.1)));
}
}
}
Ok(None)
}
impl WayVRProcess {
fn is_running(&mut self) -> bool {
match self.child.try_wait() {
@@ -95,7 +113,7 @@ impl WayVRProcess {
Ok(None) => true,
Err(e) => {
// this shouldn't happen
log::error!("Child::try_wait failed: {}", e);
log::error!("Child::try_wait failed: {e}");
false
}
}
@@ -114,7 +132,7 @@ impl WayVRProcess {
}
fn get_exec_name_from_pid(pid: u32) -> Option<String> {
let path = format!("/proc/{}/exe", pid);
let path = format!("/proc/{pid}/exe");
match std::fs::read_link(&path) {
Ok(buf) => {
if let Some(process_name) = buf.file_name().and_then(|s| s.to_str()) {
@@ -153,34 +171,54 @@ impl ExternalProcess {
gen_id!(ProcessVec, Process, ProcessCell, ProcessHandle);
pub fn find_by_pid(processes: &ProcessVec, pid: u32) -> Option<ProcessHandle> {
log::debug!("Finding process with PID {pid}");
for (idx, cell) in processes.vec.iter().enumerate() {
if let Some(cell) = cell {
match &cell.obj {
Process::Managed(wayvr_process) => {
if wayvr_process.child.id() == pid {
return Some(ProcessVec::get_handle(cell, idx));
}
let Some(cell) = cell else {
continue;
};
match &cell.obj {
Process::Managed(wayvr_process) => {
if wayvr_process.child.id() == pid {
return Some(ProcessVec::get_handle(cell, idx));
}
Process::External(external_process) => {
if external_process.pid == pid {
return Some(ProcessVec::get_handle(cell, idx));
}
}
Process::External(external_process) => {
if external_process.pid == pid {
return Some(ProcessVec::get_handle(cell, idx));
}
}
}
}
log::debug!("Finding by PID failed, trying WAYVR_DISPLAY_AUTH...");
if let Ok(Some(value)) = get_process_env_value(pid as i32, "WAYVR_DISPLAY_AUTH") {
for (idx, cell) in processes.vec.iter().enumerate() {
let Some(cell) = cell else {
continue;
};
if let Process::Managed(wayvr_process) = &cell.obj {
if wayvr_process.auth_key == value {
return Some(ProcessVec::get_handle(cell, idx));
}
}
}
}
log::debug!("Process find with PID {pid} failed");
None
}
impl ProcessHandle {
pub fn from_packet(handle: packet_server::WvrProcessHandle) -> Self {
pub const fn from_packet(handle: packet_server::WvrProcessHandle) -> Self {
Self {
generation: handle.generation,
idx: handle.idx,
}
}
pub fn as_packet(&self) -> packet_server::WvrProcessHandle {
pub const fn as_packet(&self) -> packet_server::WvrProcessHandle {
packet_server::WvrProcessHandle {
idx: self.idx,
generation: self.generation,
+71 -50
View File
@@ -1,12 +1,15 @@
use crate::state::AppState;
use super::{display, process, window, TickTask, WayVRSignal};
use bytes::BufMut;
use glam::Vec3A;
use interprocess::local_socket::{self, traits::Listener, ToNsName};
use smallvec::SmallVec;
use std::io::{Read, Write};
use wayvr_ipc::{
ipc::{self},
packet_client::{self, PacketClient},
packet_server::{self, PacketServer},
packet_server::{self, PacketServer, WlxInputStatePointer},
};
pub struct AuthInfo {
@@ -41,11 +44,11 @@ fn read_check(expected_size: u32, res: std::io::Result<usize>) -> bool {
if count == 0 {
return false;
}
if count as u32 != expected_size {
log::error!("count {} is not {}", count, expected_size);
false
} else {
if count as u32 == expected_size {
true // read succeeded
} else {
log::error!("count {count} is not {expected_size}");
false
}
}
Err(_e) => {
@@ -60,16 +63,17 @@ type Payload = SmallVec<[u8; 64]>;
fn read_payload(conn: &mut local_socket::Stream, size: u32) -> Option<Payload> {
let mut payload = Payload::new();
payload.resize(size as usize, 0);
if !read_check(size, conn.read(&mut payload)) {
None
} else {
if read_check(size, conn.read(&mut payload)) {
Some(payload)
} else {
None
}
}
pub struct TickParams<'a> {
pub state: &'a mut super::WayVRState,
pub tasks: &'a mut Vec<TickTask>,
pub app: &'a AppState,
}
pub fn gen_args_vec(input: &str) -> Vec<&str> {
@@ -85,7 +89,7 @@ pub fn gen_env_vec(input: &[String]) -> Vec<(&str, &str)> {
}
impl Connection {
fn new(conn: local_socket::Stream) -> Self {
const fn new(conn: local_socket::Stream) -> Self {
Self {
conn,
alive: true,
@@ -177,6 +181,34 @@ impl Connection {
Ok(())
}
fn handle_wlx_input_state(
&mut self,
params: &TickParams,
serial: ipc::Serial,
) -> anyhow::Result<()> {
let input_state = &params.app.input_state;
let to_arr = |vec: &Vec3A| -> [f32; 3] { [vec.x, vec.y, vec.z] };
send_packet(
&mut self.conn,
&ipc::data_encode(&PacketServer::WlxInputStateResponse(
serial,
packet_server::WlxInputState {
hmd_pos: to_arr(&input_state.hmd.translation),
left: WlxInputStatePointer {
pos: to_arr(&input_state.pointers[0].raw_pose.translation),
},
right: WlxInputStatePointer {
pos: to_arr(&input_state.pointers[0].raw_pose.translation),
},
},
)),
)?;
Ok(())
}
fn handle_wvr_display_create(
&mut self,
params: &mut TickParams,
@@ -190,10 +222,9 @@ impl Connection {
false,
)?;
params.tasks.push(TickTask::NewDisplay(
packet_params.clone(),
Some(display_handle),
));
params
.tasks
.push(TickTask::NewDisplay(packet_params, Some(display_handle)));
send_packet(
&mut self.conn,
@@ -214,7 +245,7 @@ impl Connection {
let res = params
.state
.destroy_display(display::DisplayHandle::from_packet(handle))
.map_err(|e| format!("{:?}", e));
.map_err(|e| format!("{e:?}"));
send_packet(
&mut self.conn,
@@ -224,29 +255,25 @@ impl Connection {
}
fn handle_wvr_display_set_visible(
&mut self,
params: &mut TickParams,
handle: packet_server::WvrDisplayHandle,
visible: bool,
) -> anyhow::Result<()> {
) {
params.state.signals.send(WayVRSignal::DisplayVisibility(
display::DisplayHandle::from_packet(handle),
visible,
));
Ok(())
}
fn handle_wvr_display_set_window_layout(
&mut self,
params: &mut TickParams,
handle: packet_server::WvrDisplayHandle,
layout: packet_server::WvrDisplayWindowLayout,
) -> anyhow::Result<()> {
) {
params.state.signals.send(WayVRSignal::DisplayWindowLayout(
display::DisplayHandle::from_packet(handle),
layout,
));
Ok(())
}
fn handle_wvr_display_window_list(
@@ -299,11 +326,10 @@ impl Connection {
}
fn handle_wvr_window_set_visible(
&mut self,
params: &mut TickParams,
handle: packet_server::WvrWindowHandle,
visible: bool,
) -> anyhow::Result<()> {
) {
let mut to_resize = None;
if let Some(window) = params
@@ -323,8 +349,6 @@ impl Connection {
display.trigger_rerender();
}
}
Ok(())
}
fn handle_wvr_process_launch(
@@ -360,7 +384,7 @@ impl Connection {
serial: ipc::Serial,
display_handle: packet_server::WvrDisplayHandle,
) -> anyhow::Result<()> {
let native_handle = &display::DisplayHandle::from_packet(display_handle.clone());
let native_handle = &display::DisplayHandle::from_packet(display_handle);
let disp = params
.state
.displays
@@ -408,20 +432,17 @@ impl Connection {
// This request doesn't return anything to the client
fn handle_wvr_process_terminate(
&mut self,
params: &mut TickParams,
process_handle: packet_server::WvrProcessHandle,
) -> anyhow::Result<()> {
let native_handle = &process::ProcessHandle::from_packet(process_handle.clone());
) {
let native_handle = &process::ProcessHandle::from_packet(process_handle);
let process = params.state.processes.get_mut(native_handle);
let Some(process) = process else {
return Ok(());
return;
};
process.terminate();
Ok(())
}
fn handle_wvr_process_get(
@@ -430,7 +451,7 @@ impl Connection {
serial: ipc::Serial,
process_handle: packet_server::WvrProcessHandle,
) -> anyhow::Result<()> {
let native_handle = &process::ProcessHandle::from_packet(process_handle.clone());
let native_handle = &process::ProcessHandle::from_packet(process_handle);
let process = params
.state
.processes
@@ -446,10 +467,9 @@ impl Connection {
}
fn handle_wlx_haptics(
&mut self,
params: &mut TickParams,
haptics_params: packet_client::WlxHapticsParams,
) -> anyhow::Result<()> {
) {
params
.state
.tasks
@@ -458,7 +478,6 @@ impl Connection {
frequency: haptics_params.frequency,
intensity: haptics_params.intensity,
}));
Ok(())
}
fn process_payload(&mut self, params: &mut TickParams, payload: Payload) -> anyhow::Result<()> {
@@ -471,6 +490,9 @@ impl Connection {
match packet {
PacketClient::Handshake(_) => unreachable!(), // handled previously
PacketClient::WlxInputState(serial) => {
self.handle_wlx_input_state(params, serial)?;
}
PacketClient::WvrDisplayList(serial) => {
self.handle_wvr_display_list(params, serial)?;
}
@@ -481,16 +503,16 @@ impl Connection {
self.handle_wvr_display_remove(params, serial, display_handle)?;
}
PacketClient::WvrDisplaySetVisible(display_handle, visible) => {
self.handle_wvr_display_set_visible(params, display_handle, visible)?;
Self::handle_wvr_display_set_visible(params, display_handle, visible);
}
PacketClient::WvrDisplaySetWindowLayout(display_handle, layout) => {
self.handle_wvr_display_set_window_layout(params, display_handle, layout)?;
Self::handle_wvr_display_set_window_layout(params, display_handle, layout);
}
PacketClient::WvrDisplayWindowList(serial, display_handle) => {
self.handle_wvr_display_window_list(params, serial, display_handle)?;
}
PacketClient::WvrWindowSetVisible(window_handle, visible) => {
self.handle_wvr_window_set_visible(params, window_handle, visible)?;
Self::handle_wvr_window_set_visible(params, window_handle, visible);
}
PacketClient::WvrProcessGet(serial, process_handle) => {
self.handle_wvr_process_get(params, serial, process_handle)?;
@@ -505,10 +527,10 @@ impl Connection {
self.handle_wvr_display_create(params, serial, packet_params)?;
}
PacketClient::WvrProcessTerminate(process_handle) => {
self.handle_wvr_process_terminate(params, process_handle)?;
Self::handle_wvr_process_terminate(params, process_handle);
}
PacketClient::WlxHaptics(haptics_params) => {
self.handle_wlx_haptics(params, haptics_params)?;
Self::handle_wlx_haptics(params, haptics_params);
}
}
@@ -519,9 +541,9 @@ impl Connection {
log::debug!("payload size {}", payload.len());
if let Err(e) = self.process_payload(params, payload) {
log::error!("Invalid payload from the client, closing connection: {}", e);
log::error!("Invalid payload from the client, closing connection: {e}");
// send also error message directly to the client before disconnecting
self.kill(format!("{}", e).as_str());
self.kill(format!("{e}").as_str());
false
} else {
true
@@ -554,8 +576,7 @@ impl Connection {
if payload_size > size_limit {
// over 128 KiB?
log::error!(
"Client sent a packet header with the size over {} bytes, closing connection.",
size_limit
"Client sent a packet header with the size over {size_limit} bytes, closing connection."
);
self.kill("Too big packet received (over 128 KiB)");
return false;
@@ -602,7 +623,7 @@ impl WayVRServer {
Err(e) => anyhow::bail!("Failed to start WayVRServer IPC listener. Reason: {}", e),
};
log::info!("WayVRServer IPC running at {}", printname);
log::info!("WayVRServer IPC running at {printname}");
Ok(Self {
listener,
@@ -627,16 +648,16 @@ impl WayVRServer {
self.connections.retain(|c| c.alive);
}
pub fn tick(&mut self, params: &mut TickParams) -> anyhow::Result<()> {
pub fn tick(&mut self, params: &mut TickParams) {
self.accept_connections();
self.tick_connections(params);
Ok(())
}
pub fn broadcast(&mut self, packet: packet_server::PacketServer) -> anyhow::Result<()> {
pub fn broadcast(&mut self, packet: packet_server::PacketServer) {
for connection in &mut self.connections {
send_packet(&mut connection.conn, &ipc::data_encode(&packet))?;
if let Err(e) = send_packet(&mut connection.conn, &ipc::data_encode(&packet)) {
log::error!("failed to broadcast packet: {e:?}");
}
}
Ok(())
}
}
+41 -41
View File
@@ -2,53 +2,53 @@ use super::egl_data;
use smithay::backend::{egl as smithay_egl, renderer::gles::ffi};
pub fn get_egl_display(data: &egl_data::EGLData) -> anyhow::Result<smithay_egl::EGLDisplay> {
Ok(unsafe { smithay_egl::EGLDisplay::from_raw(data.display.as_ptr(), data.config.as_ptr())? })
Ok(unsafe { smithay_egl::EGLDisplay::from_raw(data.display.as_ptr(), data.config.as_ptr())? })
}
pub fn get_egl_context(
data: &egl_data::EGLData,
display: &smithay_egl::EGLDisplay,
data: &egl_data::EGLData,
display: &smithay_egl::EGLDisplay,
) -> anyhow::Result<smithay_egl::EGLContext> {
let display_ptr = display.get_display_handle().handle;
debug_assert!(display_ptr == data.display.as_ptr());
let config_ptr = data.config.as_ptr();
let context_ptr = data.context.as_ptr();
Ok(unsafe { smithay_egl::EGLContext::from_raw(display_ptr, config_ptr, context_ptr)? })
let display_ptr = display.get_display_handle().handle;
debug_assert!(display_ptr == data.display.as_ptr());
let config_ptr = data.config.as_ptr();
let context_ptr = data.context.as_ptr();
Ok(unsafe { smithay_egl::EGLContext::from_raw(display_ptr, config_ptr, context_ptr)? })
}
pub fn create_framebuffer_texture(
gl: &ffi::Gles2,
width: u32,
height: u32,
tex_format: u32,
internal_format: u32,
gl: &ffi::Gles2,
width: u32,
height: u32,
tex_format: u32,
internal_format: u32,
) -> u32 {
unsafe {
let mut tex = 0;
gl.GenTextures(1, &mut tex);
gl.BindTexture(ffi::TEXTURE_2D, tex);
gl.TexParameteri(
ffi::TEXTURE_2D,
ffi::TEXTURE_MIN_FILTER,
ffi::NEAREST as i32,
);
gl.TexParameteri(
ffi::TEXTURE_2D,
ffi::TEXTURE_MAG_FILTER,
ffi::NEAREST as i32,
);
gl.TexImage2D(
ffi::TEXTURE_2D,
0,
internal_format as i32,
width as i32,
height as i32,
0,
tex_format,
ffi::UNSIGNED_BYTE,
std::ptr::null(),
);
gl.BindTexture(ffi::TEXTURE_2D, 0);
tex
}
unsafe {
let mut tex = 0;
gl.GenTextures(1, &mut tex);
gl.BindTexture(ffi::TEXTURE_2D, tex);
gl.TexParameteri(
ffi::TEXTURE_2D,
ffi::TEXTURE_MIN_FILTER,
ffi::NEAREST as i32,
);
gl.TexParameteri(
ffi::TEXTURE_2D,
ffi::TEXTURE_MAG_FILTER,
ffi::NEAREST as i32,
);
gl.TexImage2D(
ffi::TEXTURE_2D,
0,
internal_format as i32,
width as i32,
height as i32,
0,
tex_format,
ffi::UNSIGNED_BYTE,
std::ptr::null(),
);
gl.BindTexture(ffi::TEXTURE_2D, 0);
tex
}
}
+4 -4
View File
@@ -2,8 +2,8 @@ use std::time::{SystemTime, UNIX_EPOCH};
// Returns milliseconds since unix epoch
pub fn get_millis() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as u64
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as u64
}
+8 -4
View File
@@ -29,7 +29,7 @@ impl Window {
}
}
pub fn set_pos(&mut self, pos_x: i32, pos_y: i32) {
pub const fn set_pos(&mut self, pos_x: i32, pos_y: i32) {
self.pos_x = pos_x;
self.pos_y = pos_y;
}
@@ -52,7 +52,7 @@ pub struct WindowManager {
}
impl WindowManager {
pub fn new() -> Self {
pub const fn new() -> Self {
Self {
windows: WindowVec::new(),
}
@@ -77,19 +77,23 @@ impl WindowManager {
) -> WindowHandle {
self.windows.add(Window::new(display_handle, toplevel))
}
pub fn remove_window(&mut self, window_handle: WindowHandle) {
self.windows.remove(&window_handle);
}
}
gen_id!(WindowVec, Window, WindowCell, WindowHandle);
impl WindowHandle {
pub fn from_packet(handle: packet_server::WvrWindowHandle) -> Self {
pub const fn from_packet(handle: packet_server::WvrWindowHandle) -> Self {
Self {
generation: handle.generation,
idx: handle.idx,
}
}
pub fn as_packet(&self) -> packet_server::WvrWindowHandle {
pub const fn as_packet(&self) -> packet_server::WvrWindowHandle {
packet_server::WvrWindowHandle {
idx: self.idx,
generation: self.generation,
+48 -44
View File
@@ -69,79 +69,76 @@ impl AStrSetExt for AStrSet {
fn arc_rm(&mut self, value: &str) -> bool {
let index = self.iter().position(|v| v.as_ref().eq(value));
index
.map(|i| {
self.remove(i);
true
})
.unwrap_or(false)
index.is_some_and(|i| {
self.remove(i);
true
})
}
}
pub type PwTokenMap = AStrMap<String>;
pub fn def_watch_pos() -> Vec3A {
pub const fn def_watch_pos() -> Vec3A {
vec3a(-0.03, -0.01, 0.125)
}
pub fn def_watch_rot() -> Quat {
Quat::from_xyzw(-0.7071066, 0.0007963618, 0.7071066, 0.0)
pub const fn def_watch_rot() -> Quat {
Quat::from_xyzw(-0.707_106_6, 0.000_796_361_8, 0.707_106_6, 0.0)
}
pub fn def_left() -> LeftRight {
pub const fn def_left() -> LeftRight {
LeftRight::Left
}
pub fn def_pw_tokens() -> PwTokenMap {
pub const fn def_pw_tokens() -> PwTokenMap {
AStrMap::new()
}
fn def_mouse_move_interval_ms() -> u32 {
const fn def_mouse_move_interval_ms() -> u32 {
10 // 100fps
}
fn def_click_freeze_time_ms() -> u32 {
const fn def_click_freeze_time_ms() -> u32 {
300
}
pub fn def_true() -> bool {
pub const fn def_true() -> bool {
true
}
fn def_false() -> bool {
const fn def_false() -> bool {
false
}
fn def_one() -> f32 {
const fn def_one() -> f32 {
1.0
}
pub fn def_half() -> f32 {
pub const fn def_half() -> f32 {
0.5
}
pub fn def_point7() -> f32 {
pub const fn def_point7() -> f32 {
0.7
}
pub fn def_point3() -> f32 {
pub const fn def_point3() -> f32 {
0.3
}
fn def_osc_port() -> u16 {
const fn def_osc_port() -> u16 {
9000
}
fn def_empty_vec_string() -> Vec<String> {
const fn def_empty_vec_string() -> Vec<String> {
Vec::new()
}
fn def_timezones() -> Vec<String> {
let offset = chrono::Local::now().offset().fix();
const EMEA: i32 = -60 * 60; // UTC-1
const APAC: i32 = 5 * 60 * 60; // UTC+5
let offset = chrono::Local::now().offset().fix();
match offset.local_minus_utc() {
i32::MIN..EMEA => vec!["Europe/Paris".into(), "Asia/Tokyo".into()],
EMEA..APAC => vec!["America/New_York".into(), "Asia/Tokyo".into()],
@@ -149,15 +146,15 @@ fn def_timezones() -> Vec<String> {
}
}
fn def_screens() -> AStrSet {
const fn def_screens() -> AStrSet {
AStrSet::new()
}
fn def_curve_values() -> AStrMap<f32> {
const fn def_curve_values() -> AStrMap<f32> {
AStrMap::new()
}
fn def_transforms() -> AStrMap<Affine3A> {
const fn def_transforms() -> AStrMap<Affine3A> {
AStrMap::new()
}
@@ -177,7 +174,7 @@ fn def_font() -> Arc<str> {
"LiberationSans:style=Bold".into()
}
fn def_max_height() -> u16 {
const fn def_max_height() -> u16 {
1440
}
@@ -207,6 +204,9 @@ pub struct GeneralConfig {
#[serde(default = "def_toast_topics")]
pub notification_topics: IdMap<ToastTopic, DisplayMethod>,
#[serde(default = "def_empty")]
pub notification_sound: Arc<str>,
#[serde(default = "def_true")]
pub keyboard_sound_enabled: bool,
@@ -273,6 +273,12 @@ pub struct GeneralConfig {
#[serde(default = "def_false")]
pub focus_follows_mouse_mode: bool,
#[serde(default = "def_false")]
pub block_game_input: bool,
#[serde(default = "def_true")]
pub block_game_input_ignore_watch: bool,
#[serde(default = "def_font")]
pub primary_font: Arc<str>,
@@ -291,7 +297,7 @@ pub struct GeneralConfig {
#[serde(default = "def_max_height")]
pub screen_max_height: u16,
#[serde(default = "def_true")]
#[serde(default = "def_false")]
pub screen_render_down: bool,
#[serde(default = "def_point3")]
@@ -312,23 +318,21 @@ pub struct GeneralConfig {
impl GeneralConfig {
fn sanitize_range(name: &str, val: f32, from: f32, to: f32) {
if !val.is_normal() || val < from || val > to {
panic!(
"GeneralConfig: {} needs to be between {} and {}",
name, from, to
);
}
assert!(
!(!val.is_normal() || val < from || val > to),
"GeneralConfig: {name} needs to be between {from} and {to}"
);
}
pub fn load_from_disk() -> GeneralConfig {
pub fn load_from_disk() -> Self {
let config = load_general();
config.post_load();
config
}
fn post_load(&self) {
GeneralConfig::sanitize_range("keyboard_scale", self.keyboard_scale, 0.05, 5.0);
GeneralConfig::sanitize_range("desktop_view_scale", self.desktop_view_scale, 0.05, 5.0);
Self::sanitize_range("keyboard_scale", self.keyboard_scale, 0.05, 5.0);
Self::sanitize_range("desktop_view_scale", self.desktop_view_scale, 0.05, 5.0);
}
}
@@ -371,8 +375,8 @@ where
match serde_yaml::from_str::<T>(yaml) {
Ok(d) => return d,
Err(e) => {
error!("Failed to parse {}, falling back to defaults.", file_name);
error!("{}", e);
error!("Failed to parse {file_name}, falling back to defaults.");
error!("{e}");
}
}
}
@@ -381,7 +385,7 @@ where
}
pub fn load_custom_ui(name: &str) -> anyhow::Result<ModularUiConfig> {
let filename = format!("{}.yaml", name);
let filename = format!("{name}.yaml");
let Some(yaml_data) = config_io::load(&filename) else {
bail!("Could not read file at {}", &filename);
};
@@ -413,13 +417,13 @@ where
.filter_map(|r| match r {
Ok(entry) => Some(entry),
Err(e) => {
error!("Failed to read conf.d directory: {}", e);
error!("Failed to read conf.d directory: {e}");
None
}
})
.collect();
// Sort paths alphabetically
paths.sort_by_key(|dir| dir.path());
paths.sort_by_key(std::fs::DirEntry::path);
for path in paths {
log::info!("Loading config file: {}", path.path().to_string_lossy());
settings_builder = settings_builder.add_source(File::from(path.path()));
@@ -430,11 +434,11 @@ where
Ok(settings) => match settings.try_deserialize::<ConfigData>() {
Ok(config) => config,
Err(e) => {
panic!("Failed to deserialize settings: {}", e);
panic!("Failed to deserialize settings: {e}");
}
},
Err(e) => {
panic!("Failed to build settings: {}", e);
panic!("Failed to build settings: {e}");
}
}
}
+6 -14
View File
@@ -1,6 +1,5 @@
use log::error;
use once_cell::sync::Lazy;
use std::path::PathBuf;
use std::{path::PathBuf, sync::LazyLock};
pub enum ConfigRoot {
Generic,
@@ -10,17 +9,14 @@ pub enum ConfigRoot {
const FALLBACK_CONFIG_PATH: &str = "/tmp/wlxoverlay";
static CONFIG_ROOT_PATH: Lazy<PathBuf> = Lazy::new(|| {
static CONFIG_ROOT_PATH: LazyLock<PathBuf> = LazyLock::new(|| {
if let Ok(xdg_dirs) = xdg::BaseDirectories::new() {
let mut dir = xdg_dirs.get_config_home();
dir.push("wlxoverlay");
return dir;
}
//Return fallback config path
error!(
"Err: Failed to find config path, using {}",
FALLBACK_CONFIG_PATH
);
error!("Err: Failed to find config path, using {FALLBACK_CONFIG_PATH}");
PathBuf::from(FALLBACK_CONFIG_PATH)
});
@@ -31,8 +27,8 @@ pub fn get_config_root() -> PathBuf {
impl ConfigRoot {
pub fn get_conf_d_path(&self) -> PathBuf {
get_config_root().join(match self {
ConfigRoot::Generic => "conf.d",
ConfigRoot::WayVR => "wayvr.conf.d",
Self::Generic => "conf.d",
Self::WayVR => "wayvr.conf.d",
})
}
@@ -55,9 +51,5 @@ pub fn load(filename: &str) -> Option<String> {
let path = get_config_file_path(filename);
log::info!("Loading config: {}", path.to_string_lossy());
if let Ok(data) = std::fs::read_to_string(path) {
Some(data)
} else {
None
}
std::fs::read_to_string(path).ok()
}
+34 -34
View File
@@ -33,23 +33,23 @@ pub enum AttachTo {
}
impl AttachTo {
pub fn get_relative_to(&self) -> RelativeTo {
pub const fn get_relative_to(&self) -> RelativeTo {
match self {
AttachTo::None => RelativeTo::None,
AttachTo::HandLeft => RelativeTo::Hand(0),
AttachTo::HandRight => RelativeTo::Hand(1),
AttachTo::Stage => RelativeTo::Stage,
AttachTo::Head => RelativeTo::Head,
Self::None => RelativeTo::None,
Self::HandLeft => RelativeTo::Hand(0),
Self::HandRight => RelativeTo::Hand(1),
Self::Stage => RelativeTo::Stage,
Self::Head => RelativeTo::Head,
}
}
pub fn from_packet(input: &wayvr_ipc::packet_client::AttachTo) -> AttachTo {
pub const fn from_packet(input: &wayvr_ipc::packet_client::AttachTo) -> Self {
match input {
wayvr_ipc::packet_client::AttachTo::None => AttachTo::None,
wayvr_ipc::packet_client::AttachTo::HandLeft => AttachTo::HandLeft,
wayvr_ipc::packet_client::AttachTo::HandRight => AttachTo::HandRight,
wayvr_ipc::packet_client::AttachTo::Head => AttachTo::Head,
wayvr_ipc::packet_client::AttachTo::Stage => AttachTo::Stage,
wayvr_ipc::packet_client::AttachTo::None => Self::None,
wayvr_ipc::packet_client::AttachTo::HandLeft => Self::HandLeft,
wayvr_ipc::packet_client::AttachTo::HandRight => Self::HandRight,
wayvr_ipc::packet_client::AttachTo::Head => Self::Head,
wayvr_ipc::packet_client::AttachTo::Stage => Self::Stage,
}
}
}
@@ -92,28 +92,28 @@ impl WayVRCatalog {
}
}
fn def_false() -> bool {
const fn def_false() -> bool {
false
}
fn def_true() -> bool {
const fn def_true() -> bool {
true
}
fn def_autohide_delay() -> u32 {
const fn def_autohide_delay() -> u32 {
750
}
fn def_keyboard_repeat_delay() -> u32 {
const fn def_keyboard_repeat_delay() -> u32 {
200
}
fn def_keyboard_repeat_rate() -> u32 {
const fn def_keyboard_repeat_rate() -> u32 {
50
}
fn def_version() -> u32 {
1
fn def_blit_method() -> String {
String::from("dmabuf")
}
#[derive(Deserialize, Serialize)]
@@ -125,8 +125,6 @@ pub struct WayVRDashboard {
#[derive(Deserialize, Serialize)]
pub struct WayVRConfig {
#[serde(default = "def_version")]
pub version: u32,
#[serde(default = "def_false")]
pub run_compositor_at_start: bool,
@@ -150,6 +148,9 @@ pub struct WayVRConfig {
#[serde(default = "def_keyboard_repeat_rate")]
pub keyboard_repeat_rate: u32,
#[serde(default = "def_blit_method")]
pub blit_method: String,
}
impl WayVRConfig {
@@ -172,18 +173,20 @@ impl WayVRConfig {
pub fn get_wayvr_config(
config_general: &crate::config::GeneralConfig,
config_wayvr: &crate::config_wayvr::WayVRConfig,
) -> wayvr::Config {
wayvr::Config {
config_wayvr: &Self,
) -> anyhow::Result<wayvr::Config> {
Ok(wayvr::Config {
click_freeze_time_ms: config_general.click_freeze_time_ms,
keyboard_repeat_delay_ms: config_wayvr.keyboard_repeat_delay,
keyboard_repeat_rate: config_wayvr.keyboard_repeat_rate,
blit_method: wayvr::BlitMethod::from_string(&config_wayvr.blit_method)
.ok_or_else(|| anyhow::anyhow!("Unknown blit method"))?,
auto_hide_delay: if config_wayvr.auto_hide {
Some(config_wayvr.auto_hide_delay)
} else {
None
},
}
})
}
pub fn post_load(
@@ -200,7 +203,9 @@ impl WayVRConfig {
if primary_count > 1 {
anyhow::bail!("Number of primary displays is more than 1")
} else if primary_count == 0 {
log::warn!("No primary display specified");
log::warn!(
"No primary display specified. External Wayland applications will not be attached."
);
}
for (catalog_name, catalog) in &self.catalogs {
@@ -219,7 +224,7 @@ impl WayVRConfig {
if self.run_compositor_at_start {
// Start Wayland server instantly
Ok(Some(Rc::new(RefCell::new(WayVRData::new(
Self::get_wayvr_config(config, self),
Self::get_wayvr_config(config, self)?,
)?))))
} else {
// Lazy-init WayVR later if the user requested
@@ -230,16 +235,11 @@ impl WayVRConfig {
pub fn load_wayvr() -> WayVRConfig {
let config_root_path = config_io::ConfigRoot::WayVR.ensure_dir();
log::info!("WayVR Config root path: {:?}", config_root_path);
log::info!("WayVR Config root path: {config_root_path:?}");
log::info!(
"WayVR conf.d path: {:?}",
config_io::ConfigRoot::WayVR.get_conf_d_path()
);
let config = load_config_with_conf_d::<WayVRConfig>("wayvr.yaml", config_io::ConfigRoot::WayVR);
if config.version != def_version() {
panic!("WayVR config version {} is not supported", config.version);
}
config
load_config_with_conf_d::<WayVRConfig>("wayvr.yaml", config_io::ConfigRoot::WayVR)
}
+2 -2
View File
@@ -9,7 +9,7 @@ use vulkano::{
DeviceSize,
};
use super::WlxCommandBuffer;
use super::WlxUploadsBuffer;
pub trait WlxCommandBufferDds {
fn texture2d_dds<R>(&mut self, r: R) -> anyhow::Result<Arc<Image>>
@@ -17,7 +17,7 @@ pub trait WlxCommandBufferDds {
R: Read;
}
impl WlxCommandBufferDds for WlxCommandBuffer {
impl WlxCommandBufferDds for WlxUploadsBuffer {
fn texture2d_dds<R>(&mut self, r: R) -> anyhow::Result<Arc<Image>>
where
R: Read,
+172
View File
@@ -0,0 +1,172 @@
use std::{mem::MaybeUninit, sync::Arc};
use smallvec::SmallVec;
use vulkano::{
device::Device,
image::{sys::RawImage, ImageCreateInfo, SubresourceLayout},
sync::Sharing,
VulkanError, VulkanObject,
};
#[allow(clippy::all, clippy::pedantic)]
pub(super) unsafe fn create_dmabuf_image(
device: Arc<Device>,
create_info: ImageCreateInfo,
) -> Result<RawImage, VulkanError> {
let &ImageCreateInfo {
flags,
image_type,
format,
ref view_formats,
extent,
array_layers,
mip_levels,
samples,
tiling,
usage,
stencil_usage,
ref sharing,
initial_layout,
ref drm_format_modifiers,
ref drm_format_modifier_plane_layouts,
external_memory_handle_types,
_ne: _,
} = &create_info;
let (sharing_mode, queue_family_index_count, p_queue_family_indices) = match sharing {
Sharing::Exclusive => (ash::vk::SharingMode::EXCLUSIVE, 0, &[] as _),
Sharing::Concurrent(queue_family_indices) => (
ash::vk::SharingMode::CONCURRENT,
queue_family_indices.len() as u32,
queue_family_indices.as_ptr(),
),
};
let mut create_info_vk = ash::vk::ImageCreateInfo {
flags: flags.into(),
image_type: image_type.into(),
format: format.into(),
extent: ash::vk::Extent3D {
width: extent[0],
height: extent[1],
depth: extent[2],
},
mip_levels,
array_layers,
samples: samples.into(),
tiling: tiling.into(),
usage: usage.into(),
sharing_mode,
queue_family_index_count,
p_queue_family_indices,
initial_layout: initial_layout.into(),
..Default::default()
};
let mut drm_format_modifier_explicit_info_vk = None;
let drm_format_modifier_plane_layouts_vk: SmallVec<[_; 4]>;
let mut drm_format_modifier_list_info_vk = None;
let mut external_memory_info_vk = None;
let mut format_list_info_vk = None;
let format_list_view_formats_vk: Vec<_>;
let mut stencil_usage_info_vk = None;
if drm_format_modifiers.len() == 1 {
drm_format_modifier_plane_layouts_vk = drm_format_modifier_plane_layouts
.iter()
.map(|subresource_layout| {
let &SubresourceLayout {
offset,
size,
row_pitch,
array_pitch,
depth_pitch,
} = subresource_layout;
ash::vk::SubresourceLayout {
offset,
size,
row_pitch,
array_pitch: array_pitch.unwrap_or(0),
depth_pitch: depth_pitch.unwrap_or(0),
}
})
.collect();
let next = drm_format_modifier_explicit_info_vk.insert(
ash::vk::ImageDrmFormatModifierExplicitCreateInfoEXT {
drm_format_modifier: drm_format_modifiers[0],
drm_format_modifier_plane_count: drm_format_modifier_plane_layouts_vk.len() as u32,
p_plane_layouts: drm_format_modifier_plane_layouts_vk.as_ptr(),
..Default::default()
},
);
next.p_next = create_info_vk.p_next;
create_info_vk.p_next = next as *const _ as *const _;
} else if drm_format_modifiers.len() > 1 {
let next = drm_format_modifier_list_info_vk.insert(
ash::vk::ImageDrmFormatModifierListCreateInfoEXT {
drm_format_modifier_count: drm_format_modifiers.len() as u32,
p_drm_format_modifiers: drm_format_modifiers.as_ptr(),
..Default::default()
},
);
next.p_next = create_info_vk.p_next;
create_info_vk.p_next = next as *const _ as *const _;
}
if !external_memory_handle_types.is_empty() {
let next = external_memory_info_vk.insert(ash::vk::ExternalMemoryImageCreateInfo {
handle_types: external_memory_handle_types.into(),
..Default::default()
});
next.p_next = create_info_vk.p_next;
create_info_vk.p_next = next as *const _ as *const _;
}
if !view_formats.is_empty() {
format_list_view_formats_vk = view_formats
.iter()
.copied()
.map(ash::vk::Format::from)
.collect();
let next = format_list_info_vk.insert(ash::vk::ImageFormatListCreateInfo {
view_format_count: format_list_view_formats_vk.len() as u32,
p_view_formats: format_list_view_formats_vk.as_ptr(),
..Default::default()
});
next.p_next = create_info_vk.p_next;
create_info_vk.p_next = next as *const _ as *const _;
}
if let Some(stencil_usage) = stencil_usage {
let next = stencil_usage_info_vk.insert(ash::vk::ImageStencilUsageCreateInfo {
stencil_usage: stencil_usage.into(),
..Default::default()
});
next.p_next = create_info_vk.p_next;
create_info_vk.p_next = next as *const _ as *const _;
}
let handle = {
let fns = device.fns();
let mut output = MaybeUninit::uninit();
(fns.v1_0.create_image)(
device.handle(),
&create_info_vk,
std::ptr::null(),
output.as_mut_ptr(),
)
.result()
.map_err(VulkanError::from)?;
output.assume_init()
};
RawImage::from_handle(device, handle, create_info)
}
+430 -540
View File
File diff suppressed because it is too large. Load diff
+4 -3
View File
@@ -153,6 +153,7 @@ impl<D, S> CanvasBuilder<D, S> {
&mut self.canvas.controls[idx]
}
#[allow(clippy::too_many_arguments)]
pub fn key_button(
&mut self,
x: f32,
@@ -195,7 +196,7 @@ impl<D, S> CanvasBuilder<D, S> {
h,
};
let rect1 = Rect {
x: x + w * 0.5 + 12.,
x: w.mul_add(0.5, x) + 12.,
y: y + h - (self.font_size as f32) + 8.,
w,
h,
@@ -212,7 +213,7 @@ impl<D, S> CanvasBuilder<D, S> {
};
let rect1 = Rect {
x,
y: y + h * 0.5 + 2.0,
y: h.mul_add(0.5, y) + 2.0,
w,
h: h * 0.5,
};
@@ -233,7 +234,7 @@ impl<D, S> CanvasBuilder<D, S> {
h,
};
let rect2 = Rect {
x: x + w * 0.5 + 8.,
x: w.mul_add(0.5, x) + 8.,
y: y + h - (self.font_size as f32) + 4.,
w,
h,
+17 -17
View File
@@ -19,7 +19,8 @@ pub type ControlRendererHl<D, S> = fn(
Vec4,
) -> anyhow::Result<()>;
pub(crate) struct Control<D, S> {
#[allow(clippy::type_complexity)]
pub struct Control<D, S> {
pub state: Option<S>,
pub rect: Rect,
pub corner_radius: f32,
@@ -29,12 +30,13 @@ pub(crate) struct Control<D, S> {
pub size: isize,
pub sprite: Option<Arc<ImageView>>,
pub sprite_st: Vec4,
pub(super) dirty: bool,
pub(super) bg_dirty: bool,
pub(super) fg_dirty: bool,
pub on_update: Option<fn(&mut Self, &mut D, &mut AppState)>,
pub on_press: Option<fn(&mut Self, &mut D, &mut AppState, PointerMode)>,
pub on_release: Option<fn(&mut Self, &mut D, &mut AppState)>,
pub on_scroll: Option<fn(&mut Self, &mut D, &mut AppState, f32)>,
pub on_scroll: Option<fn(&mut Self, &mut D, &mut AppState, f32, f32)>,
pub test_highlight: Option<fn(&Self, &mut D, &mut AppState) -> Option<Vec4>>,
pub(super) on_render_bg: Option<ControlRenderer<D, S>>,
@@ -57,7 +59,8 @@ impl<D, S> Control<D, S> {
text: Arc::from(""),
sprite: None,
sprite_st: Vec4::new(1., 1., 0., 0.),
dirty: true,
bg_dirty: true,
fg_dirty: true,
size: 24,
state: None,
on_update: None,
@@ -71,37 +74,33 @@ impl<D, S> Control<D, S> {
}
}
#[inline(always)]
pub fn set_text(&mut self, text: &str) {
if *self.text == *text {
return;
}
self.text = text.into();
self.dirty = true;
self.fg_dirty = true;
}
#[inline(always)]
pub fn set_sprite(&mut self, sprite: Arc<ImageView>) {
self.sprite.replace(sprite);
self.dirty = true;
self.bg_dirty = true;
}
#[inline(always)]
pub fn set_sprite_st(&mut self, sprite_st: Vec4) {
if self.sprite_st == sprite_st {
return;
}
self.sprite_st = sprite_st;
self.dirty = true;
self.bg_dirty = true;
}
#[inline(always)]
pub fn set_fg_color(&mut self, color: GuiColor) {
if self.fg_color == color {
return;
}
self.fg_color = color;
self.dirty = true;
self.fg_dirty = true;
}
pub fn render_rounded_rect(
@@ -173,7 +172,7 @@ impl<D, S> Control<D, S> {
let skew_radius = [clamped_radius / self.rect.w, clamped_radius / self.rect.h];
let set0 = canvas.pipeline_bg_color.uniform_buffer(
let set0 = canvas.pipeline_hl_color.uniform_buffer(
0,
vec![
color.x,
@@ -185,9 +184,9 @@ impl<D, S> Control<D, S> {
],
)?;
let pass = canvas.pipeline_bg_color.create_pass(
let pass = canvas.pipeline_hl_color.create_pass(
[canvas.width as _, canvas.height as _],
vertex_buffer.clone(),
vertex_buffer,
canvas.graphics.quad_indices.clone(),
vec![set0],
)?;
@@ -250,9 +249,10 @@ impl<D, S> Control<D, S> {
.fc
.get_text_size(&self.text, self.size, canvas.graphics.clone())?;
let mut cur_y = self.rect.y + (self.rect.h) - (h * 0.5) - (self.size as f32 * 0.25);
let mut cur_y =
(self.size as f32).mul_add(-0.25, h.mul_add(-0.5, self.rect.y + (self.rect.h)));
for line in self.text.lines() {
let mut cur_x = self.rect.x + (self.rect.w * 0.5) - (w * 0.5);
let mut cur_x = w.mul_add(-0.5, self.rect.w.mul_add(0.5, self.rect.x));
for glyph in app
.fc
.get_glyphs(line, self.size, canvas.graphics.clone())?
+149 -167
View File
@@ -1,21 +1,17 @@
pub(crate) mod builder;
pub(crate) mod control;
pub mod builder;
pub mod control;
use std::sync::Arc;
use glam::{Vec2, Vec4};
use vulkano::{
command_buffer::CommandBufferUsage,
format::Format,
image::{view::ImageView, ImageLayout},
};
use vulkano::{command_buffer::CommandBufferUsage, format::Format, image::view::ImageView};
use crate::{
backend::{
input::{Haptics, InteractionHandler, PointerHit},
overlay::{FrameTransform, OverlayBackend, OverlayRenderer},
overlay::{FrameMeta, OverlayBackend, OverlayRenderer, ShouldRender},
},
graphics::{WlxGraphics, WlxPass, WlxPipeline, WlxPipelineLegacy, BLEND_ALPHA},
graphics::{CommandBuffers, WlxGraphics, WlxPipeline, BLEND_ALPHA},
state::AppState,
};
@@ -35,16 +31,16 @@ pub struct CanvasData<D> {
graphics: Arc<WlxGraphics>,
pipeline_bg_color: Arc<WlxPipeline<WlxPipelineLegacy>>,
pipeline_fg_glyph: Arc<WlxPipeline<WlxPipelineLegacy>>,
pipeline_bg_sprite: Arc<WlxPipeline<WlxPipelineLegacy>>,
pipeline_hl_sprite: Arc<WlxPipeline<WlxPipelineLegacy>>,
pipeline_final: Arc<WlxPipeline<WlxPipelineLegacy>>,
pipeline_bg_color: Arc<WlxPipeline>,
pipeline_bg_sprite: Arc<WlxPipeline>,
pipeline_fg_glyph: Arc<WlxPipeline>,
pipeline_hl_color: Arc<WlxPipeline>,
pipeline_hl_sprite: Arc<WlxPipeline>,
}
pub struct Canvas<D, S> {
controls: Vec<control::Control<D, S>>,
canvas: CanvasData<D>,
data: CanvasData<D>,
hover_controls: [Option<usize>; 2],
pressed_controls: [Option<usize>; 2],
@@ -53,10 +49,16 @@ pub struct Canvas<D, S> {
interact_stride: usize,
interact_rows: usize,
view_final: Arc<ImageView>,
pipeline_final: Arc<WlxPipeline>,
pass_fg: WlxPass<WlxPipelineLegacy>,
pass_bg: WlxPass<WlxPipelineLegacy>,
view_fore: Arc<ImageView>,
view_back: Arc<ImageView>,
format: Format,
back_dirty: bool,
high_dirty: bool,
fore_dirty: bool,
}
impl<D, S> Canvas<D, S> {
@@ -67,94 +69,74 @@ impl<D, S> Canvas<D, S> {
format: Format,
data: D,
) -> anyhow::Result<Self> {
let tex_fg = graphics.render_texture(width as _, height as _, format)?;
let tex_bg = graphics.render_texture(width as _, height as _, format)?;
let tex_final = graphics.render_texture(width as _, height as _, format)?;
let tex_fore = graphics.render_texture(width as _, height as _, format)?;
let tex_back = graphics.render_texture(width as _, height as _, format)?;
let view_fg = ImageView::new_default(tex_fg.clone())?;
let view_bg = ImageView::new_default(tex_bg.clone())?;
let view_final = ImageView::new_default(tex_final.clone())?;
let view_fore = ImageView::new_default(tex_fore)?;
let view_back = ImageView::new_default(tex_back)?;
let Ok(shaders) = graphics.shared_shaders.read() else {
anyhow::bail!("Failed to lock shared shaders for reading");
};
let vert = shaders.get("vert_common").unwrap().clone(); // want panic
let pipeline_bg_color = graphics.create_pipeline(
view_bg.clone(),
shaders.get("vert_common").unwrap().clone(), // want panic
shaders.get("frag_color").unwrap().clone(), // want panic
vert.clone(),
shaders.get("frag_color").unwrap().clone(), // want panic
format,
Some(BLEND_ALPHA),
)?;
let pipeline_fg_glyph = graphics.create_pipeline(
view_fg.clone(),
shaders.get("vert_common").unwrap().clone(), // want panic
shaders.get("frag_glyph").unwrap().clone(), // want panic
vert.clone(),
shaders.get("frag_glyph").unwrap().clone(), // want panic
format,
Some(BLEND_ALPHA),
)?;
let pipeline_bg_sprite = graphics.create_pipeline(
view_fg.clone(),
shaders.get("vert_common").unwrap().clone(), // want panic
vert.clone(),
shaders.get("frag_sprite2").unwrap().clone(), // want panic
format,
Some(BLEND_ALPHA),
)?;
let pipeline_hl_color = graphics.create_pipeline(
vert.clone(),
shaders.get("frag_color").unwrap().clone(), // want panic
graphics.native_format,
Some(BLEND_ALPHA),
)?;
let pipeline_hl_sprite = graphics.create_pipeline(
view_fg.clone(),
shaders.get("vert_common").unwrap().clone(), // want panic
vert.clone(),
shaders.get("frag_sprite2_hl").unwrap().clone(), // want panic
format,
graphics.native_format,
Some(BLEND_ALPHA),
)?;
let vertex_buffer =
graphics.upload_verts(width as _, height as _, 0., 0., width as _, height as _)?;
let pipeline_final = graphics.create_pipeline_with_layouts(
view_final.clone(),
shaders.get("vert_common").unwrap().clone(), // want panic
shaders.get("frag_sprite").unwrap().clone(), // want panic
format,
let pipeline_final = graphics.create_pipeline(
vert,
shaders.get("frag_srgb").unwrap().clone(), // want panic
graphics.native_format,
Some(BLEND_ALPHA),
ImageLayout::TransferSrcOptimal,
ImageLayout::TransferSrcOptimal,
)?;
let set_fg =
pipeline_final.uniform_sampler(0, view_fg.clone(), graphics.texture_filtering)?;
let set_bg =
pipeline_final.uniform_sampler(0, view_bg.clone(), graphics.texture_filtering)?;
let pass_fg = pipeline_final.create_pass(
[width as _, height as _],
vertex_buffer.clone(),
graphics.quad_indices.clone(),
vec![set_fg],
)?;
let pass_bg = pipeline_final.create_pass(
[width as _, height as _],
vertex_buffer.clone(),
graphics.quad_indices.clone(),
vec![set_bg],
)?;
let stride = width / RES_DIVIDER;
let rows = height / RES_DIVIDER;
Ok(Self {
canvas: CanvasData {
data: CanvasData {
data,
width,
height,
graphics: graphics.clone(),
pipeline_bg_color,
pipeline_fg_glyph,
pipeline_bg_sprite,
pipeline_fg_glyph,
pipeline_hl_color,
pipeline_hl_sprite,
pipeline_final,
},
controls: Vec::new(),
hover_controls: [None, None],
@@ -162,9 +144,13 @@ impl<D, S> Canvas<D, S> {
interact_map: vec![None; stride * rows],
interact_stride: stride,
interact_rows: rows,
view_final,
pass_fg,
pass_bg,
pipeline_final,
view_fore,
view_back,
format,
back_dirty: false,
high_dirty: false,
fore_dirty: false,
})
}
@@ -184,67 +170,42 @@ impl<D, S> Canvas<D, S> {
}
fn interactive_get_idx(&self, uv: Vec2) -> Option<usize> {
let x = (uv.x * self.canvas.width as f32) as usize;
let y = (uv.y * self.canvas.height as f32) as usize;
let x = (uv.x * self.data.width as f32) as usize;
let y = (uv.y * self.data.height as f32) as usize;
let x = (x / RES_DIVIDER).max(0).min(self.interact_stride - 1);
let y = (y / RES_DIVIDER).max(0).min(self.interact_rows - 1);
self.interact_map[y * self.interact_stride + x].map(|x| x as usize)
}
fn render_bg(&mut self, app: &mut AppState) -> anyhow::Result<()> {
let mut cmd_buffer = self
.canvas
.graphics
.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
cmd_buffer.begin_render_pass(&self.canvas.pipeline_bg_color)?;
for c in self.controls.iter_mut() {
if let Some(fun) = c.on_render_bg {
fun(c, &self.canvas, app, &mut cmd_buffer)?;
}
}
cmd_buffer.end_render_pass()?;
cmd_buffer.build_and_execute_now()
}
fn render_fg(&mut self, app: &mut AppState) -> anyhow::Result<()> {
let mut cmd_buffer = self
.canvas
.graphics
.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
cmd_buffer.begin_render_pass(&self.canvas.pipeline_fg_glyph)?;
for c in self.controls.iter_mut() {
if let Some(fun) = c.on_render_fg {
fun(c, &self.canvas, app, &mut cmd_buffer)?;
}
}
cmd_buffer.end_render_pass()?;
cmd_buffer.build_and_execute_now()
}
pub fn data_mut(&mut self) -> &mut D {
&mut self.canvas.data
pub const fn data_mut(&mut self) -> &mut D {
&mut self.data.data
}
}
impl<D, S> InteractionHandler for Canvas<D, S> {
fn on_left(&mut self, _app: &mut AppState, pointer: usize) {
self.high_dirty = true;
self.hover_controls[pointer] = None;
}
fn on_hover(&mut self, _app: &mut AppState, hit: &PointerHit) -> Option<Haptics> {
// render on every frame if we are being hovered
self.high_dirty = true;
let old = self.hover_controls[hit.pointer];
if let Some(i) = self.interactive_get_idx(hit.uv) {
self.hover_controls[hit.pointer] = Some(i);
} else {
self.hover_controls[hit.pointer] = None;
}
if old != self.hover_controls[hit.pointer] {
if old == self.hover_controls[hit.pointer] {
None
} else {
Some(Haptics {
intensity: 0.1,
duration: 0.01,
frequency: 5.0,
})
} else {
None
}
}
fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool) {
@@ -259,30 +220,29 @@ impl<D, S> InteractionHandler for Canvas<D, S> {
if pressed {
if let Some(ref mut f) = c.on_press {
self.pressed_controls[hit.pointer] = Some(idx);
f(c, &mut self.canvas.data, app, hit.mode);
f(c, &mut self.data.data, app, hit.mode);
}
} else if let Some(ref mut f) = c.on_release {
self.pressed_controls[hit.pointer] = None;
f(c, &mut self.canvas.data, app);
f(c, &mut self.data.data, app);
}
}
}
fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta: f32) {
fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta_y: f32, delta_x: f32) {
let idx = self.hover_controls[hit.pointer];
if let Some(idx) = idx {
let c = &mut self.controls[idx];
if let Some(ref mut f) = c.on_scroll {
f(c, &mut self.canvas.data, app, delta);
f(c, &mut self.data.data, app, delta_y, delta_x);
}
}
}
}
impl<D, S> OverlayRenderer for Canvas<D, S> {
fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.render_bg(app)?;
self.render_fg(app)
fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
Ok(())
}
fn pause(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
Ok(())
@@ -290,61 +250,96 @@ impl<D, S> OverlayRenderer for Canvas<D, S> {
fn resume(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
Ok(())
}
fn render(&mut self, app: &mut AppState) -> anyhow::Result<()> {
let mut dirty = false;
for c in self.controls.iter_mut() {
fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender> {
for c in &mut self.controls {
if let Some(fun) = c.on_update {
fun(c, &mut self.canvas.data, app);
fun(c, &mut self.data.data, app);
}
if c.dirty {
dirty = true;
c.dirty = false;
if c.fg_dirty {
self.fore_dirty = true;
c.fg_dirty = false;
}
if c.bg_dirty {
self.back_dirty = true;
c.bg_dirty = false;
}
}
if dirty {
self.render_bg(app)?;
self.render_fg(app)?;
}
/*
let image = self.view_final.image().clone();
if self.first_render {
self.first_render = false;
if self.back_dirty || self.fore_dirty || self.high_dirty {
Ok(ShouldRender::Should)
} else {
self.canvas
.graphics
.transition_layout(
image.clone(),
ImageLayout::TransferSrcOptimal,
ImageLayout::ColorAttachmentOptimal,
)
.wait(None)
.unwrap();
Ok(ShouldRender::Can)
}
*/
}
fn render(
&mut self,
app: &mut AppState,
tgt: Arc<ImageView>,
buf: &mut CommandBuffers,
alpha: f32,
) -> anyhow::Result<bool> {
self.high_dirty = false;
let mut cmd_buffer = self
.canvas
.data
.graphics
.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
cmd_buffer.begin_render_pass(&self.canvas.pipeline_final)?;
// static background
cmd_buffer.run_ref(&self.pass_bg)?;
if self.back_dirty {
cmd_buffer.begin_rendering(self.view_back.clone())?;
for c in &mut self.controls {
if let Some(fun) = c.on_render_bg {
fun(c, &self.data, app, &mut cmd_buffer)?;
}
}
cmd_buffer.end_rendering()?;
self.back_dirty = false;
}
if self.fore_dirty {
cmd_buffer.begin_rendering(self.view_fore.clone())?;
for c in &mut self.controls {
if let Some(fun) = c.on_render_fg {
fun(c, &self.data, app, &mut cmd_buffer)?;
}
}
cmd_buffer.end_rendering()?;
self.fore_dirty = false;
}
let set0_fg = self.pipeline_final.uniform_sampler(
0,
self.view_fore.clone(),
app.graphics.texture_filtering,
)?;
let set0_bg = self.pipeline_final.uniform_sampler(
0,
self.view_back.clone(),
app.graphics.texture_filtering,
)?;
let set1 = self.pipeline_final.uniform_buffer(1, vec![alpha])?;
let pass_fore = self
.pipeline_final
.create_pass_for_target(tgt.clone(), vec![set0_fg, set1.clone()])?;
let pass_back = self
.pipeline_final
.create_pass_for_target(tgt.clone(), vec![set0_bg, set1])?;
cmd_buffer.begin_rendering(tgt)?;
cmd_buffer.run_ref(&pass_back)?;
for (i, c) in self.controls.iter_mut().enumerate() {
if let Some(render) = c.on_render_hl {
if let Some(test) = c.test_highlight {
if let Some(hl_color) = test(c, &mut self.canvas.data, app) {
render(c, &self.canvas, app, &mut cmd_buffer, hl_color)?;
if let Some(hl_color) = test(c, &mut self.data.data, app) {
render(c, &self.data, app, &mut cmd_buffer, hl_color)?;
}
}
if self.hover_controls.contains(&Some(i)) {
render(
c,
&self.canvas,
&self.data,
app,
&mut cmd_buffer,
Vec4::new(1., 1., 1., 0.3),
@@ -354,30 +349,17 @@ impl<D, S> OverlayRenderer for Canvas<D, S> {
}
// mostly static text
cmd_buffer.run_ref(&self.pass_fg)?;
cmd_buffer.run_ref(&pass_fore)?;
cmd_buffer.end_render_pass()?;
cmd_buffer.build_and_execute_now()
/*
self.canvas
.graphics
.transition_layout(
image,
ImageLayout::ColorAttachmentOptimal,
ImageLayout::TransferSrcOptimal,
)
.wait(None)
.unwrap();
*/
}
fn view(&mut self) -> Option<Arc<ImageView>> {
Some(self.view_final.clone())
cmd_buffer.end_rendering()?;
buf.push(cmd_buffer.build()?);
Ok(true)
}
fn frame_transform(&mut self) -> Option<FrameTransform> {
Some(FrameTransform {
extent: self.view_final.image().extent(),
fn frame_meta(&mut self) -> Option<FrameMeta> {
Some(FrameMeta {
extent: [self.data.width as _, self.data.height as _, 1],
format: self.format,
..Default::default()
})
}
+42 -13
View File
@@ -5,7 +5,7 @@ use freetype::{bitmap::PixelMode, face::LoadFlag, Face, Library};
use idmap::IdMap;
use vulkano::{command_buffer::CommandBufferUsage, format::Format, image::Image};
use crate::graphics::WlxGraphics;
use crate::graphics::{WlxGraphics, WlxUploadsBuffer};
pub struct FontCache {
primary_font: Arc<str>,
@@ -39,7 +39,7 @@ impl FontCache {
let ft = Library::init()?;
let fc = FontConfig::default();
Ok(FontCache {
Ok(Self {
primary_font,
fc,
ft,
@@ -55,14 +55,15 @@ impl FontCache {
) -> anyhow::Result<(f32, f32)> {
let sizef = size as f32;
let height = sizef + ((text.lines().count() as f32) - 1f32) * (sizef * 1.5);
let height = ((text.lines().count() as f32) - 1f32).mul_add(sizef * 1.5, sizef);
let mut cmd_buffer = None;
let mut max_w = sizef * 0.33;
for line in text.lines() {
let w: f32 = line
.chars()
.filter_map(|c| {
self.get_glyph_for_cp(c as usize, size, graphics.clone())
self.get_glyph_for_cp(c as usize, size, graphics.clone(), &mut cmd_buffer)
.map(|glyph| glyph.advance)
.ok()
})
@@ -72,6 +73,11 @@ impl FontCache {
max_w = w;
}
}
if let Some(cmd_buffer) = cmd_buffer {
cmd_buffer.build_and_execute_now()?;
}
Ok((max_w, height))
}
@@ -82,11 +88,23 @@ impl FontCache {
graphics: Arc<WlxGraphics>,
) -> anyhow::Result<Vec<Rc<Glyph>>> {
let mut glyphs = Vec::new();
let mut cmd_buffer = None;
for line in text.lines() {
for c in line.chars() {
glyphs.push(self.get_glyph_for_cp(c as usize, size, graphics.clone())?);
glyphs.push(self.get_glyph_for_cp(
c as usize,
size,
graphics.clone(),
&mut cmd_buffer,
)?);
}
}
if let Some(cmd_buffer) = cmd_buffer {
cmd_buffer.build_and_execute_now()?;
}
Ok(glyphs)
}
@@ -125,26 +143,26 @@ impl FontCache {
if let Some(path) = pattern.filename() {
let name = pattern.name().unwrap_or(path);
log::debug!("Loading font: {} {}pt", name, size);
log::debug!("Loading font: {name} {size}pt");
let font_idx = pattern.face_index().unwrap_or(0);
let face = match self.ft.new_face(path, font_idx as _) {
Ok(face) => face,
Err(e) => {
log::warn!("Failed to load font at {}: {:?}", path, e);
log::warn!("Failed to load font at {path}: {e:?}");
coll.cp_map.insert(cp, 0);
return 0;
}
};
match face.set_char_size(size << 6, size << 6, 96, 96) {
Ok(_) => {}
Ok(()) => {}
Err(e) => {
log::warn!("Failed to set font size: {:?}", e);
log::warn!("Failed to set font size: {e:?}");
coll.cp_map.insert(cp, 0);
return 0;
}
};
}
let idx = coll.fonts.len();
for (cp, _) in face.chars() {
@@ -184,6 +202,7 @@ impl FontCache {
cp: usize,
size: isize,
graphics: Arc<WlxGraphics>,
cmd_buffer: &mut Option<WlxUploadsBuffer>,
) -> anyhow::Result<Rc<Glyph>> {
let key = self.get_font_for_cp(cp, size);
@@ -220,9 +239,19 @@ impl FontCache {
_ => return Ok(self.collections[size].zero_glyph.clone()),
};
let mut cmd_buffer = graphics.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
let texture = cmd_buffer.texture2d_raw(bmp.width() as _, bmp.rows() as _, format, &buf)?;
cmd_buffer.build_and_execute_now()?;
if cmd_buffer.is_none() {
*cmd_buffer = Some(graphics.create_uploads_command_buffer(
graphics.transfer_queue.clone(),
CommandBufferUsage::OneTimeSubmit,
)?);
}
let texture = cmd_buffer.as_mut().unwrap().texture2d_raw(
bmp.width() as _,
bmp.rows() as _,
format,
&buf,
)?;
let g = Glyph {
tex: Some(texture),
+10 -9
View File
@@ -1,12 +1,13 @@
use std::sync::LazyLock;
use glam::Vec4;
use once_cell::sync::Lazy;
pub mod canvas;
pub mod font;
pub mod modular;
pub type GuiColor = Vec4;
pub(super) static FALLBACK_COLOR: Lazy<GuiColor> = Lazy::new(|| Vec4::new(1., 0., 1., 1.));
pub static FALLBACK_COLOR: LazyLock<GuiColor> = LazyLock::new(|| Vec4::new(1., 0., 1., 1.));
// Parses a color from a HTML hex string
pub fn color_parse(html_hex: &str) -> anyhow::Result<GuiColor> {
@@ -17,9 +18,9 @@ pub fn color_parse(html_hex: &str) -> anyhow::Result<GuiColor> {
u8::from_str_radix(&html_hex[5..7], 16),
) {
return Ok(Vec4::new(
r as f32 / 255.,
g as f32 / 255.,
b as f32 / 255.,
f32::from(r) / 255.,
f32::from(g) / 255.,
f32::from(b) / 255.,
1.,
));
}
@@ -31,10 +32,10 @@ pub fn color_parse(html_hex: &str) -> anyhow::Result<GuiColor> {
u8::from_str_radix(&html_hex[7..9], 16),
) {
return Ok(Vec4::new(
r as f32 / 255.,
g as f32 / 255.,
b as f32 / 255.,
a as f32 / 255.,
f32::from(r) / 255.,
f32::from(g) / 255.,
f32::from(b) / 255.,
f32::from(a) / 255.,
));
}
}
+39 -23
View File
@@ -326,13 +326,19 @@ fn modular_button_up(button: &mut ModularControl, _: &mut (), app: &mut AppState
}
}
fn modular_button_scroll(button: &mut ModularControl, _: &mut (), app: &mut AppState, delta: f32) {
fn modular_button_scroll(
button: &mut ModularControl,
_: &mut (),
app: &mut AppState,
delta_y: f32,
_delta_x: f32,
) {
// want panic
let ModularData::Button(data) = button.state.as_mut().unwrap() else {
panic!("modular_button_scroll: button state is not Button");
};
let actions = if delta < 0.0 {
let actions = if delta_y < 0.0 {
data.scroll_down.as_ref()
} else {
data.scroll_up.as_ref()
@@ -373,7 +379,7 @@ fn modular_button_highlight(
fn handle_action(action: &ButtonAction, press: &mut PressData, app: &mut AppState) {
match action {
ButtonAction::Exec { command, toast } => run_exec(command, toast, press, app),
ButtonAction::Exec { command, toast } => run_exec(command, toast.clone(), press, app),
ButtonAction::Watch { action } => run_watch(action, app),
ButtonAction::Overlay { target, action } => run_overlay(target, action, app),
ButtonAction::Window { target, action } => run_window(target, action, app),
@@ -433,7 +439,7 @@ fn handle_action(action: &ButtonAction, press: &mut PressData, app: &mut AppStat
let _ = sender.send_single_param(parameter.to_string(), converted);
audio_thump(app); // play sound for feedback
};
}
#[cfg(not(feature = "osc"))]
{
@@ -447,6 +453,7 @@ fn handle_action(action: &ButtonAction, press: &mut PressData, app: &mut AppStat
const ENABLED_DISABLED: [&str; 2] = ["enabled", "disabled"];
#[allow(clippy::too_many_lines)]
fn run_system(action: &SystemAction, app: &mut AppState) {
match action {
SystemAction::ToggleAllowSliding => {
@@ -455,7 +462,7 @@ fn run_system(action: &SystemAction, app: &mut AppState) {
ToastTopic::System,
format!(
"Sliding is {}.",
ENABLED_DISABLED[app.session.config.allow_sliding as usize]
ENABLED_DISABLED[usize::from(app.session.config.allow_sliding)]
)
.into(),
"".into(),
@@ -468,7 +475,7 @@ fn run_system(action: &SystemAction, app: &mut AppState) {
ToastTopic::System,
format!(
"Auto realign is {}.",
ENABLED_DISABLED[app.session.config.realign_on_showhide as usize]
ENABLED_DISABLED[usize::from(app.session.config.realign_on_showhide)]
)
.into(),
"".into(),
@@ -481,7 +488,7 @@ fn run_system(action: &SystemAction, app: &mut AppState) {
ToastTopic::System,
format!(
"Space rotate axis lock now {}.",
ENABLED_DISABLED[!app.session.config.space_rotate_unlocked as usize]
ENABLED_DISABLED[usize::from(!app.session.config.space_rotate_unlocked)]
)
.into(),
"".into(),
@@ -500,7 +507,7 @@ fn run_system(action: &SystemAction, app: &mut AppState) {
let display = 5 - i;
Toast::new(
ToastTopic::System,
format!("Fixing floor in {}", display).into(),
format!("Fixing floor in {display}").into(),
"Place either controller on the floor.".into(),
)
.with_timeout(1.0)
@@ -518,7 +525,7 @@ fn run_system(action: &SystemAction, app: &mut AppState) {
ToastTopic::System,
format!(
"Notifications are {}.",
ENABLED_DISABLED[app.session.config.notifications_enabled as usize]
ENABLED_DISABLED[usize::from(app.session.config.notifications_enabled)]
)
.into(),
"".into(),
@@ -532,7 +539,7 @@ fn run_system(action: &SystemAction, app: &mut AppState) {
ToastTopic::System,
format!(
"Notification sounds are {}.",
ENABLED_DISABLED[app.session.config.notifications_sound_enabled as usize]
ENABLED_DISABLED[usize::from(app.session.config.notifications_sound_enabled)]
)
.into(),
"".into(),
@@ -552,7 +559,7 @@ fn run_system(action: &SystemAction, app: &mut AppState) {
}
}
fn run_exec(args: &ExecArgs, toast: &Option<Arc<str>>, press: &mut PressData, app: &mut AppState) {
fn run_exec(args: &ExecArgs, toast: Option<Arc<str>>, press: &mut PressData, app: &mut AppState) {
if let Some(proc) = press.child.as_mut() {
match proc.try_wait() {
Ok(Some(code)) => {
@@ -571,7 +578,10 @@ fn run_exec(args: &ExecArgs, toast: &Option<Arc<str>>, press: &mut PressData, ap
}
}
}
let args = args.iter().map(|s| s.as_ref()).collect::<Vec<&str>>();
let args = args
.iter()
.map(std::convert::AsRef::as_ref)
.collect::<Vec<&str>>();
match process::Command::new(args[0]).args(&args[1..]).spawn() {
Ok(proc) => {
press.child = Some(proc);
@@ -580,11 +590,12 @@ fn run_exec(args: &ExecArgs, toast: &Option<Arc<str>>, press: &mut PressData, ap
}
}
Err(e) => {
error_toast(app, &format!("Failed to spawn process {:?}", args), e);
error_toast(app, &format!("Failed to spawn process {args:?}"), e);
}
};
}
}
#[allow(clippy::too_many_lines)]
fn run_watch(data: &WatchAction, app: &mut AppState) {
match data {
WatchAction::Hide => {
@@ -615,7 +626,7 @@ fn run_watch(data: &WatchAction, app: &mut AppState) {
app.tasks.enqueue(TaskType::Overlay(
OverlaySelector::Name(WATCH_NAME.into()),
Box::new(|app, o| {
if let RelativeTo::Hand(0) = o.relative_to {
if matches!(o.relative_to, RelativeTo::Hand(0)) {
o.relative_to = RelativeTo::Hand(1);
o.spawn_rotation = app.session.config.watch_rot
* Quat::from_rotation_x(PI)
@@ -695,6 +706,7 @@ fn run_watch(data: &WatchAction, app: &mut AppState) {
}
}
#[allow(clippy::too_many_lines)]
fn run_overlay(overlay: &OverlaySelector, action: &OverlayAction, app: &mut AppState) {
match action {
OverlayAction::Reset => {
@@ -730,7 +742,7 @@ fn run_overlay(overlay: &OverlaySelector, action: &OverlayAction, app: &mut AppS
if state_dirty {
match save_layout(&app.session.config) {
Ok(_) => log::debug!("Saved state"),
Ok(()) => log::debug!("Saved state"),
Err(e) => {
error_toast(app, "Failed to save state", e);
}
@@ -746,17 +758,17 @@ fn run_overlay(overlay: &OverlaySelector, action: &OverlayAction, app: &mut AppS
o.recenter = !o.recenter;
o.grabbable = o.recenter;
o.show_hide = o.recenter;
if !o.recenter {
if o.recenter {
Toast::new(
ToastTopic::System,
format!("{} is now locked in place!", o.name).into(),
format!("{} is now unlocked!", o.name).into(),
"".into(),
)
.submit(app);
} else {
Toast::new(
ToastTopic::System,
format!("{} is now unlocked!", o.name).into(),
format!("{} is now locked in place!", o.name).into(),
"".into(),
)
.submit(app);
@@ -770,17 +782,17 @@ fn run_overlay(overlay: &OverlaySelector, action: &OverlayAction, app: &mut AppS
overlay.clone(),
Box::new(|app, o| {
o.interactable = !o.interactable;
if !o.interactable {
if o.interactable {
Toast::new(
ToastTopic::System,
format!("{} is now non-interactable!", o.name).into(),
format!("{} is now interactable!", o.name).into(),
"".into(),
)
.submit(app);
} else {
Toast::new(
ToastTopic::System,
format!("{} is now interactable!", o.name).into(),
format!("{} is now non-interactable!", o.name).into(),
"".into(),
)
.submit(app);
@@ -821,7 +833,11 @@ fn run_window(window: &Arc<str>, action: &WindowAction, app: &mut AppState) {
)
.with_sound(true)
.submit(app);
crate::overlays::mirror::new_mirror(name.clone(), false, &app.session)
Some(crate::overlays::mirror::new_mirror(
name,
false,
&app.session,
))
}
}),
));
+23 -29
View File
@@ -100,15 +100,14 @@ pub fn modular_label_init(label: &mut ModularControl, content: &LabelContent, ap
}),
LabelContent::Clock { format, timezone } => {
let tz_str = match timezone {
Some(TimezoneDef::Idx(idx)) => {
if let Some(tz) = app.session.config.timezones.get(*idx) {
Some(tz.as_str())
} else {
log::error!("Timezone index out of range '{}'", idx);
Some(TimezoneDef::Idx(idx)) => app.session.config.timezones.get(*idx).map_or_else(
|| {
log::error!("Timezone index out of range '{idx}'");
label.set_fg_color(*FALLBACK_COLOR);
None
}
}
},
|tz| Some(tz.as_str()),
),
Some(TimezoneDef::Str(tz_str)) => Some(tz_str.as_ref()),
None => None,
};
@@ -127,14 +126,13 @@ pub fn modular_label_init(label: &mut ModularControl, content: &LabelContent, ap
}
LabelContent::Timezone { timezone } => {
if let Some(tz) = app.session.config.timezones.get(*timezone) {
let pretty_tz = tz.split('/').last().map(|x| x.replace("_", " "));
let pretty_tz = tz.split('/').next_back().map(|x| x.replace('_', " "));
if let Some(pretty_tz) = pretty_tz {
label.set_text(&pretty_tz);
return;
} else {
log::error!("Timezone name not valid '{}'", &tz);
}
log::error!("Timezone name not valid '{}'", &tz);
} else {
log::error!("Timezone index out of range '{}'", &timezone);
}
@@ -166,6 +164,7 @@ pub fn modular_label_init(label: &mut ModularControl, content: &LabelContent, ap
}
}
#[allow(clippy::too_many_lines)]
pub(super) fn label_update(control: &mut ModularControl, _: &mut (), app: &mut AppState) {
// want panic
let ModularData::Label(data) = control.state.as_mut().unwrap() else {
@@ -184,9 +183,9 @@ pub(super) fn label_update(control: &mut ModularControl, _: &mut (), app: &mut A
let tags = ["", "H", "L", "R", "T"];
if let Some(device) = device {
let (text, color) = device
.soc
.map(|soc| {
let (text, color) = device.soc.map_or_else(
|| (String::new(), Vec4::ZERO),
|soc| {
let text = format!(
"{}{}",
tags[device.role as usize],
@@ -200,8 +199,8 @@ pub(super) fn label_update(control: &mut ModularControl, _: &mut (), app: &mut A
*normal_color
};
(text, color)
})
.unwrap_or_else(|| ("".into(), Vec4::ZERO));
},
);
control.set_text(&text);
control.set_fg_color(color);
@@ -212,11 +211,11 @@ pub(super) fn label_update(control: &mut ModularControl, _: &mut (), app: &mut A
LabelData::Clock { format, timezone } => {
let format = format.clone();
if let Some(tz) = timezone {
let date = Local::now().with_timezone(tz);
control.set_text(&format!("{}", &date.format(&format)));
let date_time = Local::now().with_timezone(tz);
control.set_text(&format!("{}", &date_time.format(&format)));
} else {
let date = Local::now();
control.set_text(&format!("{}", &date.format(&format)));
let date_time = Local::now();
control.set_text(&format!("{}", &date_time.format(&format)));
}
}
LabelData::Timer { format, start } => {
@@ -236,13 +235,7 @@ pub(super) fn label_update(control: &mut ModularControl, _: &mut (), app: &mut A
if let Some(mut proc) = child.take() {
match proc.try_wait() {
Ok(Some(code)) => {
if !code.success() {
error_toast(
app,
"LabelData::Exec: Child process exited with code",
code,
);
} else {
if code.success() {
if let Some(mut stdout) = proc.stdout.take() {
let mut buf = String::new();
if stdout.read_to_string(&mut buf).is_ok() {
@@ -259,6 +252,7 @@ pub(super) fn label_update(control: &mut ModularControl, _: &mut (), app: &mut A
log::error!("No stdout for child process");
return;
}
error_toast(app, "LabelData::Exec: Child process exited with code", code);
}
Ok(None) => {
*child = Some(proc);
@@ -281,7 +275,7 @@ pub(super) fn label_update(control: &mut ModularControl, _: &mut (), app: &mut A
*last_exec = Instant::now();
let args = command
.iter()
.map(|s| s.as_ref())
.map(std::convert::AsRef::as_ref)
.collect::<SmallVec<[&str; 8]>>();
match process::Command::new(args[0])
@@ -293,9 +287,9 @@ pub(super) fn label_update(control: &mut ModularControl, _: &mut (), app: &mut A
*child = Some(proc);
}
Err(e) => {
error_toast(app, &format!("Failed to spawn process {:?}", args), e);
error_toast(app, &format!("Failed to spawn process {args:?}"), e);
}
};
}
}
}
LabelData::Ipd { last_ipd } => {
+11 -9
View File
@@ -149,8 +149,9 @@ pub enum ModularData {
Button(Box<ButtonData>),
}
#[allow(clippy::too_many_lines, clippy::many_single_char_names)]
pub fn modular_canvas(
size: &[u32; 2],
size: [u32; 2],
elements: &[ModularElement],
state: &mut AppState,
) -> anyhow::Result<Canvas<(), ModularData>> {
@@ -162,7 +163,7 @@ pub fn modular_canvas(
(),
)?;
let empty_str: Arc<str> = Arc::from("");
for elem in elements.iter() {
for elem in elements {
match elem {
ModularElement::Panel {
rect: [x, y, w, h],
@@ -226,7 +227,7 @@ pub fn modular_canvas(
sprite.set_sprite_st(st);
}
Err(e) => {
log::warn!("Could not load custom UI sprite: {:?}", e);
log::warn!("Could not load custom UI sprite: {e:?}");
}
},
ModularElement::Button {
@@ -326,7 +327,7 @@ pub fn modular_canvas(
canvas.fg_color = color_parse(fg_color).unwrap_or(*FALLBACK_COLOR);
canvas.font_size = *font_size;
for screen in state.screens.iter() {
for screen in &state.screens {
let button = canvas.button(
button_x + 2.,
button_y + 2.,
@@ -465,7 +466,7 @@ pub fn modular_canvas(
button_x += button_w;
}
} else {
log::error!("WayVR catalog \"{}\" not found", catalog_name);
log::error!("WayVR catalog \"{catalog_name}\" not found");
}
}
#[cfg(not(feature = "wayvr"))]
@@ -535,7 +536,7 @@ pub fn modular_canvas(
pub fn color_parse_or_default(color: &str) -> GuiColor {
color_parse(color).unwrap_or_else(|e| {
log::error!("Failed to parse color '{}': {}", color, e);
log::error!("Failed to parse color '{color}': {e}");
*FALLBACK_COLOR
})
}
@@ -551,9 +552,10 @@ fn sprite_from_path(path: Arc<str>, app: &mut AppState) -> anyhow::Result<Arc<Im
anyhow::bail!("Could not open custom sprite at: {}", path);
};
let mut command_buffer = app
.graphics
.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
let mut command_buffer = app.graphics.create_uploads_command_buffer(
app.graphics.transfer_queue.clone(),
CommandBufferUsage::OneTimeSubmit,
)?;
match command_buffer.texture2d_dds(f) {
Ok(image) => {
+111 -108
View File
@@ -1,4 +1,4 @@
use glam::Vec2;
use glam::{IVec2, Vec2};
use idmap::{idmap, IdMap};
use idmap_derive::IntegerId;
use input_linux::{
@@ -6,9 +6,9 @@ use input_linux::{
UInputHandle,
};
use libc::{input_event, timeval};
use once_cell::sync::Lazy;
use serde::Deserialize;
use std::mem::transmute;
use std::sync::LazyLock;
use std::{fs::File, sync::atomic::AtomicBool};
use strum::{EnumIter, EnumString, IntoEnumIterator};
use xkbcommon::xkb;
@@ -34,10 +34,7 @@ pub fn initialize() -> Box<dyn HidProvider> {
log::error!("Could not create uinput provider. Keyboard/Mouse input will not work!");
log::error!("To check if you're in input group, run: id -nG");
if let Ok(user) = std::env::var("USER") {
log::error!(
"To add yourself to the input group, run: sudo usermod -aG input {}",
user
);
log::error!("To add yourself to the input group, run: sudo usermod -aG input {user}");
log::error!("After adding yourself to the input group, you will need to reboot.");
}
Box::new(DummyProvider {})
@@ -46,7 +43,7 @@ pub fn initialize() -> Box<dyn HidProvider> {
pub trait HidProvider {
fn mouse_move(&mut self, pos: Vec2);
fn send_button(&mut self, button: u16, down: bool);
fn wheel(&mut self, delta: i32);
fn wheel(&mut self, delta_y: i32, delta_x: i32);
fn set_modifiers(&mut self, mods: u8);
fn send_key(&self, key: VirtualKey, down: bool);
fn set_desktop_extent(&mut self, extent: Vec2);
@@ -64,11 +61,12 @@ struct MouseAction {
last_requested_pos: Option<Vec2>,
pos: Option<Vec2>,
button: Option<MouseButtonAction>,
scroll: Option<i32>,
scroll: Option<IVec2>,
}
pub struct UInputProvider {
handle: UInputHandle<File>,
keyboard_handle: UInputHandle<File>,
mouse_handle: UInputHandle<File>,
desktop_extent: Vec2,
desktop_origin: Vec2,
cur_modifiers: u8,
@@ -90,103 +88,100 @@ const EV_ABS: u16 = 0x3;
impl UInputProvider {
fn try_new() -> Option<Self> {
if let Ok(file) = File::create("/dev/uinput") {
let handle = UInputHandle::new(file);
let keyboard_file = File::create("/dev/uinput").ok()?;
let keyboard_handle = UInputHandle::new(keyboard_file);
let id = InputId {
bustype: 0x03,
vendor: 0x4711,
product: 0x0829,
version: 5,
};
let mouse_file = File::create("/dev/uinput").ok()?;
let mouse_handle = UInputHandle::new(mouse_file);
let name = b"WlxOverlay-S Keyboard-Mouse Hybrid Thing\0";
let kbd_id = InputId {
bustype: 0x03,
vendor: 0x4711,
product: 0x0829,
version: 5,
};
let mouse_id = InputId {
bustype: 0x03,
vendor: 0x4711,
product: 0x0830,
version: 5,
};
let kbd_name = b"WlxOverlay-S Keyboard\0";
let mouse_name = b"WlxOverlay-S Mouse\0";
let abs_info = vec![
AbsoluteInfoSetup {
axis: input_linux::AbsoluteAxis::X,
info: AbsoluteInfo {
value: 0,
minimum: 0,
maximum: MOUSE_EXTENT as _,
fuzz: 0,
flat: 0,
resolution: 10,
},
let abs_info = vec![
AbsoluteInfoSetup {
axis: input_linux::AbsoluteAxis::X,
info: AbsoluteInfo {
value: 0,
minimum: 0,
maximum: MOUSE_EXTENT as _,
fuzz: 0,
flat: 0,
resolution: 10,
},
AbsoluteInfoSetup {
axis: input_linux::AbsoluteAxis::Y,
info: AbsoluteInfo {
value: 0,
minimum: 0,
maximum: MOUSE_EXTENT as _,
fuzz: 0,
flat: 0,
resolution: 10,
},
},
AbsoluteInfoSetup {
axis: input_linux::AbsoluteAxis::Y,
info: AbsoluteInfo {
value: 0,
minimum: 0,
maximum: MOUSE_EXTENT as _,
fuzz: 0,
flat: 0,
resolution: 10,
},
];
},
];
if handle.set_evbit(EventKind::Key).is_err() {
return None;
}
if handle.set_evbit(EventKind::Absolute).is_err() {
return None;
}
if handle.set_evbit(EventKind::Relative).is_err() {
return None;
}
for btn in MOUSE_LEFT..=MOUSE_MIDDLE {
let key: Key = unsafe { transmute(btn) };
if handle.set_keybit(key).is_err() {
return None;
}
}
for key in VirtualKey::iter() {
let key: Key = unsafe { transmute((key as u16) - 8) };
if handle.set_keybit(key).is_err() {
return None;
}
}
if handle.set_absbit(AbsoluteAxis::X).is_err() {
return None;
}
if handle.set_absbit(AbsoluteAxis::Y).is_err() {
return None;
}
if handle.set_relbit(RelativeAxis::Wheel).is_err() {
return None;
}
if handle.create(&id, name, 0, &abs_info).is_ok() {
return Some(UInputProvider {
handle,
desktop_extent: Vec2::ZERO,
desktop_origin: Vec2::ZERO,
current_action: Default::default(),
cur_modifiers: 0,
});
}
keyboard_handle.set_evbit(EventKind::Key).ok()?;
for key in VirtualKey::iter() {
let mapped_key: Key = unsafe { std::mem::transmute((key as u16) - 8) };
keyboard_handle.set_keybit(mapped_key).ok()?;
}
None
}
keyboard_handle.create(&kbd_id, kbd_name, 0, &[]).ok()?;
mouse_handle.set_evbit(EventKind::Absolute).ok()?;
mouse_handle.set_evbit(EventKind::Relative).ok()?;
mouse_handle.set_absbit(AbsoluteAxis::X).ok()?;
mouse_handle.set_absbit(AbsoluteAxis::Y).ok()?;
mouse_handle.set_relbit(RelativeAxis::WheelHiRes).ok()?;
mouse_handle
.set_relbit(RelativeAxis::HorizontalWheelHiRes)
.ok()?;
mouse_handle.set_evbit(EventKind::Key).ok()?;
for btn in MOUSE_LEFT..=MOUSE_MIDDLE {
let mouse_btn: Key = unsafe { transmute(btn) };
mouse_handle.set_keybit(mouse_btn).ok()?;
}
mouse_handle
.create(&mouse_id, mouse_name, 0, &abs_info)
.ok()?;
Some(Self {
keyboard_handle,
mouse_handle,
desktop_extent: Vec2::ZERO,
desktop_origin: Vec2::ZERO,
current_action: MouseAction::default(),
cur_modifiers: 0,
})
}
fn send_button_internal(&self, button: u16, down: bool) {
let time = get_time();
let events = [
new_event(time, EV_KEY, button, down as _),
new_event(time, EV_KEY, button, down.into()),
new_event(time, EV_SYN, 0, 0),
];
if let Err(res) = self.handle.write(&events) {
log::error!("send_button: {}", res.to_string());
if let Err(res) = self.mouse_handle.write(&events) {
log::error!("send_button: {res}");
}
}
fn mouse_move_internal(&mut self, pos: Vec2) {
#[cfg(debug_assertions)]
log::trace!("Mouse move: {:?}", pos);
log::trace!("Mouse move: {pos:?}");
let pos = (pos - self.desktop_origin) * (MOUSE_EXTENT / self.desktop_extent);
@@ -196,18 +191,24 @@ impl UInputProvider {
new_event(time, EV_ABS, AbsoluteAxis::Y as _, pos.y as i32),
new_event(time, EV_SYN, 0, 0),
];
if let Err(res) = self.handle.write(&events) {
log::error!("{}", res.to_string());
if let Err(res) = self.mouse_handle.write(&events) {
log::error!("{res}");
}
}
fn wheel_internal(&self, delta: i32) {
fn wheel_internal(&self, delta_y: i32, delta_x: i32) {
let time = get_time();
let events = [
new_event(time, EV_REL, RelativeAxis::Wheel as _, delta),
new_event(time, EV_REL, RelativeAxis::WheelHiRes as _, delta_y),
new_event(
time,
EV_REL,
RelativeAxis::HorizontalWheelHiRes as _,
delta_x,
),
new_event(time, EV_SYN, 0, 0),
];
if let Err(res) = self.handle.write(&events) {
log::error!("wheel: {}", res.to_string());
if let Err(res) = self.mouse_handle.write(&events) {
log::error!("wheel: {res}");
}
}
}
@@ -226,14 +227,16 @@ impl HidProvider for UInputProvider {
self.cur_modifiers = modifiers;
}
fn send_key(&self, key: VirtualKey, down: bool) {
log::debug!("send_key: {:?} {}", key, down);
#[cfg(debug_assertions)]
log::trace!("send_key: {key:?} {down}");
let time = get_time();
let events = [
new_event(time, EV_KEY, (key as u16) - 8, down as _),
new_event(time, EV_KEY, (key as u16) - 8, down.into()),
new_event(time, EV_SYN, 0, 0),
];
if let Err(res) = self.handle.write(&events) {
log::error!("send_key: {}", res.to_string());
if let Err(res) = self.keyboard_handle.write(&events) {
log::error!("send_key: {res}");
}
}
fn set_desktop_extent(&mut self, extent: Vec2) {
@@ -254,9 +257,9 @@ impl HidProvider for UInputProvider {
self.current_action.pos = self.current_action.last_requested_pos;
}
}
fn wheel(&mut self, delta: i32) {
fn wheel(&mut self, delta_y: i32, delta_x: i32) {
if self.current_action.scroll.is_none() {
self.current_action.scroll = Some(delta);
self.current_action.scroll = Some(IVec2::new(delta_x, delta_y));
// Pass mouse motion events only if not scrolling
// (allows scrolling on all Chromium-based applications)
self.current_action.pos = None;
@@ -270,7 +273,7 @@ impl HidProvider for UInputProvider {
self.send_button_internal(button.button, button.down);
}
if let Some(scroll) = self.current_action.scroll.take() {
self.wheel_internal(scroll);
self.wheel_internal(scroll.y, scroll.x);
}
}
}
@@ -278,7 +281,7 @@ impl HidProvider for UInputProvider {
impl HidProvider for DummyProvider {
fn mouse_move(&mut self, _pos: Vec2) {}
fn send_button(&mut self, _button: u16, _down: bool) {}
fn wheel(&mut self, _delta: i32) {}
fn wheel(&mut self, _delta_y: i32, _delta_x: i32) {}
fn set_modifiers(&mut self, _modifiers: u8) {}
fn send_key(&self, _key: VirtualKey, _down: bool) {}
fn set_desktop_extent(&mut self, _extent: Vec2) {}
@@ -297,7 +300,7 @@ fn get_time() -> timeval {
}
#[inline]
fn new_event(time: timeval, type_: u16, code: u16, value: i32) -> input_event {
const fn new_event(time: timeval, type_: u16, code: u16, value: i32) -> input_event {
input_event {
time,
type_,
@@ -317,7 +320,7 @@ pub const META: KeyModifier = 0x80;
#[allow(non_camel_case_types)]
#[repr(u16)]
#[derive(Debug, Deserialize, PartialEq, Clone, Copy, IntegerId, EnumString, EnumIter)]
#[derive(Debug, Deserialize, PartialEq, Eq, Clone, Copy, IntegerId, EnumString, EnumIter)]
pub enum VirtualKey {
Escape = 9,
N1, // number row
@@ -473,7 +476,7 @@ pub enum VirtualKey {
XF86Search = 225,
}
pub static KEYS_TO_MODS: Lazy<IdMap<VirtualKey, KeyModifier>> = Lazy::new(|| {
pub static KEYS_TO_MODS: LazyLock<IdMap<VirtualKey, KeyModifier>> = LazyLock::new(|| {
idmap! {
VirtualKey::LShift => SHIFT,
VirtualKey::RShift => SHIFT,
@@ -488,7 +491,7 @@ pub static KEYS_TO_MODS: Lazy<IdMap<VirtualKey, KeyModifier>> = Lazy::new(|| {
}
});
pub static MODS_TO_KEYS: Lazy<IdMap<KeyModifier, Vec<VirtualKey>>> = Lazy::new(|| {
pub static MODS_TO_KEYS: LazyLock<IdMap<KeyModifier, Vec<VirtualKey>>> = LazyLock::new(|| {
idmap! {
SHIFT => vec![VirtualKey::LShift, VirtualKey::RShift],
CAPS_LOCK => vec![VirtualKey::Caps],
@@ -518,7 +521,7 @@ macro_rules! key_is {
};
}
pub fn get_key_type(key: VirtualKey) -> KeyType {
pub const fn get_key_type(key: VirtualKey) -> KeyType {
if key_between!(key, VirtualKey::N1, VirtualKey::Plus)
|| key_between!(key, VirtualKey::Q, VirtualKey::Oem6)
|| key_between!(key, VirtualKey::A, VirtualKey::Oem3)
+6 -8
View File
@@ -46,11 +46,9 @@ pub fn get_keymap_wl() -> anyhow::Result<XkbKeymap> {
// this gets us the wl_keyboard
let _ = queue.blocking_dispatch(&mut me);
if let Some(keymap) = me.keymap.take() {
Ok(keymap)
} else {
Err(anyhow::anyhow!("Could not load keymap"))
}
me.keymap
.take()
.ok_or_else(|| anyhow::anyhow!("Could not load keymap"))
}
impl Dispatch<WlRegistry, GlobalListContents> for WlKeymapHandler {
@@ -84,7 +82,7 @@ impl Dispatch<WlSeat, ()> for WlKeymapHandler {
}
}
wl_seat::Event::Name { name } => {
log::debug!("Using WlSeat: {}", name);
log::debug!("Using WlSeat: {name}");
}
_ => {}
}
@@ -127,14 +125,14 @@ impl Dispatch<WlKeyboard, ()> for WlKeymapHandler {
log::error!("Default layout will be used.");
}
Err(err) => {
log::error!("Could not load keymap: {}", err);
log::error!("Could not load keymap: {err}");
log::error!("Default layout will be used.");
}
}
}
}
wl_keyboard::Event::RepeatInfo { rate, delay } => {
log::debug!("WlKeyboard RepeatInfo rate: {}, delay: {}", rate, delay);
log::debug!("WlKeyboard RepeatInfo rate: {rate}, delay: {delay}");
}
_ => {}
}
+76 -51
View File
@@ -1,4 +1,18 @@
#[allow(dead_code)]
#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
#![allow(
dead_code,
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_possible_wrap,
clippy::cast_lossless,
clippy::match_wildcard_for_single_variants,
clippy::doc_markdown,
clippy::struct_excessive_bools,
clippy::needless_pass_by_value,
clippy::needless_pass_by_ref_mut,
clippy::multiple_crate_versions
)]
mod backend;
mod config;
mod config_io;
@@ -22,6 +36,8 @@ use std::{
use clap::Parser;
use sysinfo::Pid;
use tracing::level_filters::LevelFilter;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
/// The lightweight desktop overlay for OpenVR and OpenXR
#[derive(Default, Parser, Debug)]
@@ -63,6 +79,7 @@ struct Args {
uidev: Option<String>,
}
#[allow(clippy::unnecessary_wraps)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut args = if std::env::args().skip(1).any(|a| !a.is_empty()) {
Args::parse()
@@ -76,7 +93,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
return Ok(());
}
logging_init(&mut args)?;
logging_init(&mut args);
log::info!(
"Welcome to {} version {}!",
@@ -120,23 +137,23 @@ fn auto_run(running: Arc<AtomicBool>, args: Args) {
Ok(()) => return,
Err(BackendError::NotSupported) => (),
Err(e) => {
log::error!("{}", e.to_string());
log::error!("{e}");
return;
}
};
}
}
#[cfg(feature = "openvr")]
if !args_get_openxr(&args) {
use crate::backend::openvr::openvr_run;
match openvr_run(running.clone(), args.show) {
match openvr_run(running, args.show) {
Ok(()) => return,
Err(BackendError::NotSupported) => (),
Err(e) => {
log::error!("{}", e.to_string());
log::error!("{e}");
return;
}
};
}
}
log::error!("No more backends to try");
@@ -148,10 +165,10 @@ fn auto_run(running: Arc<AtomicBool>, args: Args) {
compile_error!("No desktop support! Enable either wayland or x11 features!");
}
#[allow(dead_code)]
fn args_get_openvr(_args: &Args) -> bool {
#[allow(dead_code, unused_variables)]
const fn args_get_openvr(args: &Args) -> bool {
#[cfg(feature = "openvr")]
let ret = _args.openvr;
let ret = args.openvr;
#[cfg(not(feature = "openvr"))]
let ret = false;
@@ -159,10 +176,10 @@ fn args_get_openvr(_args: &Args) -> bool {
ret
}
#[allow(dead_code)]
fn args_get_openxr(_args: &Args) -> bool {
#[allow(dead_code, unused_variables)]
const fn args_get_openxr(args: &Args) -> bool {
#[cfg(feature = "openxr")]
let ret = _args.openxr;
let ret = args.openxr;
#[cfg(not(feature = "openxr"))]
let ret = false;
@@ -170,56 +187,64 @@ fn args_get_openxr(_args: &Args) -> bool {
ret
}
fn logging_init(args: &mut Args) -> anyhow::Result<()> {
let log_file = args
fn logging_init(args: &mut Args) {
let log_file_path = args
.log_to
.take()
.or_else(|| std::env::var("WLX_LOGFILE").ok())
.or_else(|| Some("/tmp/wlx.log".to_string()));
.unwrap_or_else(|| String::from("/tmp/wlx.log"));
if let Some(log_to) = log_file.filter(|s| !s.is_empty()) {
if let Err(e) = file_logging_init(&log_to) {
log::error!("Failed to initialize file logging: {}", e);
env_logger::init();
let file_writer = match std::fs::OpenOptions::new()
.write(true)
.truncate(true)
.open(&log_file_path)
{
Ok(file) => {
println!("Logging to {}", &log_file_path);
Some(file)
}
Err(e) => {
println!("Failed to open log file (path: {e:?}): {log_file_path}");
None
}
};
let registry = tracing_subscriber::registry()
.with(
tracing_subscriber::fmt::layer()
.pretty()
.with_writer(std::io::stderr),
)
.with(
/* read RUST_LOG env var */
EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy()
.add_directive("zbus=warn".parse().unwrap())
.add_directive("wlx_capture::wayland=info".parse().unwrap())
.add_directive("smithay=debug".parse().unwrap()), /* GLES render spam */
);
if let Some(writer) = file_writer {
registry
.with(
tracing_subscriber::fmt::layer()
.with_file(true)
.with_line_number(true)
.with_writer(writer)
.with_ansi(false),
)
.init();
} else {
env_logger::init();
registry.init();
}
log_panics::init();
Ok(())
}
fn file_logging_init(log_to: &str) -> anyhow::Result<()> {
use std::io::Write;
let target = Box::new(std::fs::File::create(log_to)?);
env_logger::Builder::new()
.target(env_logger::Target::Pipe(target))
.filter(None, log::LevelFilter::Info)
.filter_module("zbus", log::LevelFilter::Warn)
.parse_default_env()
.format(|buf, record| {
eprintln!("[{}] {}", record.level(), record.args());
writeln!(
buf,
"[{} {} {}] {}",
chrono::Local::now().format("%H:%M:%S%.3f"),
record.level(),
record.module_path().unwrap_or_default(),
record.args()
)
})
.init();
println!("Logging to: {}", log_to);
Ok(())
}
fn ensure_single_instance(replace: bool) -> bool {
let mut path = std::env::var("XDG_RUNTIME_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from("/tmp"));
let mut path =
std::env::var("XDG_RUNTIME_DIR").map_or_else(|_| PathBuf::from("/tmp"), PathBuf::from);
path.push("wlx-overlay-s.pid");
if path.exists() {
+3 -4
View File
@@ -1,13 +1,12 @@
use glam::Vec3A;
use once_cell::sync::Lazy;
use std::sync::Arc;
use std::sync::{Arc, LazyLock};
use crate::backend::overlay::{OverlayData, OverlayState, RelativeTo, Z_ORDER_ANCHOR};
use crate::config::{load_known_yaml, ConfigType};
use crate::gui::modular::{modular_canvas, ModularUiConfig};
use crate::state::AppState;
pub static ANCHOR_NAME: Lazy<Arc<str>> = Lazy::new(|| Arc::from("anchor"));
pub static ANCHOR_NAME: LazyLock<Arc<str>> = LazyLock::new(|| Arc::from("anchor"));
pub fn create_anchor<O>(state: &mut AppState) -> anyhow::Result<OverlayData<O>>
where
@@ -27,7 +26,7 @@ where
relative_to: RelativeTo::Stage,
..Default::default()
},
backend: Box::new(modular_canvas(&config.size, &config.elements, state)?),
backend: Box::new(modular_canvas(config.size, &config.elements, state)?),
..Default::default()
})
}
+5 -5
View File
@@ -21,28 +21,28 @@ pub fn create_custom(
match load_custom_ui(&name) {
Ok(config) => config,
Err(e) => {
log::error!("Failed to load custom UI config for {}: {:?}", name, e);
log::error!("Failed to load custom UI config for {name}: {e:?}");
return None;
}
}
};
let canvas = match modular_canvas(&config.size, &config.elements, state) {
let canvas = match modular_canvas(config.size, &config.elements, state) {
Ok(canvas) => canvas,
Err(e) => {
log::error!("Failed to create canvas for {}: {:?}", name, e);
log::error!("Failed to create canvas for {name}: {e:?}");
return None;
}
};
let state = OverlayState {
name: name.clone(),
name,
want_visible: true,
interactable: true,
grabbable: true,
spawn_scale: config.width,
spawn_point: Vec3A::from_array(config.spawn_pos.unwrap_or([0., 0., -0.5])),
interaction_transform: ui_transform(&config.size),
interaction_transform: ui_transform(config.size),
..Default::default()
};
let backend = Box::new(canvas);
+74 -58
View File
@@ -2,14 +2,18 @@ use std::{
collections::HashMap,
process::{Child, Command},
str::FromStr,
sync::{Arc, LazyLock},
};
use crate::{
backend::{
input::{InteractionHandler, PointerMode},
overlay::{FrameTransform, OverlayBackend, OverlayData, OverlayRenderer, OverlayState},
overlay::{
FrameMeta, OverlayBackend, OverlayData, OverlayRenderer, OverlayState, ShouldRender,
},
},
config::{self, ConfigType},
graphics::CommandBuffers,
gui::{
canvas::{builder::CanvasBuilder, control::Control, Canvas},
color_parse, KeyCapType,
@@ -21,9 +25,9 @@ use crate::{
state::{AppState, KeyboardFocus},
};
use glam::{vec2, vec3a, Affine2, Vec4};
use once_cell::sync::Lazy;
use regex::Regex;
use serde::{Deserialize, Serialize};
use vulkano::image::view::ImageView;
const PIXELS_PER_UNIT: f32 = 80.;
const BUTTON_PADDING: f32 = 4.;
@@ -51,10 +55,17 @@ fn set_modifiers(app: &mut AppState, mods: u8) {
KeyboardFocus::PhysicalScreen => {
app.hid_provider.set_modifiers(mods);
}
KeyboardFocus::WayVR => {}
KeyboardFocus::WayVR =>
{
#[cfg(feature = "wayvr")]
if let Some(wayvr) = &app.wayvr {
wayvr.borrow_mut().data.state.set_modifiers(mods);
}
}
}
}
#[allow(clippy::too_many_lines)]
pub fn create_keyboard<O>(
app: &AppState,
mut keymap: Option<XkbKeymap>,
@@ -95,23 +106,25 @@ where
canvas.fg_color = color_parse("#cad3f5").unwrap(); //safe
canvas.bg_color = color_parse("#1e2030").unwrap(); //safe
let has_altgr = keymap.as_ref().map_or(false, |k| k.has_altgr());
let has_altgr = keymap
.as_ref()
.is_some_and(super::super::hid::XkbKeymap::has_altgr);
if !LAYOUT.auto_labels.unwrap_or(true) {
keymap = None;
}
let unit_size = size.x / LAYOUT.row_size;
let h = unit_size - 2. * BUTTON_PADDING;
let h = 2.0f32.mul_add(-BUTTON_PADDING, unit_size);
for row in 0..LAYOUT.key_sizes.len() {
let y = unit_size * (row as f32) + BUTTON_PADDING;
let y = unit_size.mul_add(row as f32, BUTTON_PADDING);
let mut sum_size = 0f32;
for col in 0..LAYOUT.key_sizes[row].len() {
let my_size = LAYOUT.key_sizes[row][col];
let x = unit_size * sum_size + BUTTON_PADDING;
let w = unit_size * my_size - 2. * BUTTON_PADDING;
let x = unit_size.mul_add(sum_size, BUTTON_PADDING);
let w = unit_size.mul_add(my_size, -(2. * BUTTON_PADDING));
if let Some(key) = LAYOUT.main_layout[row][col].as_ref() {
let mut label = Vec::with_capacity(2);
@@ -125,7 +138,7 @@ where
let label0 = keymap.label_for_key(vk, 0);
let label1 = keymap.label_for_key(vk, SHIFT);
if label0.chars().next().map_or(false, |f| f.is_alphabetic()) {
if label0.chars().next().is_some_and(char::is_alphabetic) {
label.push(label1);
if has_altgr {
cap_type = KeyCapType::RegularAltGr;
@@ -165,7 +178,7 @@ where
});
} else if let Some(exec_args) = LAYOUT.exec_commands.get(key) {
if exec_args.is_empty() {
log::error!("Keyboard: EXEC args empty for {}", key);
log::error!("Keyboard: EXEC args empty for {key}");
continue;
}
let mut iter = exec_args.iter().cloned();
@@ -175,10 +188,10 @@ where
args: iter.by_ref().take_while(|arg| arg[..] != *"null").collect(),
release_program: iter.next(),
release_args: iter.collect(),
})
};
});
}
} else {
log::error!("Unknown key: {}", key);
log::error!("Unknown key: {key}");
}
if let Some(state) = maybe_state {
@@ -229,7 +242,7 @@ fn key_press(
) {
match control.state.as_mut() {
Some(KeyButtonData::Key { vk, pressed }) => {
data.key_click(app);
key_click(app);
data.modifiers |= match mode {
PointerMode::Right => SHIFT,
@@ -245,11 +258,11 @@ fn key_press(
Some(KeyButtonData::Modifier { modifier, sticky }) => {
*sticky = data.modifiers & *modifier == 0;
data.modifiers |= *modifier;
data.key_click(app);
key_click(app);
set_modifiers(app, data.modifiers);
}
Some(KeyButtonData::Macro { verbs }) => {
data.key_click(app);
key_click(app);
for (vk, press) in verbs {
send_key(app, *vk, *press);
}
@@ -259,7 +272,7 @@ fn key_press(
data.processes
.retain_mut(|child| !matches!(child.try_wait(), Ok(Some(_))));
data.key_click(app);
key_click(app);
if let Ok(child) = Command::new(program).args(args).spawn() {
data.processes.push(child);
}
@@ -278,7 +291,7 @@ fn key_release(
send_key(app, *vk, false);
*pressed = false;
for m in AUTO_RELEASE_MODS.iter() {
for m in &AUTO_RELEASE_MODS {
if data.modifiers & *m != 0 {
data.modifiers &= !*m;
set_modifiers(app, data.modifiers);
@@ -338,11 +351,9 @@ struct KeyboardData {
const KEY_AUDIO_WAV: &[u8] = include_bytes!("../res/421581.wav");
impl KeyboardData {
fn key_click(&mut self, app: &mut AppState) {
if app.session.config.keyboard_sound_enabled {
app.audio.play(KEY_AUDIO_WAV);
}
fn key_click(app: &mut AppState) {
if app.session.config.keyboard_sound_enabled {
app.audio.play(KEY_AUDIO_WAV);
}
}
@@ -366,10 +377,10 @@ enum KeyButtonData {
},
}
static LAYOUT: Lazy<Layout> = Lazy::new(Layout::load_from_disk);
static LAYOUT: LazyLock<Layout> = LazyLock::new(Layout::load_from_disk);
static MACRO_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^([A-Za-z0-9_-]+)(?: +(UP|DOWN))?$").unwrap()); // want panic
static MACRO_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"^([A-Za-z0-9_-]+)(?: +(UP|DOWN))?$").unwrap()); // want panic
#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize)]
#[repr(usize)]
@@ -384,6 +395,7 @@ pub enum AltModifier {
}
#[derive(Debug, Deserialize, Serialize)]
#[allow(clippy::struct_field_names)]
pub struct Layout {
name: String,
row_size: f32,
@@ -397,8 +409,8 @@ pub struct Layout {
}
impl Layout {
fn load_from_disk() -> Layout {
let mut layout = config::load_known_yaml::<Layout>(ConfigType::Keyboard);
fn load_from_disk() -> Self {
let mut layout = config::load_known_yaml::<Self>(ConfigType::Keyboard);
layout.post_load();
layout
}
@@ -407,25 +419,25 @@ impl Layout {
for i in 0..self.key_sizes.len() {
let row = &self.key_sizes[i];
let width: f32 = row.iter().sum();
if (width - self.row_size).abs() > 0.001 {
panic!(
"Row {} has a width of {}, but the row size is {}",
i, width, self.row_size
);
}
assert!(
(width - self.row_size).abs() < 0.001,
"Row {} has a width of {}, but the row size is {}",
i,
width,
self.row_size
);
}
for i in 0..self.main_layout.len() {
let row = &self.main_layout[i];
let width = row.len();
if width != self.key_sizes[i].len() {
panic!(
"Row {} has {} keys, needs to have {} according to key_sizes",
i,
width,
self.key_sizes[i].len()
);
}
assert!(
(width == self.key_sizes[i].len()),
"Row {} has {} keys, needs to have {} according to key_sizes",
i,
width,
self.key_sizes[i].len()
);
}
}
@@ -445,10 +457,7 @@ impl Layout {
}
if key.contains('_') {
key = key.split('_').next().unwrap_or_else(|| {
log::error!(
"keyboard.yaml: Key '{}' must not start or end with '_'!",
key
);
log::error!("keyboard.yaml: Key '{key}' must not start or end with '_'!");
"???"
});
}
@@ -496,10 +505,10 @@ struct KeyboardBackend {
impl OverlayBackend for KeyboardBackend {
fn set_interaction(&mut self, interaction: Box<dyn crate::backend::input::InteractionHandler>) {
self.canvas.set_interaction(interaction)
self.canvas.set_interaction(interaction);
}
fn set_renderer(&mut self, renderer: Box<dyn crate::backend::overlay::OverlayRenderer>) {
self.canvas.set_renderer(renderer)
self.canvas.set_renderer(renderer);
}
}
@@ -510,18 +519,19 @@ impl InteractionHandler for KeyboardBackend {
hit: &crate::backend::input::PointerHit,
pressed: bool,
) {
self.canvas.on_pointer(app, hit, pressed)
self.canvas.on_pointer(app, hit, pressed);
}
fn on_scroll(
&mut self,
app: &mut AppState,
hit: &crate::backend::input::PointerHit,
delta: f32,
delta_y: f32,
delta_x: f32,
) {
self.canvas.on_scroll(app, hit, delta)
self.canvas.on_scroll(app, hit, delta_y, delta_x);
}
fn on_left(&mut self, app: &mut AppState, pointer: usize) {
self.canvas.on_left(app, pointer)
self.canvas.on_left(app, pointer);
}
fn on_hover(
&mut self,
@@ -536,14 +546,20 @@ impl OverlayRenderer for KeyboardBackend {
fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.canvas.init(app)
}
fn render(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.canvas.render(app)
fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender> {
self.canvas.should_render(app)
}
fn frame_transform(&mut self) -> Option<FrameTransform> {
self.canvas.frame_transform()
fn render(
&mut self,
app: &mut AppState,
tgt: Arc<ImageView>,
buf: &mut CommandBuffers,
alpha: f32,
) -> anyhow::Result<bool> {
self.canvas.render(app, tgt, buf, alpha)
}
fn view(&mut self) -> Option<std::sync::Arc<vulkano::image::view::ImageView>> {
self.canvas.view()
fn frame_meta(&mut self) -> Option<FrameMeta> {
self.canvas.frame_meta()
}
fn pause(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.canvas.data_mut().modifiers = 0;
+27 -16
View File
@@ -4,17 +4,19 @@ use std::{
};
use futures::{Future, FutureExt};
use vulkano::image::view::ImageView;
use wlx_capture::pipewire::{pipewire_select_screen, PipewireCapture, PipewireSelectScreenResult};
use crate::{
backend::{
common::OverlaySelector,
overlay::{
ui_transform, FrameTransform, OverlayBackend, OverlayRenderer, OverlayState,
ui_transform, FrameMeta, OverlayBackend, OverlayRenderer, OverlayState, ShouldRender,
SplitOverlayBackend,
},
task::TaskType,
},
graphics::CommandBuffers,
state::{AppSession, AppState},
};
@@ -45,7 +47,18 @@ impl OverlayRenderer for MirrorRenderer {
fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
Ok(())
}
fn render(&mut self, app: &mut AppState) -> anyhow::Result<()> {
fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender> {
self.renderer
.as_mut()
.map_or(Ok(ShouldRender::Unable), |r| r.should_render(app))
}
fn render(
&mut self,
app: &mut AppState,
tgt: Arc<ImageView>,
buf: &mut CommandBuffers,
alpha: f32,
) -> anyhow::Result<bool> {
if let Some(mut selector) = self.selector.take() {
let maybe_pw_result = match selector
.poll_unpin(&mut Context::from_waker(futures::task::noop_waker_ref()))
@@ -53,7 +66,7 @@ impl OverlayRenderer for MirrorRenderer {
Poll::Ready(result) => result,
Poll::Pending => {
self.selector = Some(selector);
return Ok(());
return Ok(false);
}
};
@@ -76,7 +89,7 @@ impl OverlayRenderer for MirrorRenderer {
));
}
Err(e) => {
log::warn!("Failed to create mirror due to PipeWire error: {:?}", e);
log::warn!("Failed to create mirror due to PipeWire error: {e:?}");
self.renderer = None;
// drop self
app.tasks
@@ -87,24 +100,25 @@ impl OverlayRenderer for MirrorRenderer {
}
}
let mut result = false;
if let Some(renderer) = self.renderer.as_mut() {
renderer.render(app)?;
if let Some(view) = renderer.view() {
let extent = view.image().extent();
result = renderer.render(app, tgt, buf, alpha)?;
if let Some(meta) = renderer.frame_meta() {
let extent = meta.extent;
if self.last_extent != extent {
self.last_extent = extent;
// resized
app.tasks.enqueue(TaskType::Overlay(
OverlaySelector::Name(self.name.clone()),
Box::new(move |_app, o| {
o.interaction_transform = ui_transform(&[extent[0], extent[1]]);
o.interaction_transform = ui_transform([extent[0], extent[1]]);
}),
));
}
}
}
Ok(())
Ok(result)
}
fn pause(&mut self, app: &mut AppState) -> anyhow::Result<()> {
if let Some(renderer) = self.renderer.as_mut() {
@@ -118,12 +132,9 @@ impl OverlayRenderer for MirrorRenderer {
}
Ok(())
}
fn view(&mut self) -> Option<std::sync::Arc<vulkano::image::view::ImageView>> {
self.renderer.as_mut().and_then(|r| r.view())
}
fn frame_transform(&mut self) -> Option<FrameTransform> {
Some(FrameTransform {
fn frame_meta(&mut self) -> Option<FrameMeta> {
Some(FrameMeta {
extent: self.last_extent,
..Default::default()
})
@@ -134,7 +145,7 @@ pub fn new_mirror(
name: Arc<str>,
show_hide: bool,
session: &AppSession,
) -> Option<(OverlayState, Box<dyn OverlayBackend>)> {
) -> (OverlayState, Box<dyn OverlayBackend>) {
let state = OverlayState {
name: name.clone(),
show_hide,
@@ -147,5 +158,5 @@ pub fn new_mirror(
..Default::default()
});
Some((state, backend))
(state, backend)
}
+438 -393
View File
File diff suppressed because it is too large. Load diff
+32 -30
View File
@@ -1,8 +1,12 @@
use std::{f32::consts::PI, ops::Add, sync::Arc, time::Instant};
use std::{
f32::consts::PI,
ops::Add,
sync::{Arc, LazyLock},
time::Instant,
};
use glam::{vec3a, Quat};
use idmap_derive::IntegerId;
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use crate::{
@@ -18,8 +22,7 @@ use crate::{
const FONT_SIZE: isize = 16;
const PADDING: (f32, f32) = (25., 7.);
const PIXELS_TO_METERS: f32 = 1. / 2000.;
const TOAST_AUDIO_WAV: &[u8] = include_bytes!("../res/557297.wav");
static TOAST_NAME: Lazy<Arc<str>> = Lazy::new(|| "toast".into());
static TOAST_NAME: LazyLock<Arc<str>> = LazyLock::new(|| "toast".into());
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum DisplayMethod {
@@ -47,8 +50,8 @@ pub struct Toast {
#[allow(dead_code)]
impl Toast {
pub fn new(topic: ToastTopic, title: Arc<str>, body: Arc<str>) -> Self {
Toast {
pub const fn new(topic: ToastTopic, title: Arc<str>, body: Arc<str>) -> Self {
Self {
title,
body,
opacity: 1.0,
@@ -57,15 +60,15 @@ impl Toast {
topic,
}
}
pub fn with_timeout(mut self, timeout: f32) -> Self {
pub const fn with_timeout(mut self, timeout: f32) -> Self {
self.timeout = timeout;
self
}
pub fn with_opacity(mut self, opacity: f32) -> Self {
pub const fn with_opacity(mut self, opacity: f32) -> Self {
self.opacity = opacity;
self
}
pub fn with_sound(mut self, sound: bool) -> Self {
pub const fn with_sound(mut self, sound: bool) -> Self {
self.sound = sound;
self
}
@@ -78,6 +81,9 @@ impl Toast {
let destroy_at = instant.add(std::time::Duration::from_secs_f32(self.timeout));
let has_sound = self.sound && app.session.config.notifications_sound_enabled;
if has_sound {
app.audio.play(app.toast_sound);
}
// drop any toast that was created before us.
// (DropOverlay only drops overlays that were
@@ -91,7 +97,7 @@ impl Toast {
// frame, only the first one gets created
app.tasks.enqueue_at(
TaskType::CreateOverlay(
selector.clone(),
selector,
Box::new(move |app| {
let mut maybe_toast = new_toast(self, app);
if let Some((state, _)) = maybe_toast.as_mut() {
@@ -108,10 +114,6 @@ impl Toast {
),
instant,
);
if has_sound {
app.audio.play(TOAST_AUDIO_WAV);
}
}
}
@@ -141,13 +143,19 @@ fn new_toast(toast: Toast, app: &mut AppState) -> Option<(OverlayState, Box<dyn
}
};
let title = if toast.title.len() > 0 {
toast.title
} else {
let title = if toast.title.is_empty() {
"Notification".into()
} else {
toast.title
};
let mut size = if toast.body.len() > 0 {
let mut size = if toast.body.is_empty() {
let (w, h) = app
.fc
.get_text_size(&title, FONT_SIZE, app.graphics.clone())
.ok()?;
(w, h + 20.)
} else {
let (w0, _) = app
.fc
.get_text_size(&title, FONT_SIZE, app.graphics.clone())
@@ -157,12 +165,6 @@ fn new_toast(toast: Toast, app: &mut AppState) -> Option<(OverlayState, Box<dyn
.get_text_size(&toast.body, FONT_SIZE, app.graphics.clone())
.ok()?;
(w0.max(w1), h1 + 50.)
} else {
let (w, h) = app
.fc
.get_text_size(&title, FONT_SIZE, app.graphics.clone())
.ok()?;
(w, h + 20.)
};
let og_width = size.0;
@@ -182,15 +184,15 @@ fn new_toast(toast: Toast, app: &mut AppState) -> Option<(OverlayState, Box<dyn
canvas.bg_color = color_parse("#1e2030").unwrap(); // want panic
canvas.panel(0., 0., size.0, size.1, 16.);
if toast.body.len() > 0 {
if toast.body.is_empty() {
canvas.label_centered(PADDING.0, 0., og_width, size.1, 16., title);
} else {
canvas.label(PADDING.0, 54., og_width, size.1 - 54., 3., toast.body);
canvas.fg_color = color_parse("#b8c0e0").unwrap(); // want panic
canvas.bg_color = color_parse("#24273a").unwrap(); // want panic
canvas.panel(0., 0., size.0, 30., 16.);
canvas.label_centered(PADDING.0, 16., og_width, FONT_SIZE as f32 + 2., 16., title);
} else {
canvas.label_centered(PADDING.0, 0., og_width, size.1, 16., title);
}
let state = OverlayState {
@@ -220,13 +222,13 @@ pub fn error_toast<ErrorType>(app: &mut AppState, title: &str, err: ErrorType)
where
ErrorType: std::fmt::Display + std::fmt::Debug,
{
log::error!("{}: {:?}", title, err); // More detailed version (use Debug)
log::error!("{title}: {err:?}"); // More detailed version (use Debug)
// Brief version (use Display)
msg_err(app, &format!("{}: {}", title, err));
msg_err(app, &format!("{title}: {err}"));
}
pub fn error_toast_str(app: &mut AppState, message: &str) {
log::error!("{}", message);
log::error!("{message}");
msg_err(app, message);
}
+2 -2
View File
@@ -29,7 +29,7 @@ where
spawn_scale: config.width,
spawn_point: state.session.config.watch_pos,
spawn_rotation: state.session.config.watch_rot,
interaction_transform: ui_transform(&config.size),
interaction_transform: ui_transform(config.size),
relative_to,
..Default::default()
},
@@ -44,7 +44,7 @@ pub fn create_watch_canvas(
) -> anyhow::Result<Canvas<(), ModularData>> {
let config = config.unwrap_or_else(|| load_known_yaml::<ModularUiConfig>(ConfigType::Watch));
modular_canvas(&config.size, &config.elements, state)
modular_canvas(config.size, &config.elements, state)
}
pub fn watch_fade<D>(app: &mut AppState, watch: &mut OverlayData<D>)
+260 -149
View File
@@ -1,7 +1,10 @@
use glam::{vec3a, Affine2, Vec3, Vec3A};
use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc};
use vulkano::image::SubresourceLayout;
use wayvr_ipc::packet_server;
use vulkano::{
command_buffer::CommandBufferUsage,
image::{view::ImageView, SubresourceLayout},
};
use wayvr_ipc::packet_server::{self, PacketServer, WvrStateChanged};
use wlx_capture::frame::{DmabufFrame, FourCC, FrameFormat, FramePlane};
use crate::{
@@ -9,8 +12,8 @@ use crate::{
common::{OverlayContainer, OverlaySelector},
input::{self, InteractionHandler},
overlay::{
ui_transform, FrameTransform, OverlayData, OverlayID, OverlayRenderer, OverlayState,
SplitOverlayBackend, Z_ORDER_DASHBOARD,
ui_transform, FrameMeta, OverlayData, OverlayID, OverlayRenderer, OverlayState,
ShouldRender, SplitOverlayBackend, Z_ORDER_DASHBOARD,
},
task::TaskType,
wayvr::{
@@ -20,7 +23,7 @@ use crate::{
},
},
config_wayvr,
graphics::WlxGraphics,
graphics::{CommandBuffers, WlxGraphics, WlxPipeline},
gui::modular::button::{WayVRAction, WayVRDisplayClickAction},
state::{self, AppState, KeyboardFocus},
};
@@ -38,14 +41,11 @@ pub struct WayVRContext {
}
impl WayVRContext {
pub fn new(
wvr: Rc<RefCell<WayVRData>>,
display: wayvr::display::DisplayHandle,
) -> anyhow::Result<Self> {
Ok(Self {
wayvr: wvr.clone(),
pub const fn new(wvr: Rc<RefCell<WayVRData>>, display: wayvr::display::DisplayHandle) -> Self {
Self {
wayvr: wvr,
display,
})
}
}
}
@@ -64,7 +64,7 @@ pub struct WayVRData {
impl WayVRData {
pub fn new(config: wayvr::Config) -> anyhow::Result<Self> {
Ok(Self {
display_handle_map: Default::default(),
display_handle_map: HashMap::default(),
data: WayVR::new(config)?,
overlays_to_create: Vec::new(),
dashboard_executed: false,
@@ -84,7 +84,7 @@ impl WayVRData {
.any(|d| d.obj.name == candidate)
{
if num > 0 {
candidate = format!("{} ({})", candidate, num);
candidate = format!("{candidate} ({num})");
}
num += 1;
}
@@ -99,7 +99,7 @@ pub struct WayVRInteractionHandler {
}
impl WayVRInteractionHandler {
pub fn new(context: Rc<RefCell<WayVRContext>>, mouse_transform: Affine2) -> Self {
pub const fn new(context: Rc<RefCell<WayVRContext>>, mouse_transform: Affine2) -> Self {
Self {
context,
mouse_transform,
@@ -119,8 +119,8 @@ impl InteractionHandler for WayVRInteractionHandler {
if let Some(disp) = wayvr.state.displays.get(&ctx.display) {
let pos = self.mouse_transform.transform_point2(hit.uv);
let x = ((pos.x * disp.width as f32) as i32).max(0);
let y = ((pos.y * disp.height as f32) as i32).max(0);
let x = ((pos.x * f32::from(disp.width)) as i32).max(0);
let y = ((pos.y * f32::from(disp.height)) as i32).max(0);
let ctx = self.context.borrow();
wayvr.state.send_mouse_move(ctx.display, x as u32, y as u32);
@@ -148,24 +148,31 @@ impl InteractionHandler for WayVRInteractionHandler {
if pressed {
wayvr.state.send_mouse_down(ctx.display, index);
} else {
wayvr.state.send_mouse_up(ctx.display, index);
wayvr.state.send_mouse_up(index);
}
}
}
fn on_scroll(&mut self, _app: &mut state::AppState, _hit: &input::PointerHit, delta: f32) {
fn on_scroll(
&mut self,
_app: &mut state::AppState,
_hit: &input::PointerHit,
delta_y: f32,
delta_x: f32,
) {
let ctx = self.context.borrow();
ctx.wayvr
.borrow_mut()
.data
.state
.send_mouse_scroll(ctx.display, delta);
.send_mouse_scroll(delta_y, delta_x);
}
}
pub struct WayVRRenderer {
dmabuf_image: Option<Arc<vulkano::image::Image>>,
view: Option<Arc<vulkano::image::view::ImageView>>,
pipeline: Arc<WlxPipeline>,
vk_image: Option<Arc<vulkano::image::Image>>,
vk_image_view: Option<Arc<vulkano::image::view::ImageView>>,
context: Rc<RefCell<WayVRContext>>,
graphics: Arc<WlxGraphics>,
}
@@ -176,10 +183,22 @@ impl WayVRRenderer {
wvr: Rc<RefCell<WayVRData>>,
display: wayvr::display::DisplayHandle,
) -> anyhow::Result<Self> {
let Ok(shaders) = app.graphics.shared_shaders.read() else {
anyhow::bail!("Failed to lock shared shaders for reading");
};
let pipeline = app.graphics.create_pipeline(
shaders.get("vert_common").unwrap().clone(), // want panic
shaders.get("frag_srgb").unwrap().clone(), // want panic
app.graphics.native_format,
None,
)?;
Ok(Self {
context: Rc::new(RefCell::new(WayVRContext::new(wvr, display)?)),
dmabuf_image: None,
view: None,
pipeline,
context: Rc::new(RefCell::new(WayVRContext::new(wvr, display))),
vk_image: None,
vk_image_view: None,
graphics: app.graphics.clone(),
})
}
@@ -198,10 +217,7 @@ fn get_or_create_display_by_name(
.session
.wayvr_config
.get_display(disp_name)
.ok_or(anyhow::anyhow!(
"Cannot find display named \"{}\"",
disp_name
))?
.ok_or_else(|| anyhow::anyhow!("Cannot find display named \"{}\"", disp_name))?
.clone();
let disp_handle = wayvr.data.state.create_display(
@@ -280,6 +296,7 @@ where
},
)?;
overlay.state.curvature = Some(0.15);
overlay.state.want_visible = true;
overlay.state.spawn_scale = 2.0;
overlay.state.spawn_point = vec3a(0.0, -0.35, -1.75);
@@ -290,25 +307,17 @@ where
unreachable!(); /* safe, not possible to trigger */
};
// FIXME: overlay curvature needs to be dispatched for some unknown reason, this value is not set otherwise
app.tasks.enqueue(TaskType::Overlay(
OverlaySelector::Id(overlay.state.id),
Box::new(move |_app, o| {
o.curvature = Some(0.15);
}),
));
overlays.add(overlay);
let args_vec = match &conf_dash.args {
Some(args) => gen_args_vec(args),
None => vec![],
};
let args_vec = &conf_dash
.args
.as_ref()
.map_or_else(Vec::new, |args| gen_args_vec(args.as_str()));
let env_vec = match &conf_dash.env {
Some(env) => gen_env_vec(env),
None => vec![],
};
let env_vec = &conf_dash
.env
.as_ref()
.map_or_else(Vec::new, |env| gen_env_vec(env));
let mut userdata = HashMap::new();
userdata.insert(String::from("type"), String::from("dashboard"));
@@ -317,8 +326,8 @@ where
let _process_handle_unused = wayvr.data.state.spawn_process(
disp_handle,
&conf_dash.exec,
&args_vec,
&env_vec,
args_vec,
env_vec,
userdata,
)?;
@@ -332,12 +341,23 @@ where
anyhow::bail!("Overlay ID not set for dashboard display");
};
let cur_visibility = !display.visible;
wayvr
.data
.ipc_server
.broadcast(PacketServer::WvrStateChanged(if cur_visibility {
WvrStateChanged::DashboardShown
} else {
WvrStateChanged::DashboardHidden
}));
app.tasks.enqueue(TaskType::Overlay(
OverlaySelector::Id(overlay_id),
Box::new(move |app, o| {
// Toggle visibility
o.want_visible = !o.want_visible;
if o.want_visible {
o.want_visible = cur_visibility;
if cur_visibility {
o.reset(app, true);
}
}),
@@ -413,6 +433,7 @@ where
Ok(())
}
#[allow(clippy::too_many_lines)]
pub fn tick_events<O>(app: &mut AppState, overlays: &mut OverlayContainer<O>) -> anyhow::Result<()>
where
O: Default,
@@ -447,46 +468,52 @@ where
wayvr
.data
.ipc_server
.broadcast(packet_server::PacketServer::WvrStateChanged(packet))?;
.broadcast(packet_server::PacketServer::WvrStateChanged(packet));
}
}
}
let res = wayvr.data.tick_events()?;
let res = wayvr.data.tick_events(app)?;
drop(wayvr);
for result in res {
match result {
wayvr::TickTask::NewExternalProcess(req) => {
wayvr::TickTask::NewExternalProcess(request) => {
let config = &app.session.wayvr_config;
let disp_name = if let Some(display_name) = req.env.display_name {
config
.get_display(display_name.as_str())
.map(|_| display_name)
} else {
config
.get_default_display()
.map(|(display_name, _)| display_name)
};
let disp_name = request.env.display_name.map_or_else(
|| {
config
.get_default_display()
.map(|(display_name, _)| display_name)
},
|display_name| {
config
.get_display(display_name.as_str())
.map(|_| display_name)
},
);
if let Some(disp_name) = disp_name {
let mut wayvr = r_wayvr.borrow_mut();
log::info!("Registering external process with PID {}", req.pid);
log::info!("Registering external process with PID {}", request.pid);
let disp_handle = get_or_create_display_by_name(app, &mut wayvr, &disp_name)?;
wayvr.data.state.add_external_process(disp_handle, req.pid);
wayvr
.data
.state
.add_external_process(disp_handle, request.pid);
wayvr
.data
.state
.manager
.add_client(wayvr::client::WayVRClient {
client: req.client,
client: request.client,
display_handle: disp_handle,
pid: req.pid,
pid: request.pid,
});
}
}
@@ -534,54 +561,87 @@ where
}
impl WayVRRenderer {
fn ensure_dmabuf(&mut self, data: wayvr::egl_data::DMAbufData) -> anyhow::Result<()> {
if self.dmabuf_image.is_none() {
// First init
let mut planes = [FramePlane::default(); 4];
planes[0].fd = Some(data.fd);
planes[0].offset = data.offset as u32;
planes[0].stride = data.stride;
fn ensure_software_data(
&mut self,
data: &wayvr::egl_data::RenderSoftwarePixelsData,
) -> anyhow::Result<()> {
let mut upload = self.graphics.create_uploads_command_buffer(
self.graphics.transfer_queue.clone(),
CommandBufferUsage::OneTimeSubmit,
)?;
let ctx = self.context.borrow_mut();
let wayvr = ctx.wayvr.borrow_mut();
if let Some(disp) = wayvr.data.state.displays.get(&ctx.display) {
let frame = DmabufFrame {
format: FrameFormat {
width: disp.width as u32,
height: disp.height as u32,
fourcc: FourCC {
value: data.mod_info.fourcc,
},
modifier: data.mod_info.modifiers[0], /* possibly not proper? */
..Default::default()
},
num_planes: 1,
planes,
};
let tex = upload.texture2d_raw(
u32::from(data.width),
u32::from(data.height),
vulkano::format::Format::R8G8B8A8_UNORM,
&data.data,
)?;
drop(wayvr);
// FIXME: can we use _buffers_ here?
upload.build_and_execute_now()?;
let layouts: Vec<SubresourceLayout> = vec![SubresourceLayout {
offset: data.offset as _,
size: 0,
row_pitch: data.stride as _,
array_pitch: None,
depth_pitch: None,
}];
//buffers.push(upload.build()?);
let tex = self.graphics.dmabuf_texture_ex(
frame,
vulkano::image::ImageTiling::DrmFormatModifier,
layouts,
data.mod_info.modifiers,
)?;
self.dmabuf_image = Some(tex.clone());
self.view = Some(vulkano::image::view::ImageView::new_default(tex).unwrap());
} else {
anyhow::bail!("Failed to fetch WayVR display")
}
self.vk_image = Some(tex.clone());
self.vk_image_view = Some(ImageView::new_default(tex).unwrap());
Ok(())
}
fn ensure_dmabuf_data(
&mut self,
data: &wayvr::egl_data::RenderDMAbufData,
) -> anyhow::Result<()> {
if self.vk_image.is_some() {
return Ok(()); // already initialized and automatically updated due to direct zero-copy textue access
}
// First init
let mut planes = [FramePlane::default(); 4];
planes[0].fd = Some(data.fd);
planes[0].offset = data.offset as u32;
planes[0].stride = data.stride;
let ctx = self.context.borrow_mut();
let wayvr = ctx.wayvr.borrow_mut();
let Some(disp) = wayvr.data.state.displays.get(&ctx.display) else {
anyhow::bail!("Failed to fetch WayVR display")
};
let frame = DmabufFrame {
format: FrameFormat {
width: u32::from(disp.width),
height: u32::from(disp.height),
fourcc: FourCC {
value: data.mod_info.fourcc,
},
modifier: data.mod_info.modifiers[0], /* possibly not proper? */
..Default::default()
},
num_planes: 1,
planes,
..Default::default()
};
drop(wayvr);
let layouts: Vec<SubresourceLayout> = vec![SubresourceLayout {
offset: data.offset as _,
size: 0,
row_pitch: data.stride as _,
array_pitch: None,
depth_pitch: None,
}];
let tex = self.graphics.dmabuf_texture_ex(
frame,
vulkano::image::ImageTiling::DrmFormatModifier,
layouts,
&data.mod_info.modifiers,
)?;
self.vk_image = Some(tex.clone());
self.vk_image_view = Some(vulkano::image::view::ImageView::new_default(tex).unwrap());
Ok(())
}
}
@@ -605,41 +665,92 @@ impl OverlayRenderer for WayVRRenderer {
Ok(())
}
fn render(&mut self, _app: &mut state::AppState) -> anyhow::Result<()> {
fn should_render(&mut self, _app: &mut AppState) -> anyhow::Result<ShouldRender> {
let ctx = self.context.borrow();
let mut wayvr = ctx.wayvr.borrow_mut();
match wayvr.data.tick_display(ctx.display) {
Ok(_) => {}
let redrawn = match wayvr.data.tick_display(ctx.display) {
Ok(r) => r,
Err(e) => {
log::error!("tick_display failed: {}", e);
return Ok(()); // do not proceed further
log::error!("tick_display failed: {e}");
return Ok(ShouldRender::Unable);
}
}
};
let dmabuf_data = wayvr
if redrawn {
Ok(ShouldRender::Should)
} else {
Ok(ShouldRender::Can)
}
}
fn render(
&mut self,
app: &mut state::AppState,
tgt: Arc<ImageView>,
buf: &mut CommandBuffers,
alpha: f32,
) -> anyhow::Result<bool> {
let ctx = self.context.borrow();
let wayvr = ctx.wayvr.borrow_mut();
let data = wayvr
.data
.state
.get_dmabuf_data(ctx.display)
.ok_or(anyhow::anyhow!("Failed to fetch dmabuf data"))?
.get_render_data(ctx.display)
.ok_or_else(|| anyhow::anyhow!("Failed to fetch render data"))?
.clone();
drop(wayvr);
drop(ctx);
self.ensure_dmabuf(dmabuf_data.clone())?;
Ok(())
match data {
wayvr::egl_data::RenderData::Dmabuf(data) => {
self.ensure_dmabuf_data(&data)?;
}
wayvr::egl_data::RenderData::Software(data) => {
if let Some(new_frame) = &data {
self.ensure_software_data(new_frame)?;
}
}
}
let Some(view) = self.vk_image_view.as_ref() else {
return Ok(false);
};
let set0 =
self.pipeline
.uniform_sampler(0, view.clone(), app.graphics.texture_filtering)?;
let set1 = self.pipeline.uniform_buffer(1, vec![alpha])?;
let pass = self
.pipeline
.create_pass_for_target(tgt.clone(), vec![set0, set1])?;
let mut cmd_buffer = app
.graphics
.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
cmd_buffer.begin_rendering(tgt)?;
cmd_buffer.run_ref(&pass)?;
cmd_buffer.end_rendering()?;
buf.push(cmd_buffer.build()?);
Ok(true)
}
fn view(&mut self) -> Option<Arc<vulkano::image::view::ImageView>> {
self.view.clone()
}
fn frame_transform(&mut self) -> Option<FrameTransform> {
self.view.as_ref().map(|view| FrameTransform {
extent: view.image().extent(),
..Default::default()
})
fn frame_meta(&mut self) -> Option<FrameMeta> {
let ctx = self.context.borrow();
let wayvr = ctx.wayvr.borrow_mut();
wayvr
.data
.state
.displays
.get(&ctx.display)
.map(|disp| FrameMeta {
extent: [disp.width as _, disp.height as _, 1],
..Default::default()
})
}
}
@@ -655,10 +766,10 @@ pub fn create_wayvr_display_overlay<O>(
where
O: Default,
{
let transform = ui_transform(&[display_width as u32, display_height as u32]);
let transform = ui_transform([u32::from(display_width), u32::from(display_height)]);
let state = OverlayState {
name: format!("WayVR - {}", name).into(),
name: format!("WayVR - {name}").into(),
keyboard_focus: Some(KeyboardFocus::WayVR),
want_visible: true,
interactable: true,
@@ -710,40 +821,40 @@ fn action_app_click<O>(
where
O: Default,
{
let wayvr = app.get_wayvr()?.clone();
let wayvr = app.get_wayvr()?;
let catalog = app
.session
.wayvr_config
.get_catalog(catalog_name)
.ok_or(anyhow::anyhow!(
"Failed to get catalog \"{}\"",
catalog_name
))?
.ok_or_else(|| anyhow::anyhow!("Failed to get catalog \"{}\"", catalog_name))?
.clone();
if let Some(app_entry) = catalog.get_app(app_name) {
let mut wayvr = wayvr.borrow_mut();
let disp_handle =
get_or_create_display_by_name(app, &mut wayvr, &app_entry.target_display)?;
let disp_handle = get_or_create_display_by_name(
app,
&mut wayvr,
&app_entry.target_display.to_lowercase(),
)?;
let args_vec = match &app_entry.args {
Some(args) => gen_args_vec(args),
None => vec![],
};
let args_vec = &app_entry
.args
.as_ref()
.map_or_else(Vec::new, |args| gen_args_vec(args.as_str()));
let env_vec = match &app_entry.env {
Some(env) => gen_env_vec(env),
None => vec![],
};
let env_vec = &app_entry
.env
.as_ref()
.map_or_else(Vec::new, |env| gen_env_vec(env));
// Terminate existing process if required
if let Some(process_handle) =
wayvr
.data
.state
.process_query(disp_handle, &app_entry.exec, &args_vec, &env_vec)
.process_query(disp_handle, &app_entry.exec, args_vec, env_vec)
{
// Terminate process
wayvr.data.terminate_process(process_handle);
@@ -752,9 +863,9 @@ where
wayvr.data.state.spawn_process(
disp_handle,
&app_entry.exec,
&args_vec,
&env_vec,
Default::default(),
args_vec,
env_vec,
HashMap::default(),
)?;
show_display::<O>(&mut wayvr, overlays, app_entry.target_display.as_str());
+20 -9
View File
@@ -5,6 +5,13 @@
version: 1
# If your gpu has some issues with zero-copy textures, you can set this option to "software".
#
# Possible options:
# "dmabuf": Use zero-copy texture access (from EGL to Vulkan) - no performance impact
# "software": Read pixel data to memory via glReadPixels() every time a content has been updated. Minor performance impact on large resolutions
blit_method: "dmabuf"
# Set to true if you want to make Wyland server instantly available.
# By default, WayVR starts only when it's needed.
# (this option is primarily used for remote starting external processes and development purposes)
@@ -25,27 +32,31 @@ keyboard_repeat_rate: 50
# WayVR-compatible dashboard.
# For now, there is only one kind of dashboard with WayVR IPC support (WayVR Dashboard).
#
# Build instructions: https://github.com/olekolek1000/wayvr-dashboard
# exec: Executable path, for example /home/USER/wayvr_dashboard/src-tauri/target/release/wayvr_dashboard
# GDK_BACKEND=wayland: Force-use Wayland for GTK apps
#
# exec: Executable path, for example "/home/USER/wayvr-dashboard/src-tauri/target/release/wayvr-dashboard"
# or just "wayvr-dashboard" if you have it installed from your package manager.
#
# GDK_BACKEND=wayland: Force-use Wayland for GTK apps.
dashboard:
exec: "wayvr_dashboard"
exec: "wayvr-dashboard"
args: ""
env: ["GDK_BACKEND=wayland"]
displays:
Watch:
watch:
width: 400
height: 600
scale: 0.4
attach_to: "HandRight" # HandLeft, HandRight
pos: [0.0, 0.0, 0.125]
rotation: {axis: [1.0, 0.0, 0.0], angle: -45.0}
Disp1:
disp1:
width: 640
height: 480
primary: true # Required if you want to attach external processes (not spawned by WayVR itself) without WAYVR_DISPLAY_NAME set
Disp2:
disp2:
width: 1280
height: 720
scale: 2.0
@@ -54,18 +65,18 @@ catalogs:
default_catalog:
apps:
- name: "Calc"
target_display: "Disp1"
target_display: "disp1"
exec: "kcalc"
env: ["FOO=bar"]
shown_at_start: false
- name: "htop"
target_display: "Watch"
target_display: "watch"
exec: "konsole"
args: "-e htop"
- name: "Browser"
target_display: "Disp2"
target_display: "disp2"
exec: "cage"
args: "chromium -- --incognito"
+33 -13
View File
@@ -46,11 +46,12 @@ pub mod frag_color {
",
}
}
//layout (location = 1) in float corner_radius;
//out_color = in_color;
// Some equation that determines whether to keep the pixel
// Use Lerp not if
//out_color.a = 0;
//layout (location = 1) in float corner_radius;
//out_color = in_color;
// Some equation that determines whether to keep the pixel
// Use Lerp not if
//out_color.a = 0;
pub mod frag_glyph {
vulkano_shaders::shader! {
@@ -176,17 +177,14 @@ pub mod frag_screen {
layout (location = 0) out vec4 out_color;
layout (set = 0, binding = 0) uniform sampler2D in_texture;
layout (set = 1, binding = 0) uniform AlphaBlock {
uniform float alpha;
};
void main()
{
out_color = texture(in_texture, in_uv);
// linear to srgb
bvec4 cutoff = lessThan(out_color, vec4(0.0031308));
vec4 higher = (pow(out_color, vec4(0.4166667)) * vec4(1.055)) - vec4(0.055);
vec4 lower = out_color*vec4(12.92);
out_color = mix(higher, lower, cutoff);
out_color.a = 1.0;
out_color.a = alpha;
}
",
}
@@ -216,7 +214,7 @@ pub mod frag_srgb {
vec4 lower = out_color/vec4(12.92);
out_color = mix(higher, lower, cutoff);
out_color.a = alpha;
out_color.a *= alpha;
}
",
}
@@ -244,3 +242,25 @@ pub mod frag_swapchain {
",
}
}
pub mod frag_line {
vulkano_shaders::shader! {
ty: "fragment",
src: r"#version 310 es
precision highp float;
layout (location = 0) in vec2 in_uv;
layout (location = 0) out vec4 out_color;
layout (set = 0, binding = 0) uniform ColorBlock {
uniform vec4 in_color;
uniform vec2 unused;
};
void main()
{
out_color = in_color;
}
",
}
}
+46 -10
View File
@@ -26,8 +26,8 @@ use crate::{
hid::HidProvider,
overlays::toast::{DisplayMethod, ToastTopic},
shaders::{
frag_color, frag_glyph, frag_grid, frag_screen, frag_sprite, frag_sprite2, frag_sprite2_hl,
frag_swapchain, vert_common,
frag_color, frag_glyph, frag_grid, frag_line, frag_screen, frag_sprite, frag_sprite2,
frag_sprite2_hl, frag_srgb, frag_swapchain, vert_common,
},
};
@@ -51,6 +51,7 @@ pub struct AppState {
pub anchor: Affine3A,
pub sprites: AStrMap<Arc<ImageView>>,
pub keyboard_focus: KeyboardFocus,
pub toast_sound: &'static [u8],
#[cfg(feature = "osc")]
pub osc_sender: Option<OscSender>,
@@ -73,6 +74,12 @@ impl AppState {
let shader = frag_color::load(graphics.device.clone())?;
shaders.insert("frag_color", shader);
let shader = frag_line::load(graphics.device.clone())?;
shaders.insert("frag_line", shader);
let shader = frag_srgb::load(graphics.device.clone())?;
shaders.insert("frag_srgb", shader);
let shader = frag_glyph::load(graphics.device.clone())?;
shaders.insert("frag_glyph", shader);
@@ -111,7 +118,12 @@ impl AppState {
#[cfg(feature = "osc")]
let osc_sender = crate::backend::osc::OscSender::new(session.config.osc_out_port).ok();
Ok(AppState {
let toast_sound_wav = Self::try_load_bytes(
&session.config.notification_sound,
include_bytes!("res/557297.wav"),
);
Ok(Self {
fc: FontCache::new(session.config.primary_font.clone())?,
session,
tasks,
@@ -123,6 +135,7 @@ impl AppState {
anchor: Affine3A::IDENTITY,
sprites: AStrMap::new(),
keyboard_focus: KeyboardFocus::PhysicalScreen,
toast_sound: toast_sound_wav,
#[cfg(feature = "osc")]
osc_sender,
@@ -139,12 +152,35 @@ impl AppState {
Ok(wvr.clone())
} else {
let wayvr = Rc::new(RefCell::new(WayVRData::new(
WayVRConfig::get_wayvr_config(&self.session.config, &self.session.wayvr_config),
WayVRConfig::get_wayvr_config(&self.session.config, &self.session.wayvr_config)?,
)?));
self.wayvr = Some(wayvr.clone());
Ok(wayvr)
}
}
pub fn try_load_bytes(path: &str, fallback_data: &'static [u8]) -> &'static [u8] {
if path.is_empty() {
return fallback_data;
}
let real_path = config_io::get_config_root().join(path);
if std::fs::File::open(real_path.clone()).is_err() {
log::warn!("Could not open file at: {path}");
return fallback_data;
}
match std::fs::read(real_path) {
// Box is used here to work around `f`'s limited lifetime
Ok(f) => Box::leak(Box::new(f)).as_slice(),
Err(e) => {
log::warn!("Failed to read file at: {path}");
log::warn!("{e:?}");
fallback_data
}
}
}
}
pub struct AppSession {
@@ -159,7 +195,7 @@ pub struct AppSession {
impl AppSession {
pub fn load() -> Self {
let config_root_path = config_io::ConfigRoot::Generic.ensure_dir();
log::info!("Config root path: {:?}", config_root_path);
log::info!("Config root path: {config_root_path:?}");
let config = GeneralConfig::load_from_disk();
let mut toast_topics = IdMap::new();
@@ -174,11 +210,11 @@ impl AppSession {
#[cfg(feature = "wayvr")]
let wayvr_config = config_wayvr::load_wayvr();
AppSession {
Self {
config,
toast_topics,
#[cfg(feature = "wayvr")]
wayvr_config,
toast_topics,
}
}
}
@@ -189,8 +225,8 @@ pub struct AudioOutput {
}
impl AudioOutput {
pub fn new() -> Self {
AudioOutput {
pub const fn new() -> Self {
Self {
audio_stream: None,
first_try: true,
}
@@ -217,7 +253,7 @@ impl AudioOutput {
let source = match Decoder::new_wav(cursor) {
Ok(source) => source,
Err(e) => {
log::error!("Failed to play sound: {:?}", e);
log::error!("Failed to play sound: {e:?}");
return;
}
};