keyboard progress & refactors

This commit is contained in:
galister
2025-06-20 00:48:37 +09:00
parent e0e30dedfb
commit 44a9faac14
37 changed files with 1226 additions and 761 deletions

View File

@@ -10,14 +10,14 @@ use thiserror::Error;
use crate::{
config::AStrSetExt,
hid::{get_keymap_wl, get_keymap_x11},
overlays::{
anchor::create_anchor,
keyboard::{create_keyboard, KEYBOARD_NAME},
keyboard::{KEYBOARD_NAME, builder::create_keyboard},
screen::WlxClientAlias,
watch::{create_watch, WATCH_NAME},
watch::{WATCH_NAME, create_watch},
},
state::AppState,
subsystem::hid::{get_keymap_wl, get_keymap_x11},
};
use super::overlay::{OverlayData, OverlayID};
@@ -248,9 +248,12 @@ where
if extent_dirty && !create_ran {
let extent = wl.get_desktop_extent();
let origin = wl.get_desktop_origin();
app.hid_provider
let mut hid_provider = app.hid_provider.borrow_mut();
hid_provider
.inner
.set_desktop_extent(vec2(extent.0 as f32, extent.1 as f32));
app.hid_provider
hid_provider
.inner
.set_desktop_origin(vec2(origin.0 as f32, origin.1 as f32));
}

View File

