notifications

This commit is contained in:
galister
2024-02-21 19:52:42 +01:00
parent 39cc22474b
commit e7710b56d9
18 changed files with 381 additions and 85 deletions

View File

@@ -14,7 +14,6 @@ use thiserror::Error;
use crate::{
overlays::{
keyboard::create_keyboard,
toast::Toast,
watch::{create_watch, WATCH_NAME, WATCH_SCALE},
},
state::AppState,
@@ -58,6 +57,7 @@ where
};
let mut watch = create_watch::<T>(app, &screens)?;
log::info!("Watch Rotation: {:?}", watch.state.spawn_rotation);
watch.state.want_visible = true;
overlays.insert(watch.state.id, watch);
@@ -68,9 +68,9 @@ where
let mut show_screens = app.session.config.show_screens.clone();
if show_screens.is_empty() {
screens.first().map(|s| {
if let Some(s) = screens.first() {
show_screens.push(s.state.name.clone());
});
}
}
for mut screen in screens {
@@ -173,7 +173,7 @@ impl PartialEq<AppTask> for AppTask {
}
impl PartialOrd<AppTask> for AppTask {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.not_before.cmp(&other.not_before).reverse())
Some(self.cmp(other))
}
}
impl Eq for AppTask {}
@@ -194,7 +194,6 @@ pub enum TaskType {
Box<dyn FnOnce(&mut AppState) -> Option<(OverlayState, Box<dyn OverlayBackend>)> + Send>,
),
DropOverlay(OverlaySelector),
Toast(Toast),
}
pub struct TaskContainer {

View File

@@ -1,9 +1,16 @@
pub mod common;
pub mod input;
#[cfg(feature = "notifications")]
pub mod notifications;
#[cfg(feature = "openvr")]
pub mod openvr;
#[cfg(feature = "openxr")]
pub mod openxr;
#[cfg(feature = "osc")]
pub mod osc;
pub mod overlay;

View File

@@ -0,0 +1,153 @@
use dbus::{
blocking::Connection,
channel::{MatchingReceiver, Token},
message::MatchRule,
};
use serde::Deserialize;
use std::sync::{mpsc, Arc};
use crate::{overlays::toast::Toast, state::AppState};
pub struct NotificationManager {
rx_toast: mpsc::Receiver<Toast>,
tx_toast: mpsc::SyncSender<Toast>,
dbus_data: Option<(Connection, Token)>,
}
impl NotificationManager {
pub fn new() -> Self {
let (tx_toast, rx_toast) = mpsc::sync_channel(10);
Self {
rx_toast,
tx_toast,
dbus_data: None,
}
}
pub fn submit_pending(&self, app: &mut AppState) {
self.rx_toast.try_iter().for_each(|toast| {
toast.submit(app);
});
}
pub fn run_dbus(&mut self) {
let Ok(c) = Connection::new_session() else {
log::error!("Failed to connect to dbus. Desktop notifications will not work.");
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 sender = self.tx_toast.clone();
let token = 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
}),
);
self.dbus_data = Some((c, token));
}
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 _ = std::thread::spawn(move || {
let socket = match std::net::UdpSocket::bind("127.0.0.1:42069") {
Ok(s) => s,
Err(e) => {
log::error!("Failed to bind notification socket: {:?}", e);
return;
}
};
let mut buf = [0u8; 1500];
loop {
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;
}
};
let toast = Toast::new(msg.title, msg.content.unwrap_or_else(|| "".into()))
.with_timeout(msg.timeout)
.with_volume(msg.volume)
.with_opacity(msg.opacity);
match sender.try_send(toast) {
Ok(_) => {}
Err(e) => {
log::error!("Failed to send notification: {:?}", e);
}
}
}
}
});
}
}
impl Drop for NotificationManager {
fn drop(&mut self) {
if let Some((c, token)) = self.dbus_data.take() {
let _ = c.stop_receive(token);
}
}
}
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(title.into(), body.into()))
}
#[allow(non_snake_case)]
#[derive(Debug, Deserialize)]
struct XsoMessage {
messageType: i32,
index: i32,
volume: f32,
audioPath: Arc<str>,
timeout: f32,
title: Arc<str>,
content: Option<Arc<str>>,
icon: Option<Arc<str>>,
height: f32,
opacity: f32,
useBase64Icon: bool,
sourceApp: Option<Arc<str>>,
alwaysShow: bool,
}

View File

