Compare commits

..
27 Commits
Author SHA1 Message Date
galister 85aa0fb1bc release 0.6 2024-09-28 01:54:53 +09:00
galister 7f72abc62a release modifiers on keyboard hide 2024-09-28 01:53:20 +09:00
galister 83ae798215 add virtualkey action for modular button 2024-09-27 02:23:13 +09:00
galister bdbdbea20b screen notification popups 2024-09-26 23:51:46 +09:00
galister a077f2dda4 fix dragging cursor off-screen 2024-09-26 23:51:44 +09:00
galister 8d35b45c2b rollback vulkano 2024-09-24 11:45:29 +02:00
galister 6af5623fa7 update readme 2024-09-19 06:18:06 +00:00
galister 62ef7a1210 update readme 2024-09-19 06:47:01 +09:00
galister 3f425820d7 release 0.5.0 2024-09-19 05:29:27 +09:00
galister 6a2d8b9f90 fresh watch style 2024-09-19 03:09:09 +09:00
galister 583a5f7fe0 openxr: toggle for space rotate axis lock 2024-09-18 04:06:37 +09:00
galister 03f2eaf97f fps-stabilize openxr smoothing 2024-09-16 07:05:26 +09:00
galister 21331b5c42 fps-aware openxr pointer smoothing 2024-09-16 05:09:10 +09:00
galister 72a82530ae fix openxr space rotate 2024-09-16 05:01:15 +09:00
galister 13d9284bb8 move mouse move-scroll exclusivity to hid module 2024-09-11 06:03:22 +09:00
galister b0a33a1181 openxr: laser pointer smoothing 2024-09-09 07:54:19 +09:00
galister 4f38d1ad44 make pipewire default on wlroots 2024-08-24 02:21:43 +09:00
galister c6bc880bc3 add logging for screen pipeline 2024-08-20 16:49:35 +09:00
galister 0a2be76150 change some version stuff 2024-08-20 16:40:08 +09:00
galister 1a87823847 rename github action 2024-08-20 14:04:44 +09:00
galister e763e15bdc build dev appimages 2024-08-20 14:02:12 +09:00
galister 6a3b60f298 feat: screen_render_down 2024-08-20 13:54:18 +09:00
Adalyn Black 397b3eedd1 Use git hash if no tags can be found 2024-08-19 17:35:58 +02:00
hypevhs 9d18628354 feat: XR binding for playspace reset 2024-08-15 21:30:39 +02:00
galister dc985f6da6 docs: update known issues 2024-08-13 07:43:37 +00:00
raku-cat 850601b3b9 Update wlx-capture 2024-08-12 12:16:10 +00:00
galister 8a4c7a400f dirty versions 2024-08-11 06:41:17 +09:00
23 changed files with 824 additions and 151 deletions

No files matched your search

