new workspace
This commit is contained in:
234
wlx-overlay-s/src/state.rs
Normal file
234
wlx-overlay-s/src/state.rs
Normal file
@@ -0,0 +1,234 @@
|
||||
use glam::Affine3A;
|
||||
use idmap::IdMap;
|
||||
use rodio::{Decoder, OutputStream, OutputStreamHandle, Source};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use std::{io::Cursor, sync::Arc};
|
||||
use vulkano::image::view::ImageView;
|
||||
use wgui::gfx::WGfx;
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
use {
|
||||
crate::config_wayvr::{self, WayVRConfig},
|
||||
crate::overlays::wayvr::WayVRData,
|
||||
std::{cell::RefCell, rc::Rc},
|
||||
};
|
||||
|
||||
#[cfg(feature = "osc")]
|
||||
use crate::backend::osc::OscSender;
|
||||
|
||||
use crate::{
|
||||
backend::{input::InputState, overlay::OverlayID, task::TaskContainer},
|
||||
config::{AStrMap, GeneralConfig},
|
||||
config_io,
|
||||
graphics::WGfxExtras,
|
||||
hid::HidProvider,
|
||||
overlays::toast::{DisplayMethod, ToastTopic},
|
||||
};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum KeyboardFocus {
|
||||
PhysicalScreen,
|
||||
|
||||
#[allow(dead_code)] // Not available if "wayvr" feature is disabled
|
||||
WayVR, // (for now without wayland window id data, it's handled internally),
|
||||
}
|
||||
|
||||
pub struct AppState {
|
||||
pub session: AppSession,
|
||||
pub tasks: TaskContainer,
|
||||
|
||||
pub gfx: Arc<WGfx>,
|
||||
pub gfx_extras: WGfxExtras,
|
||||
|
||||
pub input_state: InputState,
|
||||
pub hid_provider: Box<dyn HidProvider>,
|
||||
pub audio: AudioOutput,
|
||||
pub screens: SmallVec<[ScreenMeta; 8]>,
|
||||
pub anchor: Affine3A,
|
||||
pub sprites: AStrMap<Arc<ImageView>>,
|
||||
pub keyboard_focus: KeyboardFocus,
|
||||
pub toast_sound: &'static [u8],
|
||||
|
||||
#[cfg(feature = "osc")]
|
||||
pub osc_sender: Option<OscSender>,
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
pub wayvr: Option<Rc<RefCell<WayVRData>>>, // Dynamically created if requested
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn from_graphics(gfx: Arc<WGfx>, gfx_extras: WGfxExtras) -> anyhow::Result<Self> {
|
||||
// insert shared resources
|
||||
#[cfg(feature = "wayvr")]
|
||||
let mut tasks = TaskContainer::new();
|
||||
|
||||
#[cfg(not(feature = "wayvr"))]
|
||||
let tasks = TaskContainer::new();
|
||||
|
||||
let session = AppSession::load();
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
let wayvr = session
|
||||
.wayvr_config
|
||||
.post_load(&session.config, &mut tasks)?;
|
||||
|
||||
#[cfg(feature = "osc")]
|
||||
let osc_sender = crate::backend::osc::OscSender::new(session.config.osc_out_port).ok();
|
||||
|
||||
let toast_sound_wav = Self::try_load_bytes(
|
||||
&session.config.notification_sound,
|
||||
include_bytes!("res/557297.wav"),
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
session,
|
||||
tasks,
|
||||
gfx,
|
||||
gfx_extras,
|
||||
input_state: InputState::new(),
|
||||
hid_provider: crate::hid::initialize(),
|
||||
audio: AudioOutput::new(),
|
||||
screens: smallvec![],
|
||||
anchor: Affine3A::IDENTITY,
|
||||
sprites: AStrMap::new(),
|
||||
keyboard_focus: KeyboardFocus::PhysicalScreen,
|
||||
toast_sound: toast_sound_wav,
|
||||
|
||||
#[cfg(feature = "osc")]
|
||||
osc_sender,
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
wayvr,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
#[allow(dead_code)]
|
||||
pub fn get_wayvr(&mut self) -> anyhow::Result<Rc<RefCell<WayVRData>>> {
|
||||
if let Some(wvr) = &self.wayvr {
|
||||
Ok(wvr.clone())
|
||||
} else {
|
||||
let wayvr = Rc::new(RefCell::new(WayVRData::new(
|
||||
WayVRConfig::get_wayvr_config(&self.session.config, &self.session.wayvr_config)?,
|
||||
)?));
|
||||
self.wayvr = Some(wayvr.clone());
|
||||
Ok(wayvr)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_load_bytes(path: &str, fallback_data: &'static [u8]) -> &'static [u8] {
|
||||
if path.is_empty() {
|
||||
return fallback_data;
|
||||
}
|
||||
|
||||
let real_path = config_io::get_config_root().join(path);
|
||||
|
||||
if std::fs::File::open(real_path.clone()).is_err() {
|
||||
log::warn!("Could not open file at: {path}");
|
||||
return fallback_data;
|
||||
}
|
||||
|
||||
match std::fs::read(real_path) {
|
||||
// Box is used here to work around `f`'s limited lifetime
|
||||
Ok(f) => Box::leak(Box::new(f)).as_slice(),
|
||||
Err(e) => {
|
||||
log::warn!("Failed to read file at: {path}");
|
||||
log::warn!("{e:?}");
|
||||
fallback_data
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AppSession {
|
||||
pub config: GeneralConfig,
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
pub wayvr_config: WayVRConfig,
|
||||
|
||||
pub toast_topics: IdMap<ToastTopic, DisplayMethod>,
|
||||
}
|
||||
|
||||
impl AppSession {
|
||||
pub fn load() -> Self {
|
||||
let config_root_path = config_io::ConfigRoot::Generic.ensure_dir();
|
||||
log::info!("Config root path: {}", config_root_path.display());
|
||||
let config = GeneralConfig::load_from_disk();
|
||||
|
||||
let mut toast_topics = IdMap::new();
|
||||
toast_topics.insert(ToastTopic::System, DisplayMethod::Center);
|
||||
toast_topics.insert(ToastTopic::DesktopNotification, DisplayMethod::Center);
|
||||
toast_topics.insert(ToastTopic::XSNotification, DisplayMethod::Center);
|
||||
|
||||
config.notification_topics.iter().for_each(|(k, v)| {
|
||||
toast_topics.insert(*k, *v);
|
||||
});
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
let wayvr_config = config_wayvr::load_wayvr();
|
||||
|
||||
Self {
|
||||
config,
|
||||
#[cfg(feature = "wayvr")]
|
||||
wayvr_config,
|
||||
toast_topics,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AudioOutput {
|
||||
audio_stream: Option<(OutputStream, OutputStreamHandle)>,
|
||||
first_try: bool,
|
||||
}
|
||||
|
||||
impl AudioOutput {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
audio_stream: None,
|
||||
first_try: true,
|
||||
}
|
||||
}
|
||||
|
||||
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. 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 {
|
||||
pub name: Arc<str>,
|
||||
pub id: OverlayID,
|
||||
pub native_handle: u32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, Default)]
|
||||
#[repr(u8)]
|
||||
pub enum LeftRight {
|
||||
#[default]
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
Reference in New Issue
Block a user