feat: handle screen changes at runtime

This commit is contained in:
galister
2024-04-04 20:06:34 +09:00
parent e845ac8d53
commit c02edb7a43
11 changed files with 372 additions and 214 deletions

4
Cargo.lock generated
View File

@@ -3603,8 +3603,8 @@ dependencies = [
[[package]] [[package]]
name = "wlx-capture" name = "wlx-capture"
version = "0.3.1" version = "0.3.8"
source = "git+https://github.com/galister/wlx-capture?tag=v0.3.1#f9c9c98e0752bfbdaa2af69b59376c697c223915" source = "git+https://github.com/galister/wlx-capture?tag=v0.3.8#e88ff6c0ceb360c61c5f26ede6a339a2daa09d8b"
dependencies = [ dependencies = [
"ashpd", "ashpd",
"drm-fourcc", "drm-fourcc",

View File

@@ -43,7 +43,7 @@ strum = { version = "0.25.0", features = ["derive"] }
thiserror = "1.0.56" thiserror = "1.0.56"
vulkano = { git = "https://github.com/vulkano-rs/vulkano", rev = "94f50f1" } vulkano = { git = "https://github.com/vulkano-rs/vulkano", rev = "94f50f1" }
vulkano-shaders = { 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", tag = "v0.3.1", default-features = false } wlx-capture = { git = "https://github.com/galister/wlx-capture", tag = "v0.3.8", default-features = false }
xdg = "2.5.2" xdg = "2.5.2"
[features] [features]

View File

@@ -8,17 +8,19 @@ use std::{
#[cfg(feature = "openxr")] #[cfg(feature = "openxr")]
use openxr as xr; use openxr as xr;
use glam::{Affine3A, Vec2, Vec3A, Vec3Swizzles}; use glam::{vec2, Affine3A, Vec2, Vec3A, Vec3Swizzles};
use idmap::IdMap; use idmap::IdMap;
use serde::Deserialize; use serde::Deserialize;
use thiserror::Error; use thiserror::Error;
use wlx_capture::wayland::{OutputChangeEvent, WlxClient};
use crate::{ use crate::{
overlays::{ overlays::{
keyboard::create_keyboard, keyboard::create_keyboard,
watch::{create_watch, WATCH_NAME}, screen::{create_screen_interaction, create_screen_renderer_wl, load_pw_token_config},
watch::{create_watch, create_watch_canvas, WATCH_NAME},
}, },
state::{AppState, ScreenMeta}, state::AppState,
}; };
use super::overlay::{OverlayBackend, OverlayData, OverlayState}; use super::overlay::{OverlayBackend, OverlayData, OverlayState};
@@ -43,7 +45,7 @@ where
T: Default, T: Default,
{ {
overlays: IdMap<usize, OverlayData<T>>, overlays: IdMap<usize, OverlayData<T>>,
pub extent: Vec2, wl: Option<WlxClient>,
} }
impl<T> OverlayContainer<T> impl<T> OverlayContainer<T>
@@ -52,18 +54,36 @@ where
{ {
pub fn new(app: &mut AppState) -> anyhow::Result<Self> { pub fn new(app: &mut AppState) -> anyhow::Result<Self> {
let mut overlays = IdMap::new(); let mut overlays = IdMap::new();
let (screens, extent) = if std::env::var("WAYLAND_DISPLAY").is_ok() { let mut wl = WlxClient::new();
crate::overlays::screen::get_screens_wayland(&app.session)?
} else {
crate::overlays::screen::get_screens_x11(&app.session)?
};
app.screens.clear(); app.screens.clear();
for screen in screens.iter() { let data = if let Some(wl) = wl.as_mut() {
app.screens.push(ScreenMeta { crate::overlays::screen::create_screens_wayland(wl, app)?
name: screen.state.name.clone(), } else {
id: screen.state.id, crate::overlays::screen::create_screens_x11(app)?
}); };
let mut show_screens = app.session.config.show_screens.clone();
if show_screens.is_empty() {
if let Some((_, s, _)) = data.screens.first() {
show_screens.push(s.name.clone());
}
}
for (meta, mut state, backend) in data.screens {
if show_screens.contains(&state.name) {
state.show_hide = true;
state.want_visible = false;
}
overlays.insert(
state.id,
OverlayData::<T> {
state,
backend,
..Default::default()
},
);
app.screens.push(meta);
} }
let mut watch = create_watch::<T>(app)?; let mut watch = create_watch::<T>(app)?;
@@ -75,21 +95,134 @@ where
keyboard.state.want_visible = false; keyboard.state.want_visible = false;
overlays.insert(keyboard.state.id, keyboard); overlays.insert(keyboard.state.id, keyboard);
let mut show_screens = app.session.config.show_screens.clone(); Ok(Self { overlays, wl })
if show_screens.is_empty() { }
if let Some(s) = screens.first() {
show_screens.push(s.state.name.clone()); pub fn update(&mut self, app: &mut AppState) -> anyhow::Result<Vec<OverlayData<T>>> {
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,
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).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) 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) 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.session,
) {
overlay.backend.set_renderer(Box::new(renderer));
}
extent_dirty = true;
}
} }
} }
for mut screen in screens { if extent_dirty && !create_ran {
if show_screens.contains(&screen.state.name) { let extent = wl.get_desktop_extent();
screen.state.show_hide = true; let origin = wl.get_desktop_origin();
screen.state.want_visible = false; app.hid_provider
} .set_desktop_extent(vec2(extent.0 as f32, extent.1 as f32));
overlays.insert(screen.state.id, screen); app.hid_provider
.set_desktop_origin(vec2(origin.0 as f32, origin.1 as f32));
} }
Ok(Self { overlays, extent })
if watch_dirty {
let watch = self.mut_by_name(WATCH_NAME).unwrap(); // want panic
match create_watch_canvas(None, app) {
Ok(canvas) => {
watch.backend = Box::new(canvas);
}
Err(e) => {
log::error!("Failed to create watch canvas: {}", e);
}
}
}
Ok(removed_overlays)
} }
pub fn mut_by_selector(&mut self, selector: &OverlaySelector) -> Option<&mut OverlayData<T>> { pub fn mut_by_selector(&mut self, selector: &OverlaySelector) -> Option<&mut OverlayData<T>> {

View File

@@ -118,8 +118,6 @@ pub fn openvr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
let mut osc_sender = let mut osc_sender =
crate::backend::osc::OscSender::new(state.session.config.osc_out_port).ok(); crate::backend::osc::OscSender::new(state.session.config.osc_out_port).ok();
state.hid_provider.set_desktop_extent(overlays.extent);
set_action_manifest(&mut input_mgr)?; set_action_manifest(&mut input_mgr)?;
let mut input_source = OpenVrInputSource::new(&mut input_mgr)?; let mut input_source = OpenVrInputSource::new(&mut input_mgr)?;
@@ -203,6 +201,12 @@ pub fn openvr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
notifications.submit_pending(&mut state); notifications.submit_pending(&mut state);
state.tasks.retrieve_due(&mut due_tasks); state.tasks.retrieve_due(&mut due_tasks);
let mut removed_overlays = overlays.update(&mut state)?;
for o in removed_overlays.iter_mut() {
o.destroy(&mut overlay_mgr);
}
while let Some(task) = due_tasks.pop_front() { while let Some(task) = due_tasks.pop_front() {
match task { match task {
TaskType::Global(f) => f(&mut state), TaskType::Global(f) => f(&mut state),

View File

@@ -78,8 +78,6 @@ pub fn openxr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
let mut osc_sender = let mut osc_sender =
crate::backend::osc::OscSender::new(app_state.session.config.osc_out_port).ok(); crate::backend::osc::OscSender::new(app_state.session.config.osc_out_port).ok();
app_state.hid_provider.set_desktop_extent(overlays.extent);
let (session, mut frame_wait, mut frame_stream) = unsafe { let (session, mut frame_wait, mut frame_stream) = unsafe {
let raw_session = helpers::create_overlay_session( let raw_session = helpers::create_overlay_session(
&xr_instance, &xr_instance,
@@ -322,6 +320,8 @@ pub fn openxr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
&frame_ref, &frame_ref,
)?; )?;
let _ = overlays.update(&mut app_state)?;
notifications.submit_pending(&mut app_state); notifications.submit_pending(&mut app_state);
app_state.tasks.retrieve_due(&mut due_tasks); app_state.tasks.retrieve_due(&mut due_tasks);

View File

@@ -16,7 +16,10 @@ use super::input::{DummyInteractionHandler, Haptics, InteractionHandler, Pointer
static AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(0); static AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(0);
pub trait OverlayBackend: OverlayRenderer + InteractionHandler {} pub trait OverlayBackend: OverlayRenderer + InteractionHandler {
fn set_renderer(&mut self, renderer: Box<dyn OverlayRenderer>);
fn set_interaction(&mut self, interaction: Box<dyn InteractionHandler>);
}
pub struct OverlayState { pub struct OverlayState {
pub id: usize, pub id: usize,
@@ -258,7 +261,14 @@ impl Default for SplitOverlayBackend {
} }
} }
impl OverlayBackend for SplitOverlayBackend {} 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 { impl OverlayRenderer for SplitOverlayBackend {
fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> { fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.renderer.init(app) self.renderer.init(app)

View File

@@ -514,7 +514,10 @@ impl<D, S> OverlayRenderer for Canvas<D, S> {
} }
} }
impl<D, S> OverlayBackend for Canvas<D, S> {} impl<D, S> OverlayBackend for Canvas<D, S> {
fn set_renderer(&mut self, _renderer: Box<dyn OverlayRenderer>) {}
fn set_interaction(&mut self, _interaction: Box<dyn InteractionHandler>) {}
}
pub struct Control<D, S> { pub struct Control<D, S> {
pub state: Option<S>, pub state: Option<S>,

View File

@@ -35,12 +35,14 @@ pub trait HidProvider {
fn set_modifiers(&mut self, mods: u8); fn set_modifiers(&mut self, mods: u8);
fn send_key(&self, key: u16, down: bool); fn send_key(&self, key: u16, down: bool);
fn set_desktop_extent(&mut self, extent: Vec2); fn set_desktop_extent(&mut self, extent: Vec2);
fn set_desktop_origin(&mut self, origin: Vec2);
fn on_new_frame(&mut self); fn on_new_frame(&mut self);
} }
pub struct UInputProvider { pub struct UInputProvider {
handle: UInputHandle<File>, handle: UInputHandle<File>,
desktop_extent: Vec2, desktop_extent: Vec2,
desktop_origin: Vec2,
mouse_moved: bool, mouse_moved: bool,
cur_modifiers: u8, cur_modifiers: u8,
} }
@@ -135,6 +137,7 @@ impl UInputProvider {
return Some(UInputProvider { return Some(UInputProvider {
handle, handle,
desktop_extent: Vec2::ZERO, desktop_extent: Vec2::ZERO,
desktop_origin: Vec2::ZERO,
mouse_moved: false, mouse_moved: false,
cur_modifiers: 0, cur_modifiers: 0,
}); });
@@ -154,7 +157,7 @@ impl HidProvider for UInputProvider {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
log::trace!("Mouse move: {:?}", pos); log::trace!("Mouse move: {:?}", pos);
let pos = pos * (MOUSE_EXTENT / self.desktop_extent); let pos = (pos - self.desktop_origin) * (MOUSE_EXTENT / self.desktop_extent);
let time = get_time(); let time = get_time();
let events = [ let events = [
@@ -209,9 +212,11 @@ impl HidProvider for UInputProvider {
} }
} }
fn set_desktop_extent(&mut self, extent: Vec2) { fn set_desktop_extent(&mut self, extent: Vec2) {
log::info!("Desktop extent: {:?}", extent);
self.desktop_extent = extent; self.desktop_extent = extent;
} }
fn set_desktop_origin(&mut self, origin: Vec2) {
self.desktop_origin = origin;
}
fn on_new_frame(&mut self) { fn on_new_frame(&mut self) {
self.mouse_moved = false; self.mouse_moved = false;
} }
@@ -224,6 +229,7 @@ impl HidProvider for DummyProvider {
fn set_modifiers(&mut self, _modifiers: u8) {} fn set_modifiers(&mut self, _modifiers: u8) {}
fn send_key(&self, _key: u16, _down: bool) {} fn send_key(&self, _key: u16, _down: bool) {}
fn set_desktop_extent(&mut self, _extent: Vec2) {} fn set_desktop_extent(&mut self, _extent: Vec2) {}
fn set_desktop_origin(&mut self, _origin: Vec2) {}
fn on_new_frame(&mut self) {} fn on_new_frame(&mut self) {}
} }

View File

@@ -40,12 +40,12 @@ use glam::{vec2, vec3a, Affine2, Quat, Vec2};
use crate::{ use crate::{
backend::{ backend::{
input::{Haptics, InteractionHandler, PointerHit, PointerMode}, input::{Haptics, InteractionHandler, PointerHit, PointerMode},
overlay::{OverlayData, OverlayRenderer, OverlayState, SplitOverlayBackend}, overlay::{OverlayRenderer, OverlayState, SplitOverlayBackend},
}, },
config::def_pw_tokens, config::def_pw_tokens,
graphics::{fourcc_to_vk, WlxCommandBuffer, WlxPipeline, WlxPipelineLegacy}, graphics::{fourcc_to_vk, WlxCommandBuffer, WlxPipeline, WlxPipelineLegacy},
hid::{MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT}, hid::{MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT},
state::{AppSession, AppState}, state::{AppSession, AppState, ScreenMeta},
}; };
const CURSOR_SIZE: f32 = 16. / 1440.; const CURSOR_SIZE: f32 = 16. / 1440.;
@@ -130,6 +130,7 @@ impl InteractionHandler for ScreenInteractionHandler {
fn on_left(&mut self, _app: &mut AppState, _hand: usize) {} fn on_left(&mut self, _app: &mut AppState, _hand: usize) {}
} }
#[derive(Clone)]
struct ScreenPipeline { struct ScreenPipeline {
view: Arc<ImageView>, view: Arc<ImageView>,
mouse: Option<Arc<ImageView>>, mouse: Option<Arc<ImageView>>,
@@ -500,36 +501,22 @@ impl OverlayRenderer for ScreenRenderer {
} }
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
/// Panics if id is not a valid output id pub fn create_screen_renderer_wl(
fn try_create_screen<O>( output: &WlxOutput,
wl: &WlxClient, has_wlr_dmabuf: bool,
id: u32, has_wlr_screencopy: bool,
pw_token_store: &mut HashMap<String, String>, pw_token_store: &mut HashMap<String, String>,
session: &AppSession, session: &AppSession,
) -> Option<OverlayData<O>> ) -> Option<ScreenRenderer> {
where
O: Default,
{
let output = &wl.outputs.get(id).unwrap(); // safe due to contract
log::info!(
"{}: Res {}x{} Size {:?} Pos {:?}",
output.name,
output.size.0,
output.size.1,
output.logical_size,
output.logical_pos,
);
let mut capture: Option<ScreenRenderer> = None; let mut capture: Option<ScreenRenderer> = None;
if (&*session.config.capture_method == "auto" || &*session.config.capture_method == "dmabuf") if (&*session.config.capture_method == "auto" || &*session.config.capture_method == "dmabuf")
&& wl.maybe_wlr_dmabuf_mgr.is_some() && has_wlr_dmabuf
{ {
log::info!("{}: Using Wlr DMA-Buf", &output.name); log::info!("{}: Using Wlr DMA-Buf", &output.name);
capture = ScreenRenderer::new_wlr_dmabuf(output); capture = ScreenRenderer::new_wlr_dmabuf(output);
} }
if &*session.config.capture_method == "screencopy" && wl.maybe_wlr_screencopy_mgr.is_some() { if &*session.config.capture_method == "screencopy" && has_wlr_screencopy {
log::info!("{}: Using Wlr Screencopy Wl-SHM", &output.name); log::info!("{}: Using Wlr Screencopy Wl-SHM", &output.name);
capture = ScreenRenderer::new_wlr_screencopy(output); capture = ScreenRenderer::new_wlr_screencopy(output);
} }
@@ -572,78 +559,67 @@ where
} }
} }
} }
if let Some(capture) = capture { capture
let backend = Box::new(SplitOverlayBackend { }
renderer: Box::new(capture),
interaction: Box::new(ScreenInteractionHandler::new(
vec2(output.logical_pos.0 as f32, output.logical_pos.1 as f32),
vec2(output.logical_size.0 as f32, output.logical_size.1 as f32),
output.transform.into(),
)),
});
let axis = Vec3::new(0., 0., 1.); pub fn create_screen_interaction(
logical_pos: Vec2,
logical_size: Vec2,
transform: Transform,
) -> ScreenInteractionHandler {
ScreenInteractionHandler::new(logical_pos, logical_size, transform)
}
let transform = output.transform.into(); fn create_screen_state(
name: Arc<str>,
let angle = if session.config.upright_screen_fix { res: (i32, i32),
match transform { transform: Transform,
Transform::_90 | Transform::Flipped90 => PI / 2., session: &AppSession,
Transform::_180 | Transform::Flipped180 => PI, ) -> OverlayState {
Transform::_270 | Transform::Flipped270 => -PI / 2., let angle = if session.config.upright_screen_fix {
_ => 0., match transform {
} Transform::_90 | Transform::Flipped90 => PI / 2.,
} else { Transform::_180 | Transform::Flipped180 => PI,
0. Transform::_270 | Transform::Flipped270 => -PI / 2.,
}; _ => 0.,
}
let center = Vec2 { x: 0.5, y: 0.5 };
let interaction_transform = match transform {
Transform::_90 | Transform::Flipped90 => Affine2::from_cols(
Vec2::NEG_Y * (output.size.0 as f32 / output.size.1 as f32),
Vec2::NEG_X,
center,
),
Transform::_180 | Transform::Flipped180 => Affine2::from_cols(
Vec2::NEG_X,
Vec2::NEG_Y * (-output.size.0 as f32 / output.size.1 as f32),
center,
),
Transform::_270 | Transform::Flipped270 => Affine2::from_cols(
Vec2::Y * (output.size.0 as f32 / output.size.1 as f32),
Vec2::X,
center,
),
_ => Affine2::from_cols(
Vec2::X,
Vec2::Y * (-output.size.0 as f32 / output.size.1 as f32),
center,
),
};
Some(OverlayData {
state: OverlayState {
name: output.name.clone(),
show_hide: session
.config
.show_screens
.iter()
.any(|s| s.as_ref() == output.name.as_ref()),
grabbable: true,
recenter: true,
interactable: true,
spawn_scale: 1.5 * session.config.desktop_view_scale,
spawn_point: vec3a(0., 0.5, -1.),
spawn_rotation: Quat::from_axis_angle(axis, angle),
interaction_transform,
..Default::default()
},
backend,
..Default::default()
})
} else { } else {
log::warn!("{}: Will not be used", &output.name); 0.
None };
let center = Vec2 { x: 0.5, y: 0.5 };
let interaction_transform = match transform {
Transform::_90 | Transform::Flipped90 => Affine2::from_cols(
Vec2::NEG_Y * (res.0 as f32 / res.1 as f32),
Vec2::NEG_X,
center,
),
Transform::_180 | Transform::Flipped180 => Affine2::from_cols(
Vec2::NEG_X,
Vec2::NEG_Y * (-res.0 as f32 / res.1 as f32),
center,
),
Transform::_270 | Transform::Flipped270 => {
Affine2::from_cols(Vec2::Y * (res.0 as f32 / res.1 as f32), Vec2::X, center)
}
_ => Affine2::from_cols(Vec2::X, Vec2::Y * (-res.0 as f32 / res.1 as f32), center),
};
OverlayState {
name: name.clone(),
show_hide: session
.config
.show_screens
.iter()
.any(|s| s.as_ref() == name.as_ref()),
grabbable: true,
recenter: true,
interactable: true,
spawn_scale: 1.5 * session.config.desktop_view_scale,
spawn_point: vec3a(0., 0.5, -1.),
spawn_rotation: Quat::from_axis_angle(Vec3::Z, angle),
interaction_transform,
..Default::default()
} }
} }
@@ -688,21 +664,24 @@ pub fn load_pw_token_config() -> Result<HashMap<String, String>, Box<dyn Error>>
Ok(map) Ok(map)
} }
pub(crate) struct ScreenCreateData {
pub screens: Vec<(ScreenMeta, OverlayState, Box<SplitOverlayBackend>)>,
}
#[cfg(not(feature = "wayland"))] #[cfg(not(feature = "wayland"))]
pub fn get_screens_wayland<O>(_session: &AppSession) -> anyhow::Result<(Vec<OverlayData<O>>, Vec2)> pub fn create_screens_wayland(
where wl: &mut WlxClient,
O: Default, app: &AppState,
{ ) -> anyhow::Result<ScreenCreateData> {
anyhow::bail!("Wayland support not enabled") anyhow::bail!("Wayland support not enabled")
} }
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
pub fn get_screens_wayland<O>(session: &AppSession) -> anyhow::Result<(Vec<OverlayData<O>>, Vec2)> pub fn create_screens_wayland(
where wl: &mut WlxClient,
O: Default, app: &mut AppState,
{ ) -> anyhow::Result<ScreenCreateData> {
let mut overlays = vec![]; let mut screens = vec![];
let mut wl = WlxClient::new().ok_or_else(|| anyhow::anyhow!("Failed to connect to Wayland"))?;
// Load existing Pipewire tokens from file // Load existing Pipewire tokens from file
let mut pw_tokens: HashMap<String, String> = if let Ok(conf) = load_pw_token_config() { let mut pw_tokens: HashMap<String, String> = if let Ok(conf) = load_pw_token_config() {
@@ -712,24 +691,47 @@ where
}; };
let pw_tokens_copy = pw_tokens.clone(); let pw_tokens_copy = pw_tokens.clone();
let has_wlr_dmabuf = wl.maybe_wlr_dmabuf_mgr.is_some();
let has_wlr_screencopy = wl.maybe_wlr_screencopy_mgr.is_some();
let mut origin = (i32::MAX, i32::MAX); for (id, output) in wl.outputs.iter() {
for output in wl.outputs.values() { if app.screens.iter().any(|s| s.name == output.name) {
origin.0 = origin.0.min(output.logical_pos.0); continue;
origin.1 = origin.1.min(output.logical_pos.1); }
}
log::info!("Desktop origin: {:?}", origin); log::info!(
"{}: Init screen of res {:?}, logical {:?} at {:?}",
output.name,
output.size,
output.logical_size,
output.logical_pos,
);
// adjust all outputs so that the top-left corner is at (0, 0) if let Some(renderer) = create_screen_renderer_wl(
for output in wl.outputs.values_mut() { output,
output.logical_pos.0 -= origin.0; has_wlr_dmabuf,
output.logical_pos.1 -= origin.1; has_wlr_screencopy,
} &mut pw_tokens,
&app.session,
) {
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();
let interaction = create_screen_interaction(logical_pos, logical_size, transform);
let state =
create_screen_state(output.name.clone(), output.size, transform, &app.session);
for id in wl.outputs.keys() { let meta = ScreenMeta {
if let Some(overlay) = try_create_screen(&wl, *id, &mut pw_tokens, session) { name: wl.outputs[id].name.clone(),
overlays.push(overlay); id: state.id,
native_handle: *id,
};
let backend = Box::new(SplitOverlayBackend {
renderer: Box::new(renderer),
interaction: Box::new(interaction),
});
screens.push((meta, state, backend));
} }
} }
@@ -741,22 +743,23 @@ where
} }
let extent = wl.get_desktop_extent(); let extent = wl.get_desktop_extent();
Ok((overlays, Vec2::new(extent.0 as f32, extent.1 as f32))) let origin = wl.get_desktop_origin();
app.hid_provider
.set_desktop_extent(vec2(extent.0 as f32, extent.1 as f32));
app.hid_provider
.set_desktop_origin(vec2(origin.0 as f32, origin.1 as f32));
Ok(ScreenCreateData { screens })
} }
#[cfg(not(feature = "x11"))] #[cfg(not(feature = "x11"))]
pub fn get_screens_x11<O>(_session: &AppSession) -> anyhow::Result<(Vec<OverlayData<O>>, Vec2)> pub fn create_screens_x11(session: &AppSession) -> anyhow::Result<ScreenCreateData> {
where
O: Default,
{
anyhow::bail!("X11 support not enabled") anyhow::bail!("X11 support not enabled")
} }
#[cfg(feature = "x11")] #[cfg(feature = "x11")]
pub fn get_screens_x11<O>(session: &AppSession) -> anyhow::Result<(Vec<OverlayData<O>>, Vec2)> pub fn create_screens_x11(app: &mut AppState) -> anyhow::Result<ScreenCreateData> {
where
O: Default,
{
use anyhow::bail; use anyhow::bail;
let mut extent = vec2(0., 0.); let mut extent = vec2(0., 0.);
@@ -768,64 +771,52 @@ where
} }
}; };
let overlays = monitors let screens = monitors
.into_iter() .into_iter()
.map(|s| { .map(|s| {
log::info!(
"{}: Res {}x{}, Pos {}x{}",
s.name,
s.monitor.width(),
s.monitor.height(),
s.monitor.x(),
s.monitor.y()
);
let size = (s.monitor.width(), s.monitor.height());
let capture: ScreenRenderer = ScreenRenderer::new_xshm(s.clone());
let backend = Box::new(SplitOverlayBackend {
renderer: Box::new(capture),
interaction: Box::new(ScreenInteractionHandler::new(
vec2(s.monitor.x() as f32, s.monitor.y() as f32),
vec2(s.monitor.width() as f32, s.monitor.height() as f32),
Transform::Normal,
)),
});
let interaction_transform = Affine2::from_translation(Vec2 { x: 0.5, y: 0.5 })
* Affine2::from_scale(Vec2 {
x: 1.,
y: -size.0 as f32 / size.1 as f32,
});
extent.x = extent.x.max((s.monitor.x() + s.monitor.width()) as f32); extent.x = extent.x.max((s.monitor.x() + s.monitor.width()) as f32);
extent.y = extent.y.max((s.monitor.y() + s.monitor.height()) as f32); extent.y = extent.y.max((s.monitor.y() + s.monitor.height()) as f32);
OverlayData {
state: OverlayState { let size = (s.monitor.width(), s.monitor.height());
name: s.name.clone(), let pos = (s.monitor.x(), s.monitor.y());
show_hide: session let renderer = ScreenRenderer::new_xshm(s.clone());
.config
.show_screens log::info!(
.iter() "{}: Init screen of res {:?} at {:?}",
.any(|x| x.as_ref() == s.name.as_ref()), s.name.clone(),
grabbable: true, size,
recenter: true, pos,
interactable: true, );
spawn_scale: 1.5 * session.config.desktop_view_scale,
spawn_point: vec3a(0., 0.5, -1.), let interaction = create_screen_interaction(
spawn_rotation: Quat::IDENTITY, vec2(s.monitor.x() as f32, s.monitor.y() as f32),
interaction_transform, vec2(size.0 as f32, size.1 as f32),
..Default::default() Transform::Normal,
}, );
backend,
..Default::default() let state = create_screen_state(s.name.clone(), size, Transform::Normal, &app.session);
}
let meta = ScreenMeta {
name: s.name.clone(),
id: state.id,
native_handle: 0,
};
let backend = Box::new(SplitOverlayBackend {
renderer: Box::new(renderer),
interaction: Box::new(interaction),
});
(meta, state, backend)
}) })
.collect(); .collect();
Ok((overlays, extent)) app.hid_provider.set_desktop_extent(extent);
Ok(ScreenCreateData { screens })
} }
#[allow(unused)] #[allow(unused)]
#[derive(Clone, Copy)]
pub enum Transform { pub enum Transform {
Normal, Normal,
_90, _90,

View File

@@ -8,7 +8,10 @@ use crate::{
def_half, def_left, def_point7, def_watch_pos, def_watch_rot, load_known_yaml, ConfigType, def_half, def_left, def_point7, def_watch_pos, def_watch_rot, load_known_yaml, ConfigType,
}, },
config_io, config_io,
gui::modular::{modular_canvas, ModularUiConfig}, gui::{
modular::{modular_canvas, ModularData, ModularUiConfig},
Canvas,
},
state::{AppState, LeftRight}, state::{AppState, LeftRight},
}; };
@@ -20,8 +23,6 @@ where
{ {
let config = load_known_yaml::<ModularUiConfig>(ConfigType::Watch); let config = load_known_yaml::<ModularUiConfig>(ConfigType::Watch);
let canvas = modular_canvas(&config.size, &config.elements, state)?;
let relative_to = RelativeTo::Hand(state.session.config.watch_hand as usize); let relative_to = RelativeTo::Hand(state.session.config.watch_hand as usize);
Ok(OverlayData { Ok(OverlayData {
@@ -36,11 +37,20 @@ where
relative_to, relative_to,
..Default::default() ..Default::default()
}, },
backend: Box::new(canvas), backend: Box::new(create_watch_canvas(Some(config), state)?),
..Default::default() ..Default::default()
}) })
} }
pub fn create_watch_canvas(
config: Option<ModularUiConfig>,
state: &AppState,
) -> anyhow::Result<Canvas<(), ModularData>> {
let config = config.unwrap_or_else(|| load_known_yaml::<ModularUiConfig>(ConfigType::Watch));
modular_canvas(&config.size, &config.elements, state)
}
pub fn watch_fade<D>(app: &mut AppState, watch: &mut OverlayData<D>) pub fn watch_fade<D>(app: &mut AppState, watch: &mut OverlayData<D>)
where where
D: Default, D: Default,

View File

@@ -172,6 +172,7 @@ impl AudioOutput {
pub struct ScreenMeta { pub struct ScreenMeta {
pub name: Arc<str>, pub name: Arc<str>,
pub id: usize, pub id: usize,
pub native_handle: u32,
} }
#[derive(Serialize, Deserialize, Clone, Copy, Default)] #[derive(Serialize, Deserialize, Clone, Copy, Default)]