@@ -129,12 +129,12 @@ impl OpenVrInputSource {
app: &mut AppState,
) {
let aas = ActiveActionSet(ovr_overlay::sys::VRActiveActionSet_t {
ulActionSet: self.set_hnd.0,
ulRestrictedToDevice: 0,
ulSecondaryActionSet: 0,
unPadding: 0,
nPriority: 0,
});
ulActionSet: self.set_hnd.0,
ulRestrictedToDevice: 0,
ulSecondaryActionSet: 0,
unPadding: 0,
nPriority: 0,
});
let _ = input.update_actions(&mut [aas]);
@@ -156,7 +156,6 @@ impl OpenVrInputSource {
.map(|pose| {
app_hand.pose = pose.0.pose.mDeviceToAbsoluteTracking.to_affine();
hand.has_pose = true;
});
app_hand.now.click = input
@@ -235,10 +234,9 @@ impl OpenVrInputSource {
_ => continue,
};
get_tracked_device(system, device, role).map(|device| {
if let Some(device) = get_tracked_device(system, device, role) {
app.input_state.devices.push(device);
});
}
}
app.input_state.devices.sort_by(|a, b| {

View File

@@ -18,7 +18,7 @@ pub(super) fn install_manifest(app_mgr: &mut ApplicationsManager) -> anyhow::Res
if let Ok(true) = app_mgr.is_application_installed(APP_KEY) {
if let Ok(mut file) = File::open(&manifest_path) {
let mut buf = String::new();
if let Ok(_) = file.read_to_string(&mut buf) {
if file.read_to_string(&mut buf).is_ok() {
let manifest: json::JsonValue = json::parse(&buf)?;
if manifest["applications"][0]["binary_path_linux"] == executable_path {
log::info!("Manifest already up to date");
@@ -50,16 +50,20 @@ pub(super) fn install_manifest(app_mgr: &mut ApplicationsManager) -> anyhow::Res
bail!("Failed to create manifest file at {:?}", manifest_path);
};
let Ok(()) = manifest.write(&mut file) else {
bail!("Failed to write manifest file at {:?}", manifest_path);
if let Err(e) = manifest.write(&mut file) {
bail!(
"Failed to write manifest file at {:?}: {:?}",
manifest_path,
e
);
};
let Ok(()) = app_mgr.add_application_manifest(&manifest_path, false) else {
bail!("Failed to add manifest to OpenVR");
if let Err(e) = app_mgr.add_application_manifest(&manifest_path, false) {
bail!("Failed to add manifest to OpenVR: {}", e.description());
};
let Ok(()) = app_mgr.set_application_auto_launch(APP_KEY, true) else {
bail!("Failed to set auto launch");
if let Err(e) = app_mgr.set_application_auto_launch(APP_KEY, true) {
bail!("Failed to set auto launch: {}", e.description());
};
Ok(())
@@ -69,8 +73,8 @@ pub(super) fn uninstall_manifest(app_mgr: &mut ApplicationsManager) -> anyhow::R
let manifest_path = CONFIG_ROOT_PATH.join("wlx-overlay-s.vrmanifest");
if let Ok(true) = app_mgr.is_application_installed(APP_KEY) {
let Ok(()) = app_mgr.remove_application_manifest(&manifest_path) else {
bail!("Failed to remove manifest from OpenVR");
if let Err(e) = app_mgr.remove_application_manifest(&manifest_path) {
bail!("Failed to remove manifest from OpenVR: {}", e.description());
};
log::info!("Uninstalled manifest");
}

View File

@@ -21,6 +21,7 @@ use vulkano::{
use crate::{
backend::{
input::interact,
notifications::NotificationManager,
openvr::{
input::{set_action_manifest, OpenVrInputSource},
lines::LinePool,
@@ -91,6 +92,9 @@ pub fn openvr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
let _ = install_manifest(&mut app_mgr);
let mut overlays = OverlayContainer::<OpenVrOverlayData>::new(&mut state)?;
let mut notifications = NotificationManager::new();
notifications.run_dbus();
notifications.run_udp();
let mut space_mover = playspace::PlayspaceMover::new();
#[cfg(feature = "osc")]
@@ -149,6 +153,8 @@ pub fn openvr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
next_device_update = Instant::now() + Duration::from_secs(30);
}
notifications.submit_pending(&mut state);
state.tasks.retrieve_due(&mut due_tasks);
while let Some(task) = due_tasks.pop_front() {
match task {
@@ -167,6 +173,8 @@ pub fn openvr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
continue;
};
log::info!("Creating overlay: {}", state.name);
overlays.add(OverlayData {
state,
backend,
@@ -175,14 +183,11 @@ pub fn openvr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
}
TaskType::DropOverlay(sel) => {
if let Some(o) = overlays.mut_by_selector(&sel) {
log::info!("Dropping overlay: {}", o.state.name);
o.destroy(&mut overlay_mngr);
overlays.drop_by_selector(&sel);
}
}
TaskType::Toast(t) => {
// TODO toasts
log::info!("Toast: {} {}", t.title, t.body);
}
}
}
@@ -249,8 +254,6 @@ pub fn openvr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
// close font handles?
// playspace moved end frame
state.hid_provider.on_new_frame();
let mut seconds_since_vsync = 0f32;

View File

@@ -111,7 +111,7 @@ pub(super) unsafe fn create_overlay_session(
}
}
pub(super) fn hmd_pose_from_views(views: &Vec<xr::View>) -> Affine3A {
pub(super) fn hmd_pose_from_views(views: &[xr::View]) -> Affine3A {
let pos = {
let pos0: Vec3 = unsafe { std::mem::transmute(views[0].pose.position) };
let pos1: Vec3 = unsafe { std::mem::transmute(views[1].pose.position) };

View File

@@ -15,6 +15,7 @@ use crate::{
backend::{
common::{OverlayContainer, TaskType},
input::interact,
notifications::NotificationManager,
openxr::{input::DoubleClickCounter, lines::LinePool, overlay::OpenXrOverlayData},
overlay::OverlayData,
},
@@ -63,6 +64,10 @@ pub fn openxr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
let mut overlays = OverlayContainer::<OpenXrOverlayData>::new(&mut app_state)?;
let mut lines = LinePool::new(app_state.graphics.clone())?;
let mut notifications = NotificationManager::new();
notifications.run_dbus();
notifications.run_udp();
#[cfg(feature = "osc")]
let mut osc_sender =
crate::backend::osc::OscSender::new(app_state.session.config.osc_out_port).ok();
@@ -179,6 +184,8 @@ pub fn openxr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
continue 'main_loop;
}
notifications.submit_pending(&mut app_state);
app_state.tasks.retrieve_due(&mut due_tasks);
while let Some(task) = due_tasks.pop_front() {
match task {
@@ -206,10 +213,6 @@ pub fn openxr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
TaskType::DropOverlay(sel) => {
overlays.drop_by_selector(&sel);
}
TaskType::Toast(t) => {
// TODO toasts
log::info!("Toast: {} {}", t.title, t.body);
}
}
}
@@ -221,7 +224,9 @@ pub fn openxr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
.input_state
.pointers
.iter()
.any(|p| p.now.show_hide && !p.before.show_hide) && show_hide_counter.click() {
.any(|p| p.now.show_hide && !p.before.show_hide)
&& show_hide_counter.click()
{
overlays.show_hide(&mut app_state);
}

View File

@@ -36,20 +36,23 @@ impl OverlayData<OpenXrOverlayData> {
};
let extent = my_view.image().extent();
let data = self.data.swapchain.get_or_insert_with(|| {
let srd =
create_swapchain_render_data(xr, command_buffer.graphics.clone(), extent).unwrap(); //TODO
log::info!(
"{}: Created swapchain {}x{}, {} images, {} MB",
self.state.name,
extent[0],
extent[1],
srd.images.len(),
extent[0] * extent[1] * 4 * srd.images.len() as u32 / 1024 / 1024
);
srd
});
let data = match self.data.swapchain {
Some(ref mut data) => data,
None => {
let srd =
create_swapchain_render_data(xr, command_buffer.graphics.clone(), extent)?;
log::debug!(
"{}: Created swapchain {}x{}, {} images, {} MB",
self.state.name,
extent[0],
extent[1],
srd.images.len(),
extent[0] * extent[1] * 4 * srd.images.len() as u32 / 1024 / 1024
);
self.data.swapchain = Some(srd);
self.data.swapchain.as_mut().unwrap() //safe
}
};
let sub_image = data.acquire_present_release(command_buffer, my_view, self.state.alpha)?;
let posef = helpers::transform_to_posef(&self.state.transform);

View File

@@ -101,9 +101,7 @@ impl OverlayState {
self.transform = parent
* Affine3A::from_scale_rotation_translation(
Vec3::ONE * self.spawn_scale,
self.spawn_rotation
* Quat::from_rotation_x(f32::to_radians(-180.0))
* Quat::from_rotation_z(f32::to_radians(180.0)),
self.spawn_rotation,
point.into(),
);