+51
View File
@@ -0,0 +1,51 @@
name: Build AppImage
on:
push:
branches:
- 'main'
env:
APPDIR: WlxOverlay-S.AppDir
CARGO_TERM_COLOR: always
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache"
jobs:
build_appimage:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- 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
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
rustup update
test -f linuxdeploy-x86_64.AppImage || wget -q "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage"
chmod +x linuxdeploy-x86_64.AppImage
test -d ${APPDIR} && rm -rf ${APPDIR}
mkdir -p ${APPDIR}/usr/bin
- name: Cargo Build
run: |
cargo build --release
mv target/release/wlx-overlay-s ${APPDIR}/usr/bin
chmod +x ${APPDIR}/usr/bin/wlx-overlay-s
- name: Package AppImage
run: |
export VERSION=$GITHUB_REF_NAME
./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: Upload AppImage
uses: actions/upload-artifact@v4
with:
name: WlxOverlay-S-${{ github.ref_name }}-x86_64.AppImage
path: ./WlxOverlay-S-x86_64.AppImage
+1 -1
View File
@@ -2,7 +2,7 @@ name: Check Full
on:
pull_request:
#branches: [ "main" ]
branches: [ "!main" ]
env:
CARGO_TERM_COLOR: always
Generated
+24 -21
View File
@@ -225,11 +225,11 @@ checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b"
[[package]]
name = "ash"
version = "0.38.0+1.3.281"
version = "0.37.3+1.3.251"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f"
checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a"
dependencies = [
"libloading",
"libloading 0.7.4",
]
[[package]]
@@ -789,7 +789,7 @@ checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
"glob",
"libc",
"libloading",
"libloading 0.8.5",
]
[[package]]
@@ -1264,7 +1264,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
dependencies = [
"libloading",
"libloading 0.8.5",
]
[[package]]
@@ -2031,6 +2031,16 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "libloading"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
dependencies = [
"cfg-if",
"winapi",
]
[[package]]
name = "libloading"
version = "0.8.5"
@@ -2671,7 +2681,7 @@ version = "0.18.0"
source = "git+https://github.com/galister/openxrs?rev=af4a55d#af4a55df60125491c80c61464c824219c6019b76"
dependencies = [
"libc",
"libloading",
"libloading 0.8.5",
"ndk-context",
"openxr-sys",
]
@@ -3472,12 +3482,6 @@ dependencies = [
"autocfg",
]
[[package]]
name = "slabbin"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8305086044614627ed85432d27b87cf9fc047204eaa036a11de6cf0120f273"
[[package]]
name = "slice-of-array"
version = "0.3.2"
@@ -3908,7 +3912,7 @@ dependencies = [
[[package]]
name = "vulkano"
version = "0.34.0"
source = "git+https://github.com/vulkano-rs/vulkano?rev=b9f3e89#b9f3e893005078b5680cd1672037465a2b5f3133"
source = "git+https://github.com/vulkano-rs/vulkano?rev=94f50f1#94f50f18bd25971ea123adb8b5782ad65a8f085c"
dependencies = [
"ahash",
"ash",
@@ -3918,17 +3922,16 @@ dependencies = [
"half",
"heck 0.4.1",
"indexmap 2.3.0",
"libloading",
"nom",
"libloading 0.8.5",
"objc",
"once_cell",
"parking_lot",
"proc-macro2",
"quote",
"raw-window-handle",
"regex",
"serde",
"serde_json",
"slabbin",
"smallvec",
"thread_local",
"vk-parse",
@@ -3938,7 +3941,7 @@ dependencies = [
[[package]]
name = "vulkano-macros"
version = "0.34.0"
source = "git+https://github.com/vulkano-rs/vulkano?rev=b9f3e89#b9f3e893005078b5680cd1672037465a2b5f3133"
source = "git+https://github.com/vulkano-rs/vulkano?rev=94f50f1#94f50f18bd25971ea123adb8b5782ad65a8f085c"
dependencies = [
"proc-macro-crate 2.0.2",
"proc-macro2",
@@ -3949,7 +3952,7 @@ dependencies = [
[[package]]
name = "vulkano-shaders"
version = "0.34.0"
source = "git+https://github.com/vulkano-rs/vulkano?rev=b9f3e89#b9f3e893005078b5680cd1672037465a2b5f3133"
source = "git+https://github.com/vulkano-rs/vulkano?rev=94f50f1#94f50f18bd25971ea123adb8b5782ad65a8f085c"
dependencies = [
"ahash",
"heck 0.4.1",
@@ -4556,7 +4559,7 @@ dependencies = [
[[package]]
name = "wlx-capture"
version = "0.3.12"
source = "git+https://github.com/galister/wlx-capture?tag=v0.3.12#14d2b8a13dad62849973fe90bed7be262aa07143"
source = "git+https://github.com/galister/wlx-capture?tag=v0.3.13#c375a7c1d4b57741655a052ebe8af9fb4774f92d"
dependencies = [
"ashpd",
"drm-fourcc",
@@ -4574,7 +4577,7 @@ dependencies = [
[[package]]
name = "wlx-overlay-s"
version = "0.4.4"
version = "0.6.0"
dependencies = [
"anyhow",
"ash",
@@ -4644,7 +4647,7 @@ dependencies = [
"as-raw-xcb-connection",
"gethostname",
"libc",
"libloading",
"libloading 0.8.5",
"once_cell",
"rustix",
"x11rb-protocol",
+5 -5
View File
@@ -4,14 +4,14 @@ debug = true
[package]
name = "wlx-overlay-s"
version = "0.4.4"
version = "0.6.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.86"
ash = "^0.38.0"
ash = "^0.37.2"
chrono = "0.4.38"
chrono-tz = "0.9.0"
clap = { version = "4.5.6", features = ["derive"] }
@@ -53,9 +53,9 @@ smallvec = "1.13.2"
strum = { version = "0.26.2", features = ["derive"] }
sysinfo = { version = "0.31.2" }
thiserror = "1.0.61"
vulkano = { git = "https://github.com/vulkano-rs/vulkano", rev = "b9f3e89" }
vulkano-shaders = { git = "https://github.com/vulkano-rs/vulkano", rev = "b9f3e89" }
wlx-capture = { git = "https://github.com/galister/wlx-capture", tag = "v0.3.12", default-features = false }
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.3.13", default-features = false }
libmonado-rs = { git = "https://github.com/technobaboo/libmonado-rs", rev = "a495f6d", optional = true }
winit = { version = "0.30.0", optional = true }
xdg = "2.5.2"
+27 -23
View File
@@ -6,21 +6,18 @@ WlxOverlay-S allows you to access your desktop screens while in VR.
In comparison to similar overlays, WlxOverlay-S aims to run alongside VR games and experiences while having as little performance impact as possible. The UI appearance and rendering techniques are kept as simple and efficient as possible, while still allowing a high degree of customizability.
This is the coming-together of two of my previous projects:
- [WlxOverlay](https://github.com/galister/WlxOverlay) (SteamVR overlay written in C#)
- [WlxOverlay-X](https://github.com/galister/wlx-overlay-x) (OpenXR overlay using StereoKit, written in Rust)
![Screenshot of WlxOverlay-S being used as an OpenXR home environment](https://github.com/galister/wlx-overlay-s/blob/guide/wlx-s.png?raw=true)
![Screenshot of WlxOverlay-S in Action](https://github.com/galister/wlx-overlay-s/blob/guide/wlx-s.png?raw=true)
# Join the Linux VR Community
## Join the Linux VR Community
We are available on either:
- Discord: https://discord.gg/gHwJ2vwSWV
- Discord: <https://discord.gg/gHwJ2vwSWV>
- Matrix Space: `#linux-vr-adventures:matrix.org`
Questions/issues specific to WlxOverlay-S will be handled in the `wlxoverlay` chat room.
# Setup
## Setup
1. Grab the latest AppImage from [Releases](https://github.com/galister/wlx-overlay-s/releases).
1. `chmod +x WlxOverlay-S-*.AppImage`
@@ -31,18 +28,21 @@ AUR package is [wlx-overlay-s-git](https://aur.archlinux.org/packages/wlx-overla
You may also want to [build from source](https://github.com/galister/wlx-overlay-s/wiki/Building-from-Source).
# First Start
## First Start
**If you get a screen share pop-up, check the terminal and select the screens in the order it tells you to.**
**When the screen share pop-up appears, check the terminal and select the screens in the order it requests.**
If you selected the screens in the wrong order:
- `rm ~/.config/wlxoverlay/conf.d/pw_tokens.yaml` then restart
SteamVR users: WlxOverlay-S will register itself for auto-start, so you will not need to start it every time.
- `rm ~/.config/wlxoverlay/conf.d/pw_tokens.yaml` then restart
**SteamVR users**: WlxOverlay-S will register itself for auto-start, so you will not need to start it every time.
**Envision users**: Set `wlx-overlay-s --openxr --show` as the _Autostart Command_ on your Envision profile! This will give you a home environment with a [customizable background](https://github.com/galister/wlx-overlay-s/wiki/OpenXR-Skybox)!
**Please continue reading the guide below.**
# Getting Started
## Getting Started
### Working Set
@@ -51,6 +51,7 @@ Your working set consists of your currently selected overlays; screens, mirrors,
The working set appears in front of you when shown, and can be re-centered by hiding and showing again.
Show and hide your working set using:
- Non-vive controller: double-tap B or Y on your left controller.
- Vive controller: double-tap the menu button on your left controller (OpenXR, for SteamVR, you might need to bind `showhide` yourself.)
@@ -58,6 +59,7 @@ Show and hide your working set using:
Much of the functionality in WlxOverlay-S depends on what color of laser you are using to interact with a UI element. \
Using the default settings, there are 3 modes:
- Regular Mode: Blue laser
- Right-click Mode: Orange laser
- Middle-click Mode: Purple laser
@@ -77,6 +79,7 @@ Check your left wrist for the watch. The watch is your primary tool for controll
Hovering a pointer over a screen will move the mouse. If there are more than one pointers hovering a screen, the pointer that was last used to click will take precedence.
The click depends on the laser color:
- Blue laser: Left click
- Orange laser: Right click
- Purple laser: Middle click
@@ -92,6 +95,7 @@ The keyboard is fully customizable via the [keyboard.yaml](https://raw.githubuse
Download it into your `~/.config/wlxoverlay/` folder and edit it to your liking.
Typing
- Use the BLUE laser when typing regularly.
- While using ORANGE laser, all keystrokes will have SHIFT applied.
- Purple laser has no effect as of now.
@@ -104,36 +108,36 @@ Typing
![Touch Controller Bindings](https://github.com/galister/wlx-overlay-s/blob/guide/wlx-oculus.png)
To customize bindings on OpenXR, refer to the [OpenXR Bindings wiki page](https://github.com/galister/wlx-overlay-s/wiki/OpenXR-Bindings).
If your bindings are not supported, please reach out. \
We would like to work with you and include additional bindings.
# Troubleshooting
## Troubleshooting
Check [here](https://github.com/galister/wlx-overlay-s/wiki/Troubleshooting) for tips.
# Known Issues
## Known Issues
### Mouse is not where it should be
## Mouse is not where it should be
Hyprland users: Hyprland v0.41.0 changed their absolute input implementation to one that does not respect existing absolute input standards. Make your voice heard: [Hyprland#6023](https://github.com/hyprwm/Hyprland/issues/6023)・[Hyprland#6889](https://github.com/hyprwm/Hyprland/issues/6889)
Niri users: use on Niri 0.1.7 or later.
Other desktops: You may have selected the screens in the wrong order, see [First Start](#first-start).
## Space-drag crashes SteamVR
### Space-drag crashes SteamVR
This has been idenfitied as an issue with SteamVR versions 2.5.5 and above (latest tested 2.7.2). One way to avoid the crash is by switching to the `temp-v1.27.5` branch of SteamVR (via beta selection) and selecting [Steam-Play-None](https://github.com/Scrumplex/Steam-Play-None) under the compatibility tab.
## Modifiers get stuck in weird ways
### Modifiers get stuck in weird ways
This is a rare issue that can make your desktop not react to click or keys due to a modifier being stuck somewhere. Restarting the overlay fixes this.
## Scroll wheel doesn't work
This seems to be an issue specific to Electron apps (Discord, Element, Slack, Spotify) on Wayland. Scrolling will work when using these in your web browser.
## X11 limitations
### X11 limitations
- X11 capture can generally seem slow. This is because zero-copy GPU capture is not supported on the general X11 desktop. Consider trying Wayland or Picom.
- DPI scaling is not supported and may cause the mouse to not follow the laser properly.
- Upright screens are not supported and can cause the mouse to act weirdly.
- Screen changes (connecting / disconnecting a display, resolution changes, etc) are not handled at runtime. Restart the overlay for these to take effect.
+23 -6
View File
@@ -2,22 +2,39 @@ use regex::Regex;
use std::process::Command;
fn main() {
let mut wlx_build = get_version().unwrap_or(format!("{} (Cargo)", env!("CARGO_PKG_VERSION")));
let mut wlx_build = get_version().unwrap_or(format!("{}-unknown", env!("CARGO_PKG_VERSION")));
if std::env::var("GITHUB_ACTIONS").is_ok() {
wlx_build = format!("{} (AppImage)", &wlx_build)
match std::env::var("GITHUB_JOB").as_deref() {
Ok("make_release") => {
wlx_build = format!("{} (Release)", &wlx_build);
}
Ok("build_appimage") => {
wlx_build = format!("{} (AppImage)", &wlx_build);
}
_ => {}
}
println!("cargo:rustc-env=WLX_BUILD={}", &wlx_build);
}
fn get_version() -> Result<String, Box<dyn std::error::Error>> {
let re = Regex::new(r"v([0-9.]+)-([0-9]+)-g([a-f0-9]+)").unwrap(); // safe
let output = Command::new("git")
.args(["describe", "--tags", "--abbrev=7"])
.args(["describe", "--tags", "--abbrev=7", "--dirty"])
.output()?;
let output_str = String::from_utf8(output.stdout)?;
let mut output_str = String::from_utf8(output.stdout)?;
if output_str.is_empty() {
let output = Command::new("git")
.args(["describe", "--tags", "--abbrev=7", "--dirty", "--always"])
.output()?;
output_str = format!(
"{}-{}",
env!("CARGO_PKG_VERSION"),
String::from_utf8(output.stdout)?
);
}
Ok(re.replace_all(&output_str, "${1}.r${2}.${3}").into_owned())
}
+17 -16
View File
@@ -73,6 +73,9 @@ impl InputState {
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 {}",
@@ -192,6 +195,7 @@ pub struct PointerState {
pub show_hide: bool,
pub space_drag: bool,
pub space_rotate: bool,
pub space_reset: bool,
pub click_modifier_right: bool,
pub click_modifier_middle: bool,
pub move_mouse: bool,
@@ -309,15 +313,17 @@ where
pointer = &mut app.input_state.pointers[idx];
pointer.interaction.hovered_id = None;
}
if let Some(clicked_id) = pointer.interaction.clicked_id.take() {
if let Some(clicked) = overlays.mut_by_id(clicked_id) {
let hit = PointerHit {
pointer: pointer.idx,
overlay: clicked_id,
mode: pointer.interaction.mode,
..Default::default()
};
clicked.backend.on_pointer(app, &hit, false);
if !pointer.now.click && pointer.before.click {
if let Some(clicked_id) = pointer.interaction.clicked_id.take() {
if let Some(clicked) = overlays.mut_by_id(clicked_id) {
let hit = PointerHit {
pointer: pointer.idx,
overlay: clicked_id,
mode: pointer.interaction.mode,
..Default::default()
};
clicked.backend.on_pointer(app, &hit, false);
}
}
}
return (0.0, None); // no hit
@@ -366,18 +372,13 @@ where
);
}
let scroll_threshold = 0.1;
let mut haptics: Option<Haptics> = None;
// Pass mouse motion events only if not scrolling
// (allows scrolling on all Chromium-based applications)
if app.input_state.pointers[idx].now.scroll.abs() <= scroll_threshold {
haptics = hovered.backend.on_hover(app, &hit);
}
let haptics = hovered.backend.on_hover(app, &hit);
pointer = &mut app.input_state.pointers[idx];
if pointer.now.scroll.abs() > scroll_threshold {
if pointer.now.scroll.abs() > 0.1 {
let scroll = pointer.now.scroll;
if app.input_state.pointers[1 - idx]
.interaction
+3
View File
@@ -2,6 +2,9 @@ pub mod common;
pub mod input;
pub mod notifications;
#[allow(clippy::all)]
mod notifications_dbus;
#[cfg(feature = "openvr")]
pub mod openvr;
+61 -1
View File
@@ -1,4 +1,9 @@
use dbus::{blocking::Connection, channel::MatchingReceiver, message::MatchRule};
use dbus::{
arg::{PropMap, Variant},
blocking::Connection,
channel::MatchingReceiver,
message::MatchRule,
};
use serde::Deserialize;
use std::{
sync::{
@@ -9,6 +14,7 @@ use std::{
};
use crate::{
backend::notifications_dbus::OrgFreedesktopNotifications,
overlays::toast::{Toast, ToastTopic},
state::AppState,
};
@@ -196,6 +202,60 @@ impl Drop for NotificationManager {
}
}
pub struct DbusNotificationSender {
connection: Connection,
}
impl DbusNotificationSender {
pub fn new() -> anyhow::Result<Self> {
Ok(Self {
connection: Connection::new_session()?,
})
}
pub fn notify_send(
&self,
summary: &str,
body: &str,
urgency: u8,
timeout: i32,
replaces_id: u32,
transient: bool,
) -> anyhow::Result<u32> {
let proxy = self.connection.with_proxy(
"org.freedesktop.Notifications",
"/org/freedesktop/Notifications",
Duration::from_millis(1000),
);
let mut hints = PropMap::new();
hints.insert("urgency".to_string(), Variant(Box::new(urgency)));
hints.insert("transient".to_string(), Variant(Box::new(transient)));
Ok(proxy.notify(
"WlxOverlay-S",
replaces_id,
"",
summary,
body,
vec![],
hints,
timeout,
)?)
}
pub fn notify_close(&self, id: u32) -> anyhow::Result<()> {
let proxy = self.connection.with_proxy(
"org.freedesktop.Notifications",
"/org/freedesktop/Notifications",
Duration::from_millis(1000),
);
proxy.close_notification(id)?;
Ok(())
}
}
fn parse_dbus(msg: &dbus::Message) -> anyhow::Result<Toast> {
let mut args = msg.iter_init();
let app_name: String = args.read()?;
+267
View File
@@ -0,0 +1,267 @@
// 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;
#[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_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>;
}
#[derive(Debug)]
pub struct OrgFreedesktopDBusPropertiesPropertiesChanged {
pub interface_name: String,
pub changed_properties: arg::PropMap,
pub invalidated_properties: Vec<String>,
}
impl arg::AppendAll for OrgFreedesktopDBusPropertiesPropertiesChanged {
fn append(&self, i: &mut arg::IterAppend) {
arg::RefArg::append(&self.interface_name, i);
arg::RefArg::append(&self.changed_properties, i);
arg::RefArg::append(&self.invalidated_properties, i);
}
}
impl arg::ReadAll for OrgFreedesktopDBusPropertiesPropertiesChanged {
fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
Ok(OrgFreedesktopDBusPropertiesPropertiesChanged {
interface_name: i.read()?,
changed_properties: i.read()?,
invalidated_properties: i.read()?,
})
}
}
impl dbus::message::SignalArgs for OrgFreedesktopDBusPropertiesPropertiesChanged {
const NAME: &'static str = "PropertiesChanged";
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, ))
}
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, ))
}
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), ))
}
}
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> {
fn introspect(&self) -> Result<String, dbus::Error> {
self.method_call("org.freedesktop.DBus.Introspectable", "Introspect", ())
.and_then(|r: (String, )| Ok(r.0, ))
}
}
pub trait OrgFreedesktopDBusPeer {
fn ping(&self) -> Result<(), dbus::Error>;
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> {
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, ))
}
}
pub trait OrgFreedesktopNotifications {
fn set_noti_window_visibility(&self, value: bool) -> Result<(), dbus::Error>;
fn toggle_dnd(&self) -> Result<bool, dbus::Error>;
fn set_dnd(&self, state: bool) -> Result<(), dbus::Error>;
fn get_dnd(&self) -> Result<bool, dbus::Error>;
fn manually_close_notification(&self, id: u32, timeout: bool) -> Result<(), dbus::Error>;
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 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>;
fn set_dnd_(&self, value: bool) -> Result<(), dbus::Error>;
}
#[derive(Debug)]
pub struct OrgFreedesktopNotificationsOnDndToggle {
pub dnd: bool,
}
impl arg::AppendAll for OrgFreedesktopNotificationsOnDndToggle {
fn append(&self, i: &mut arg::IterAppend) {
arg::RefArg::append(&self.dnd, i);
}
}
impl arg::ReadAll for OrgFreedesktopNotificationsOnDndToggle {
fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
Ok(OrgFreedesktopNotificationsOnDndToggle {
dnd: i.read()?,
})
}
}
impl dbus::message::SignalArgs for OrgFreedesktopNotificationsOnDndToggle {
const NAME: &'static str = "OnDndToggle";
const INTERFACE: &'static str = "org.freedesktop.Notifications";
}
#[derive(Debug)]
pub struct OrgFreedesktopNotificationsNotificationClosed {
pub id: u32,
pub reason: u32,
}
impl arg::AppendAll for OrgFreedesktopNotificationsNotificationClosed {
fn append(&self, i: &mut arg::IterAppend) {
arg::RefArg::append(&self.id, i);
arg::RefArg::append(&self.reason, i);
}
}
impl arg::ReadAll for OrgFreedesktopNotificationsNotificationClosed {
fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
Ok(OrgFreedesktopNotificationsNotificationClosed {
id: i.read()?,
reason: i.read()?,
})
}
}
impl dbus::message::SignalArgs for OrgFreedesktopNotificationsNotificationClosed {
const NAME: &'static str = "NotificationClosed";
const INTERFACE: &'static str = "org.freedesktop.Notifications";
}
#[derive(Debug)]
pub struct OrgFreedesktopNotificationsActionInvoked {
pub id: u32,
pub action_key: String,
}
impl arg::AppendAll for OrgFreedesktopNotificationsActionInvoked {
fn append(&self, i: &mut arg::IterAppend) {
arg::RefArg::append(&self.id, i);
arg::RefArg::append(&self.action_key, i);
}
}
impl arg::ReadAll for OrgFreedesktopNotificationsActionInvoked {
fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
Ok(OrgFreedesktopNotificationsActionInvoked {
id: i.read()?,
action_key: i.read()?,
})
}
}
impl dbus::message::SignalArgs for OrgFreedesktopNotificationsActionInvoked {
const NAME: &'static str = "ActionInvoked";
const INTERFACE: &'static str = "org.freedesktop.Notifications";
}
#[derive(Debug)]
pub struct OrgFreedesktopNotificationsNotificationReplied {
pub id: u32,
pub text: String,
}
impl arg::AppendAll for OrgFreedesktopNotificationsNotificationReplied {
fn append(&self, i: &mut arg::IterAppend) {
arg::RefArg::append(&self.id, i);
arg::RefArg::append(&self.text, i);
}
}
impl arg::ReadAll for OrgFreedesktopNotificationsNotificationReplied {
fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
Ok(OrgFreedesktopNotificationsNotificationReplied {
id: i.read()?,
text: i.read()?,
})
}
}
impl dbus::message::SignalArgs for OrgFreedesktopNotificationsNotificationReplied {
const NAME: &'static str = "NotificationReplied";
const INTERFACE: &'static str = "org.freedesktop.Notifications";
}
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, ))
}
fn toggle_dnd(&self) -> Result<bool, dbus::Error> {
self.method_call("org.freedesktop.Notifications", "ToggleDnd", ())
.and_then(|r: (bool, )| Ok(r.0, ))
}
fn set_dnd(&self, state: bool) -> Result<(), dbus::Error> {
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, ))
}
fn manually_close_notification(&self, id: u32, timeout: bool) -> Result<(), dbus::Error> {
self.method_call("org.freedesktop.Notifications", "ManuallyCloseNotification", (id, timeout, ))
}
fn close_all_notifications(&self) -> Result<(), dbus::Error> {
self.method_call("org.freedesktop.Notifications", "CloseAllNotifications", ())
}
fn hide_latest_notification(&self, close: bool) -> Result<(), dbus::Error> {
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, ))
}
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, ))
}
fn get_server_information(&self) -> Result<(String, String, String, String), dbus::Error> {
self.method_call("org.freedesktop.Notifications", "GetServerInformation", ())
}
fn dnd(&self) -> Result<bool, dbus::Error> {
<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)
}
}
+26 -2
View File
@@ -156,6 +156,7 @@ pub(super) struct OpenXrHandSource {
action_show_hide: 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,
@@ -315,13 +316,21 @@ impl OpenXrHand {
.location_flags
.contains(xr::SpaceLocationFlags::ORIENTATION_VALID)
{
let (quat, pos) = unsafe {
let (cur_quat, cur_pos) = (Quat::from_affine3(&pointer.pose), pointer.pose.translation);
let (new_quat, new_pos) = unsafe {
(
transmute::<Quaternionf, Quat>(location.pose.orientation),
transmute::<Vector3f, Vec3>(location.pose.position),
)
};
pointer.pose = Affine3A::from_rotation_translation(quat, pos);
let lerp_factor =
(1.0 / (xr.fps / 100.0) * session.config.pointer_lerp_factor).clamp(0.1, 1.0);
pointer.raw_pose = Affine3A::from_rotation_translation(new_quat, new_pos);
pointer.pose = Affine3A::from_rotation_translation(
cur_quat.lerp(new_quat, lerp_factor),
cur_pos.lerp(new_pos.into(), lerp_factor).into(),
);
}
pointer.now.click = self
@@ -377,6 +386,11 @@ impl OpenXrHand {
.action_space_rotate
.state(pointer.before.space_rotate, xr, session)?;
pointer.now.space_reset =
self.source
.action_space_reset
.state(pointer.before.space_reset, xr, session)?;
Ok(())
}
}
@@ -410,6 +424,7 @@ impl OpenXrHandSource {
action_show_hide: CustomClickAction::new(action_set, "show_hide", 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",
@@ -579,6 +594,14 @@ fn suggest_bindings(instance: &xr::Instance, hands: &[&OpenXrHandSource; 2]) ->
instance
);
add_custom!(
profile.space_reset,
&hands[0].action_space_reset,
&hands[1].action_space_reset,
bindings,
instance
);
add_custom!(
profile.click_modifier_right,
&hands[0].action_modifier_right,
@@ -634,6 +657,7 @@ struct OpenXrActionConfProfile {
show_hide: Option<OpenXrActionConfAction>,
space_drag: Option<OpenXrActionConfAction>,
space_rotate: Option<OpenXrActionConfAction>,
space_reset: Option<OpenXrActionConfAction>,
click_modifier_right: Option<OpenXrActionConfAction>,
click_modifier_middle: Option<OpenXrActionConfAction>,
move_mouse: Option<OpenXrActionConfAction>,
+22
View File
@@ -48,6 +48,7 @@ struct XrState {
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,
@@ -131,6 +132,7 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
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,
@@ -153,6 +155,8 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
let mut next_device_update = Instant::now();
let mut due_tasks = VecDeque::with_capacity(4);
let mut fps_counter: VecDeque<Instant> = VecDeque::new();
let mut main_session_visible = false;
'main_loop: loop {
@@ -231,6 +235,24 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
frame_stream.begin()?;
xr_state.predicted_display_time = xr_frame_state.predicted_display_time;
xr_state.fps = {
fps_counter.push_back(Instant::now());
while let Some(time) = fps_counter.front() {
if time.elapsed().as_secs_f32() > 1. {
fps_counter.pop_front();
} else {
break;
}
}
let total_elapsed = fps_counter
.front()
.map(|time| time.elapsed().as_secs_f32())
.unwrap_or(0f32);
fps_counter.len() as f32 / total_elapsed
};
if !xr_frame_state.should_render {
frame_stream.end(
+7
View File
@@ -15,6 +15,9 @@
// -- space_rotate --
// rotate your stage (playspace rotate, WIP)
//
// -- space_reset --
// reset your stage (reset the offset from playspace drag)
//
// -- click_modifier_right --
// while this is held, your pointer will turn ORANGE and your mouse clicks will be RIGHT clicks
//
@@ -132,6 +135,10 @@
left: "/user/hand/left/input/trackpad/force",
// right trackpad is alt_click
},
space_reset: {
double_click: true,
left: "/user/hand/left/input/trackpad/force",
},
click_modifier_right: {
left: "/user/hand/left/input/b/touch",
right: "/user/hand/right/input/b/touch"
+25 -22
View File
@@ -55,6 +55,16 @@ impl PlayspaceMover {
state: &AppState,
monado: &mut Monado,
) {
for pointer in state.input_state.pointers.iter() {
if pointer.now.space_reset {
if !pointer.before.space_reset {
log::info!("Space reset");
self.reset_offset(monado);
}
return;
}
}
if let Some(mut data) = self.rotate.take() {
let pointer = &state.input_state.pointers[data.hand];
if !pointer.now.space_rotate {
@@ -64,32 +74,25 @@ impl PlayspaceMover {
}
let new_hand =
Quat::from_affine3(&(data.pose * state.input_state.pointers[data.hand].pose));
Quat::from_affine3(&(data.pose * state.input_state.pointers[data.hand].raw_pose));
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,
);
let mut space_transform = if state.session.config.space_rotate_unlocked {
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,
);
//let mut space_transform = Affine3A::from_rotation_translation(dq, Vec3::ZERO);
let mut space_transform = Affine3A::from_rotation_y(rel_y);
Affine3A::from_rotation_y(rel_y)
};
let offset = (space_transform.transform_vector3a(state.input_state.hmd.translation)
- state.input_state.hmd.translation)
* -1.0;
let mut overlay_transform = Affine3A::from_rotation_y(-rel_y);
overlay_transform.translation = offset;
space_transform.translation = offset;
overlays.iter_mut().for_each(|overlay| {
if overlay.state.grabbable {
overlay.state.dirty = true;
overlay.state.transform.translation =
overlay_transform.transform_point3a(overlay.state.transform.translation);
}
});
data.pose *= space_transform;
data.hand_pose = new_hand;
@@ -98,7 +101,7 @@ impl PlayspaceMover {
} else {
for (i, pointer) in state.input_state.pointers.iter().enumerate() {
if pointer.now.space_rotate {
let hand_pose = Quat::from_affine3(&self.last_transform);
let hand_pose = Quat::from_affine3(&(self.last_transform * pointer.raw_pose));
self.rotate = Some(MoverData {
pose: self.last_transform,
hand: i,
@@ -121,7 +124,7 @@ impl PlayspaceMover {
let new_hand = data
.pose
.transform_point3a(state.input_state.pointers[data.hand].pose.translation);
.transform_point3a(state.input_state.pointers[data.hand].raw_pose.translation);
let relative_pos =
(new_hand - data.hand_pose) * state.session.config.space_drag_multiplier;
@@ -149,7 +152,7 @@ impl PlayspaceMover {
if pointer.now.space_drag {
let hand_pos = self
.last_transform
.transform_point3a(pointer.pose.translation);
.transform_point3a(pointer.raw_pose.translation);
self.drag = Some(MoverData {
pose: self.last_transform,
hand: i,
@@ -186,8 +189,8 @@ impl PlayspaceMover {
self.rotate = None;
}
let y1 = input.pointers[0].pose.translation.y;
let y2 = input.pointers[1].pose.translation.y;
let y1 = input.pointers[0].raw_pose.translation.y;
let y2 = input.pointers[1].raw_pose.translation.y;
let delta = y1.min(y2) - 0.03;
self.last_transform.translation.y += delta;
self.apply_offset(self.last_transform, monado);
+13
View File
@@ -121,6 +121,10 @@ pub fn def_point7() -> f32 {
0.7
}
pub fn def_point3() -> f32 {
0.3
}
fn def_osc_port() -> u16 {
9000
}
@@ -260,6 +264,15 @@ pub struct GeneralConfig {
#[serde(default = "def_max_height")]
pub screen_max_height: u16,
#[serde(default = "def_false")]
pub screen_render_down: bool,
#[serde(default = "def_point3")]
pub pointer_lerp_factor: f32,
#[serde(default = "def_false")]
pub space_rotate_unlocked: bool,
}
impl GeneralConfig {
+22 -17
View File
@@ -44,8 +44,8 @@ use vulkano::{
allocator::StandardDescriptorSetAllocator, DescriptorSet, WriteDescriptorSet,
},
device::{
physical::PhysicalDevice, Device, DeviceCreateInfo, DeviceExtensions, DeviceFeatures,
Queue, QueueCreateInfo, QueueFlags,
physical::PhysicalDevice, Device, DeviceCreateInfo, DeviceExtensions, Features, Queue,
QueueCreateInfo, QueueFlags,
},
format::Format,
image::{
@@ -195,7 +195,7 @@ impl WlxGraphics {
let target_version = vulkano::Version::V1_3;
let library = get_vulkan_library();
let vk_app_info_raw = vk::ApplicationInfo::default()
let vk_app_info_raw = vk::ApplicationInfo::builder()
.application_version(0)
.engine_version(0)
.api_version(vk_target_version);
@@ -205,7 +205,7 @@ impl WlxGraphics {
.create_vulkan_instance(
system,
get_instance_proc_addr,
&vk::InstanceCreateInfo::default()
&vk::InstanceCreateInfo::builder()
.application_info(&vk_app_info_raw)
.enabled_extension_names(&instance_extensions_raw)
as *const _ as *const _,
@@ -286,23 +286,25 @@ impl WlxGraphics {
})
.collect::<Vec<_>>();
let features = DeviceFeatures {
let features = Features {
dynamic_rendering: true,
..Default::default()
};
let queue_priorities = [1.0];
let queue_create_infos = [vk::DeviceQueueCreateInfo::default()
let queue_create_infos = [vk::DeviceQueueCreateInfo::builder()
.queue_family_index(queue_family_index)
.queue_priorities(&queue_priorities)];
.queue_priorities(&queue_priorities)
.build()];
let mut device_create_info = vk::DeviceCreateInfo::default()
let mut device_create_info = vk::DeviceCreateInfo::builder()
.queue_create_infos(&queue_create_infos)
.enabled_extension_names(&device_extensions_raw);
.enabled_extension_names(&device_extensions_raw)
.build();
let mut dynamic_rendering =
PhysicalDeviceDynamicRenderingFeatures::default().dynamic_rendering(true);
PhysicalDeviceDynamicRenderingFeatures::builder().dynamic_rendering(true);
dynamic_rendering.p_next = device_create_info.p_next as _;
device_create_info.p_next = (&mut dynamic_rendering) as *const _ as *const c_void;
@@ -487,9 +489,9 @@ impl WlxGraphics {
physical_device,
DeviceCreateInfo {
enabled_extensions: my_extensions,
enabled_features: DeviceFeatures {
enabled_features: Features {
dynamic_rendering: true,
..DeviceFeatures::empty()
..Features::empty()
},
queue_create_infos: vec![QueueCreateInfo {
queue_family_index,
@@ -625,9 +627,9 @@ impl WlxGraphics {
physical_device,
DeviceCreateInfo {
enabled_extensions: my_extensions,
enabled_features: DeviceFeatures {
enabled_features: Features {
dynamic_rendering: true,
..DeviceFeatures::empty()
..Features::empty()
},
queue_create_infos: vec![QueueCreateInfo {
queue_family_index,
@@ -1034,7 +1036,10 @@ impl WlxGraphics {
(fns.v1_0.queue_submit)(
self.queue.handle(),
1,
[SubmitInfo::default().command_buffers(&[command_buffer.handle()])].as_ptr(),
[SubmitInfo::builder()
.command_buffers(&[command_buffer.handle()])
.build()]
.as_ptr(),
fence.handle(),
)
}
@@ -1186,7 +1191,7 @@ impl WlxPipeline<WlxPipelineDynamic> {
let vep = vert.entry_point("main").unwrap(); // want panic
let fep = frag.entry_point("main").unwrap(); // want panic
let vertex_input_state = Vert2Uv::per_vertex().definition(&vep)?;
let vertex_input_state = Vert2Uv::per_vertex().definition(&vep.info().input_interface)?;
let stages = smallvec![
vulkano::pipeline::PipelineShaderStageCreateInfo::new(vep),
@@ -1343,7 +1348,7 @@ impl WlxPipeline<WlxPipelineLegacy> {
let vep = vert.entry_point("main").unwrap(); // want panic
let fep = frag.entry_point("main").unwrap(); // want panic
let vertex_input_state = Vert2Uv::per_vertex().definition(&vep)?;
let vertex_input_state = Vert2Uv::per_vertex().definition(&vep.info().input_interface)?;
let stages = smallvec![
vulkano::pipeline::PipelineShaderStageCreateInfo::new(vep),
+18 -2
View File
@@ -52,7 +52,15 @@ impl<D, S> CanvasBuilder<D, S> {
}
// Creates a label with fg_color, font_size inherited from the canvas
pub fn label(&mut self, x: f32, y: f32, w: f32, h: f32, radius: f32, text: Arc<str>) -> &mut Control<D, S> {
pub fn label(
&mut self,
x: f32,
y: f32,
w: f32,
h: f32,
radius: f32,
text: Arc<str>,
) -> &mut Control<D, S> {
let idx = self.canvas.controls.len();
self.canvas.controls.push(Control {
rect: Rect { x, y, w, h },
@@ -116,7 +124,15 @@ impl<D, S> CanvasBuilder<D, S> {
}
// Creates a button with fg_color, bg_color, font_size inherited from the canvas
pub fn button(&mut self, x: f32, y: f32, w: f32, h: f32, radius: f32, text: Arc<str>) -> &mut Control<D, S> {
pub fn button(
&mut self,
x: f32,
y: f32,
w: f32,
h: f32,
radius: f32,
text: Arc<str>,
) -> &mut Control<D, S> {
let idx = self.canvas.controls.len();
self.canvas.interactive_set_idx(x, y, w, h, idx);
+4
View File
@@ -220,6 +220,10 @@ impl<D, S> Canvas<D, S> {
cmd_buffer.end_render_pass()?;
cmd_buffer.build_and_execute_now()
}
pub fn data_mut(&mut self) -> &mut D {
&mut self.canvas.data
}
}
impl<D, S> InteractionHandler for Canvas<D, S> {
+30
View File
@@ -17,6 +17,7 @@ use crate::{
task::{ColorChannel, SystemTask, TaskType},
},
config::{save_layout, save_settings, AStrSetExt},
hid::VirtualKey,
overlays::{
toast::{Toast, ToastTopic},
watch::WATCH_NAME,
@@ -26,6 +27,12 @@ use crate::{
use super::{ExecArgs, ModularControl, ModularData};
#[derive(Deserialize, Clone)]
pub enum PressRelease {
Release,
Press,
}
#[derive(Deserialize, Clone, Copy)]
pub enum ViewAngleKind {
/// The cosine of the angle at which the watch becomes fully transparent
@@ -47,6 +54,7 @@ pub enum HighlightTest {
AutoRealign,
NotificationSounds,
Notifications,
RorateLock,
}
#[derive(Deserialize, Clone)]
@@ -55,6 +63,7 @@ pub enum SystemAction {
ToggleAutoRealign,
ToggleNotificationSounds,
ToggleNotifications,
ToggleRotateLock,
PlayspaceResetOffset,
PlayspaceFixFloor,
RecalculateExtent,
@@ -114,6 +123,10 @@ pub enum ButtonAction {
command: ExecArgs,
toast: Option<Arc<str>>,
},
VirtualKey {
keycode: VirtualKey,
action: PressRelease,
},
Watch {
action: WatchAction,
},
@@ -298,6 +311,7 @@ fn modular_button_highlight(
HighlightTest::AutoRealign => app.session.config.realign_on_showhide,
HighlightTest::NotificationSounds => app.session.config.notifications_sound_enabled,
HighlightTest::Notifications => app.session.config.notifications_enabled,
HighlightTest::RorateLock => !app.session.config.space_rotate_unlocked,
};
if lit {
@@ -313,6 +327,9 @@ fn handle_action(action: &ButtonAction, press: &mut PressData, app: &mut AppStat
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),
ButtonAction::VirtualKey { keycode, action } => app
.hid_provider
.send_key(*keycode, matches!(*action, PressRelease::Press)),
ButtonAction::Toast {
message,
body,
@@ -369,6 +386,19 @@ fn run_system(action: &SystemAction, app: &mut AppState) {
)
.submit(app);
}
SystemAction::ToggleRotateLock => {
app.session.config.space_rotate_unlocked = !app.session.config.space_rotate_unlocked;
Toast::new(
ToastTopic::System,
format!(
"Space rotate axis lock now {}.",
ENABLED_DISABLED[!app.session.config.space_rotate_unlocked as usize]
)
.into(),
"".into(),
)
.submit(app);
}
SystemAction::PlayspaceResetOffset => {
app.tasks
.enqueue(TaskType::System(SystemTask::ResetPlayspace));
+6 -2
View File
@@ -7,6 +7,7 @@ use input_linux::{
};
use libc::{input_event, timeval};
use once_cell::sync::Lazy;
use serde::Deserialize;
use std::mem::transmute;
use std::{fs::File, sync::atomic::AtomicBool};
use strum::{EnumIter, EnumString, IntoEnumIterator};
@@ -242,7 +243,7 @@ impl HidProvider for UInputProvider {
self.desktop_origin = origin;
}
fn mouse_move(&mut self, pos: Vec2) {
if self.current_action.pos.is_none() {
if self.current_action.pos.is_none() && self.current_action.scroll.is_none() {
self.current_action.pos = Some(pos);
}
self.current_action.last_requested_pos = Some(pos);
@@ -256,6 +257,9 @@ impl HidProvider for UInputProvider {
fn wheel(&mut self, delta: i32) {
if self.current_action.scroll.is_none() {
self.current_action.scroll = Some(delta);
// Pass mouse motion events only if not scrolling
// (allows scrolling on all Chromium-based applications)
self.current_action.pos = None;
}
}
fn commit(&mut self) {
@@ -313,7 +317,7 @@ pub const META: KeyModifier = 0x80;
#[allow(non_camel_case_types)]
#[repr(u16)]
#[derive(Debug, PartialEq, Clone, Copy, IntegerId, EnumString, EnumIter)]
#[derive(Debug, Deserialize, PartialEq, Clone, Copy, IntegerId, EnumString, EnumIter)]
pub enum VirtualKey {
Escape = 9,
N1, // number row
+69 -4
View File
@@ -6,12 +6,12 @@ use std::{
use crate::{
backend::{
input::PointerMode,
overlay::{OverlayData, OverlayState},
input::{InteractionHandler, PointerMode},
overlay::{OverlayBackend, OverlayData, OverlayRenderer, OverlayState},
},
config::{self, ConfigType},
gui::{
canvas::{builder::CanvasBuilder, control::Control},
canvas::{builder::CanvasBuilder, control::Control, Canvas},
color_parse, KeyCapType,
},
hid::{
@@ -180,7 +180,7 @@ where
interaction_transform,
..Default::default()
},
backend: Box::new(canvas),
backend: Box::new(KeyboardBackend { canvas }),
..Default::default()
})
}
@@ -435,3 +435,68 @@ fn key_events_for_macro(macro_verbs: &Vec<String>) -> Vec<(VirtualKey, bool)> {
}
key_events
}
struct KeyboardBackend {
canvas: Canvas<KeyboardData, KeyButtonData>,
}
impl OverlayBackend for KeyboardBackend {
fn set_interaction(&mut self, interaction: Box<dyn crate::backend::input::InteractionHandler>) {
self.canvas.set_interaction(interaction)
}
fn set_renderer(&mut self, renderer: Box<dyn crate::backend::overlay::OverlayRenderer>) {
self.canvas.set_renderer(renderer)
}
}
impl InteractionHandler for KeyboardBackend {
fn on_pointer(
&mut self,
app: &mut AppState,
hit: &crate::backend::input::PointerHit,
pressed: bool,
) {
self.canvas.on_pointer(app, hit, pressed)
}
fn on_scroll(
&mut self,
app: &mut AppState,
hit: &crate::backend::input::PointerHit,
delta: f32,
) {
self.canvas.on_scroll(app, hit, delta)
}
fn on_left(&mut self, app: &mut AppState, pointer: usize) {
self.canvas.on_left(app, pointer)
}
fn on_hover(
&mut self,
app: &mut AppState,
hit: &crate::backend::input::PointerHit,
) -> Option<crate::backend::input::Haptics> {
self.canvas.on_hover(app, hit)
}
}
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 extent(&mut self) -> Option<[u32; 3]> {
self.canvas.extent()
}
fn view(&mut self) -> Option<std::sync::Arc<vulkano::image::view::ImageView>> {
self.canvas.view()
}
fn pause(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.canvas.data_mut().modifiers = 0;
app.hid_provider.set_modifiers(0);
self.canvas.pause(app)
}
fn resume(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.canvas.resume(app)
}
}
+94 -20
View File
@@ -18,6 +18,7 @@ use wlx_capture::{
DrmFormat, FrameFormat, MouseMeta, WlxFrame, DRM_FORMAT_ABGR2101010, DRM_FORMAT_ABGR8888,
DRM_FORMAT_ARGB8888, DRM_FORMAT_XBGR2101010, DRM_FORMAT_XBGR8888, DRM_FORMAT_XRGB8888,
},
pipewire::PipewireSelectScreenResult,
WlxCapture,
};
@@ -25,8 +26,8 @@ use wlx_capture::{
use {
crate::config_io,
std::error::Error,
std::{ops::Deref, path::PathBuf},
wlx_capture::pipewire::{pipewire_select_screen, PipewireCapture},
std::{ops::Deref, path::PathBuf, task},
wlx_capture::pipewire::PipewireCapture,
};
#[cfg(all(feature = "x11", feature = "pipewire"))]
@@ -337,21 +338,22 @@ impl ScreenRenderer {
)> {
let name = output.name.clone();
let embed_mouse = !session.config.double_cursor_fix;
log::info!(
"On screen share prompt, pick: {} {} {} (pos {}, {})",
&output.name,
&output.make,
&output.model,
&output.logical_pos.0,
&output.logical_pos.1,
);
let select_screen_result = futures::executor::block_on(pipewire_select_screen(
let select_screen_result = select_pw_screen(
&format!(
"Now select: {} {} {} @ {},{}",
&output.name,
&output.make,
&output.model,
&output.logical_pos.0,
&output.logical_pos.1
),
token,
embed_mouse,
true,
true,
false,
))?;
)?;
let node_id = select_screen_result.streams.first().unwrap().node_id; // streams guaranteed to have at least one element
@@ -471,9 +473,23 @@ impl OverlayRenderer for ScreenRenderer {
});
match app.graphics.dmabuf_texture(frame) {
Ok(new) => {
let view = ImageView::new_default(new.clone())?;
self.last_view = Some(view);
let pipeline = match self.pipeline {
Some(ref mut p) => Some(p),
None if app.session.config.screen_render_down => {
log::info!("{}: Using render-down pass.", self.name);
let pipeline = ScreenPipeline::new(&self.extent.unwrap(), app)?; // safe
self.last_view = Some(pipeline.view.clone());
self.pipeline = Some(pipeline);
self.pipeline.as_mut()
}
None => None,
};
if let Some(pipeline) = pipeline {
pipeline.render(new.clone(), None, app)?;
} else {
let view = ImageView::new_default(new.clone())?;
self.last_view = Some(view);
}
}
Err(e) => {
log::error!(
@@ -549,6 +565,7 @@ impl OverlayRenderer for ScreenRenderer {
let pipeline = Some(match self.pipeline {
Some(ref mut p) => p,
_ => {
log::info!("{}: Using render-down pass.", self.name);
let extent = extent_from_format(frame.format, &app.session.config);
let mut pipeline = ScreenPipeline::new(&extent, app)?;
self.last_view = Some(pipeline.view.clone());
@@ -598,9 +615,7 @@ pub fn create_screen_renderer_wl(
session: &AppSession,
) -> Option<ScreenRenderer> {
let mut capture: Option<ScreenRenderer> = None;
if (&*session.config.capture_method == "auto" || &*session.config.capture_method == "dmabuf")
&& has_wlr_dmabuf
{
if (&*session.config.capture_method == "wlr-dmabuf") && has_wlr_dmabuf {
log::info!("{}: Using Wlr DMA-Buf", &output.name);
capture = ScreenRenderer::new_wlr_dmabuf(output);
}
@@ -853,8 +868,16 @@ pub fn create_screens_x11pw(app: &mut AppState) -> anyhow::Result<ScreenCreateDa
let pw_tokens_copy = pw_tokens.clone();
let token = pw_tokens.arc_get("x11").map(|s| s.as_str());
let embed_mouse = !app.session.config.double_cursor_fix;
let select_screen_result =
futures::executor::block_on(pipewire_select_screen(token, embed_mouse, true, true, true))?;
let select_screen_result = select_pw_screen(
"Select ALL screens on the screencast pop-up!",
token,
embed_mouse,
true,
true,
true,
)?;
if let Some(restore_token) = select_screen_result.restore_token {
if pw_tokens.arc_set("x11".into(), restore_token.clone()) {
log::info!("Adding Pipewire token {}", restore_token);
@@ -1053,3 +1076,54 @@ fn best_match<'a>(
log::debug!("best: {:?}", best.map(|b| &b.monitor));
best
}
#[cfg(feature = "pipewire")]
fn select_pw_screen(
instructions: &str,
token: Option<&str>,
embed_mouse: bool,
screens_only: bool,
persist: bool,
multiple: bool,
) -> Result<PipewireSelectScreenResult, wlx_capture::pipewire::AshpdError> {
use crate::backend::notifications::DbusNotificationSender;
use wlx_capture::pipewire::pipewire_select_screen;
let future = async move {
let print_at = Instant::now() + Duration::from_millis(250);
let mut notify = None;
let f = pipewire_select_screen(token, embed_mouse, screens_only, persist, multiple);
futures::pin_mut!(f);
loop {
match futures::poll!(&mut f) {
task::Poll::Ready(result) => return result,
task::Poll::Pending => {
if Instant::now() >= print_at {
log::info!("{}", instructions);
if let Ok(sender) = DbusNotificationSender::new() {
if let Ok(id) = sender.notify_send(instructions, "", 2, 0, 0, true) {
notify = Some((sender, id));
}
}
break;
}
futures::future::lazy(|_| {
std::thread::sleep(Duration::from_millis(10));
})
.await;
continue;
}
}
}
let result = f.await;
if let Some((sender, id)) = notify {
let _ = sender.notify_close(id);
}
result
};
futures::executor::block_on(future)
}
+9 -9
View File
@@ -9,14 +9,14 @@ size: [400, 200]
elements:
# background panel
- type: Panel
rect: [0, 0, 400, 200]
corner_radius: 4
rect: [0, 30, 400, 130]
corner_radius: 20
bg_color: "#24273a"
- type: Button
rect: [2, 162, 26, 36]
corner_radius: 4
font_size: 14
font_size: 15
bg_color: "#c6a0f6"
fg_color: "#24273a"
text: "C"
@@ -32,7 +32,7 @@ elements:
- type: Button
rect: [32, 162, 60, 36]
corner_radius: 4
font_size: 14
font_size: 15
fg_color: "#24273a"
bg_color: "#a6da95"
text: Kbd
@@ -67,7 +67,7 @@ elements:
- type: OverlayList
rect: [94, 160, 306, 40]
corner_radius: 4
font_size: 14
font_size: 15
fg_color: "#cad3f5"
bg_color: "#1e2030"
layout: Horizontal
@@ -147,9 +147,9 @@ elements:
# batteries
- type: BatteryList
rect: [0, 0, 400, 30]
rect: [0, 5, 400, 30]
corner_radius: 4
font_size: 14
font_size: 16
fg_color: "#8bd5ca"
fg_color_low: "#B06060"
fg_color_charging: "#6080A0"
@@ -161,7 +161,7 @@ elements:
- type: Button
rect: [315, 52, 70, 32]
corner_radius: 4
font_size: 14
font_size: 13
fg_color: "#cad3f5"
bg_color: "#5b6078"
text: "Vol +"
@@ -171,7 +171,7 @@ elements:
- type: Button
rect: [315, 116, 70, 32]
corner_radius: 4
font_size: 14
font_size: 13
fg_color: "#cad3f5"
bg_color: "#5b6078"
text: "Vol -"