mirror of
https://github.com/wayvr-org/wayvr.git
synced 2026-10-28 22:02:16 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ab506e192 | ||
|
|
7ab036024d | ||
|
|
e70aaa002e | ||
|
|
a1d3b9684d | ||
|
|
9fe9832a8d | ||
|
|
0b813aeb10 | ||
|
|
155f653f32 | ||
|
|
82374a60a0 | ||
|
|
0d41bf1a3e | ||
|
|
3907e089de | ||
|
|
72bac6465e | ||
|
|
271e037692 | ||
|
|
bc78450c6a | ||
|
|
dce634edd1 | ||
|
|
69d14eebe5 | ||
|
|
54dfb297ac | ||
|
|
1d1230b1e7 | ||
|
|
67b6b90158 | ||
|
|
31ec035db7 | ||
|
|
deb43f0d94 | ||
|
|
4434d82981 | ||
|
|
8ba5d68e5a | ||
|
|
0c6e319fbe | ||
|
|
4679961cb2 | ||
|
|
cb12357d68 | ||
|
|
b401fb28fd | ||
|
|
06478bbfc9 | ||
|
|
b57b77b06a | ||
|
|
5a077e394c | ||
|
|
a08c260898 | ||
|
|
45d08cd89b | ||
|
|
49b9cc361d | ||
|
|
2f8dcaf110 | ||
|
|
473c6e2038 | ||
|
|
4c91470b5c | ||
|
|
85e8e80538 | ||
|
|
49e1e8639d | ||
|
|
88bcd42e56 | ||
|
|
38395c9183 |
No files matched your search
Generated
+411
-496
File diff suppressed because it is too large.
Load diff
+5
-5
@@ -4,7 +4,7 @@ debug = true
|
||||
|
||||
[package]
|
||||
name = "wlx-overlay-s"
|
||||
version = "0.2.4"
|
||||
version = "0.3.2"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
@@ -27,9 +27,9 @@ idmap = "0.2.21"
|
||||
idmap-derive = "0.1.2"
|
||||
input-linux = "0.6.0"
|
||||
json = { version = "0.12.4", optional = true }
|
||||
libc = "0.2.147"
|
||||
log = "0.4.20"
|
||||
once_cell = "1.18.0"
|
||||
libc = "0.2.153"
|
||||
log = "0.4.21"
|
||||
once_cell = "1.19.0"
|
||||
openxr = { version = "0.17.1", features = ["linked"], optional = true }
|
||||
ovr_overlay = { features = ["ovr_input", "ovr_system"], git = "https://github.com/galister/ovr_overlay_oyasumi", optional = true }
|
||||
regex = "1.9.5"
|
||||
@@ -43,7 +43,7 @@ strum = { version = "0.25.0", features = ["derive"] }
|
||||
thiserror = "1.0.56"
|
||||
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", default-features = false }
|
||||
wlx-capture = { git = "https://github.com/galister/wlx-capture", tag = "v0.3.1", default-features = false }
|
||||
xdg = "2.5.2"
|
||||
|
||||
[features]
|
||||
|
||||
@@ -22,7 +22,7 @@ Questions/issues specific to WlxOverlay-S will be handled in the `wlxoverlay` ch
|
||||
|
||||
1. Grab the latest AppImage from [Releases](https://github.com/galister/wlx-overlay-s/releases).
|
||||
1. `chmod +x WlxOverlay-S-*.AppImage`
|
||||
1. Start Monado or SteamVR.
|
||||
1. Start Monado, WiVRn or SteamVR.
|
||||
1. Run the overlay
|
||||
|
||||
AUR package is [wlx-overlay-s-git](https://aur.archlinux.org/packages/wlx-overlay-s-git).
|
||||
@@ -92,18 +92,14 @@ We would like to work with you and include additional bindings.
|
||||
|
||||
# Known Issues
|
||||
|
||||
## SteamVR: laser pointers not visible
|
||||
## Modifiers get stuck in weird ways
|
||||
|
||||
This seems to be a rare issue with SteamVR startup overlays. Restarting the overlay will fix the issue.
|
||||
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.
|
||||
|
||||
## WiVRn support
|
||||
|
||||
While WiVRn technically supports EXTX_overlay, I do not recommend using this software with WiVRn at this time, due to WiVRn not being optimized for overlay apps. You will likely get ghosting or stuttering while rotating your head.
|
||||
|
||||
## X11 limitations
|
||||
|
||||
DPI scaling and upright screens are not supported on X11. These might display incorrectly or mess up your mouse position.
|
||||
+64
-7
@@ -1,5 +1,6 @@
|
||||
use std::{
|
||||
collections::{BinaryHeap, VecDeque},
|
||||
f32::consts::PI,
|
||||
sync::Arc,
|
||||
time::Instant,
|
||||
};
|
||||
@@ -7,7 +8,7 @@ use std::{
|
||||
#[cfg(feature = "openxr")]
|
||||
use openxr as xr;
|
||||
|
||||
use glam::{Affine3A, Vec2, Vec3A};
|
||||
use glam::{Affine3A, Vec2, Vec3A, Vec3Swizzles};
|
||||
use idmap::IdMap;
|
||||
use serde::Deserialize;
|
||||
use thiserror::Error;
|
||||
@@ -252,23 +253,79 @@ impl TaskContainer {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn raycast(
|
||||
pub fn raycast_plane(
|
||||
source: &Affine3A,
|
||||
source_fwd: Vec3A,
|
||||
plane: &Affine3A,
|
||||
plane_norm: Vec3A,
|
||||
) -> Option<(Vec3A, f32)> {
|
||||
) -> Option<(f32, Vec2)> {
|
||||
let plane_normal = plane.transform_vector3a(plane_norm);
|
||||
let ray_dir = source.transform_vector3a(source_fwd);
|
||||
|
||||
let d = plane.translation.dot(-plane_normal);
|
||||
let dist = -(d + source.translation.dot(plane_normal)) / ray_dir.dot(plane_normal);
|
||||
|
||||
if dist < 0.0 {
|
||||
// plane is behind the caster
|
||||
let hit_local = plane
|
||||
.inverse()
|
||||
.transform_point3a(source.translation + ray_dir * dist)
|
||||
.xy();
|
||||
|
||||
Some((dist, hit_local))
|
||||
}
|
||||
|
||||
pub fn raycast_cylinder(
|
||||
source: &Affine3A,
|
||||
source_fwd: Vec3A,
|
||||
plane: &Affine3A,
|
||||
curvature: f32,
|
||||
) -> Option<(f32, Vec2)> {
|
||||
// this is solved locally; (0,0) is the center of the cylinder, and the cylinder is aligned with the Y axis
|
||||
let size = plane.x_axis.length();
|
||||
let to_local = Affine3A {
|
||||
matrix3: plane.matrix3.mul_scalar(1.0 / size),
|
||||
translation: plane.translation,
|
||||
}
|
||||
.inverse();
|
||||
|
||||
let r = size / (2.0 * PI * curvature);
|
||||
|
||||
let ray_dir = to_local.transform_vector3a(source.transform_vector3a(source_fwd));
|
||||
let ray_origin = to_local.transform_point3a(source.translation) + Vec3A::NEG_Z * r;
|
||||
|
||||
let d = ray_dir.xz();
|
||||
let s = ray_origin.xz();
|
||||
|
||||
let a = d.dot(d);
|
||||
let b = d.dot(s);
|
||||
let c = s.dot(s) - r * r;
|
||||
|
||||
let d = (b * b) - (a * c);
|
||||
if d < f32::EPSILON {
|
||||
return None;
|
||||
}
|
||||
|
||||
let hit_pos = source.translation + ray_dir * dist;
|
||||
Some((hit_pos, dist))
|
||||
let sqrt_d = d.sqrt();
|
||||
|
||||
let t1 = (-b - sqrt_d) / a;
|
||||
let t2 = (-b + sqrt_d) / a;
|
||||
|
||||
let t = t1.max(t2);
|
||||
|
||||
if t < f32::EPSILON {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut hit_local = ray_origin + ray_dir * t;
|
||||
if hit_local.z > 0.0 {
|
||||
// hitting the opposite half of the cylinder
|
||||
return None;
|
||||
}
|
||||
|
||||
let max_angle = 2.0 * (size / (2.0 * r));
|
||||
let x_angle = (hit_local.x / r).asin();
|
||||
|
||||
hit_local.x = x_angle / max_angle;
|
||||
hit_local.y /= size;
|
||||
|
||||
Some((t, hit_local.xy()))
|
||||
}
|
||||
+52
-14
@@ -9,7 +9,7 @@ use smallvec::{smallvec, SmallVec};
|
||||
use crate::state::AppState;
|
||||
|
||||
use super::{
|
||||
common::{raycast, OverlayContainer},
|
||||
common::{raycast_cylinder, raycast_plane, OverlayContainer},
|
||||
overlay::OverlayData,
|
||||
};
|
||||
|
||||
@@ -71,6 +71,9 @@ impl InputState {
|
||||
if hand.now.space_drag != hand.before.space_drag {
|
||||
log::debug!("Hand {}: space_drag {}", hand.idx, hand.now.space_drag);
|
||||
}
|
||||
if hand.now.space_rotate != hand.before.space_rotate {
|
||||
log::debug!("Hand {}: space_rotate {}", hand.idx, hand.now.space_rotate);
|
||||
}
|
||||
if hand.now.click_modifier_right != hand.before.click_modifier_right {
|
||||
log::debug!(
|
||||
"Hand {}: click_modifier_right {}",
|
||||
@@ -189,6 +192,7 @@ pub struct PointerState {
|
||||
pub alt_click: bool,
|
||||
pub show_hide: bool,
|
||||
pub space_drag: bool,
|
||||
pub space_rotate: bool,
|
||||
pub click_modifier_right: bool,
|
||||
pub click_modifier_middle: bool,
|
||||
}
|
||||
@@ -230,7 +234,8 @@ impl InteractionHandler for DummyInteractionHandler {
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
struct RayHit {
|
||||
overlay: usize,
|
||||
hit_pos: Vec3A,
|
||||
global_pos: Vec3A,
|
||||
local_pos: Vec2,
|
||||
dist: f32,
|
||||
}
|
||||
|
||||
@@ -358,7 +363,28 @@ where
|
||||
|
||||
if pointer.now.scroll.abs() > 0.1 {
|
||||
let scroll = pointer.now.scroll;
|
||||
hovered.backend.on_scroll(app, &hit, scroll);
|
||||
if app.input_state.pointers[1 - idx]
|
||||
.interaction
|
||||
.grabbed
|
||||
.is_some_and(|x| x.grabbed_id == hit.overlay)
|
||||
{
|
||||
let is_portrait = hovered.view().is_some_and(|v| {
|
||||
let extent = v.image().extent();
|
||||
extent[0] >= extent[1]
|
||||
});
|
||||
|
||||
if is_portrait {
|
||||
let cur = hovered.state.curvature.unwrap_or(0.0);
|
||||
let new = (cur - scroll * 0.01).min(0.35);
|
||||
if new <= f32::EPSILON {
|
||||
hovered.state.curvature = None;
|
||||
} else {
|
||||
hovered.state.curvature = Some(new);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
hovered.backend.on_scroll(app, &hit, scroll);
|
||||
}
|
||||
pointer = &mut app.input_state.pointers[idx];
|
||||
}
|
||||
|
||||
@@ -389,7 +415,11 @@ impl Pointer {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(hit) = self.ray_test(overlay.state.id, &overlay.state.transform) {
|
||||
if let Some(hit) = self.ray_test(
|
||||
overlay.state.id,
|
||||
&overlay.state.transform,
|
||||
&overlay.state.curvature,
|
||||
) {
|
||||
if hit.dist.is_infinite() || hit.dist.is_nan() {
|
||||
continue;
|
||||
}
|
||||
@@ -404,12 +434,8 @@ impl Pointer {
|
||||
|
||||
let uv = overlay
|
||||
.state
|
||||
.transform
|
||||
.inverse()
|
||||
.transform_point3a(hit.hit_pos)
|
||||
.truncate();
|
||||
|
||||
let uv = overlay.state.interaction_transform.transform_point2(uv);
|
||||
.interaction_transform
|
||||
.transform_point2(hit.local_pos);
|
||||
|
||||
if uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0 {
|
||||
continue;
|
||||
@@ -485,14 +511,26 @@ impl Pointer {
|
||||
}
|
||||
}
|
||||
|
||||
fn ray_test(&self, overlay: usize, plane: &Affine3A) -> Option<RayHit> {
|
||||
let Some((hit_pos, dist)) = raycast(&self.pose, Vec3A::NEG_Z, plane, Vec3A::NEG_Z) else {
|
||||
fn ray_test(
|
||||
&self,
|
||||
overlay: usize,
|
||||
transform: &Affine3A,
|
||||
curvature: &Option<f32>,
|
||||
) -> Option<RayHit> {
|
||||
let (dist, local_pos) = match curvature {
|
||||
Some(curvature) => raycast_cylinder(&self.pose, Vec3A::NEG_Z, transform, *curvature),
|
||||
_ => raycast_plane(&self.pose, Vec3A::NEG_Z, transform, Vec3A::NEG_Z),
|
||||
}?;
|
||||
|
||||
if dist < 0.0 {
|
||||
// hit is behind us
|
||||
return None;
|
||||
};
|
||||
}
|
||||
|
||||
Some(RayHit {
|
||||
overlay,
|
||||
hit_pos,
|
||||
global_pos: self.pose.transform_point3a(Vec3A::NEG_Z * dist),
|
||||
local_pos,
|
||||
dist,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ use dbus::{blocking::Connection, channel::MatchingReceiver, message::MatchRule};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::{mpsc, Arc},
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
mpsc, Arc,
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
@@ -12,6 +15,7 @@ pub struct NotificationManager {
|
||||
rx_toast: mpsc::Receiver<Toast>,
|
||||
tx_toast: mpsc::SyncSender<Toast>,
|
||||
dbus_data: Option<Connection>,
|
||||
running: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl NotificationManager {
|
||||
@@ -21,6 +25,7 @@ impl NotificationManager {
|
||||
rx_toast,
|
||||
tx_toast,
|
||||
dbus_data: None,
|
||||
running: Arc::new(AtomicBool::new(true)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +130,7 @@ impl NotificationManager {
|
||||
|
||||
pub fn run_udp(&mut self) {
|
||||
let sender = self.tx_toast.clone();
|
||||
// NOTE: We're detaching the thread, as there's no simple way to gracefully stop it other than app shutdown.
|
||||
let running = self.running.clone();
|
||||
let _ = std::thread::spawn(move || {
|
||||
let addr = "127.0.0.1:42069";
|
||||
let socket = match std::net::UdpSocket::bind(addr) {
|
||||
@@ -135,9 +140,13 @@ impl NotificationManager {
|
||||
return;
|
||||
}
|
||||
};
|
||||
if let Err(err) = socket.set_read_timeout(Some(Duration::from_millis(200))) {
|
||||
log::error!("Failed to set read timeout: {:?}", err);
|
||||
}
|
||||
|
||||
let mut buf = [0u8; 1024 * 16]; // vrcx embeds icons as b64
|
||||
|
||||
loop {
|
||||
while running.load(Ordering::Relaxed) {
|
||||
if let Ok((num_bytes, _)) = socket.recv_from(&mut buf) {
|
||||
let json_str = match std::str::from_utf8(&buf[..num_bytes]) {
|
||||
Ok(s) => s,
|
||||
@@ -170,10 +179,17 @@ impl NotificationManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
log::info!("Notification listener stopped.");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for NotificationManager {
|
||||
fn drop(&mut self) {
|
||||
self.running.store(false, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_dbus(msg: &dbus::Message) -> anyhow::Result<Toast> {
|
||||
let mut args = msg.iter_init();
|
||||
let app_name: String = args.read()?;
|
||||
|
||||
@@ -36,6 +36,7 @@ const PATH_SCROLL: &str = "/actions/default/in/Scroll";
|
||||
const PATH_ALT_CLICK: &str = "/actions/default/in/AltClick";
|
||||
const PATH_SHOW_HIDE: &str = "/actions/default/in/ShowHide";
|
||||
const PATH_SPACE_DRAG: &str = "/actions/default/in/SpaceDrag";
|
||||
const PATH_SPACE_ROTATE: &str = "/actions/default/in/SpaceRotate";
|
||||
const PATH_CLICK_MODIFIER_RIGHT: &str = "/actions/default/in/ClickModifierRight";
|
||||
const PATH_CLICK_MODIFIER_MIDDLE: &str = "/actions/default/in/ClickModifierMiddle";
|
||||
|
||||
@@ -50,6 +51,7 @@ pub(super) struct OpenVrInputSource {
|
||||
alt_click_hnd: ActionHandle,
|
||||
show_hide_hnd: ActionHandle,
|
||||
space_drag_hnd: ActionHandle,
|
||||
space_rotate_hnd: ActionHandle,
|
||||
click_modifier_right_hnd: ActionHandle,
|
||||
click_modifier_middle_hnd: ActionHandle,
|
||||
}
|
||||
@@ -72,6 +74,7 @@ impl OpenVrInputSource {
|
||||
let alt_click_hnd = input.get_action_handle(PATH_ALT_CLICK)?;
|
||||
let show_hide_hnd = input.get_action_handle(PATH_SHOW_HIDE)?;
|
||||
let space_drag_hnd = input.get_action_handle(PATH_SPACE_DRAG)?;
|
||||
let space_rotate_hnd = input.get_action_handle(PATH_SPACE_ROTATE)?;
|
||||
let click_modifier_right_hnd = input.get_action_handle(PATH_CLICK_MODIFIER_RIGHT)?;
|
||||
let click_modifier_middle_hnd = input.get_action_handle(PATH_CLICK_MODIFIER_MIDDLE)?;
|
||||
|
||||
@@ -106,6 +109,7 @@ impl OpenVrInputSource {
|
||||
alt_click_hnd,
|
||||
show_hide_hnd,
|
||||
space_drag_hnd,
|
||||
space_rotate_hnd,
|
||||
click_modifier_right_hnd,
|
||||
click_modifier_middle_hnd,
|
||||
hands,
|
||||
@@ -193,6 +197,11 @@ impl OpenVrInputSource {
|
||||
.map(|x| x.0.bState)
|
||||
.unwrap_or(false);
|
||||
|
||||
app_hand.now.space_rotate = input
|
||||
.get_digital_action_data(self.space_rotate_hnd, hand.input_hnd)
|
||||
.map(|x| x.0.bState)
|
||||
.unwrap_or(false);
|
||||
|
||||
app_hand.now.click_modifier_right = input
|
||||
.get_digital_action_data(self.click_modifier_right_hnd, hand.input_hnd)
|
||||
.map(|x| x.0.bState)
|
||||
@@ -302,9 +311,7 @@ fn get_tracked_device(
|
||||
pub fn set_action_manifest(input: &mut InputManager) -> anyhow::Result<()> {
|
||||
let action_path = CONFIG_ROOT_PATH.join("actions.json");
|
||||
|
||||
if !action_path.is_file() {
|
||||
File::create(&action_path)?.write_all(include_bytes!("../../res/actions.json"))?;
|
||||
}
|
||||
File::create(&action_path)?.write_all(include_bytes!("../../res/actions.json"))?;
|
||||
|
||||
let binding_path = CONFIG_ROOT_PATH.join("actions_binding_knuckles.json");
|
||||
if !binding_path.is_file() {
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::sync::Arc;
|
||||
use glam::{Affine3A, Vec3, Vec3A, Vec4};
|
||||
use idmap::IdMap;
|
||||
use ovr_overlay::overlay::OverlayManager;
|
||||
use ovr_overlay::sys::ETrackingUniverseOrigin;
|
||||
use vulkano::command_buffer::CommandBufferUsage;
|
||||
use vulkano::format::Format;
|
||||
use vulkano::image::view::ImageView;
|
||||
@@ -74,6 +75,7 @@ impl LinePool {
|
||||
}),
|
||||
data: OpenVrOverlayData {
|
||||
width: 0.002,
|
||||
override_width: true,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
@@ -141,6 +143,7 @@ impl LinePool {
|
||||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
universe: ETrackingUniverseOrigin,
|
||||
overlay: &mut OverlayManager,
|
||||
app: &mut AppState,
|
||||
) -> anyhow::Result<()> {
|
||||
@@ -152,12 +155,18 @@ impl LinePool {
|
||||
data.state.dirty = false;
|
||||
}
|
||||
|
||||
data.upload_transform(overlay);
|
||||
data.upload_transform(universe.clone(), overlay);
|
||||
data.upload_color(overlay);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn mark_dirty(&mut self) {
|
||||
for data in self.lines.values_mut() {
|
||||
data.state.dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct StaticRenderer {
|
||||
|
||||
@@ -158,7 +158,8 @@ pub fn openvr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
|
||||
}
|
||||
EVREventType::VREvent_SeatedZeroPoseReset
|
||||
| EVREventType::VREvent_StandingZeroPoseReset
|
||||
| EVREventType::VREvent_ChaperoneUniverseHasChanged => {
|
||||
| EVREventType::VREvent_ChaperoneUniverseHasChanged
|
||||
| EVREventType::VREvent_SceneApplicationChanged => {
|
||||
playspace.playspace_changed(&mut compositor_mgr, &mut chaperone_mgr);
|
||||
}
|
||||
_ => {}
|
||||
@@ -213,15 +214,17 @@ pub fn openvr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
|
||||
playspace.fix_floor(&mut chaperone_mgr, &state.input_state);
|
||||
}
|
||||
SystemTask::ResetPlayspace => {
|
||||
playspace.reset_offset(&mut chaperone_mgr);
|
||||
playspace.reset_offset(&mut chaperone_mgr, &state.input_state);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let universe = playspace.get_universe();
|
||||
|
||||
state.input_state.pre_update();
|
||||
input_source.update(
|
||||
playspace.get_universe(),
|
||||
universe.clone(),
|
||||
&mut input_mgr,
|
||||
&mut system_mgr,
|
||||
&mut state,
|
||||
@@ -234,6 +237,7 @@ pub fn openvr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
|
||||
.iter()
|
||||
.any(|p| p.now.show_hide && !p.before.show_hide)
|
||||
{
|
||||
lines.mark_dirty(); // workaround to prevent lines from not showing
|
||||
overlays.show_hide(&mut state);
|
||||
}
|
||||
|
||||
@@ -258,7 +262,7 @@ pub fn openvr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
|
||||
}
|
||||
}
|
||||
|
||||
lines.update(&mut overlay_mgr, &mut state)?;
|
||||
lines.update(universe.clone(), &mut overlay_mgr, &mut state)?;
|
||||
|
||||
for o in overlays.iter_mut() {
|
||||
o.after_input(&mut overlay_mgr, &mut state)?;
|
||||
@@ -281,7 +285,7 @@ pub fn openvr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
|
||||
|
||||
overlays
|
||||
.iter_mut()
|
||||
.for_each(|o| o.after_render(&mut overlay_mgr, &state.graphics));
|
||||
.for_each(|o| o.after_render(universe.clone(), &mut overlay_mgr, &state.graphics));
|
||||
|
||||
// chaperone
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use core::f32;
|
||||
|
||||
use glam::Vec4;
|
||||
use ovr_overlay::{
|
||||
overlay::{OverlayHandle, OverlayManager},
|
||||
@@ -21,9 +23,9 @@ pub(super) struct OpenVrOverlayData {
|
||||
pub(super) last_image: Option<u64>,
|
||||
pub(super) visible: bool,
|
||||
pub(super) color: Vec4,
|
||||
pub(super) curvature: f32,
|
||||
pub(super) sort_order: u32,
|
||||
pub(crate) width: f32,
|
||||
pub(super) override_width: bool,
|
||||
pub(super) relative_to: RelativeTo,
|
||||
}
|
||||
|
||||
@@ -79,10 +81,17 @@ impl OverlayData<OpenVrOverlayData> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn after_render(&mut self, overlay: &mut OverlayManager, graphics: &WlxGraphics) {
|
||||
pub(super) fn after_render(
|
||||
&mut self,
|
||||
universe: ETrackingUniverseOrigin,
|
||||
overlay: &mut OverlayManager,
|
||||
graphics: &WlxGraphics,
|
||||
) {
|
||||
if self.data.visible {
|
||||
if self.state.dirty {
|
||||
self.upload_transform(overlay);
|
||||
self.upload_curvature(overlay);
|
||||
|
||||
self.upload_transform(universe, overlay);
|
||||
self.upload_alpha(overlay);
|
||||
self.state.dirty = false;
|
||||
}
|
||||
@@ -166,7 +175,7 @@ impl OverlayData<OpenVrOverlayData> {
|
||||
log::debug!("{}: No overlay handle", self.state.name);
|
||||
return;
|
||||
};
|
||||
if let Err(e) = overlay.set_curvature(handle, self.data.curvature) {
|
||||
if let Err(e) = overlay.set_curvature(handle, self.state.curvature.unwrap_or(0.0)) {
|
||||
log::error!(
|
||||
"{}: Failed to set overlay curvature: {}",
|
||||
self.state.name,
|
||||
@@ -185,7 +194,11 @@ impl OverlayData<OpenVrOverlayData> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn upload_transform(&self, overlay: &mut OverlayManager) {
|
||||
pub(super) fn upload_transform(
|
||||
&self,
|
||||
universe: ETrackingUniverseOrigin,
|
||||
overlay: &mut OverlayManager,
|
||||
) {
|
||||
let Some(handle) = self.data.handle else {
|
||||
log::debug!("{}: No overlay handle", self.state.name);
|
||||
return;
|
||||
@@ -193,11 +206,7 @@ impl OverlayData<OpenVrOverlayData> {
|
||||
|
||||
let transform = Matrix3x4::from_affine(&self.state.transform);
|
||||
|
||||
if let Err(e) = overlay.set_transform_absolute(
|
||||
handle,
|
||||
ETrackingUniverseOrigin::TrackingUniverseStanding,
|
||||
&transform,
|
||||
) {
|
||||
if let Err(e) = overlay.set_transform_absolute(handle, universe, &transform) {
|
||||
log::error!(
|
||||
"{}: Failed to set overlay transform: {}",
|
||||
self.state.name,
|
||||
@@ -228,6 +237,15 @@ impl OverlayData<OpenVrOverlayData> {
|
||||
}
|
||||
|
||||
let dimensions = image.extent();
|
||||
if !self.data.override_width {
|
||||
let new_width = ((dimensions[0] as f32) / (dimensions[1] as f32)).min(1.0);
|
||||
if (new_width - self.data.width).abs() > f32::EPSILON {
|
||||
log::info!("{}: New width {}", self.state.name, new_width);
|
||||
self.data.width = new_width;
|
||||
self.upload_width(overlay);
|
||||
}
|
||||
}
|
||||
|
||||
let format = image.format();
|
||||
|
||||
let mut texture = VRVulkanTextureData_t {
|
||||
|
||||
+143
-24
@@ -1,4 +1,4 @@
|
||||
use glam::{Affine3A, Vec3A};
|
||||
use glam::{Affine3A, Quat, Vec3, Vec3A};
|
||||
use ovr_overlay::{
|
||||
chaperone_setup::ChaperoneSetupManager,
|
||||
compositor::CompositorManager,
|
||||
@@ -12,22 +12,24 @@ use crate::{
|
||||
|
||||
use super::{helpers::Affine3AConvert, overlay::OpenVrOverlayData};
|
||||
|
||||
struct DragData {
|
||||
struct MoverData<T> {
|
||||
pose: Affine3A,
|
||||
hand: usize,
|
||||
hand_pos: Vec3A,
|
||||
hand_pose: T,
|
||||
}
|
||||
|
||||
pub(super) struct PlayspaceMover {
|
||||
universe: ETrackingUniverseOrigin,
|
||||
last: Option<DragData>,
|
||||
drag: Option<MoverData<Vec3A>>,
|
||||
rotate: Option<MoverData<Quat>>,
|
||||
}
|
||||
|
||||
impl PlayspaceMover {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
universe: ETrackingUniverseOrigin::TrackingUniverseRawAndUncalibrated,
|
||||
last: None,
|
||||
drag: None,
|
||||
rotate: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,33 +40,102 @@ impl PlayspaceMover {
|
||||
state: &AppState,
|
||||
) {
|
||||
let universe = self.universe.clone();
|
||||
if let Some(data) = self.last.as_mut() {
|
||||
|
||||
if let Some(data) = self.rotate.as_mut() {
|
||||
let pointer = &state.input_state.pointers[data.hand];
|
||||
if !pointer.now.space_drag {
|
||||
self.last = None;
|
||||
log::info!("End space drag");
|
||||
if !pointer.now.space_rotate {
|
||||
self.rotate = None;
|
||||
log::info!("End space rotate");
|
||||
return;
|
||||
}
|
||||
|
||||
let new_hand =
|
||||
state.input_state.pointers[data.hand].raw_pose.translation + data.pose.translation;
|
||||
let relative_pos = data.pose.transform_vector3a(new_hand - data.hand_pos);
|
||||
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 = 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;
|
||||
|
||||
if self.universe == ETrackingUniverseOrigin::TrackingUniverseStanding {
|
||||
apply_chaperone_transform(space_transform.inverse(), chaperone_mgr);
|
||||
}
|
||||
set_working_copy(&universe, chaperone_mgr, &data.pose);
|
||||
chaperone_mgr.commit_working_copy(EChaperoneConfigFile::EChaperoneConfigFile_Live);
|
||||
} else {
|
||||
for (i, pointer) in state.input_state.pointers.iter().enumerate() {
|
||||
if pointer.now.space_rotate {
|
||||
let Some(mat) = get_working_copy(&universe, chaperone_mgr) else {
|
||||
log::warn!("Can't space rotate - failed to get zero pose");
|
||||
return;
|
||||
};
|
||||
let hand_pose = Quat::from_affine3(&(mat * pointer.raw_pose));
|
||||
self.rotate = Some(MoverData {
|
||||
pose: mat,
|
||||
hand: i,
|
||||
hand_pose,
|
||||
});
|
||||
self.drag = None;
|
||||
log::info!("Start space rotate");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(data) = self.drag.as_mut() {
|
||||
let pointer = &state.input_state.pointers[data.hand];
|
||||
if !pointer.now.space_drag {
|
||||
self.drag = None;
|
||||
log::info!("End space drag");
|
||||
return;
|
||||
}
|
||||
|
||||
let new_hand = data
|
||||
.pose
|
||||
.transform_point3a(state.input_state.pointers[data.hand].raw_pose.translation);
|
||||
let relative_pos = new_hand - data.hand_pose;
|
||||
|
||||
if relative_pos.length_squared() > 1000.0 {
|
||||
log::warn!("Space drag too fast, ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
let overlay_offset = data.pose.inverse().transform_vector3a(relative_pos) * -1.0;
|
||||
|
||||
overlays.iter_mut().for_each(|overlay| {
|
||||
if overlay.state.grabbable {
|
||||
overlay.state.dirty = true;
|
||||
overlay.state.transform.translation -= relative_pos;
|
||||
overlay.state.transform.translation += overlay_offset;
|
||||
}
|
||||
});
|
||||
|
||||
data.pose.translation += relative_pos;
|
||||
data.hand_pos = new_hand;
|
||||
data.hand_pose = new_hand;
|
||||
|
||||
if self.universe == ETrackingUniverseOrigin::TrackingUniverseStanding {
|
||||
apply_chaperone_offset(overlay_offset, chaperone_mgr);
|
||||
}
|
||||
set_working_copy(&universe, chaperone_mgr, &data.pose);
|
||||
chaperone_mgr.commit_working_copy(EChaperoneConfigFile::EChaperoneConfigFile_Live);
|
||||
} else {
|
||||
@@ -74,26 +145,45 @@ impl PlayspaceMover {
|
||||
log::warn!("Can't space drag - failed to get zero pose");
|
||||
return;
|
||||
};
|
||||
let hand_pos = pointer.raw_pose.translation + mat.translation;
|
||||
self.last = Some(DragData {
|
||||
let hand_pos = mat.transform_point3a(pointer.raw_pose.translation);
|
||||
self.drag = Some(MoverData {
|
||||
pose: mat,
|
||||
hand: i,
|
||||
hand_pos,
|
||||
hand_pose: hand_pos,
|
||||
});
|
||||
self.rotate = None;
|
||||
log::info!("Start space drag");
|
||||
break;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_offset(&mut self, chaperone_mgr: &mut ChaperoneSetupManager) {
|
||||
set_working_copy(&self.universe, chaperone_mgr, &Affine3A::IDENTITY);
|
||||
pub fn reset_offset(&mut self, chaperone_mgr: &mut ChaperoneSetupManager, input: &InputState) {
|
||||
let mut height = 1.6;
|
||||
if let Some(mat) = get_working_copy(&self.universe, chaperone_mgr) {
|
||||
height = input.hmd.translation.y - mat.translation.y;
|
||||
if self.universe == ETrackingUniverseOrigin::TrackingUniverseStanding {
|
||||
apply_chaperone_transform(mat, chaperone_mgr);
|
||||
}
|
||||
}
|
||||
|
||||
let xform = if self.universe == ETrackingUniverseOrigin::TrackingUniverseSeated {
|
||||
Affine3A::from_translation(Vec3::NEG_Y * height)
|
||||
} else {
|
||||
Affine3A::IDENTITY
|
||||
};
|
||||
|
||||
set_working_copy(&self.universe, chaperone_mgr, &xform);
|
||||
chaperone_mgr.commit_working_copy(EChaperoneConfigFile::EChaperoneConfigFile_Live);
|
||||
|
||||
if self.last.is_some() {
|
||||
if self.drag.is_some() {
|
||||
log::info!("Space drag interrupted by manual reset");
|
||||
self.last = None;
|
||||
self.drag = None;
|
||||
}
|
||||
if self.rotate.is_some() {
|
||||
log::info!("Space rotate interrupted by manual reset");
|
||||
self.rotate = None;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +194,9 @@ impl PlayspaceMover {
|
||||
log::warn!("Can't fix floor - failed to get zero pose");
|
||||
return;
|
||||
};
|
||||
mat.translation.y += y1.min(y2) - 0.03;
|
||||
let offset = y1.min(y2) - 0.03;
|
||||
mat.translation.y += offset;
|
||||
|
||||
set_working_copy(&self.universe, chaperone_mgr, &mat);
|
||||
chaperone_mgr.commit_working_copy(EChaperoneConfigFile::EChaperoneConfigFile_Live);
|
||||
}
|
||||
@@ -124,9 +216,13 @@ impl PlayspaceMover {
|
||||
self.universe = new_universe;
|
||||
}
|
||||
|
||||
if self.last.is_some() {
|
||||
if self.drag.is_some() {
|
||||
log::info!("Space drag interrupted by external change");
|
||||
self.last = None;
|
||||
self.drag = None;
|
||||
}
|
||||
if self.rotate.is_some() {
|
||||
log::info!("Space rotate interrupted by external change");
|
||||
self.rotate = None;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,3 +266,26 @@ fn set_working_copy(
|
||||
_ => chaperone_mgr.set_working_seated_zero_pose_to_raw_tracking_pose(&mat),
|
||||
};
|
||||
}
|
||||
|
||||
fn apply_chaperone_offset(offset: Vec3A, chaperone_mgr: &mut ChaperoneSetupManager) {
|
||||
let mut quads = chaperone_mgr.get_live_collision_bounds_info();
|
||||
quads.iter_mut().for_each(|quad| {
|
||||
quad.vCorners.iter_mut().for_each(|corner| {
|
||||
corner.v[0] += offset.x;
|
||||
corner.v[2] += offset.z;
|
||||
});
|
||||
});
|
||||
chaperone_mgr.set_working_collision_bounds_info(quads.as_mut_slice());
|
||||
}
|
||||
|
||||
fn apply_chaperone_transform(transform: Affine3A, chaperone_mgr: &mut ChaperoneSetupManager) {
|
||||
let mut quads = chaperone_mgr.get_live_collision_bounds_info();
|
||||
quads.iter_mut().for_each(|quad| {
|
||||
quad.vCorners.iter_mut().for_each(|corner| {
|
||||
let coord = transform.transform_point3a(Vec3A::from_slice(&corner.v));
|
||||
corner.v[0] = coord.x;
|
||||
corner.v[2] = coord.z;
|
||||
});
|
||||
});
|
||||
chaperone_mgr.set_working_collision_bounds_info(quads.as_mut_slice());
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
use anyhow::{bail, ensure};
|
||||
use glam::{Affine3A, Quat, Vec3};
|
||||
use glam::{Affine3A, Quat, Vec3, Vec3A};
|
||||
use openxr as xr;
|
||||
use xr::OverlaySessionCreateFlagsEXTX;
|
||||
|
||||
pub(super) fn init_xr() -> Result<(xr::Instance, xr::SystemId), anyhow::Error> {
|
||||
let Ok(entry) = (unsafe { xr::Entry::load() }) else {
|
||||
bail!("OpenXR Loader not found.");
|
||||
};
|
||||
let entry = xr::Entry::linked();
|
||||
|
||||
let Ok(available_extensions) = entry.enumerate_extensions() else {
|
||||
bail!("Failed to enumerate OpenXR extensions.");
|
||||
@@ -141,12 +139,14 @@ fn quat_lerp(a: Quat, mut b: Quat, t: f32) -> Quat {
|
||||
.normalize()
|
||||
}
|
||||
|
||||
pub(super) fn transform_to_posef(transform: &Affine3A) -> xr::Posef {
|
||||
let translation = transform.translation;
|
||||
pub(super) fn transform_to_norm_quat(transform: &Affine3A) -> Quat {
|
||||
let norm_mat3 = transform
|
||||
.matrix3
|
||||
.mul_scalar(1.0 / transform.matrix3.x_axis.length());
|
||||
let mut rotation = Quat::from_mat3a(&norm_mat3).normalize();
|
||||
Quat::from_mat3a(&norm_mat3).normalize()
|
||||
}
|
||||
|
||||
pub(super) fn translation_rotation_to_posef(translation: Vec3A, mut rotation: Quat) -> xr::Posef {
|
||||
if !rotation.is_finite() {
|
||||
rotation = Quat::IDENTITY;
|
||||
}
|
||||
@@ -165,3 +165,9 @@ pub(super) fn transform_to_posef(transform: &Affine3A) -> xr::Posef {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn transform_to_posef(transform: &Affine3A) -> xr::Posef {
|
||||
let translation = transform.translation;
|
||||
let rotation = transform_to_norm_quat(transform);
|
||||
translation_rotation_to_posef(translation, rotation)
|
||||
}
|
||||
+35
-23
@@ -5,7 +5,7 @@ use openxr as xr;
|
||||
|
||||
use crate::{
|
||||
backend::input::{Haptics, Pointer},
|
||||
state::AppState,
|
||||
state::{AppSession, AppState},
|
||||
};
|
||||
|
||||
use super::XrState;
|
||||
@@ -98,12 +98,7 @@ impl OpenXrInputSource {
|
||||
xr.session.sync_actions(&[(&self.action_set).into()])?;
|
||||
|
||||
for i in 0..2 {
|
||||
self.hands[i].update(
|
||||
&mut state.input_state.pointers[i],
|
||||
&xr.stage,
|
||||
&xr.session,
|
||||
xr.predicted_display_time,
|
||||
)?;
|
||||
self.hands[i].update(&mut state.input_state.pointers[i], xr, &state.session)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -123,11 +118,10 @@ impl OpenXrHand {
|
||||
pub(super) fn update(
|
||||
&self,
|
||||
pointer: &mut Pointer,
|
||||
stage: &xr::Space,
|
||||
session: &XrSession,
|
||||
time: xr::Time,
|
||||
xr: &XrState,
|
||||
session: &AppSession,
|
||||
) -> Result<(), xr::sys::Result> {
|
||||
let location = self.space.locate(stage, time)?;
|
||||
let location = self.space.locate(&xr.stage, xr.predicted_display_time)?;
|
||||
if location
|
||||
.location_flags
|
||||
.contains(xr::SpaceLocationFlags::ORIENTATION_VALID)
|
||||
@@ -137,49 +131,67 @@ impl OpenXrHand {
|
||||
pointer.pose = Affine3A::from_rotation_translation(quat, pos);
|
||||
}
|
||||
|
||||
let click_sensitivity = if pointer.before.click {
|
||||
session.config.xr_click_sensitivity_release
|
||||
} else {
|
||||
session.config.xr_click_sensitivity
|
||||
};
|
||||
|
||||
pointer.now.click = self
|
||||
.source
|
||||
.action_click
|
||||
.state(session, xr::Path::NULL)?
|
||||
.state(&xr.session, xr::Path::NULL)?
|
||||
.current_state
|
||||
> 0.7;
|
||||
> click_sensitivity;
|
||||
|
||||
let grab_sensitivity = if pointer.before.grab {
|
||||
session.config.xr_grab_sensitivity_release
|
||||
} else {
|
||||
session.config.xr_grab_sensitivity
|
||||
};
|
||||
|
||||
pointer.now.grab = self
|
||||
.source
|
||||
.action_grab
|
||||
.state(session, xr::Path::NULL)?
|
||||
.state(&xr.session, xr::Path::NULL)?
|
||||
.current_state
|
||||
> 0.7;
|
||||
> grab_sensitivity;
|
||||
|
||||
pointer.now.scroll = self
|
||||
.source
|
||||
.action_scroll
|
||||
.state(session, xr::Path::NULL)?
|
||||
.state(&xr.session, xr::Path::NULL)?
|
||||
.current_state;
|
||||
|
||||
let alt_click_sensitivity = if pointer.before.alt_click {
|
||||
session.config.xr_alt_click_sensitivity_release
|
||||
} else {
|
||||
session.config.xr_alt_click_sensitivity
|
||||
};
|
||||
|
||||
pointer.now.alt_click = self
|
||||
.source
|
||||
.action_alt_click
|
||||
.state(session, xr::Path::NULL)?
|
||||
.state(&xr.session, xr::Path::NULL)?
|
||||
.current_state
|
||||
> 0.7;
|
||||
> alt_click_sensitivity;
|
||||
|
||||
pointer.now.show_hide = self
|
||||
.source
|
||||
.action_show_hide
|
||||
.state(session, xr::Path::NULL)?
|
||||
.state(&xr.session, xr::Path::NULL)?
|
||||
.current_state;
|
||||
|
||||
pointer.now.click_modifier_right = self
|
||||
.source
|
||||
.action_click_modifier_right
|
||||
.state(session, xr::Path::NULL)?
|
||||
.state(&xr.session, xr::Path::NULL)?
|
||||
.current_state;
|
||||
|
||||
pointer.now.click_modifier_middle = self
|
||||
.source
|
||||
.action_click_modifier_middle
|
||||
.state(session, xr::Path::NULL)?
|
||||
.state(&xr.session, xr::Path::NULL)?
|
||||
.current_state;
|
||||
|
||||
Ok(())
|
||||
@@ -380,11 +392,11 @@ fn suggest_bindings(
|
||||
),
|
||||
xr::Binding::new(
|
||||
&hands[0].action_grab,
|
||||
instance.string_to_path("/user/hand/left/input/squeeze/value")?,
|
||||
instance.string_to_path("/user/hand/left/input/squeeze/force")?,
|
||||
),
|
||||
xr::Binding::new(
|
||||
&hands[1].action_grab,
|
||||
instance.string_to_path("/user/hand/right/input/squeeze/value")?,
|
||||
instance.string_to_path("/user/hand/right/input/squeeze/force")?,
|
||||
),
|
||||
xr::Binding::new(
|
||||
&hands[0].action_scroll,
|
||||
|
||||
@@ -19,7 +19,7 @@ use crate::{
|
||||
|
||||
use super::{
|
||||
swapchain::{create_swapchain_render_data, SwapchainRenderData},
|
||||
XrState,
|
||||
CompositionLayer, XrState,
|
||||
};
|
||||
|
||||
static AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(1);
|
||||
@@ -135,7 +135,7 @@ impl LinePool {
|
||||
&'a mut self,
|
||||
xr: &'a XrState,
|
||||
command_buffer: &mut WlxCommandBuffer,
|
||||
) -> anyhow::Result<Vec<xr::CompositionLayerQuad<xr::Vulkan>>> {
|
||||
) -> anyhow::Result<Vec<CompositionLayer>> {
|
||||
let mut quads = Vec::new();
|
||||
|
||||
for line in self.lines.values_mut() {
|
||||
@@ -155,7 +155,7 @@ impl LinePool {
|
||||
height: inner.length,
|
||||
});
|
||||
|
||||
quads.push(quad);
|
||||
quads.push(CompositionLayer::Quad(quad));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -275,13 +275,18 @@ pub fn openxr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(quad) = o.present_xr(&xr_state, &mut command_buffer)? {
|
||||
layers.push((dist_sq, quad));
|
||||
};
|
||||
let maybe_layer = o.present_xr(&xr_state, &mut command_buffer)?;
|
||||
if let CompositionLayer::None = maybe_layer {
|
||||
continue;
|
||||
}
|
||||
layers.push((dist_sq, maybe_layer));
|
||||
}
|
||||
|
||||
for quad in lines.present_xr(&xr_state, &mut command_buffer)? {
|
||||
layers.push((0.0, quad));
|
||||
for maybe_layer in lines.present_xr(&xr_state, &mut command_buffer)? {
|
||||
if let CompositionLayer::None = maybe_layer {
|
||||
continue;
|
||||
}
|
||||
layers.push((0.0, maybe_layer));
|
||||
}
|
||||
|
||||
command_buffer.build_and_execute_now()?;
|
||||
@@ -290,7 +295,11 @@ pub fn openxr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
|
||||
|
||||
let frame_ref = layers
|
||||
.iter()
|
||||
.map(|f| &f.1 as &xr::CompositionLayerBase<xr::Vulkan>)
|
||||
.map(|f| match f.1 {
|
||||
CompositionLayer::Quad(ref l) => l as &xr::CompositionLayerBase<xr::Vulkan>,
|
||||
CompositionLayer::Cylinder(ref l) => l as &xr::CompositionLayerBase<xr::Vulkan>,
|
||||
CompositionLayer::None => unreachable!(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
frame_stream.end(
|
||||
@@ -351,3 +360,9 @@ pub fn openxr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) enum CompositionLayer<'a> {
|
||||
None,
|
||||
Quad(xr::CompositionLayerQuad<'a, xr::Vulkan>),
|
||||
Cylinder(xr::CompositionLayerCylinderKHR<'a, xr::Vulkan>),
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
use glam::Vec3A;
|
||||
use openxr as xr;
|
||||
use std::sync::Arc;
|
||||
use std::{f32::consts::PI, sync::Arc};
|
||||
use xr::{CompositionLayerFlags, EyeVisibility};
|
||||
|
||||
use super::{helpers, swapchain::SwapchainRenderData, XrState};
|
||||
use super::{helpers, swapchain::SwapchainRenderData, CompositionLayer, XrState};
|
||||
use crate::{
|
||||
backend::{openxr::swapchain::create_swapchain_render_data, overlay::OverlayData},
|
||||
graphics::WlxCommandBuffer,
|
||||
@@ -23,7 +24,7 @@ impl OverlayData<OpenXrOverlayData> {
|
||||
&'a mut self,
|
||||
xr: &'a XrState,
|
||||
command_buffer: &mut WlxCommandBuffer,
|
||||
) -> anyhow::Result<Option<xr::CompositionLayerQuad<xr::Vulkan>>> {
|
||||
) -> anyhow::Result<CompositionLayer> {
|
||||
if let Some(new_view) = self.view() {
|
||||
self.data.last_view = Some(new_view);
|
||||
}
|
||||
@@ -32,7 +33,7 @@ impl OverlayData<OpenXrOverlayData> {
|
||||
view.clone()
|
||||
} else {
|
||||
log::warn!("{}: Will not show - image not ready", self.state.name);
|
||||
return Ok(None);
|
||||
return Ok(CompositionLayer::None);
|
||||
};
|
||||
let extent = my_view.image().extent();
|
||||
|
||||
@@ -55,10 +56,8 @@ impl OverlayData<OpenXrOverlayData> {
|
||||
};
|
||||
|
||||
let sub_image = data.acquire_present_release(command_buffer, my_view, self.state.alpha)?;
|
||||
let posef = helpers::transform_to_posef(&self.state.transform);
|
||||
|
||||
let aspect_ratio = extent[1] as f32 / extent[0] as f32;
|
||||
|
||||
let (scale_x, scale_y) = if aspect_ratio < 1.0 {
|
||||
let major = self.state.transform.matrix3.col(0).length();
|
||||
(major, major * aspect_ratio)
|
||||
@@ -67,17 +66,38 @@ impl OverlayData<OpenXrOverlayData> {
|
||||
(major / aspect_ratio, major)
|
||||
};
|
||||
|
||||
let quad = xr::CompositionLayerQuad::new()
|
||||
.pose(posef)
|
||||
.sub_image(sub_image)
|
||||
.eye_visibility(EyeVisibility::BOTH)
|
||||
.layer_flags(CompositionLayerFlags::CORRECT_CHROMATIC_ABERRATION)
|
||||
.space(&xr.stage)
|
||||
.size(xr::Extent2Df {
|
||||
width: scale_x,
|
||||
height: scale_y,
|
||||
});
|
||||
Ok(Some(quad))
|
||||
if let Some(curvature) = self.state.curvature {
|
||||
let radius = scale_x / (2.0 * PI * curvature);
|
||||
let quat = helpers::transform_to_norm_quat(&self.state.transform);
|
||||
let center_point = self.state.transform.translation + quat.mul_vec3a(Vec3A::Z * radius);
|
||||
|
||||
let posef = helpers::translation_rotation_to_posef(center_point, quat);
|
||||
let angle = 2.0 * (scale_x / (2.0 * radius));
|
||||
|
||||
let cylinder = xr::CompositionLayerCylinderKHR::new()
|
||||
.pose(posef)
|
||||
.sub_image(sub_image)
|
||||
.eye_visibility(EyeVisibility::BOTH)
|
||||
.layer_flags(CompositionLayerFlags::CORRECT_CHROMATIC_ABERRATION)
|
||||
.space(&xr.stage)
|
||||
.radius(radius)
|
||||
.central_angle(angle)
|
||||
.aspect_ratio(aspect_ratio);
|
||||
Ok(CompositionLayer::Cylinder(cylinder))
|
||||
} else {
|
||||
let posef = helpers::transform_to_posef(&self.state.transform);
|
||||
let quad = xr::CompositionLayerQuad::new()
|
||||
.pose(posef)
|
||||
.sub_image(sub_image)
|
||||
.eye_visibility(EyeVisibility::BOTH)
|
||||
.layer_flags(CompositionLayerFlags::CORRECT_CHROMATIC_ABERRATION)
|
||||
.space(&xr.stage)
|
||||
.size(xr::Extent2Df {
|
||||
width: scale_x,
|
||||
height: scale_y,
|
||||
});
|
||||
Ok(CompositionLayer::Quad(quad))
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn after_input(&mut self, app: &mut AppState) -> anyhow::Result<()> {
|
||||
|
||||
+16
-5
@@ -35,6 +35,7 @@ pub struct OverlayState {
|
||||
pub spawn_point: Vec3A,
|
||||
pub spawn_rotation: Quat,
|
||||
pub relative_to: RelativeTo,
|
||||
pub curvature: Option<f32>,
|
||||
pub primary_pointer: Option<usize>,
|
||||
pub interaction_transform: Affine2,
|
||||
pub birthframe: usize,
|
||||
@@ -53,6 +54,7 @@ impl Default for OverlayState {
|
||||
dirty: true,
|
||||
alpha: 1.0,
|
||||
relative_to: RelativeTo::None,
|
||||
curvature: None,
|
||||
saved_point: None,
|
||||
saved_scale: None,
|
||||
spawn_scale: 1.0,
|
||||
@@ -161,6 +163,7 @@ impl OverlayState {
|
||||
}
|
||||
|
||||
let scale = self.transform.x_axis.length();
|
||||
self.saved_scale = Some(scale);
|
||||
|
||||
let col_z = (self.transform.translation - hmd.translation).normalize();
|
||||
let col_y = up_dir;
|
||||
@@ -287,10 +290,18 @@ impl InteractionHandler for SplitOverlayBackend {
|
||||
}
|
||||
|
||||
pub fn ui_transform(extent: &[u32; 2]) -> Affine2 {
|
||||
let aspect = extent[0] as f32 / extent[1] as f32;
|
||||
let scale = if aspect < 1.0 {
|
||||
Vec2 {
|
||||
x: 1.0 / aspect,
|
||||
y: -1.0,
|
||||
}
|
||||
} else {
|
||||
Vec2 {
|
||||
x: 1.0,
|
||||
y: -1.0 * aspect,
|
||||
}
|
||||
};
|
||||
let center = Vec2 { x: 0.5, y: 0.5 };
|
||||
Affine2::from_cols(
|
||||
Vec2::X,
|
||||
Vec2::NEG_Y * (extent[0] as f32 / extent[1] as f32),
|
||||
center,
|
||||
)
|
||||
Affine2::from_scale_angle_translation(scale, 0.0, center)
|
||||
}
|
||||
+42
-5
@@ -117,6 +117,24 @@ pub struct GeneralConfig {
|
||||
|
||||
#[serde(default = "def_auto")]
|
||||
pub capture_method: Arc<str>,
|
||||
|
||||
#[serde(default = "def_point7")]
|
||||
pub xr_grab_sensitivity: f32,
|
||||
|
||||
#[serde(default = "def_point7")]
|
||||
pub xr_click_sensitivity: f32,
|
||||
|
||||
#[serde(default = "def_point7")]
|
||||
pub xr_alt_click_sensitivity: f32,
|
||||
|
||||
#[serde(default = "def_half")]
|
||||
pub xr_grab_sensitivity_release: f32,
|
||||
|
||||
#[serde(default = "def_half")]
|
||||
pub xr_click_sensitivity_release: f32,
|
||||
|
||||
#[serde(default = "def_half")]
|
||||
pub xr_alt_click_sensitivity_release: f32,
|
||||
}
|
||||
|
||||
impl GeneralConfig {
|
||||
@@ -136,8 +154,8 @@ impl GeneralConfig {
|
||||
}
|
||||
|
||||
fn post_load(&self) {
|
||||
GeneralConfig::sanitize_range("keyboard_scale", self.keyboard_scale, 0.0, 5.0);
|
||||
GeneralConfig::sanitize_range("desktop_view_scale", self.desktop_view_scale, 0.0, 5.0);
|
||||
GeneralConfig::sanitize_range("keyboard_scale", self.keyboard_scale, 0.05, 5.0);
|
||||
GeneralConfig::sanitize_range("desktop_view_scale", self.desktop_view_scale, 0.05, 5.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,12 +210,31 @@ pub fn load_general() -> GeneralConfig {
|
||||
// Add files from conf.d directory
|
||||
let path_conf_d = get_conf_d_path();
|
||||
if let Ok(paths_unsorted) = std::fs::read_dir(path_conf_d) {
|
||||
let mut paths: Vec<_> = paths_unsorted
|
||||
.filter_map(|r| match r {
|
||||
Ok(entry) => Some(entry),
|
||||
Err(e) => {
|
||||
error!("Failed to read conf.d directory: {}", e);
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
// Sort paths alphabetically
|
||||
let mut paths: Vec<_> = paths_unsorted.map(|r| r.unwrap()).collect(); // TODO safe unwrap?
|
||||
paths.sort_by_key(|dir| dir.path());
|
||||
for path in paths {
|
||||
if !path.file_type().unwrap().is_file() {
|
||||
// TODO safe unwrap?
|
||||
let file_type = match path.file_type() {
|
||||
Ok(file_type) => file_type,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Failed to get file type of {}: {}",
|
||||
path.path().to_string_lossy(),
|
||||
e
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if !file_type.is_file() {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
+20
-13
@@ -106,9 +106,7 @@ impl FontCache {
|
||||
}
|
||||
|
||||
let pattern_str = format!("{PRIMARY_FONT}-{size}:style=bold:charset={cp:04x}");
|
||||
|
||||
let mut pattern =
|
||||
OwnedPattern::from_str(&pattern_str).expect("Failed to create fontconfig pattern");
|
||||
let mut pattern = OwnedPattern::from_str(&pattern_str).unwrap(); // safe because PRIMARY_FONT is const
|
||||
self.fc
|
||||
.substitute(&mut pattern, fontconfig::MatchKind::Pattern);
|
||||
pattern.default_substitute();
|
||||
@@ -124,12 +122,22 @@ impl FontCache {
|
||||
|
||||
let font_idx = pattern.face_index().unwrap_or(0);
|
||||
|
||||
let face = self
|
||||
.ft
|
||||
.new_face(path, font_idx as _)
|
||||
.expect("Failed to load font face");
|
||||
face.set_char_size(size << 6, size << 6, 96, 96)
|
||||
.expect("Failed to set font size");
|
||||
let face = match self.ft.new_face(path, font_idx as _) {
|
||||
Ok(face) => face,
|
||||
Err(e) => {
|
||||
log::warn!("Failed to load font at {}: {:?}", path, e);
|
||||
coll.cp_map.insert(cp, 0);
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
match face.set_char_size(size << 6, size << 6, 96, 96) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
log::warn!("Failed to set font size: {:?}", e);
|
||||
coll.cp_map.insert(cp, 0);
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
let idx = coll.fonts.len();
|
||||
for cp in 0..0xFFFF {
|
||||
@@ -156,11 +164,10 @@ impl FontCache {
|
||||
let font = Font { face, glyphs };
|
||||
coll.fonts.push(font);
|
||||
|
||||
idx
|
||||
} else {
|
||||
coll.cp_map.insert(cp, 0);
|
||||
0
|
||||
return idx;
|
||||
}
|
||||
coll.cp_map.insert(cp, 0);
|
||||
0
|
||||
}
|
||||
|
||||
fn get_glyph_for_cp(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::{
|
||||
f32::consts::PI,
|
||||
io::Cursor,
|
||||
ops::Add,
|
||||
process::{self, Child},
|
||||
sync::Arc,
|
||||
@@ -8,7 +7,6 @@ use std::{
|
||||
};
|
||||
|
||||
use glam::{Quat, Vec3A};
|
||||
use rodio::{Decoder, Source};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{
|
||||
@@ -613,11 +611,8 @@ fn run_window(window: &Arc<str>, action: &WindowAction, app: &mut AppState) {
|
||||
}
|
||||
}
|
||||
|
||||
const THUMP_AUDIO_WAV: &[u8] = include_bytes!("../../res/380885.wav");
|
||||
|
||||
fn audio_thump(app: &mut AppState) {
|
||||
if let Some(handle) = app.audio.get_handle() {
|
||||
let wav = include_bytes!("../../res/380885.wav");
|
||||
let cursor = Cursor::new(wav);
|
||||
let source = Decoder::new_wav(cursor).unwrap();
|
||||
let _ = handle.play_raw(source.convert_samples());
|
||||
}
|
||||
app.audio.play(THUMP_AUDIO_WAV);
|
||||
}
|
||||
@@ -29,6 +29,9 @@ pub enum LabelContent {
|
||||
format: Arc<str>,
|
||||
timezone: Option<Arc<str>>,
|
||||
},
|
||||
Timer {
|
||||
format: Arc<str>,
|
||||
},
|
||||
Battery {
|
||||
device: usize,
|
||||
low_threshold: f32,
|
||||
@@ -49,6 +52,10 @@ pub enum LabelData {
|
||||
format: Arc<str>,
|
||||
timezone: Option<Tz>,
|
||||
},
|
||||
Timer {
|
||||
format: Arc<str>,
|
||||
start: Instant,
|
||||
},
|
||||
Exec {
|
||||
last_exec: Instant,
|
||||
interval: f32,
|
||||
@@ -85,6 +92,10 @@ pub fn modular_label_init(label: &mut ModularControl, content: &LabelContent) {
|
||||
timezone: tz,
|
||||
})
|
||||
}
|
||||
LabelContent::Timer { format } => Some(LabelData::Timer {
|
||||
format: format.clone(),
|
||||
start: Instant::now(),
|
||||
}),
|
||||
LabelContent::Exec { command, interval } => Some(LabelData::Exec {
|
||||
last_exec: Instant::now(),
|
||||
interval: *interval,
|
||||
@@ -155,6 +166,14 @@ pub(super) fn label_update(control: &mut ModularControl, _: &mut (), app: &mut A
|
||||
control.set_text(&format!("{}", &date.format(&format)));
|
||||
}
|
||||
}
|
||||
LabelData::Timer { format, start } => {
|
||||
let mut format = format.clone().to_lowercase();
|
||||
let duration = start.elapsed().as_secs();
|
||||
format = format.replace("%s", &format!("{:02}", (duration % 60)));
|
||||
format = format.replace("%m", &format!("{:02}", ((duration / 60) % 60)));
|
||||
format = format.replace("%h", &format!("{:02}", ((duration / 60) / 60)));
|
||||
control.set_text(&format);
|
||||
}
|
||||
LabelData::Exec {
|
||||
last_exec,
|
||||
interval,
|
||||
|
||||
+18
-16
@@ -1,6 +1,5 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io::Cursor,
|
||||
process::{Child, Command},
|
||||
str::FromStr,
|
||||
};
|
||||
@@ -18,7 +17,6 @@ use crate::{
|
||||
use glam::{vec2, vec3a, Affine2, Vec4};
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rodio::{Decoder, Source};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const PIXELS_PER_UNIT: f32 = 80.;
|
||||
@@ -83,10 +81,14 @@ where
|
||||
verbs: key_events_for_macro(macro_verbs),
|
||||
});
|
||||
} else if let Some(exec_args) = LAYOUT.exec_commands.get(key) {
|
||||
if exec_args.is_empty() {
|
||||
log::error!("Keyboard: EXEC args empty for {}", key);
|
||||
continue;
|
||||
}
|
||||
maybe_state = Some(KeyButtonData::Exec {
|
||||
program: exec_args
|
||||
.first()
|
||||
.expect("Keyboard: Invalid EXEC args")
|
||||
.unwrap() // safe because we checked is_empty
|
||||
.clone(),
|
||||
args: exec_args.iter().skip(1).cloned().collect(),
|
||||
});
|
||||
@@ -227,18 +229,12 @@ struct KeyboardData {
|
||||
processes: Vec<Child>,
|
||||
}
|
||||
|
||||
const KEY_AUDIO_WAV: &[u8] = include_bytes!("../res/421581.wav");
|
||||
|
||||
impl KeyboardData {
|
||||
fn key_click(&mut self, app: &mut AppState) {
|
||||
if !app.session.config.keyboard_sound_enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(handle) = app.audio.get_handle() {
|
||||
// https://freesound.org/people/UberBosser/sounds/421581/
|
||||
let wav = include_bytes!("../res/421581.wav");
|
||||
let cursor = Cursor::new(wav);
|
||||
let source = Decoder::new_wav(cursor).unwrap();
|
||||
let _ = handle.play_raw(source.convert_samples());
|
||||
if app.session.config.keyboard_sound_enabled {
|
||||
app.audio.play(KEY_AUDIO_WAV);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -253,7 +249,7 @@ enum KeyButtonData {
|
||||
static LAYOUT: Lazy<Layout> = Lazy::new(Layout::load_from_disk);
|
||||
|
||||
static MACRO_REGEX: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"^([A-Za-z0-1_-]+)(?: +(UP|DOWN))?$").unwrap());
|
||||
Lazy::new(|| Regex::new(r"^([A-Za-z0-1_-]+)(?: +(UP|DOWN))?$").unwrap()); // want panic
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Layout {
|
||||
@@ -314,11 +310,17 @@ impl Layout {
|
||||
key = &key[3..];
|
||||
}
|
||||
if key.contains('_') {
|
||||
key = key.split('_').next().unwrap();
|
||||
key = key.split('_').next().unwrap_or_else(|| {
|
||||
log::error!(
|
||||
"keyboard.yaml: Key '{}' must not start or end with '_'!",
|
||||
key
|
||||
);
|
||||
"???"
|
||||
});
|
||||
}
|
||||
vec![format!(
|
||||
"{}{}",
|
||||
key.chars().next().unwrap().to_uppercase(),
|
||||
key.chars().next().unwrap().to_uppercase(), // safe because we checked is_empty
|
||||
&key[1..].to_lowercase()
|
||||
)]
|
||||
}
|
||||
|
||||
+22
-13
@@ -1,6 +1,9 @@
|
||||
use std::{sync::Arc, thread::JoinHandle};
|
||||
use std::{
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use futures::executor;
|
||||
use futures::{Future, FutureExt};
|
||||
use glam::vec3a;
|
||||
use wlx_capture::pipewire::{pipewire_select_screen, PipewireCapture, PipewireSelectScreenResult};
|
||||
|
||||
@@ -15,21 +18,23 @@ use crate::{
|
||||
};
|
||||
|
||||
use super::screen::ScreenRenderer;
|
||||
type PinnedSelectorFuture = core::pin::Pin<
|
||||
Box<dyn Future<Output = Result<PipewireSelectScreenResult, wlx_capture::pipewire::AshpdError>>>,
|
||||
>;
|
||||
|
||||
pub struct MirrorRenderer {
|
||||
name: Arc<str>,
|
||||
renderer: Option<ScreenRenderer>,
|
||||
selector: Option<JoinHandle<Option<PipewireSelectScreenResult>>>,
|
||||
selector: Option<PinnedSelectorFuture>,
|
||||
last_extent: [u32; 3],
|
||||
}
|
||||
impl MirrorRenderer {
|
||||
pub fn new(name: Arc<str>) -> Self {
|
||||
let selector = Box::pin(pipewire_select_screen(None, false, false, false));
|
||||
Self {
|
||||
name,
|
||||
renderer: None,
|
||||
selector: Some(std::thread::spawn(|| {
|
||||
executor::block_on(pipewire_select_screen(None, false, false, false)).ok()
|
||||
})),
|
||||
selector: Some(selector),
|
||||
last_extent: [0; 3],
|
||||
}
|
||||
}
|
||||
@@ -40,14 +45,18 @@ impl OverlayRenderer for MirrorRenderer {
|
||||
Ok(())
|
||||
}
|
||||
fn render(&mut self, app: &mut AppState) -> anyhow::Result<()> {
|
||||
if let Some(selector) = self.selector.take() {
|
||||
if !selector.is_finished() {
|
||||
self.selector = Some(selector);
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(mut selector) = self.selector.take() {
|
||||
let maybe_pw_result = match selector
|
||||
.poll_unpin(&mut Context::from_waker(futures::task::noop_waker_ref()))
|
||||
{
|
||||
Poll::Ready(result) => result,
|
||||
Poll::Pending => {
|
||||
self.selector = Some(selector);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
// safe unwrap because we know it's finished
|
||||
if let Some(pw_result) = selector.join().unwrap() {
|
||||
if let Ok(pw_result) = maybe_pw_result {
|
||||
log::info!(
|
||||
"{}: PipeWire node selected: {}",
|
||||
self.name.clone(),
|
||||
|
||||
+45
-18
@@ -48,6 +48,8 @@ use crate::{
|
||||
state::{AppSession, AppState},
|
||||
};
|
||||
|
||||
const CURSOR_SIZE: f32 = 16. / 1440.;
|
||||
|
||||
static DRM_FORMATS: once_cell::sync::OnceCell<Vec<DrmFormat>> = once_cell::sync::OnceCell::new();
|
||||
|
||||
pub struct ScreenInteractionHandler {
|
||||
@@ -207,18 +209,21 @@ impl ScreenPipeline {
|
||||
cmd.run_ref(&pass)?;
|
||||
|
||||
if let (Some(mouse), Some(mouse_view)) = (mouse, self.mouse.clone()) {
|
||||
let size = CURSOR_SIZE * self.extentf[1];
|
||||
let half_size = size * 0.5;
|
||||
|
||||
let vertex_buffer = app.graphics.upload_verts(
|
||||
self.extentf[0],
|
||||
self.extentf[1],
|
||||
mouse.x * self.extentf[0] - 2.,
|
||||
mouse.y * self.extentf[1] - 2.,
|
||||
4.0,
|
||||
4.0,
|
||||
mouse.x * self.extentf[0] - half_size,
|
||||
mouse.y * self.extentf[1] - half_size,
|
||||
size,
|
||||
size,
|
||||
)?;
|
||||
|
||||
let set0 = self
|
||||
.pipeline
|
||||
.uniform_sampler(0, mouse_view.clone(), Filter::Linear)?;
|
||||
.uniform_sampler(0, mouse_view.clone(), Filter::Nearest)?;
|
||||
|
||||
let pass = self.pipeline.create_pass(
|
||||
self.extentf,
|
||||
@@ -288,19 +293,18 @@ impl ScreenRenderer {
|
||||
output: &WlxOutput,
|
||||
token: Option<&str>,
|
||||
session: &AppSession,
|
||||
) -> Option<(
|
||||
) -> anyhow::Result<(
|
||||
ScreenRenderer,
|
||||
Option<String>, /* pipewire restore token */
|
||||
)> {
|
||||
let name = output.name.clone();
|
||||
let embed_mouse = !session.config.double_cursor_fix;
|
||||
let select_screen_result =
|
||||
futures::executor::block_on(pipewire_select_screen(token, embed_mouse, true, true))
|
||||
.ok()?;
|
||||
futures::executor::block_on(pipewire_select_screen(token, embed_mouse, true, true))?;
|
||||
|
||||
let capture = PipewireCapture::new(name, select_screen_result.node_id, 60);
|
||||
|
||||
Some((
|
||||
Ok((
|
||||
ScreenRenderer {
|
||||
name: output.name.clone(),
|
||||
capture: Box::new(capture),
|
||||
@@ -546,17 +550,26 @@ where
|
||||
);
|
||||
}
|
||||
|
||||
if let Some((renderer, restore_token)) = ScreenRenderer::new_pw(output, token, session) {
|
||||
capture = Some(renderer);
|
||||
match ScreenRenderer::new_pw(output, token, session) {
|
||||
Ok((renderer, restore_token)) => {
|
||||
capture = Some(renderer);
|
||||
|
||||
if let Some(token) = restore_token {
|
||||
if pw_token_store
|
||||
.insert(String::from(display_name), token.clone())
|
||||
.is_none()
|
||||
{
|
||||
log::info!("Adding Pipewire token {}", token);
|
||||
if let Some(token) = restore_token {
|
||||
if pw_token_store
|
||||
.insert(String::from(display_name), token.clone())
|
||||
.is_none()
|
||||
{
|
||||
log::info!("Adding Pipewire token {}", token);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!(
|
||||
"{}: Failed to create Pipewire capture: {:?}",
|
||||
&output.name,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(capture) = capture {
|
||||
@@ -689,7 +702,7 @@ where
|
||||
O: Default,
|
||||
{
|
||||
let mut overlays = vec![];
|
||||
let wl = WlxClient::new().ok_or_else(|| anyhow::anyhow!("Failed to connect to Wayland"))?;
|
||||
let mut wl = WlxClient::new().ok_or_else(|| anyhow::anyhow!("Failed to connect to Wayland"))?;
|
||||
|
||||
// Load existing Pipewire tokens from file
|
||||
let mut pw_tokens: HashMap<String, String> = if let Ok(conf) = load_pw_token_config() {
|
||||
@@ -700,6 +713,20 @@ where
|
||||
|
||||
let pw_tokens_copy = pw_tokens.clone();
|
||||
|
||||
let mut origin = (i32::MAX, i32::MAX);
|
||||
for output in wl.outputs.values() {
|
||||
origin.0 = origin.0.min(output.logical_pos.0);
|
||||
origin.1 = origin.1.min(output.logical_pos.1);
|
||||
}
|
||||
|
||||
log::info!("Desktop origin: {:?}", origin);
|
||||
|
||||
// adjust all outputs so that the top-left corner is at (0, 0)
|
||||
for output in wl.outputs.values_mut() {
|
||||
output.logical_pos.0 -= origin.0;
|
||||
output.logical_pos.1 -= origin.1;
|
||||
}
|
||||
|
||||
for id in wl.outputs.keys() {
|
||||
if let Some(overlay) = try_create_screen(&wl, *id, &mut pw_tokens, session) {
|
||||
overlays.push(overlay);
|
||||
|
||||
+2
-11
@@ -1,12 +1,9 @@
|
||||
use std::{
|
||||
io::Cursor,
|
||||
ops::Add,
|
||||
sync::{atomic::AtomicUsize, Arc},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use rodio::{Decoder, Source};
|
||||
|
||||
use glam::vec3a;
|
||||
|
||||
use crate::{
|
||||
@@ -21,6 +18,7 @@ use crate::{
|
||||
const FONT_SIZE: isize = 16;
|
||||
const PADDING: (f32, f32) = (25., 7.);
|
||||
const PIXELS_TO_METERS: f32 = 1. / 2000.;
|
||||
const TOAST_AUDIO_WAV: &[u8] = include_bytes!("../res/557297.wav");
|
||||
|
||||
static AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
@@ -79,12 +77,7 @@ impl Toast {
|
||||
.enqueue_at(TaskType::DropOverlay(selector), destroy_at);
|
||||
|
||||
if has_sound {
|
||||
if let Some(handle) = app.audio.get_handle() {
|
||||
let wav = include_bytes!("../res/557297.wav");
|
||||
let cursor = Cursor::new(wav);
|
||||
let source = Decoder::new_wav(cursor).unwrap();
|
||||
let _ = handle.play_raw(source.convert_samples());
|
||||
}
|
||||
app.audio.play(TOAST_AUDIO_WAV);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,7 +129,6 @@ fn new_toast(
|
||||
canvas.panel(0., 0., size.0, size.1);
|
||||
|
||||
if toast.body.len() > 0 {
|
||||
log::info!("{}: {}", title, toast.body);
|
||||
canvas.label(PADDING.0, 54., og_width, size.1 - 54., toast.body);
|
||||
|
||||
canvas.fg_color = color_parse("#101010").unwrap(); // want panic
|
||||
@@ -144,7 +136,6 @@ fn new_toast(
|
||||
canvas.panel(0., 0., size.0, 30.);
|
||||
canvas.label_centered(PADDING.0, 16., og_width, FONT_SIZE as f32 + 2., title);
|
||||
} else {
|
||||
log::info!("Toast: {}", title);
|
||||
canvas.label_centered(PADDING.0, 0., og_width, size.1, title);
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,11 @@
|
||||
"type": "boolean",
|
||||
"requirement": "optional"
|
||||
},
|
||||
{
|
||||
"name": "/actions/default/in/SpaceRotate",
|
||||
"type": "boolean",
|
||||
"requirement": "optional"
|
||||
},
|
||||
{
|
||||
"name": "/actions/default/in/LeftHand",
|
||||
"type": "pose",
|
||||
|
||||
+12
-32
@@ -22,47 +22,27 @@ row_size: 23
|
||||
|
||||
# Specifies the size of each key. The sum of any given row must equal RowSize
|
||||
key_sizes:
|
||||
- [1.5,0.5, 1, 1, 1, 1,0.5,1, 1, 1, 1,0.5,1, 1, 1, 1, 0.5, 1, 1, 1, 4.5]
|
||||
- [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0.5, 1, 1, 1, 0.5, 1, 1, 1, 1]
|
||||
- [1.5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.5, 0.5, 1, 1, 1, 0.5, 1, 1, 1, 1]
|
||||
- [1.75, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2.25, 4, 1, 1, 1, 1]
|
||||
- [2.25, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2.75, 1.5, 1, 1.5, 1, 1, 1, 1]
|
||||
- [1.25, 1.25, 1.25, 6.25, 1.25, 1.25, 1.25, 1.25, 0.5, 1, 1, 1, 0.5, 2, 1, 1]
|
||||
- [1.5,0.5, 1, 1, 1, 1,0.5,1, 1, 1, 1,0.5,1, 1, 1, 1, 0.5, 1, 1, 1, 0.5, 1, 1, 1, 1]
|
||||
- [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0.5, 1, 1, 1, 0.5, 1, 1, 1, 1]
|
||||
- [1.5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.5, 0.5, 1, 1, 1, 0.5, 1, 1, 1, 1]
|
||||
- [1.75, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2.25, 4, 1, 1, 1, 1]
|
||||
- [2.25, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2.75, 1.5, 1, 1.5, 1, 1, 1, 1]
|
||||
- [1.25, 1.25, 1.25, 6.25, 1.25, 1.25, 1.25, 1.25, 0.5, 1, 1, 1, 0.5, 2, 1, 1]
|
||||
|
||||
# The main (blue) layout of the keyboard.
|
||||
# Accepted are:
|
||||
# - virtual keys, for a full list go to https://github.com/galister/X11Overlay/blob/master/Types/VirtualKey.cs
|
||||
# - ExecCommands (defined below)
|
||||
# - Macros (defined below)
|
||||
# - virtual keys. For a full list, look at enum VirtualKey in https://github.com/galister/wlx-overlay-s/blob/main/src/hid.rs
|
||||
# - exec_commands (defined below)
|
||||
# - macros (defined below)
|
||||
# - ~ (null) will leave an empty space with the corresponding size from key_sizes
|
||||
main_layout:
|
||||
- ["Escape", ~, "F1", "F2", "F3", "F4", ~, "F5", "F6", "F7", "F8", ~, "F9", "F10", "F11", "F12", ~, "Print", "Scroll", "Pause", ~]
|
||||
- ["Escape", ~, "F1", "F2", "F3", "F4", ~, "F5", "F6", "F7", "F8", ~, "F9", "F10", "F11", "F12", ~, "Print", "Scroll", "Pause", ~, "COPY", "PASTE", ~, "KILL"]
|
||||
- ["Oem3", "N1", "N2", "N3", "N4", "N5", "N6", "N7", "N8", "N9", "N0", "Minus", "Plus", "BackSpace", ~, "Insert", "Home", "Prior", ~, "NumLock", "KP_Divide", "KP_Multiply", "KP_Subtract"]
|
||||
- ["Tab", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "Oem4", "Oem6", "Oem5", ~, "Delete", "End", "Next", ~, "KP_7", "KP_8", "KP_9", "KP_Add"]
|
||||
- ["XF86Favorites", "A", "S", "D", "F", "G", "H", "J", "K", "L", "Oem1", "Oem7", "Return", ~, "KP_4", "KP_5", "KP_6", ~]
|
||||
- ["LShift", "Z", "X", "C", "V", "B", "N", "M", "Comma", "Period", "Oem2", "RShift", ~, "Up", ~, "KP_1", "KP_2", "KP_3", "KP_Enter"]
|
||||
- ["LCtrl", "LSuper", "LAlt", "Space", "Meta", "RSuper", "Menu", "RCtrl", ~, "Left", "Down", "Right", ~, "KP_0", "KP_Decimal", ~]
|
||||
|
||||
# When using the purple pointer...
|
||||
# None - No special functionality when using purple pointer
|
||||
# Shift - Use same functionality as the orange pointer
|
||||
# Ctrl - Use Main layout with Ctrl modifier
|
||||
# Super - Use Main layout with Super (WinKey) modifier
|
||||
# Meta - Use Main layout with Meta (AltGr) modifier
|
||||
# Layout - Use the alt_layout defined below
|
||||
alt_layout_mode: Ctrl
|
||||
|
||||
# The alt (fn) layout of the keyboard. Only takes effect when alt_layout_mode: Layout
|
||||
# Accepted values are the same as for MainLayout.
|
||||
# The default layout here is a dummy. Change it to fit your liking.
|
||||
alt_layout:
|
||||
- ["Escape", ~, "F1", "F2", "F3", "F4", ~, "F5", "F6", "F7", "F8", ~, "F9", "F10", "F11", "F12", ~, "Print", "Scroll", "Pause", ~, "COPY", "PASTE"]
|
||||
- ["Oem3", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "Minus", "Plus", "BackSpace", ~, "Insert", "Home", "Prior", ~, "NumLock", "KP_Divide", "KP_Multiply", "KP_Subtract"]
|
||||
- ["Tab", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "Oem4", "Oem6", "Oem5", ~, "Delete", "End", "Next", ~, "KP_7", "KP_8", "KP_9", "KP_Add"]
|
||||
- ["XF86Favorites", "A", "S", "D", "F", "G", "H", "J", "K", "L", "Oem1", "Oem7", "Return", ~, "KP_4", "KP_5", "KP_6", ~]
|
||||
- ["LShift", "Oem102", "Z", "X", "C", "V", "B", "N", "M", "Comma", "Period", "Oem2", "RShift", ~, "Up", ~, "KP_1", "KP_2", "KP_3", "KP_Enter"]
|
||||
- ["LCtrl", "LSuper", "LAlt", "Space", "Meta", "RSuper", "Menu", "RCtrl", ~, "Left", "Down", "Right", ~, "KP_0", "KP_Decimal", ~]
|
||||
|
||||
# Shell commands to be used in a layout.
|
||||
# Value is an array of string arguments.
|
||||
exec_commands:
|
||||
@@ -70,7 +50,7 @@ exec_commands:
|
||||
|
||||
# Series of keypresses to be used in a layout.
|
||||
# Format: keyName [DOWN|UP]
|
||||
# keyName must be a valid key from `xmodmap -pke`
|
||||
# keyName must be a valid virtual key from the VirtualKey enum (see above)
|
||||
# DOWN|UP: can be omitted for an implicit "keyName DOWN, keyName UP"
|
||||
macros:
|
||||
KILL: [ "LSuper DOWN", "LCtrl DOWN", "Escape", "LCtrl UP", "LSuper UP" ]
|
||||
@@ -78,7 +58,7 @@ macros:
|
||||
PASTE: [ "LCtrl DOWN", "V", "LCtrl UP" ]
|
||||
|
||||
# Custom labels to use.
|
||||
# Key: element of MainLayout / AltLayout
|
||||
# Key: element of main_layout
|
||||
# Value: Array of strings. 0th element is the upper row, 1st element is lower row.
|
||||
# For empty labels, use [] (do not use ~)
|
||||
labels:
|
||||
|
||||
@@ -94,6 +94,12 @@ pub mod frag_screen {
|
||||
void main()
|
||||
{
|
||||
out_color = texture(in_texture, in_uv);
|
||||
|
||||
// linear to srgb
|
||||
bvec4 cutoff = lessThan(out_color, vec4(0.0031308));
|
||||
vec4 higher = (pow(out_color, vec4(0.41666)) * vec4(1.055)) - vec4(0.055);
|
||||
vec4 lower = out_color*vec4(12.92);
|
||||
out_color = mix(higher, lower, cutoff);
|
||||
out_color.a = 1.0;
|
||||
}
|
||||
",
|
||||
|
||||
+19
-4
@@ -1,8 +1,8 @@
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
use std::{io::Cursor, path::PathBuf, sync::Arc};
|
||||
|
||||
use anyhow::bail;
|
||||
use glam::Vec3;
|
||||
use rodio::{OutputStream, OutputStreamHandle};
|
||||
use rodio::{Decoder, OutputStream, OutputStreamHandle, Source};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use vulkano::format::Format;
|
||||
@@ -126,18 +126,33 @@ impl AudioOutput {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_handle(&mut self) -> Option<&OutputStreamHandle> {
|
||||
fn get_handle(&mut self) -> Option<&OutputStreamHandle> {
|
||||
if self.audio_stream.is_none() && self.first_try {
|
||||
self.first_try = false;
|
||||
if let Ok((stream, handle)) = OutputStream::try_default() {
|
||||
self.audio_stream = Some((stream, handle));
|
||||
} else {
|
||||
log::error!("Failed to open audio stream");
|
||||
log::error!("Failed to open audio stream. Audio will not work.");
|
||||
return None;
|
||||
}
|
||||
}
|
||||
self.audio_stream.as_ref().map(|(_, h)| h)
|
||||
}
|
||||
|
||||
pub fn play(&mut self, wav_bytes: &'static [u8]) {
|
||||
let Some(handle) = self.get_handle() else {
|
||||
return;
|
||||
};
|
||||
let cursor = Cursor::new(wav_bytes);
|
||||
let source = match Decoder::new_wav(cursor) {
|
||||
Ok(source) => source,
|
||||
Err(e) => {
|
||||
log::error!("Failed to play sound: {:?}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let _ = handle.play_raw(source.convert_samples());
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ScreenMeta {
|
||||
|
||||
Reference in new issue
Block a user