Compare commits

..
22 Commits
Author SHA1 Message Date
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
37 changed files with 948 additions and 387 deletions

No files matched your search

+17
View File
@@ -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
@@ -64,3 +71,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
+96 -32
View File
@@ -494,7 +494,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d123397e75f904758fef490775a00b0ada545ab409cb0163d919799e5a30119b"
dependencies = [
"autocxx-engine",
"env_logger 0.9.3",
"env_logger",
"indexmap 1.9.3",
"syn 2.0.98",
]
@@ -1509,16 +1509,6 @@ dependencies = [
"syn 2.0.98",
]
[[package]]
name = "env_filter"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
dependencies = [
"log",
"regex",
]
[[package]]
name = "env_logger"
version = "0.9.3"
@@ -1532,19 +1522,6 @@ dependencies = [
"termcolor",
]
[[package]]
name = "env_logger"
version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"humantime",
"log",
]
[[package]]
name = "equivalent"
version = "1.0.2"
@@ -2514,6 +2491,15 @@ dependencies = [
"libc",
]
[[package]]
name = "matchers"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
dependencies = [
"regex-automata 0.1.10",
]
[[package]]
name = "memchr"
version = "2.7.4"
@@ -2705,6 +2691,16 @@ dependencies = [
"winapi",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
dependencies = [
"overload",
"winapi",
]
[[package]]
name = "num-derive"
version = "0.4.2"
@@ -3045,6 +3041,12 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "ovr_overlay"
version = "0.0.0"
@@ -3539,8 +3541,17 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
"regex-automata 0.4.9",
"regex-syntax 0.8.5",
]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
dependencies = [
"regex-syntax 0.6.29",
]
[[package]]
@@ -3551,9 +3562,15 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
"regex-syntax 0.8.5",
]
[[package]]
name = "regex-syntax"
version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "regex-syntax"
version = "0.8.5"
@@ -3820,6 +3837,15 @@ dependencies = [
"roxmltree",
]
[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
"lazy_static",
]
[[package]]
name = "shlex"
version = "1.3.0"
@@ -3864,8 +3890,9 @@ checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
[[package]]
name = "smithay"
version = "0.4.0"
source = "git+https://github.com/Smithay/smithay.git#0cd3345c59f7cb139521f267956a1a4e33248393"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497aa82b881bf392470c286b213e01f7aa30b2fb08857831f81c87b251ba4d89"
dependencies = [
"appendlist",
"bitflags 2.8.0",
@@ -4239,6 +4266,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
dependencies = [
"once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
dependencies = [
"matchers",
"nu-ansi-term",
"once_cell",
"regex",
"sharded-slab",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
]
[[package]]
@@ -4334,6 +4391,12 @@ dependencies = [
"rand 0.9.0",
]
[[package]]
name = "valuable"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "version-compare"
version = "0.2.0"
@@ -4655,7 +4718,7 @@ dependencies = [
[[package]]
name = "wayvr_ipc"
version = "0.1.0"
source = "git+https://github.com/olekolek1000/wayvr-ipc.git?rev=3c411d09ba1bba2609288e29739c0f1ec736b012#3c411d09ba1bba2609288e29739c0f1ec736b012"
source = "git+https://github.com/olekolek1000/wayvr-ipc.git?rev=a72587d23f3bb8624d9aeb1f13c0a21e65350f51#a72587d23f3bb8624d9aeb1f13c0a21e65350f51"
dependencies = [
"anyhow",
"bytes",
@@ -5121,7 +5184,7 @@ dependencies = [
[[package]]
name = "wlx-overlay-s"
version = "25.2.0"
version = "25.4.0"
dependencies = [
"anyhow",
"ash",
@@ -5133,7 +5196,6 @@ dependencies = [
"cstr",
"ctrlc",
"dbus",
"env_logger 0.11.6",
"fontconfig-rs",
"freetype-rs",
"futures",
@@ -5166,6 +5228,8 @@ dependencies = [
"strum",
"sysinfo",
"thiserror 2.0.11",
"tracing",
"tracing-subscriber",
"uuid",
"vulkano",
"vulkano-shaders",
+5 -4
View File
@@ -4,7 +4,7 @@ debug = true
[package]
name = "wlx-overlay-s"
version = "25.2.0"
version = "25.4.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -19,7 +19,6 @@ config = "0.14.0"
cstr = "0.2.12"
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"
@@ -69,12 +68,14 @@ image_dds = { version = "0.6.0", 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,7 +87,7 @@ 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]
+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"
+14 -12
View File
@@ -217,7 +217,8 @@ impl Pointer {
#[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 +253,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 +264,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)]
@@ -419,8 +420,9 @@ 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
@@ -432,7 +434,7 @@ where
if can_curve {
let cur = hovered.state.curvature.unwrap_or(0.0);
let new = (cur - scroll * 0.01).min(0.5);
let new = (cur - scroll_y * 0.01).min(0.5);
if new <= f32::EPSILON {
hovered.state.curvature = None;
} else {
@@ -442,7 +444,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];
}
@@ -560,10 +562,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 +573,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(1.0 - 0.025 * self.now.scroll_y);
} 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);
+123 -37
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)]
@@ -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(OrgFreedesktopNotificationsOnDndToggle { dnd: 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,
)
}
}
+5 -3
View File
@@ -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;
}
}
+1 -1
View File
@@ -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 {
+7 -4
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::{
@@ -161,7 +161,7 @@ pub(super) struct OpenXrHandSource {
action_modifier_right: CustomClickAction,
action_modifier_middle: CustomClickAction,
action_move_mouse: CustomClickAction,
action_scroll: xr::Action<f32>,
action_scroll: xr::Action<Vector2f>,
action_haptics: xr::Action<xr::Haptic>,
}
@@ -350,12 +350,15 @@ impl OpenXrHand {
.action_grab
.state(pointer.before.grab, xr, session)?;
pointer.now.scroll = self
let scroll = self
.source
.action_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
@@ -417,7 +420,7 @@ impl OpenXrHandSource {
&[],
)?;
let action_scroll = action_set.create_action::<f32>(
let action_scroll = action_set.create_action::<Vector2f>(
&format!("{}_scroll", side),
&format!("{} hand scroll", side),
&[],
+20
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",
@@ -129,6 +133,10 @@
scroll: {
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"
},
toggle_dashboard: {
double_click: false,
@@ -180,6 +188,10 @@
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",
},
@@ -215,6 +227,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: {
left: "/user/hand/left/input/menu/click",
},
@@ -246,6 +262,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: {
left: "/user/hand/left/input/menu/click",
},
+4 -4
View File
@@ -146,24 +146,24 @@ 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,
)],
)?;
self.send_message(
format!("/avatar/parameters/averageTrackerBattery").into(),
String::from("/avatar/parameters/averageTrackerBattery"),
vec![OscType::Float(tracker_total_bat / tracker_count as f32)],
)?;
}
+2 -2
View File
@@ -365,8 +365,8 @@ 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);
+7
View File
@@ -157,6 +157,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
}
+57 -18
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,
@@ -128,7 +130,13 @@ impl Display {
})?;
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();
@@ -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,6 +191,11 @@ 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();
@@ -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 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,
self.width as i32,
self.height as i32,
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(())
}
@@ -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(),
);
}
}
@@ -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(&self, 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: (delta_x as f64, -delta_y as f64),
v120: Some((0, (delta_y * -120.0) as i32)),
stop: (false, false),
},
);
+21 -3
View File
@@ -1,3 +1,5 @@
use std::sync::Arc;
use super::egl_ex;
use anyhow::anyhow;
@@ -23,13 +25,26 @@ 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!(
@@ -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],
+105 -35
View File
@@ -40,6 +40,8 @@ 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";
@@ -71,6 +73,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 +90,27 @@ pub enum WayVRSignal {
BroadcastStateChanged(packet_server::WvrStateChanged),
}
pub enum BlitMethod {
Dmabuf,
Software,
}
impl BlitMethod {
pub fn from_string(str: &str) -> Option<BlitMethod> {
match str {
"dmabuf" => Some(BlitMethod::Dmabuf),
"software" => Some(BlitMethod::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 +120,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 {
@@ -246,12 +266,13 @@ 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
@@ -261,12 +282,12 @@ impl WayVR {
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;
@@ -274,15 +295,16 @@ 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>> {
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
@@ -341,36 +363,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) => {
@@ -460,9 +509,14 @@ impl WayVRState {
}
}
pub fn send_mouse_scroll(&mut self, display: display::DisplayHandle, delta: f32) {
pub fn send_mouse_scroll(
&mut self,
display: display::DisplayHandle,
delta_y: f32,
delta_x: f32,
) {
if let Some(display) = self.displays.get(&display) {
display.send_mouse_scroll(&mut self.manager, delta);
display.send_mouse_scroll(&mut self.manager, delta_y, delta_x);
}
}
@@ -470,6 +524,19 @@ impl WayVRState {
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);
@@ -486,10 +553,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(
@@ -504,12 +574,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(
+48 -11
View File
@@ -1,4 +1,4 @@
use std::collections::HashMap;
use std::{collections::HashMap, io::Read};
use wayvr_ipc::packet_server;
@@ -88,6 +88,23 @@ impl Drop for WayVRProcess {
}
}
fn get_process_env_value(pid: i32, key: &str) -> anyhow::Result<Option<String>> {
let path = format!("/proc/{}/environ", pid);
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() {
@@ -153,22 +170,42 @@ 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 {} failed", pid);
None
}
+40 -4
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 {
@@ -70,6 +73,7 @@ fn read_payload(conn: &mut local_socket::Stream, size: u32) -> Option<Payload> {
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> {
@@ -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,
@@ -471,6 +503,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)?;
}
@@ -633,10 +668,11 @@ impl WayVRServer {
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
}
+4
View File
@@ -77,6 +77,10 @@ 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);
+3
View File
@@ -207,6 +207,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,
+15 -15
View File
@@ -112,8 +112,8 @@ 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 {
@@ -173,17 +174,19 @@ impl WayVRConfig {
pub fn get_wayvr_config(
config_general: &crate::config::GeneralConfig,
config_wayvr: &crate::config_wayvr::WayVRConfig,
) -> wayvr::Config {
wayvr::Config {
) -> 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(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
@@ -236,10 +241,5 @@ pub fn load_wayvr() -> WayVRConfig {
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)
}
+5 -4
View File
@@ -802,10 +802,9 @@ impl WlxGraphics {
frame: DmabufFrame,
tiling: ImageTiling,
layouts: Vec<SubresourceLayout>,
modifiers: Vec<u64>,
modifiers: &[u64],
) -> anyhow::Result<Arc<Image>> {
let extent = [frame.format.width, frame.format.height, 1];
let format = fourcc_to_vk(frame.format.fourcc)?;
let image = unsafe {
@@ -817,7 +816,7 @@ impl WlxGraphics {
usage: ImageUsage::SAMPLED,
external_memory_handle_types: ExternalMemoryHandleTypes::DMA_BUF,
tiling,
drm_format_modifiers: modifiers,
drm_format_modifiers: modifiers.to_owned(),
drm_format_modifier_plane_layouts: layouts,
..Default::default()
},
@@ -894,7 +893,7 @@ impl WlxGraphics {
tiling = ImageTiling::DrmFormatModifier;
};
self.dmabuf_texture_ex(frame, tiling, layouts, modifiers)
self.dmabuf_texture_ex(frame, tiling, layouts, &modifiers)
}
pub fn render_texture(
@@ -942,6 +941,7 @@ impl WlxGraphics {
)?))
}
#[allow(clippy::too_many_arguments)]
pub fn create_pipeline_with_layouts(
self: &Arc<Self>,
render_target: Arc<ImageView>,
@@ -1304,6 +1304,7 @@ impl WlxPipeline<WlxPipelineLegacy> {
)
}
#[allow(clippy::too_many_arguments)]
fn new_with_layout(
render_target: Arc<ImageView>,
graphics: Arc<WlxGraphics>,
+1
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,
+2 -1
View File
@@ -19,6 +19,7 @@ pub type ControlRendererHl<D, S> = fn(
Vec4,
) -> anyhow::Result<()>;
#[allow(clippy::type_complexity)]
pub(crate) struct Control<D, S> {
pub state: Option<S>,
pub rect: Rect,
@@ -34,7 +35,7 @@ pub(crate) struct Control<D, S> {
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>>,
+2 -2
View File
@@ -267,13 +267,13 @@ impl<D, S> InteractionHandler for Canvas<D, S> {
}
}
}
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.canvas.data, app, delta_y, delta_x);
}
}
}
+8 -2
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()
+25 -12
View File
@@ -1,4 +1,4 @@
use glam::Vec2;
use glam::{IVec2, Vec2};
use idmap::{idmap, IdMap};
use idmap_derive::IntegerId;
use input_linux::{
@@ -46,7 +46,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,7 +64,7 @@ struct MouseAction {
last_requested_pos: Option<Vec2>,
pos: Option<Vec2>,
button: Option<MouseButtonAction>,
scroll: Option<i32>,
scroll: Option<IVec2>,
}
pub struct UInputProvider {
@@ -149,14 +149,19 @@ impl UInputProvider {
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::Wheel).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()?;
mouse_handle
.create(&mouse_id, mouse_name, 0, &abs_info)
.ok()?;
Some(UInputProvider {
keyboard_handle,
@@ -193,10 +198,16 @@ impl UInputProvider {
log::error!("{}", res.to_string());
}
}
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.mouse_handle.write(&events) {
@@ -219,7 +230,9 @@ 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 _),
@@ -247,9 +260,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;
@@ -263,7 +276,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);
}
}
}
@@ -271,7 +284,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) {}
+45 -33
View File
@@ -22,6 +22,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)]
@@ -171,51 +173,61 @@ fn args_get_openxr(_args: &Args) -> bool {
}
fn logging_init(args: &mut Args) -> anyhow::Result<()> {
let log_file = 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(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)
+12 -5
View File
@@ -51,7 +51,13 @@ 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);
}
}
}
}
@@ -95,7 +101,7 @@ 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(|k| k.has_altgr());
if !LAYOUT.auto_labels.unwrap_or(true) {
keymap = None;
@@ -125,7 +131,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(|f| f.is_alphabetic()) {
label.push(label1);
if has_altgr {
cap_type = KeyCapType::RegularAltGr;
@@ -516,9 +522,10 @@ impl InteractionHandler for KeyboardBackend {
&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)
+3 -16
View File
@@ -3,7 +3,6 @@ use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use std::{
f32::consts::PI,
ops::Add,
ptr,
sync::{atomic::AtomicU64, Arc},
time::{Duration, Instant},
@@ -88,7 +87,6 @@ fn set_next_move(millis_from_now: u64) {
}
pub struct ScreenInteractionHandler {
next_scroll: Instant,
mouse_transform: Affine2,
}
impl ScreenInteractionHandler {
@@ -113,7 +111,6 @@ impl ScreenInteractionHandler {
};
ScreenInteractionHandler {
next_scroll: Instant::now(),
mouse_transform: transform,
}
}
@@ -152,19 +149,9 @@ impl InteractionHandler for ScreenInteractionHandler {
let pos = self.mouse_transform.transform_point2(hit.uv);
app.hid_provider.mouse_move(pos);
}
fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta: f32) {
if self.next_scroll > Instant::now() {
return;
}
let max_millis = if matches!(hit.mode, PointerMode::Left) {
200.0
} else {
100.0
};
let millis = (1. - delta.abs()) * max_millis;
self.next_scroll = Instant::now().add(Duration::from_millis(millis as _));
app.hid_provider.wheel(if delta < 0. { -1 } else { 1 })
fn on_scroll(&mut self, app: &mut AppState, _hit: &PointerHit, delta_y: f32, delta_x: f32) {
app.hid_provider
.wheel((delta_y * 64.) as i32, (delta_x * 64.) as i32)
}
fn on_left(&mut self, _app: &mut AppState, _hand: usize) {}
}
+3 -4
View File
@@ -18,7 +18,6 @@ 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());
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
@@ -78,6 +77,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
@@ -109,9 +111,6 @@ impl Toast {
instant,
);
if has_sound {
app.audio.play(TOAST_AUDIO_WAV);
}
}
}
+129 -64
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::{
@@ -153,19 +156,25 @@ impl InteractionHandler for WayVRInteractionHandler {
}
}
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(ctx.display, delta_y, delta_x);
}
}
pub struct WayVRRenderer {
dmabuf_image: Option<Arc<vulkano::image::Image>>,
view: Option<Arc<vulkano::image::view::ImageView>>,
vk_image: Option<Arc<vulkano::image::Image>>,
vk_image_view: Option<Arc<vulkano::image::view::ImageView>>,
context: Rc<RefCell<WayVRContext>>,
graphics: Arc<WlxGraphics>,
}
@@ -178,8 +187,8 @@ impl WayVRRenderer {
) -> anyhow::Result<Self> {
Ok(Self {
context: Rc::new(RefCell::new(WayVRContext::new(wvr, display)?)),
dmabuf_image: None,
view: None,
vk_image: None,
vk_image_view: None,
graphics: app.graphics.clone(),
})
}
@@ -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);
}
}),
@@ -447,12 +467,12 @@ 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 {
@@ -534,54 +554,82 @@ 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_command_buffer(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(
data.width as u32,
data.height as u32,
vulkano::format::Format::R8G8B8A8_UNORM,
&data.data,
)?;
drop(wayvr);
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,
}];
self.vk_image = Some(tex.clone());
self.vk_image_view = Some(ImageView::new_default(tex).unwrap());
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")
}
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: 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,
};
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(())
}
}
@@ -609,34 +657,48 @@ impl OverlayRenderer for WayVRRenderer {
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
}
};
if !redrawn {
return Ok(());
}
let dmabuf_data = wayvr
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(anyhow::anyhow!("Failed to fetch render data"))?
.clone();
drop(wayvr);
drop(ctx);
self.ensure_dmabuf(dmabuf_data.clone())?;
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)?;
}
}
}
Ok(())
}
fn view(&mut self) -> Option<Arc<vulkano::image::view::ImageView>> {
self.view.clone()
self.vk_image_view.clone()
}
fn frame_transform(&mut self) -> Option<FrameTransform> {
self.view.as_ref().map(|view| FrameTransform {
self.vk_image_view.as_ref().map(|view| FrameTransform {
extent: view.image().extent(),
..Default::default()
})
@@ -725,8 +787,11 @@ where
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),
+13 -6
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)
@@ -34,18 +41,18 @@ dashboard:
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 +61,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"
+5 -5
View File
@@ -46,11 +46,11 @@ 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! {
+42 -1
View File
@@ -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>,
@@ -111,6 +112,11 @@ impl AppState {
#[cfg(feature = "osc")]
let osc_sender = crate::backend::osc::OscSender::new(session.config.osc_out_port).ok();
let toast_sound_wav = AppState::try_load_bytes(
&session.config.notification_sound,
include_bytes!("res/557297.wav")
);
Ok(AppState {
fc: FontCache::new(session.config.primary_font.clone())?,
session,
@@ -123,6 +129,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 +146,46 @@ 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;
};
return match std::fs::read(real_path.clone()){
// 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 {