bar + overlaybackend refactor

This commit is contained in:
galister
2025-07-03 02:42:49 +09:00
parent 9bbc7b2d22
commit 40f50a147b
65 changed files with 1743 additions and 1935 deletions

View File

@@ -13,11 +13,10 @@ use crate::{
overlays::{
anchor::create_anchor,
keyboard::{KEYBOARD_NAME, builder::create_keyboard},
screen::WlxClientAlias,
screen::create_screens,
watch::{WATCH_NAME, create_watch},
},
state::AppState,
subsystem::hid::{get_keymap_wl, get_keymap_x11},
};
use super::overlay::{OverlayData, OverlayID};
@@ -37,22 +36,11 @@ pub enum BackendError {
Fatal(#[from] anyhow::Error),
}
#[cfg(feature = "wayland")]
fn create_wl_client() -> Option<WlxClientAlias> {
wlx_capture::wayland::WlxClient::new()
}
#[cfg(not(feature = "wayland"))]
fn create_wl_client() -> Option<WlxClientAlias> {
None
}
pub struct OverlayContainer<T>
where
T: Default,
{
overlays: IdMap<usize, OverlayData<T>>,
wl: Option<WlxClientAlias>,
}
impl<T> OverlayContainer<T>
@@ -62,55 +50,35 @@ where
pub fn new(app: &mut AppState, headless: bool) -> anyhow::Result<Self> {
let mut overlays = IdMap::new();
let mut show_screens = app.session.config.show_screens.clone();
let mut wl = None;
let mut keymap = None;
app.screens.clear();
let mut maybe_keymap = None;
if headless {
log::info!("Running in headless mode; keyboard will be en-US");
} else {
wl = create_wl_client();
let data = if let Some(wl) = wl.as_mut() {
log::info!("Wayland detected.");
keymap = get_keymap_wl()
.map_err(|f| log::warn!("Could not load keyboard layout: {f}"))
.ok();
crate::overlays::screen::create_screens_wayland(wl, app)
} else {
log::info!("Wayland not detected, assuming X11.");
keymap = get_keymap_x11()
.map_err(|f| log::warn!("Could not load keyboard layout: {f}"))
.ok();
match crate::overlays::screen::create_screens_x11pw(app) {
Ok(data) => data,
Err(e) => {
log::info!("Will not use X11 PipeWire capture: {e:?}");
crate::overlays::screen::create_screens_xshm(app)?
match create_screens(app) {
Ok((data, keymap)) => {
if show_screens.is_empty() {
if let Some((_, s, _)) = data.screens.first() {
show_screens.arc_set(s.name.clone());
}
for (meta, mut state, backend) in data.screens {
if show_screens.arc_get(state.name.as_ref()) {
state.show_hide = true;
}
overlays.insert(
state.id.0,
OverlayData::<T> {
state,
..OverlayData::from_backend(backend)
},
);
app.screens.push(meta);
}
}
}
};
if show_screens.is_empty() {
if let Some((_, s, _)) = data.screens.first() {
show_screens.arc_set(s.name.clone());
maybe_keymap = keymap;
}
}
for (meta, mut state, backend) in data.screens {
if show_screens.arc_get(state.name.as_ref()) {
state.show_hide = true;
}
overlays.insert(
state.id.0,
OverlayData::<T> {
state,
backend,
..Default::default()
},
);
app.screens.push(meta);
Err(e) => log::error!("Unable to initialize screens: {e:?}"),
}
}
@@ -121,147 +89,12 @@ where
watch.state.want_visible = true;
overlays.insert(watch.state.id.0, watch);
let mut keyboard = create_keyboard(app, keymap)?;
let mut keyboard = create_keyboard(app, maybe_keymap)?;
keyboard.state.show_hide = show_screens.arc_get(KEYBOARD_NAME);
keyboard.state.want_visible = false;
overlays.insert(keyboard.state.id.0, keyboard);
Ok(Self { overlays, wl })
}
#[cfg(not(feature = "wayland"))]
pub fn update(&mut self, _app: &mut AppState) -> anyhow::Result<Vec<OverlayData<T>>> {
Ok(vec![])
}
#[cfg(feature = "wayland")]
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
#[allow(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
pub fn update(&mut self, app: &mut AppState) -> anyhow::Result<Vec<OverlayData<T>>> {
use crate::overlays::screen::{
create_screen_interaction, create_screen_renderer_wl, load_pw_token_config,
};
use glam::vec2;
use wlx_capture::wayland::OutputChangeEvent;
let mut removed_overlays = vec![];
let Some(wl) = self.wl.as_mut() else {
return Ok(removed_overlays);
};
wl.dispatch_pending();
let mut create_ran = false;
let mut extent_dirty = false;
let mut watch_dirty = false;
let mut maybe_token_store = None;
for ev in wl.iter_events().collect::<Vec<_>>() {
match ev {
OutputChangeEvent::Create(_) => {
if create_ran {
continue;
}
let data = crate::overlays::screen::create_screens_wayland(wl, app);
create_ran = true;
for (meta, state, backend) in data.screens {
self.overlays.insert(
state.id.0,
OverlayData::<T> {
state,
backend,
..Default::default()
},
);
app.screens.push(meta);
watch_dirty = true;
}
}
OutputChangeEvent::Destroy(id) => {
let Some(idx) = app.screens.iter().position(|s| s.native_handle == id) else {
continue;
};
let meta = &app.screens[idx];
let removed = self.overlays.remove(meta.id.0).unwrap();
removed_overlays.push(removed);
log::info!("{}: Destroyed", meta.name);
app.screens.remove(idx);
watch_dirty = true;
extent_dirty = true;
}
OutputChangeEvent::Logical(id) => {
let Some(meta) = app.screens.iter().find(|s| s.native_handle == id) else {
continue;
};
let output = wl.outputs.get(id).unwrap();
let Some(overlay) = self.overlays.get_mut(meta.id.0) else {
continue;
};
let logical_pos =
vec2(output.logical_pos.0 as f32, output.logical_pos.1 as f32);
let logical_size =
vec2(output.logical_size.0 as f32, output.logical_size.1 as f32);
let transform = output.transform.into();
overlay
.backend
.set_interaction(Box::new(create_screen_interaction(
logical_pos,
logical_size,
transform,
)));
extent_dirty = true;
}
OutputChangeEvent::Physical(id) => {
let Some(meta) = app.screens.iter().find(|s| s.native_handle == id) else {
continue;
};
let output = wl.outputs.get(id).unwrap();
let Some(overlay) = self.overlays.get_mut(meta.id.0) else {
continue;
};
let has_wlr_dmabuf = wl.maybe_wlr_dmabuf_mgr.is_some();
let has_wlr_screencopy = wl.maybe_wlr_screencopy_mgr.is_some();
let pw_token_store = maybe_token_store.get_or_insert_with(|| {
load_pw_token_config().unwrap_or_else(|e| {
log::warn!("Failed to load PipeWire token config: {:?}", e);
Default::default()
})
});
if let Some(renderer) = create_screen_renderer_wl(
output,
has_wlr_dmabuf,
has_wlr_screencopy,
pw_token_store,
&app,
) {
overlay.backend.set_renderer(Box::new(renderer));
}
extent_dirty = true;
}
}
}
if extent_dirty && !create_ran {
let extent = wl.get_desktop_extent();
let origin = wl.get_desktop_origin();
app.hid_provider
.inner
.set_desktop_extent(vec2(extent.0 as f32, extent.1 as f32));
app.hid_provider
.inner
.set_desktop_origin(vec2(origin.0 as f32, origin.1 as f32));
}
if watch_dirty {
let _watch = self.mut_by_name(WATCH_NAME).unwrap(); // want panic
todo!();
}
Ok(removed_overlays)
Ok(Self { overlays })
}
pub fn mut_by_selector(&mut self, selector: &OverlaySelector) -> Option<&mut OverlayData<T>> {

View File

@@ -256,24 +256,6 @@ pub struct Haptics {
pub frequency: f32,
}
pub trait InteractionHandler {
fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option<Haptics>;
fn on_left(&mut self, app: &mut AppState, pointer: usize);
fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool);
fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta_y: f32, delta_x: f32);
}
pub struct DummyInteractionHandler;
impl InteractionHandler for DummyInteractionHandler {
fn on_left(&mut self, _app: &mut AppState, _pointer: usize) {}
fn on_hover(&mut self, _app: &mut AppState, _hit: &PointerHit) -> Option<Haptics> {
None
}
fn on_pointer(&mut self, _app: &mut AppState, _hit: &PointerHit, _pressed: bool) {}
fn on_scroll(&mut self, _app: &mut AppState, _hit: &PointerHit, _delta_y: f32, _delta_x: f32) {}
}
#[derive(Debug, Clone, Copy, Default)]
struct RayHit {
overlay: OverlayID,
@@ -493,12 +475,16 @@ impl Pointer {
hits.sort_by(|a, b| a.dist.total_cmp(&b.dist));
for hit in &hits {
let overlay = overlays.get_by_id(hit.overlay).unwrap(); // safe because we just got the id from the overlay
let overlay = overlays.mut_by_id(hit.overlay).unwrap(); // safe because we just got the id from the overlay
let uv = overlay
.state
.interaction_transform
.transform_point2(hit.local_pos);
let Some(uv) = overlay
.backend
.as_mut()
.get_interaction_transform()
.map(|a| a.transform_point2(hit.local_pos))
else {
continue;
};
if uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0 {
continue;

View File

@@ -1,6 +1,6 @@
use std::f32::consts::PI;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use ash::vk::SubmitInfo;
use glam::{Affine3A, Vec3, Vec3A, Vec4};
@@ -8,6 +8,7 @@ use idmap::IdMap;
use ovr_overlay::overlay::OverlayManager;
use ovr_overlay::sys::ETrackingUniverseOrigin;
use vulkano::{
VulkanObject,
command_buffer::{
CommandBufferBeginInfo, CommandBufferLevel, CommandBufferUsage, RecordingCommandBuffer,
},
@@ -15,16 +16,15 @@ use vulkano::{
image::view::ImageView,
image::{Image, ImageLayout},
sync::{
fence::{Fence, FenceCreateInfo},
AccessFlags, DependencyInfo, ImageMemoryBarrier, PipelineStages,
fence::{Fence, FenceCreateInfo},
},
VulkanObject,
};
use wgui::gfx::WGfx;
use crate::backend::input::{Haptics, PointerHit};
use crate::backend::overlay::{
FrameMeta, OverlayData, OverlayRenderer, OverlayState, ShouldRender, SplitOverlayBackend,
Z_ORDER_LINES,
FrameMeta, OverlayBackend, OverlayData, OverlayState, ShouldRender, Z_ORDER_LINES,
};
use crate::graphics::CommandBuffers;
use crate::state::AppState;
@@ -81,12 +81,6 @@ impl LinePool {
show_hide: true,
..Default::default()
},
backend: Box::new(SplitOverlayBackend {
renderer: Box::new(StaticRenderer {
view: self.view.clone(),
}),
..Default::default()
}),
data: OpenVrOverlayData {
width: 0.002,
override_width: true,
@@ -94,7 +88,9 @@ impl LinePool {
image_dirty: true,
..Default::default()
},
..Default::default()
..OverlayData::from_backend(Box::new(LineBackend {
view: self.view.clone(),
}))
};
data.state.z_order = Z_ORDER_LINES;
data.state.dirty = true;
@@ -177,29 +173,29 @@ impl LinePool {
}
}
struct StaticRenderer {
struct LineBackend {
view: Arc<ImageView>,
}
impl OverlayRenderer for StaticRenderer {
fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
impl OverlayBackend for LineBackend {
fn init(&mut self, _: &mut AppState) -> anyhow::Result<()> {
Ok(())
}
fn pause(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
fn pause(&mut self, _: &mut AppState) -> anyhow::Result<()> {
Ok(())
}
fn resume(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
fn resume(&mut self, _: &mut AppState) -> anyhow::Result<()> {
Ok(())
}
fn should_render(&mut self, _app: &mut AppState) -> anyhow::Result<ShouldRender> {
fn should_render(&mut self, _: &mut AppState) -> anyhow::Result<ShouldRender> {
Ok(ShouldRender::Unable)
}
fn render(
&mut self,
_app: &mut AppState,
_tgt: Arc<ImageView>,
_buf: &mut CommandBuffers,
_alpha: f32,
_: &mut AppState,
_: Arc<ImageView>,
_: &mut CommandBuffers,
_: f32,
) -> anyhow::Result<bool> {
Ok(false)
}
@@ -209,6 +205,16 @@ impl OverlayRenderer for StaticRenderer {
..Default::default()
})
}
fn on_hover(&mut self, _: &mut AppState, _: &PointerHit) -> Option<Haptics> {
None
}
fn on_left(&mut self, _: &mut AppState, _: usize) {}
fn on_pointer(&mut self, _: &mut AppState, _: &PointerHit, _: bool) {}
fn on_scroll(&mut self, _: &mut AppState, _: &PointerHit, _: f32, _: f32) {}
fn get_interaction_transform(&mut self) -> Option<glam::Affine2> {
None
}
}
pub fn transition_layout(

View File

@@ -204,11 +204,6 @@ pub fn openvr_run(
state.tasks.retrieve_due(&mut due_tasks);
let mut removed_overlays = overlays.update(&mut state)?;
for o in &mut removed_overlays {
o.destroy(&mut overlay_mgr);
}
while let Some(task) = due_tasks.pop_front() {
match task {
TaskType::Overlay(sel, f) => {
@@ -230,8 +225,7 @@ pub fn openvr_run(
overlays.add(OverlayData {
state,
backend,
..Default::default()
..OverlayData::from_backend(backend)
});
}
TaskType::DropOverlay(sel) => {

View File

@@ -486,11 +486,6 @@ pub fn openxr_run(
)?;
// End layer submit
let removed_overlays = overlays.update(&mut app)?;
for o in removed_overlays {
delete_queue.push((o, cur_frame + 5));
}
notifications.submit_pending(&mut app);
app.tasks.retrieve_due(&mut due_tasks);
@@ -515,8 +510,7 @@ pub fn openxr_run(
overlays.add(OverlayData {
state: overlay_state,
backend: overlay_backend,
..Default::default()
..OverlayData::from_backend(overlay_backend)
});
}
TaskType::DropOverlay(sel) => {

View File

@@ -6,7 +6,6 @@ use std::{
},
};
use anyhow::Ok;
use glam::{Affine2, Affine3A, Mat3A, Quat, Vec2, Vec3, Vec3A};
use serde::Deserialize;
use vulkano::{format::Format, image::view::ImageView};
@@ -17,16 +16,11 @@ use crate::{
use super::{
common::snap_upright,
input::{DummyInteractionHandler, Haptics, InteractionHandler, PointerHit},
input::{Haptics, PointerHit},
};
static OVERLAY_AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(0);
pub trait OverlayBackend: OverlayRenderer + InteractionHandler {
fn set_renderer(&mut self, renderer: Box<dyn OverlayRenderer>);
fn set_interaction(&mut self, interaction: Box<dyn InteractionHandler>);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Default)]
pub struct OverlayID(pub usize);
@@ -56,7 +50,6 @@ pub struct OverlayState {
pub saved_transform: Option<Affine3A>,
pub positioning: Positioning,
pub curvature: Option<f32>,
pub interaction_transform: Affine2,
pub birthframe: usize,
}
@@ -81,32 +74,28 @@ impl Default for OverlayState {
spawn_rotation: Quat::IDENTITY,
saved_transform: None,
transform: Affine3A::IDENTITY,
interaction_transform: Affine2::IDENTITY,
birthframe: 0,
}
}
}
pub struct OverlayData<T>
where
T: Default,
{
pub struct OverlayData<T> {
pub state: OverlayState,
pub backend: Box<dyn OverlayBackend>,
pub primary_pointer: Option<usize>,
pub data: T,
}
impl<T> Default for OverlayData<T>
impl<T> OverlayData<T>
where
T: Default,
{
fn default() -> Self {
pub fn from_backend(backend: Box<dyn OverlayBackend>) -> Self {
Self {
state: OverlayState::default(),
backend: Box::<SplitOverlayBackend>::default(),
backend,
primary_pointer: None,
data: Default::default(),
data: T::default(),
}
}
}
@@ -166,7 +155,7 @@ impl OverlayState {
app.input_state.pointers[hand].pose
}
Positioning::Anchored => app.anchor,
Positioning::Static => return,
Positioning::FollowOverlay { .. } | Positioning::Static => return,
};
if hard_reset {
@@ -191,7 +180,7 @@ impl OverlayState {
app.input_state.pointers[hand].pose
}
Positioning::Anchored => snap_upright(app.anchor, Vec3A::Y),
Positioning::Static => return false,
Positioning::FollowOverlay { .. } | Positioning::Static => return false,
};
self.saved_transform = Some(parent_transform.inverse() * self.transform);
@@ -307,7 +296,7 @@ pub enum ShouldRender {
Unable,
}
pub trait OverlayRenderer {
pub trait OverlayBackend {
/// Called once, before the first frame is rendered
fn init(&mut self, app: &mut AppState) -> anyhow::Result<()>;
fn pause(&mut self, app: &mut AppState) -> anyhow::Result<()>;
@@ -330,37 +319,13 @@ pub trait OverlayRenderer {
///
/// Must be true if should_render was also true on the same frame.
fn frame_meta(&mut self) -> Option<FrameMeta>;
}
pub struct FallbackRenderer;
impl OverlayRenderer for FallbackRenderer {
fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
Ok(())
}
fn pause(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
Ok(())
}
fn resume(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
Ok(())
}
fn should_render(&mut self, _app: &mut AppState) -> anyhow::Result<ShouldRender> {
Ok(ShouldRender::Unable)
}
fn render(
&mut self,
_app: &mut AppState,
_tgt: Arc<ImageView>,
_buf: &mut CommandBuffers,
_alpha: f32,
) -> anyhow::Result<bool> {
Ok(false)
}
fn frame_meta(&mut self) -> Option<FrameMeta> {
None
}
fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option<Haptics>;
fn on_left(&mut self, app: &mut AppState, pointer: usize);
fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool);
fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta_y: f32, delta_x: f32);
fn get_interaction_transform(&mut self) -> Option<Affine2>;
}
// Boilerplate and dummies
#[derive(Clone, Copy, Debug, Default)]
pub enum Positioning {
@@ -377,74 +342,12 @@ pub enum Positioning {
FollowHand { hand: usize, lerp: f32 },
/// Normally follows hand, but paused due to interaction
FollowHandPaused { hand: usize, lerp: f32 },
/// Follow another overlay
FollowOverlay { id: usize },
/// Stays in place, no recentering
Static,
}
pub struct SplitOverlayBackend {
pub renderer: Box<dyn OverlayRenderer>,
pub interaction: Box<dyn InteractionHandler>,
}
impl Default for SplitOverlayBackend {
fn default() -> Self {
Self {
renderer: Box::new(FallbackRenderer),
interaction: Box::new(DummyInteractionHandler),
}
}
}
impl OverlayBackend for SplitOverlayBackend {
fn set_renderer(&mut self, renderer: Box<dyn OverlayRenderer>) {
self.renderer = renderer;
}
fn set_interaction(&mut self, interaction: Box<dyn InteractionHandler>) {
self.interaction = interaction;
}
}
impl OverlayRenderer for SplitOverlayBackend {
fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.renderer.init(app)
}
fn pause(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.renderer.pause(app)
}
fn resume(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.renderer.resume(app)
}
fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender> {
self.renderer.should_render(app)
}
fn render(
&mut self,
app: &mut AppState,
tgt: Arc<ImageView>,
buf: &mut CommandBuffers,
alpha: f32,
) -> anyhow::Result<bool> {
self.renderer.render(app, tgt, buf, alpha)
}
fn frame_meta(&mut self) -> Option<FrameMeta> {
self.renderer.frame_meta()
}
}
impl InteractionHandler for SplitOverlayBackend {
fn on_left(&mut self, app: &mut AppState, pointer: usize) {
self.interaction.on_left(app, pointer);
}
fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option<Haptics> {
self.interaction.on_hover(app, hit)
}
fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta_y: f32, delta_x: f32) {
self.interaction.on_scroll(app, hit, delta_y, delta_x);
}
fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool) {
self.interaction.on_pointer(app, hit, pressed);
}
}
pub fn ui_transform(extent: [u32; 2]) -> Affine2 {
let aspect = extent[0] as f32 / extent[1] as f32;
let scale = if aspect < 1.0 {