@@ -4,13 +4,14 @@ use std::{collections::VecDeque, time::Instant};
use glam::{Affine3A, Vec2, Vec3, Vec3A, Vec3Swizzles};
use smallvec::{smallvec, SmallVec};
use smallvec::{SmallVec, smallvec};
use crate::backend::common::OverlaySelector;
use crate::backend::overlay::Positioning;
use crate::config::AStrMapExt;
use crate::overlays::anchor::ANCHOR_NAME;
use crate::state::{AppSession, AppState, KeyboardFocus};
use crate::state::{AppSession, AppState};
use crate::subsystem::input::KeyboardFocus;
use super::overlay::{OverlayID, OverlayState};
use super::task::{TaskContainer, TaskType};
@@ -402,7 +403,10 @@ where
log::trace!("Hit: {} {:?}", hovered.state.name, hit);
if pointer.now.grab && !pointer.before.grab && hovered.state.grabbable {
update_focus(&mut app.keyboard_focus, &hovered.state);
{
let mut hid_provider = app.hid_provider.borrow_mut();
update_focus(&mut hid_provider.keyboard_focus, &hovered.state);
}
pointer.start_grab(hovered, &mut app.tasks);
return (
hit.dist,
@@ -451,7 +455,10 @@ where
if pointer.now.click && !pointer.before.click {
pointer.interaction.clicked_id = Some(hit.overlay);
update_focus(&mut app.keyboard_focus, &hovered.state);
{
let mut hid_provider = app.hid_provider.borrow_mut();
update_focus(&mut hid_provider.keyboard_focus, &hovered.state);
}
hovered.backend.on_pointer(app, &hit, true);
} else if !pointer.now.click && pointer.before.click {
if let Some(clicked_id) = pointer.interaction.clicked_id.take() {

View File

@@ -1,9 +1,5 @@
pub mod common;
pub mod input;
pub mod notifications;
#[allow(clippy::all)]
mod notifications_dbus;
#[cfg(feature = "openvr")]
pub mod openvr;
@@ -11,9 +7,6 @@ pub mod openvr;
#[cfg(feature = "openxr")]
pub mod openxr;
#[cfg(feature = "osc")]
pub mod osc;
#[cfg(feature = "wayvr")]
pub mod wayvr;

View File

@@ -1,291 +0,0 @@
use dbus::{
arg::{PropMap, Variant},
blocking::Connection,
channel::MatchingReceiver,
message::MatchRule,
};
use serde::Deserialize;
use std::{
sync::{
Arc,
atomic::{AtomicBool, Ordering},
mpsc,
},
time::Duration,
};
use crate::{
backend::notifications_dbus::OrgFreedesktopNotifications,
overlays::toast::{Toast, ToastTopic},
state::AppState,
};
pub struct NotificationManager {
rx_toast: mpsc::Receiver<Toast>,
tx_toast: mpsc::SyncSender<Toast>,
dbus_data: Option<Connection>,
running: Arc<AtomicBool>,
}
impl NotificationManager {
pub fn new() -> Self {
let (tx_toast, rx_toast) = mpsc::sync_channel(10);
Self {
rx_toast,
tx_toast,
dbus_data: None,
running: Arc::new(AtomicBool::new(true)),
}
}
pub fn submit_pending(&self, app: &mut AppState) {
if let Some(c) = &self.dbus_data {
let _ = c.process(Duration::ZERO);
}
if app.session.config.notifications_enabled {
self.rx_toast.try_iter().for_each(|toast| {
toast.submit(app);
});
} else {
// consume without submitting
self.rx_toast.try_iter().last();
}
}
pub fn run_dbus(&mut self) {
let c = match Connection::new_session() {
Ok(c) => c,
Err(e) => {
log::error!(
"Failed to connect to dbus. Desktop notifications will not work. Cause: {e:?}"
);
return;
}
};
let mut rule = MatchRule::new_method_call();
rule.member = Some("Notify".into());
rule.interface = Some("org.freedesktop.Notifications".into());
rule.path = Some("/org/freedesktop/Notifications".into());
rule.eavesdrop = true;
let proxy = c.with_proxy(
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
Duration::from_millis(5000),
);
let result: Result<(), dbus::Error> = proxy.method_call(
"org.freedesktop.DBus.Monitoring",
"BecomeMonitor",
(vec![rule.match_str()], 0u32),
);
if matches!(result, Ok(())) {
let sender = self.tx_toast.clone();
c.start_receive(
rule,
Box::new(move |msg, _| {
if let Ok(toast) = parse_dbus(&msg) {
match sender.try_send(toast) {
Ok(()) => {}
Err(e) => {
log::error!("Failed to send notification: {e:?}");
}
}
}
true
}),
);
log::info!("Listening to DBus notifications via BecomeMonitor.");
} else {
let rule_with_eavesdrop = {
let mut rule = rule.clone();
rule.eavesdrop = true;
rule
};
let sender2 = self.tx_toast.clone();
let result = c.add_match(rule_with_eavesdrop, move |(): (), _, msg| {
if let Ok(toast) = parse_dbus(msg) {
match sender2.try_send(toast) {
Ok(()) => {}
Err(e) => {
log::error!("Failed to send notification: {e:?}");
}
}
}
true
});
match result {
Ok(_) => {
log::info!("Listening to DBus notifications via eavesdrop.");
}
Err(_) => {
log::error!("Failed to add DBus match. Desktop notifications will not work.",);
}
}
}
self.dbus_data = Some(c);
}
pub fn run_udp(&mut self) {
let sender = self.tx_toast.clone();
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) {
Ok(s) => s,
Err(e) => {
log::error!("Failed to bind notification socket @ {addr}: {e:?}");
return;
}
};
if let Err(err) = socket.set_read_timeout(Some(Duration::from_millis(200))) {
log::error!("Failed to set read timeout: {err:?}");
}
let mut buf = [0u8; 1024 * 16]; // vrcx embeds icons as b64
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,
Err(e) => {
log::error!("Failed to receive notification message: {e:?}");
continue;
}
};
let msg = match serde_json::from_str::<XsoMessage>(json_str) {
Ok(m) => m,
Err(e) => {
log::error!("Failed to parse notification message: {e:?}");
continue;
}
};
if msg.messageType != 1 {
continue;
}
let toast = Toast::new(
ToastTopic::XSNotification,
msg.title,
msg.content.unwrap_or(String::new()),
)
.with_timeout(msg.timeout.unwrap_or(5.))
.with_sound(msg.volume.unwrap_or(-1.) >= 0.); // XSOverlay still plays at 0,
match sender.try_send(toast) {
Ok(()) => {}
Err(e) => {
log::error!("Failed to send notification: {e:?}");
}
}
}
}
log::info!("Notification listener stopped.");
});
}
}
impl Drop for NotificationManager {
fn drop(&mut self) {
self.running.store(false, Ordering::Relaxed);
}
}
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()?;
let _replaces_id: u32 = args.read()?;
let _app_icon: String = args.read()?;
let summary: String = args.read()?;
let body: String = args.read()?;
let title = if summary.is_empty() {
app_name
} else {
summary
};
Ok(Toast::new(ToastTopic::DesktopNotification, title, body)
.with_timeout(5.0)
.with_opacity(1.0))
// leave the audio part to the desktop env
}
#[allow(dead_code)]
#[allow(non_snake_case)]
#[derive(Debug, Deserialize)]
struct XsoMessage {
messageType: i32,
index: Option<i32>,
volume: Option<f32>,
audioPath: Option<String>,
timeout: Option<f32>,
title: String,
content: Option<String>,
icon: Option<String>,
height: Option<f32>,
opacity: Option<f32>,
useBase64Icon: Option<bool>,
sourceApp: Option<String>,
alwaysShow: Option<bool>,
}

View File

@@ -1,353 +0,0 @@
// 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;
#[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(Self {
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<T: blocking::BlockingSender, C: ::std::ops::Deref<Target = T>> OrgFreedesktopDBusProperties
for blocking::Proxy<'_, 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<T: blocking::BlockingSender, C: ::std::ops::Deref<Target = T>> OrgFreedesktopDBusIntrospectable
for blocking::Proxy<'_, 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<T: blocking::BlockingSender, C: ::std::ops::Deref<Target = T>> OrgFreedesktopDBusPeer
for blocking::Proxy<'_, 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(Self { 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(Self {
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(Self {
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(Self {
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<T: blocking::BlockingSender, C: ::std::ops::Deref<Target = T>> OrgFreedesktopNotifications
for blocking::Proxy<'_, 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,
)
}
}

View File

@@ -19,7 +19,6 @@ use crate::{
backend::{
common::{BackendError, OverlayContainer},
input::interact,
notifications::NotificationManager,
openvr::{
helpers::adjust_gain,
input::{OpenVrInputSource, set_action_manifest},
@@ -36,6 +35,7 @@ use crate::{
watch::{WATCH_NAME, watch_fade},
},
state::AppState,
subsystem::notifications::NotificationManager,
};
#[cfg(feature = "wayvr")]
@@ -315,7 +315,7 @@ pub fn openvr_run(
}
}
state.hid_provider.commit();
state.hid_provider.borrow_mut().inner.commit();
let mut buffers = CommandBuffers::default();
lines.update(universe.clone(), &mut overlay_mgr, &mut state)?;

View File

@@ -19,7 +19,6 @@ use crate::{
backend::{
common::{BackendError, OverlayContainer},
input::interact,
notifications::NotificationManager,
openxr::{lines::LinePool, overlay::OpenXrOverlayData},
overlay::{OverlayData, ShouldRender},
task::{SystemTask, TaskType},
@@ -30,6 +29,7 @@ use crate::{
watch::{WATCH_NAME, watch_fade},
},
state::AppState,
subsystem::notifications::NotificationManager,
};
#[cfg(feature = "wayvr")]
@@ -352,7 +352,7 @@ pub fn openxr_run(
}
}
app.hid_provider.commit();
app.hid_provider.borrow_mut().inner.commit();
let watch = overlays.mut_by_id(watch_id).unwrap(); // want panic
let watch_transform = watch.state.transform;

View File

@@ -1,183 +0,0 @@
use std::{
net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
time::Instant,
};
use anyhow::bail;
use rosc::{OscMessage, OscPacket, OscType};
use crate::overlays::{keyboard::KEYBOARD_NAME, watch::WATCH_NAME};
use crate::backend::input::TrackedDeviceRole;
use super::{common::OverlayContainer, input::TrackedDevice};
pub struct OscSender {
last_sent_overlay: Instant,
last_sent_battery: Instant,
upstream: UdpSocket,
}
impl OscSender {
pub fn new(send_port: u16) -> anyhow::Result<Self> {
let ip = IpAddr::V4(Ipv4Addr::LOCALHOST);
let Ok(upstream) = UdpSocket::bind("0.0.0.0:0") else {
bail!("Failed to bind UDP socket - OSC will not function.");
};
let Ok(()) = upstream.connect(SocketAddr::new(ip, send_port)) else {
bail!("Failed to connect UDP socket - OSC will not function.");
};
Ok(Self {
upstream,
last_sent_overlay: Instant::now(),
last_sent_battery: Instant::now(),
})
}
pub fn send_message(&self, addr: String, args: Vec<OscType>) -> anyhow::Result<()> {
let packet = OscPacket::Message(OscMessage { addr, args });
let Ok(bytes) = rosc::encoder::encode(&packet) else {
bail!("Could not encode OSC packet.");
};
let Ok(_) = self.upstream.send(&bytes) else {
bail!("Could not send OSC packet.");
};
Ok(())
}
pub fn send_params<D>(
&mut self,
overlays: &OverlayContainer<D>,
devices: &Vec<TrackedDevice>,
) -> anyhow::Result<()>
where
D: Default,
{
// send overlay data every 0.1 seconds
if self.last_sent_overlay.elapsed().as_millis() >= 100 {
self.last_sent_overlay = Instant::now();
let mut num_overlays = 0;
let mut has_keyboard = false;
let mut has_wrist = false;
for o in overlays.iter() {
if !o.state.want_visible {
continue;
}
match o.state.name.as_ref() {
WATCH_NAME => has_wrist = true,
KEYBOARD_NAME => has_keyboard = true,
_ => {
if o.state.interactable {
num_overlays += 1;
}
}
}
}
self.send_message(
"/avatar/parameters/isOverlayOpen".into(),
vec![OscType::Bool(num_overlays > 0)],
)?;
self.send_message(
"/avatar/parameters/isKeyboardOpen".into(),
vec![OscType::Bool(has_keyboard)],
)?;
self.send_message(
"/avatar/parameters/isWristVisible".into(),
vec![OscType::Bool(has_wrist)],
)?;
self.send_message(
"/avatar/parameters/openOverlayCount".into(),
vec![OscType::Int(num_overlays)],
)?;
}
// send battery levels every 10 seconds
if self.last_sent_battery.elapsed().as_millis() >= 10000 {
self.last_sent_battery = Instant::now();
let mut tracker_count: i8 = 0;
let mut controller_count: i8 = 0;
let mut tracker_total_bat = 0.0;
let mut controller_total_bat = 0.0;
for device in devices {
let tracker_param;
// soc is the battery level (set to device status.charge)
let level = device.soc.unwrap_or(-1.0);
let parameter = match device.role {
TrackedDeviceRole::None => continue,
TrackedDeviceRole::Hmd => {
// legacy OVR Toolkit style (int)
// as of 20 Nov 2024 OVR Toolkit uses int 0-100, but this may change in a future update.
//TODO: update this once their implementation matches their docs
self.send_message(
"/avatar/parameters/hmdBattery".into(),
vec![OscType::Int((level * 100.0f32).round() as i32)],
)?;
"headset"
}
TrackedDeviceRole::LeftHand => {
controller_count += 1;
controller_total_bat += level;
"leftController"
}
TrackedDeviceRole::RightHand => {
controller_count += 1;
controller_total_bat += level;
"rightController"
}
TrackedDeviceRole::Tracker => {
tracker_count += 1;
tracker_total_bat += level;
tracker_param = format!("tracker{tracker_count}");
tracker_param.as_str()
}
};
// send device battery parameters
self.send_message(
format!("/avatar/parameters/{parameter}Battery"),
vec![OscType::Float(level)],
)?;
self.send_message(
format!("/avatar/parameters/{parameter}Charging"),
vec![OscType::Bool(device.charging)],
)?;
}
// send average controller and tracker battery parameters
self.send_message(
String::from("/avatar/parameters/averageControllerBattery"),
vec![OscType::Float(
controller_total_bat / f32::from(controller_count),
)],
)?;
self.send_message(
String::from("/avatar/parameters/averageTrackerBattery"),
vec![OscType::Float(tracker_total_bat / f32::from(tracker_count))],
)?;
}
Ok(())
}
pub fn send_single_param(
&mut self,
parameter: String,
values: Vec<OscType>,
) -> anyhow::Result<()> {
self.send_message(parameter, values)?;
Ok(())
}
}

View File

@@ -1,8 +1,8 @@
use std::{
f32::consts::PI,
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
atomic::{AtomicUsize, Ordering},
},
};
@@ -12,9 +12,7 @@ use serde::Deserialize;
use vulkano::{format::Format, image::view::ImageView};
use crate::{
config::AStrMapExt,
graphics::CommandBuffers,
state::{AppState, KeyboardFocus},
config::AStrMapExt, graphics::CommandBuffers, state::AppState, subsystem::input::KeyboardFocus,
};
use super::{

View File

@@ -20,9 +20,9 @@ use smallvec::SmallVec;
use smithay::{
backend::{
egl,
renderer::{gles::GlesRenderer, ImportDma},
renderer::{ImportDma, gles::GlesRenderer},
},
input::{keyboard::XkbConfig, SeatState},
input::{SeatState, keyboard::XkbConfig},
output::{Mode, Output},
reexports::wayland_server::{self, backend::ClientId},
wayland::{
@@ -42,7 +42,7 @@ use std::{
use time::get_millis;
use wayvr_ipc::{packet_client, packet_server};
use crate::{hid::MODS_TO_KEYS, state::AppState};
use crate::{state::AppState, subsystem::hid::MODS_TO_KEYS};
const STR_INVALID_HANDLE_DISP: &str = "Invalid display handle";
@@ -373,9 +373,9 @@ impl WayVR {
process::find_by_pid(&self.state.processes, client.pid)
else {
log::error!(
"WayVR window creation failed: Unexpected process ID {}. It wasn't registered before.",
client.pid
);
"WayVR window creation failed: Unexpected process ID {}. It wasn't registered before.",
client.pid
);
continue;
};